diff --git a/src/components/forms/ResourceForm.js b/src/components/forms/ResourceForm.js index fda5a0f..83218b5 100644 --- a/src/components/forms/ResourceForm.js +++ b/src/components/forms/ResourceForm.js @@ -1,46 +1,53 @@ -import React, { use, useState } from "react"; +import React, { useState, useEffect } from "react"; import axios from "axios"; import { InputText } from "primereact/inputtext"; import { InputNumber } from "primereact/inputnumber"; import { InputSwitch } from "primereact/inputswitch"; import { Editor } from "primereact/editor"; import { Button } from "primereact/button"; -import { nip04, verifyEvent, nip19 } from "nostr-tools"; import { useRouter } from "next/router"; import { useNostr } from "@/hooks/useNostr"; -import { v4 as uuidv4 } from 'uuid'; import { useLocalStorageWithEffect } from "@/hooks/useLocalStorage"; import EditorHeader from "./Editor/EditorHeader"; import { useToast } from "@/hooks/useToast"; import 'primeicons/primeicons.css'; -const ResourceForm = () => { - const [title, setTitle] = useState(''); - const [summary, setSummary] = useState(''); - const [isPaidResource, setIsPaidResource] = useState(false); - const [price, setPrice] = useState(0); - const [text, setText] = useState(''); - const [coverImage, setCoverImage] = useState(''); - const [topics, setTopics] = useState([]); +const ResourceForm = ({ draft = null }) => { + const [title, setTitle] = useState(draft?.title || ''); + const [summary, setSummary] = useState(draft?.summary || ''); + const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false); + const [price, setPrice] = useState(draft?.price || 0); + const [text, setText] = useState(draft?.content || ''); + const [coverImage, setCoverImage] = useState(draft?.image || ''); + const [topics, setTopics] = useState(draft?.topics || ['']); const [user] = useLocalStorageWithEffect('user', {}); - const { showToast } = useToast(); - const { publishAll } = useNostr(); - const router = useRouter(); + useEffect(() => { + if (draft) { + setTitle(draft.title); + setSummary(draft.summary); + setIsPaidResource(draft.price ? true : false); + setPrice(draft.price || 0); + setText(draft.content); + setCoverImage(draft.image); + setTopics(draft.topics || []); + } + }, [draft]); + const handleSubmit = async (e) => { e.preventDefault(); - + const userResponse = await axios.get(`/api/users/${user.pubkey}`); - + if (!userResponse.data) { showToast('error', 'Error', 'User not found', 'Please try again.'); return; } - + const payload = { title, summary, @@ -48,16 +55,23 @@ const ResourceForm = () => { price: isPaidResource ? price : null, content: text, image: coverImage, - user: userResponse.data.id, topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource'] }; - - if (payload && payload.user) { - axios.post('/api/drafts', payload) + + if (!draft) { + // Only include user when creating a new draft + payload.user = userResponse.data.id; + } + + if (payload) { + const url = draft ? `/api/drafts/${draft.id}` : '/api/drafts'; + const method = draft ? 'put' : 'post'; + + axios[method](url, payload) .then(response => { - if (response.status === 201) { - showToast('success', 'Success', 'Resource saved as draft.'); - + if (response.status === 200 || response.status === 201) { + showToast('success', 'Success', draft ? 'Resource updated successfully.' : 'Resource saved as draft.'); + if (response.data?.id) { router.push(`/draft/${response.data.id}`); } @@ -65,6 +79,7 @@ const ResourceForm = () => { }) .catch(error => { console.error(error); + showToast('error', 'Error', 'Failed to save resource. Please try again.'); }); } }; @@ -219,11 +234,13 @@ const ResourceForm = () => { setTopics(updatedTopics); }; - const addTopic = () => { + const addTopic = (e) => { + e.preventDefault(); setTopics([...topics, '']); // Add an empty string to the topics array }; - const removeTopic = (index) => { + const removeTopic = (e, index) => { + e.preventDefault(); const updatedTopics = topics.filter((_, i) => i !== index); setTopics(updatedTopics); }; @@ -296,7 +313,7 @@ const ResourceForm = () => {
handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> {index > 0 && ( -
))} @@ -305,7 +322,7 @@ const ResourceForm = () => {
-
); diff --git a/src/components/forms/WorkshopForm.js b/src/components/forms/WorkshopForm.js index 6b4b283..d8f7f51 100644 --- a/src/components/forms/WorkshopForm.js +++ b/src/components/forms/WorkshopForm.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { useRouter } from 'next/router'; import { InputText } from 'primereact/inputtext'; @@ -9,21 +9,31 @@ import { useToast } from '@/hooks/useToast'; import { useLocalStorageWithEffect } from '@/hooks/useLocalStorage'; import 'primeicons/primeicons.css'; -const WorkshopForm = () => { - const [title, setTitle] = useState(''); - const [summary, setSummary] = useState(''); - const [price, setPrice] = useState(0); - const [isPaidResource, setIsPaidResource] = useState(false); - const [videoUrl, setVideoUrl] = useState(''); - const [coverImage, setCoverImage] = useState(''); - const [topics, setTopics] = useState(['']); +const WorkshopForm = ({ draft = null }) => { + const [title, setTitle] = useState(draft?.title || ''); + const [summary, setSummary] = useState(draft?.summary || ''); + const [price, setPrice] = useState(draft?.price || 0); + const [isPaidResource, setIsPaidResource] = useState(draft?.price ? true : false); + const [videoUrl, setVideoUrl] = useState(draft?.content || ''); + const [coverImage, setCoverImage] = useState(draft?.image || ''); + const [topics, setTopics] = useState(draft?.topics || ['']); const router = useRouter(); - const [user] = useLocalStorageWithEffect('user', {}); - const { showToast } = useToast(); + useEffect(() => { + if (draft) { + setTitle(draft.title); + setSummary(draft.summary); + setPrice(draft.price || 0); + setIsPaidResource(draft.price ? true : false); + setVideoUrl(draft.content); + setCoverImage(draft.image); + setTopics(draft.topics || ['']); + } + }, [draft]); + const handleSubmit = async (e) => { e.preventDefault(); let embedCode = ''; @@ -59,10 +69,13 @@ const WorkshopForm = () => { }; if (payload && payload.user) { - axios.post('/api/drafts', payload) + const url = draft ? `/api/drafts/${draft.id}` : '/api/drafts'; + const method = draft ? 'put' : 'post'; + + axios[method](url, payload) .then(response => { - if (response.status === 201) { - showToast('success', 'Success', 'Workshop saved as draft.'); + if (response.status === 200 || response.status === 201) { + showToast('success', 'Success', draft ? 'Workshop updated successfully.' : 'Workshop saved as draft.'); if (response.data?.id) { router.push(`/draft/${response.data.id}`); @@ -71,6 +84,7 @@ const WorkshopForm = () => { }) .catch(error => { console.error(error); + showToast('error', 'Error', 'Failed to save workshop. Please try again.'); }); } }; @@ -85,11 +99,13 @@ const WorkshopForm = () => { setTopics(updatedTopics); }; - const addTopic = () => { + const addTopic = (e) => { + e.preventDefault(); setTopics([...topics, '']); // Add an empty string to the topics array }; - const removeTopic = (index) => { + const removeTopic = (e, index) => { + e.preventDefault(); const updatedTopics = topics.filter((_, i) => i !== index); setTopics(updatedTopics); }; @@ -125,7 +141,7 @@ const WorkshopForm = () => {
handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> {index > 0 && ( -
))} @@ -134,7 +150,7 @@ const WorkshopForm = () => {
-
); diff --git a/src/db/models/draftModels.js b/src/db/models/draftModels.js index ae2ad61..dc16d47 100644 --- a/src/db/models/draftModels.js +++ b/src/db/models/draftModels.js @@ -33,9 +33,15 @@ export const createDraft = async (data) => { export const updateDraft = async (id, data) => { + const { user, ...otherData } = data; return await prisma.draft.update({ where: { id }, - data, + data: { + ...otherData, + user: user ? { + connect: { id: user } + } : undefined + }, }); }; diff --git a/src/pages/draft/[slug]/edit.js b/src/pages/draft/[slug]/edit.js new file mode 100644 index 0000000..39a7925 --- /dev/null +++ b/src/pages/draft/[slug]/edit.js @@ -0,0 +1,37 @@ +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/router"; +import axios from "axios"; +import ResourceForm from "@/components/forms/ResourceForm"; +import WorkshopForm from "@/components/forms/WorkshopForm"; +import CourseForm from "@/components/forms/CourseForm"; + +const Edit = () => { + const [draft, setDraft] = useState(null); + const router = useRouter(); + + useEffect(() => { + if (router.isReady) { + const { slug } = router.query; + + axios.get(`/api/drafts/${slug}`) + .then(res => { + console.log('res:', res.data); + setDraft(res.data); + }) + .catch(err => { + console.error(err); + }); + } + }, [router.isReady, router.query]); + + return ( +
+

Edit Draft

+ {draft?.type === 'course' && } + {draft?.type === 'workshop' && } + {draft?.type === 'resource' && } +
+ ); +}; + +export default Edit; diff --git a/src/pages/draft/[slug].js b/src/pages/draft/[slug]/index.js similarity index 91% rename from src/pages/draft/[slug].js rename to src/pages/draft/[slug]/index.js index 88c8929..c4e74eb 100644 --- a/src/pages/draft/[slug].js +++ b/src/pages/draft/[slug]/index.js @@ -18,10 +18,20 @@ import ReactMarkdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; const MarkdownContent = ({ content }) => { + // Function to strip HTML tags + const stripHtml = (html) => { + let tmp = document.createElement("DIV"); + tmp.innerHTML = html; + return tmp.textContent || tmp.innerText || ""; + }; + + // Strip HTML tags from the content + const plainContent = stripHtml(content); + return (
- {content} + {plainContent}
); @@ -148,7 +158,6 @@ export default function Details() { let event = {}; let type; let encryptedContent; - console.log('draft:', draft); switch (draft?.type) { case 'resource': @@ -243,12 +252,14 @@ export default function Details() { height={50} className="rounded-full mr-4" /> -

+ {user && user?.pubkey && ( +

Created by{' '} - - {user?.username} + + {user?.username || user?.pubkey.slice(0, 10)}{'... '}

+ )}
@@ -270,7 +281,10 @@ export default function Details() {
-
{ diff --git a/src/styles/globals.css b/src/styles/globals.css index 08c8c2b..38de488 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -89,4 +89,26 @@ h3 { .p-tabmenu .p-tabmenu-nav::-webkit-scrollbar { display: none; +} + +/* markdown content display styles */ +.markdown-content { + font-family: Arial, sans-serif; + line-height: 1.6; +} + +.markdown-content h1, .markdown-content h2 { + padding-bottom: 0.3em; +} + +.markdown-content pre { + border-radius: 3px; + padding: 16px; + overflow: auto; +} + +.markdown-content code { + border-radius: 3px; + font-size: 85%; + margin: 0; } \ No newline at end of file