From 7cbaf37e307823c15b35446b2b6e2fb95038e6c2 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Wed, 19 Feb 2025 16:58:04 -0600 Subject: [PATCH 01/10] Split video froms into seperate components, fixed published video editing --- .../forms/video/EditDraftVideoForm.js | 198 ++++++++++++++++ .../forms/video/EditPublishedVideoForm.js | 221 ++++++++++++++++++ src/components/forms/{ => video}/VideoForm.js | 83 +++---- src/config/appConfig.js | 2 +- src/pages/create.js | 2 +- src/pages/details/[slug]/edit.js | 5 +- src/pages/draft/[slug]/edit.js | 4 +- 7 files changed, 457 insertions(+), 58 deletions(-) create mode 100644 src/components/forms/video/EditDraftVideoForm.js create mode 100644 src/components/forms/video/EditPublishedVideoForm.js rename src/components/forms/{ => video}/VideoForm.js (78%) diff --git a/src/components/forms/video/EditDraftVideoForm.js b/src/components/forms/video/EditDraftVideoForm.js new file mode 100644 index 0000000..878e3e6 --- /dev/null +++ b/src/components/forms/video/EditDraftVideoForm.js @@ -0,0 +1,198 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { useRouter } from 'next/router'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import { InputNumber } from 'primereact/inputnumber'; +import { InputSwitch } from 'primereact/inputswitch'; +import GenericButton from '@/components/buttons/GenericButton'; +import { useToast } from '@/hooks/useToast'; +import { useSession } from 'next-auth/react'; +import { Tooltip } from 'primereact/tooltip'; +import 'primeicons/primeicons.css'; + +const EditDraftVideoForm = ({ draft }) => { + 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 [additionalLinks, setAdditionalLinks] = useState(draft?.additionalLinks || ['']); + + const router = useRouter(); + const { data: session } = useSession(); + const [user, setUser] = useState(null); + const { showToast } = useToast(); + + useEffect(() => { + if (session) { + setUser(session.user); + } + }, [session]); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + // Verify user exists + const userResponse = await axios.get(`/api/users/${user.pubkey}`); + if (!userResponse.data) { + showToast('error', 'Error', 'User not found', 'Please try again.'); + return; + } + + let embedCode = ''; + + // Check if it's a YouTube video + if (videoUrl.includes('youtube.com') || videoUrl.includes('youtu.be')) { + const videoId = videoUrl.split('v=')[1] || videoUrl.split('/').pop(); + embedCode = `
`; + } + // Check if it's a Vimeo video + else if (videoUrl.includes('vimeo.com')) { + const videoId = videoUrl.split('/').pop(); + embedCode = `
`; + } + else if (!price || !price > 0 && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { + embedCode = `
`; + } + else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm')) { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"; + const videoEmbed = `
`; + embedCode = videoEmbed; + } + + const formData = { + title, + summary, + type: 'video', + price: isPaidResource ? price : null, + content: embedCode, + image: coverImage, + topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'video'])], + additionalLinks: additionalLinks.filter(link => link.trim() !== ''), + user: userResponse.data.id, + }; + + const response = await axios.put(`/api/drafts/${draft.id}`, formData); + + if (response.status === 200) { + showToast('success', 'Success', 'Video updated successfully.'); + if (response.data?.id) { + router.push(`/draft/${response.data.id}`); + } + } + } catch (error) { + console.error(error); + showToast('error', 'Error', 'Failed to update video. Please try again.'); + } + }; + + const handleTopicChange = (index, value) => { + const updatedTopics = topics.map((topic, i) => i === index ? value : topic); + setTopics(updatedTopics); + }; + + const addTopic = (e) => { + e.preventDefault(); + setTopics([...topics, '']); + }; + + const removeTopic = (e, index) => { + e.preventDefault(); + const updatedTopics = topics.filter((_, i) => i !== index); + setTopics(updatedTopics); + }; + + const handleLinkChange = (index, value) => { + const updatedLinks = additionalLinks.map((link, i) => i === index ? value : link); + setAdditionalLinks(updatedLinks); + }; + + const addLink = (e) => { + e.preventDefault(); + setAdditionalLinks([...additionalLinks, '']); + }; + + const removeLink = (e, index) => { + e.preventDefault(); + const updatedLinks = additionalLinks.filter((_, i) => i !== index); + setAdditionalLinks(updatedLinks); + }; + + if (!draft) { + return null; + } + + return ( +
+
+ setTitle(e.target.value)} placeholder="Title" /> +
+
+ setSummary(e.target.value)} placeholder="Summary" rows={5} cols={30} /> +
+ +
+

Paid Video

+ setIsPaidResource(e.value)} /> + {isPaidResource && ( +
+ + setPrice(e.value)} placeholder="Price (sats)" /> +
+ )} +
+
+ setVideoUrl(e.target.value)} placeholder="Video URL" /> +
+
+ setCoverImage(e.target.value)} placeholder="Cover Image URL" /> +
+
+ + External Links + + + {additionalLinks.map((link, index) => ( +
+ handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> + {index > 0 && ( + removeLink(e, index)} /> + )} +
+ ))} +
+ +
+ +
+
+ {topics.map((topic, index) => ( +
+ handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> + {index > 0 && ( + removeTopic(e, index)} /> + )} +
+ ))} +
+ +
+
+
+ +
+ + ); +}; + +export default EditDraftVideoForm; diff --git a/src/components/forms/video/EditPublishedVideoForm.js b/src/components/forms/video/EditPublishedVideoForm.js new file mode 100644 index 0000000..515ac5b --- /dev/null +++ b/src/components/forms/video/EditPublishedVideoForm.js @@ -0,0 +1,221 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { useRouter } from 'next/router'; +import { useToast } from '@/hooks/useToast'; +import { useSession } from 'next-auth/react'; +import { useNDKContext } from '@/context/NDKContext'; +import GenericButton from '@/components/buttons/GenericButton'; +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { validateEvent } from '@/utils/nostr'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import { InputNumber } from 'primereact/inputnumber'; +import { InputSwitch } from 'primereact/inputswitch'; +import { Tooltip } from 'primereact/tooltip'; + +const EditPublishedVideoForm = ({ event }) => { + const router = useRouter(); + const { data: session } = useSession(); + const { showToast } = useToast(); + const { ndk, addSigner } = useNDKContext(); + const [user, setUser] = useState(null); + const [title, setTitle] = useState(event.title); + const [summary, setSummary] = useState(event.summary); + const [price, setPrice] = useState(event.price); + const [isPaidResource, setIsPaidResource] = useState(event.price ? true : false); + const [videoUrl, setVideoUrl] = useState(event.content); + const [coverImage, setCoverImage] = useState(event.image); + const [additionalLinks, setAdditionalLinks] = useState(event.additionalLinks); + const [topics, setTopics] = useState(event.topics); + + useEffect(() => { + if (session) { + setUser(session.user); + } + }, [session]); + + useEffect(() => { + console.log("event", event); + }, [event]); + + const addLink = () => { + setAdditionalLinks([...additionalLinks, '']); + } + + const removeLink = (e, index) => { + setAdditionalLinks(additionalLinks.filter((_, i) => i !== index)); + } + + const addTopic = () => { + setTopics([...topics, '']); + } + + const removeTopic = (e, index) => { + setTopics(topics.filter((_, i) => i !== index)); + } + + const handleLinkChange = (index, value) => { + const newLinks = [...additionalLinks]; + newLinks[index] = value; + setAdditionalLinks(newLinks); + }; + + const handleTopicChange = (index, value) => { + const newTopics = [...topics]; + newTopics[index] = value; + setTopics(newTopics); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + if (!ndk.signer) { + await addSigner(); + } + + let embedCode = ''; + + // Generate embed code based on video URL + if (videoUrl.includes('youtube.com') || videoUrl.includes('youtu.be')) { + const videoId = videoUrl.split('v=')[1] || videoUrl.split('/').pop(); + embedCode = `
`; + } else if (videoUrl.includes('vimeo.com')) { + const videoId = videoUrl.split('/').pop(); + embedCode = `
`; + } else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.webm')) { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"; + embedCode = price + ? `
` + : `
`; + } + + const ndkEvent = new NDKEvent(ndk); + ndkEvent.kind = event.kind; + ndkEvent.content = embedCode; + ndkEvent.created_at = Math.floor(Date.now() / 1000); + ndkEvent.pubkey = event.pubkey; + ndkEvent.tags = [ + ['title', title], + ['summary', summary], + ['image', coverImage], + ['t', 'video'], + ['d', event.d], + ]; + + // Add topics + topics.forEach(topic => { + if (topic && topic !== 'video') { + ndkEvent.tags.push(['t', topic]); + } + }); + + // Add additional links + additionalLinks.forEach(link => { + if (link) { + ndkEvent.tags.push(['r', link]); + } + }); + + // Add price if it exists + if (price) { + ndkEvent.tags.push(['price', price.toString()]); + } + + // Validate the event + const validationResult = validateEvent(ndkEvent); + if (validationResult !== true) { + console.log("validationResult", validationResult); + showToast('error', 'Error', validationResult); + return; + } + + // Publish the event + const signedEvent = await ndk.publish(ndkEvent); + + if (signedEvent) { + // update updated_at for resource in db + const updatedResource = await axios.put(`/api/resources/${event.d}`, { + updatedAt: new Date().toISOString(), + }); + + if (updatedResource) { + showToast('success', 'Success', 'Video updated successfully'); + router.push(`/details/${signedEvent.id}`); + } + } + } catch (error) { + console.error('Error updating video:', error); + showToast('error', 'Error', 'Failed to update video'); + } + }; + + return ( +
handleSubmit(e)}> +
+ setTitle(e.target.value)} placeholder="Title" /> +
+
+ setSummary(e.target.value)} placeholder="Summary" rows={5} cols={30} /> +
+ +
+

Paid Video

+ setIsPaidResource(e.value)} /> + {isPaidResource && ( +
+ + setPrice(e.value)} placeholder="Price (sats)" /> +
+ )} +
+
+ setVideoUrl(e.target.value)} placeholder="Video URL" /> +
+
+ setCoverImage(e.target.value)} placeholder="Cover Image URL" /> +
+
+ + External Links + + + {additionalLinks.map((link, index) => ( +
+ handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> + {index > 0 && ( + removeLink(e, index)} /> + )} +
+ ))} +
+ +
+ +
+
+ {topics.map((topic, index) => ( +
+ handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> + {index > 0 && ( + removeTopic(e, index)} /> + )} +
+ ))} +
+ +
+
+
+ +
+ + ); +}; + +export default EditPublishedVideoForm; diff --git a/src/components/forms/VideoForm.js b/src/components/forms/video/VideoForm.js similarity index 78% rename from src/components/forms/VideoForm.js rename to src/components/forms/video/VideoForm.js index 2a56139..b81a88a 100644 --- a/src/components/forms/VideoForm.js +++ b/src/components/forms/video/VideoForm.js @@ -14,19 +14,18 @@ import 'primereact/resources/primereact.min.css'; const CDN_ENDPOINT = process.env.NEXT_PUBLIC_CDN_ENDPOINT; -// todo need to handle case where published video is being edited and not just draft -const VideoForm = ({ 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 [additionalLinks, setAdditionalLinks] = useState(draft?.additionalLinks || ['']); +const VideoForm = () => { + 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 [additionalLinks, setAdditionalLinks] = useState(['']); const router = useRouter(); - const { data: session, status } = useSession(); + const { data: session } = useSession(); const [user, setUser] = useState(null); const { showToast } = useToast(); @@ -36,19 +35,6 @@ const VideoForm = ({ draft = null }) => { } }, [session]); - 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 || ['']); - setAdditionalLinks(draft.additionalLinks || ['']); - } - }, [draft]); - const handleSubmit = async (e) => { e.preventDefault(); let embedCode = ''; @@ -73,43 +59,38 @@ const VideoForm = ({ draft = null }) => { } // Add more conditions here for other video services - const userResponse = await axios.get(`/api/users/${user.pubkey}`); - - if (!userResponse.data) { - showToast('error', 'Error', 'User not found', 'Please try again.'); - return; - } - - const payload = { + const formData = { title, summary, type: 'video', price: isPaidResource ? price : null, content: embedCode, image: coverImage, - user: userResponse.data.id, topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'video'])], additionalLinks: additionalLinks.filter(link => link.trim() !== ''), }; - if (payload && payload.user) { - const url = draft ? `/api/drafts/${draft.id}` : '/api/drafts'; - const method = draft ? 'put' : 'post'; + try { + const userResponse = await axios.get(`/api/users/${user.pubkey}`); + if (!userResponse.data) { + showToast('error', 'Error', 'User not found', 'Please try again.'); + return; + } - axios[method](url, payload) - .then(response => { - if (response.status === 200 || response.status === 201) { - showToast('success', 'Success', draft ? 'Video updated successfully.' : 'Video saved as draft.'); - - if (response.data?.id) { - router.push(`/draft/${response.data.id}`); - } - } - }) - .catch(error => { - console.error(error); - showToast('error', 'Error', 'Failed to save video. Please try again.'); - }); + const response = await axios.post('/api/drafts', { + ...formData, + user: userResponse.data.id, + }); + + if (response.status === 201) { + showToast('success', 'Success', 'Video saved as draft.'); + if (response.data?.id) { + router.push(`/draft/${response.data.id}`); + } + } + } catch (error) { + console.error(error); + showToast('error', 'Error', 'Failed to save video. Please try again.'); } }; @@ -209,7 +190,7 @@ const VideoForm = ({ draft = null }) => {
- +
); diff --git a/src/config/appConfig.js b/src/config/appConfig.js index aa17665..e1a15fe 100644 --- a/src/config/appConfig.js +++ b/src/config/appConfig.js @@ -10,7 +10,7 @@ const appConfig = { "wss://purplerelay.com/", "wss://relay.devs.tools/" ], - authorPubkeys: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741", "c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345"], + authorPubkeys: ["f33c8a9617cb15f705fc70cd461cfd6eaf22f9e24c33eabad981648e5ec6f741", "c67cd3e1a83daa56cff16f635db2fdb9ed9619300298d4701a58e68e84098345", "6260f29fa75c91aaa292f082e5e87b438d2ab4fdf96af398567b01802ee2fcd4"], customLightningAddresses: [ { // todo remove need for lowercase diff --git a/src/pages/create.js b/src/pages/create.js index 42c7b5d..747cc14 100644 --- a/src/pages/create.js +++ b/src/pages/create.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import MenuTab from "@/components/menutab/MenuTab"; import DocumentForm from "@/components/forms/DocumentForm"; -import VideoForm from "@/components/forms/VideoForm"; +import VideoForm from "@/components/forms/video/VideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; import { useIsAdmin } from "@/hooks/useIsAdmin"; diff --git a/src/pages/details/[slug]/edit.js b/src/pages/details/[slug]/edit.js index 5892290..24edf8c 100644 --- a/src/pages/details/[slug]/edit.js +++ b/src/pages/details/[slug]/edit.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import { parseEvent } from "@/utils/nostr"; import DocumentForm from "@/components/forms/DocumentForm"; -import VideoForm from "@/components/forms/VideoForm"; +import EditPublishedVideoForm from "@/components/forms/video/EditPublishedVideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; import { useNDKContext } from "@/context/NDKContext"; @@ -10,7 +10,6 @@ import { useToast } from "@/hooks/useToast"; export default function Edit() { const [event, setEvent] = useState(null); - const {ndk, addSigner} = useNDKContext(); const router = useRouter(); const { showToast } = useToast(); @@ -40,7 +39,7 @@ export default function Edit() {

Edit Published Event

{event?.topics.includes('course') && } - {event?.topics.includes('video') && !event?.topics.includes('document') && } + {event?.topics.includes('video') && !event?.topics.includes('document') && } {event?.topics.includes('document') && !event?.topics.includes('video') && } {event?.topics.includes('video') && event?.topics.includes('document') && }
diff --git a/src/pages/draft/[slug]/edit.js b/src/pages/draft/[slug]/edit.js index 56056fb..cb96dca 100644 --- a/src/pages/draft/[slug]/edit.js +++ b/src/pages/draft/[slug]/edit.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import axios from "axios"; import DocumentForm from "@/components/forms/DocumentForm"; -import VideoForm from "@/components/forms/VideoForm"; +import EditDraftVideoForm from "@/components/forms/video/EditDraftVideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; import { useIsAdmin } from "@/hooks/useIsAdmin"; @@ -37,7 +37,7 @@ const Edit = () => {

Edit Draft

{draft?.type === 'course' && } - {draft?.type === 'video' && } + {draft?.type === 'video' && } {draft?.type === 'document' && } {draft?.type === 'combined' && }
From f76e1bb3cf184e3b4fa2c5fc41371b36299f93a4 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sat, 22 Feb 2025 17:07:15 -0600 Subject: [PATCH 02/10] Split up document forms, fix editing of published content, also fix reencrypting content when editing published paid content. --- .../forms/{ => document}/DocumentForm.js | 179 ++++---------- .../forms/document/EditDraftDocumentForm.js | 184 ++++++++++++++ .../document/EditPublishedDocumentForm.js | 229 ++++++++++++++++++ .../forms/video/EditPublishedVideoForm.js | 41 +++- src/pages/create.js | 2 +- src/pages/details/[slug]/edit.js | 4 +- src/pages/draft/[slug]/edit.js | 4 +- 7 files changed, 488 insertions(+), 155 deletions(-) rename src/components/forms/{ => document}/DocumentForm.js (54%) create mode 100644 src/components/forms/document/EditDraftDocumentForm.js create mode 100644 src/components/forms/document/EditPublishedDocumentForm.js diff --git a/src/components/forms/DocumentForm.js b/src/components/forms/document/DocumentForm.js similarity index 54% rename from src/components/forms/DocumentForm.js rename to src/components/forms/document/DocumentForm.js index c03e741..2a2d79e 100644 --- a/src/components/forms/DocumentForm.js +++ b/src/components/forms/document/DocumentForm.js @@ -8,10 +8,10 @@ import GenericButton from "@/components/buttons/GenericButton"; import { useRouter } from "next/router"; import { useSession } from "next-auth/react"; import { useToast } from "@/hooks/useToast"; -import { useNDKContext } from "@/context/NDKContext"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; import dynamic from 'next/dynamic'; -import { useEncryptContent } from '@/hooks/encryption/useEncryptContent'; +import 'primeicons/primeicons.css'; +import { Tooltip } from 'primereact/tooltip'; +import 'primereact/resources/primereact.min.css'; const MDEditor = dynamic( () => import("@uiw/react-md-editor"), @@ -19,26 +19,21 @@ const MDEditor = dynamic( ssr: false, } ); -import 'primeicons/primeicons.css'; -import { Tooltip } from 'primereact/tooltip'; -import 'primereact/resources/primereact.min.css'; -const DocumentForm = ({ draft = null, isPublished = false }) => { - 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 [coverImage, setCoverImage] = useState(draft?.image || ''); - const [topics, setTopics] = useState(draft?.topics || ['']); - const [content, setContent] = useState(draft?.content || ''); +const DocumentForm = () => { + const [title, setTitle] = useState(''); + const [summary, setSummary] = useState(''); + const [isPaidResource, setIsPaidResource] = useState(false); + const [price, setPrice] = useState(0); + const [coverImage, setCoverImage] = useState(''); + const [topics, setTopics] = useState(['']); + const [content, setContent] = useState(''); const [user, setUser] = useState(null); - const [additionalLinks, setAdditionalLinks] = useState(draft?.additionalLinks || ['']); - const { encryptContent, isLoading: encryptLoading, error: encryptError } = useEncryptContent(); + const [additionalLinks, setAdditionalLinks] = useState(['']); - const { data: session, status } = useSession(); + const { data: session } = useSession(); const { showToast } = useToast(); const router = useRouter(); - const { ndk, addSigner } = useNDKContext(); useEffect(() => { if (session) { @@ -50,129 +45,39 @@ const DocumentForm = ({ draft = null, isPublished = false }) => { setContent(value || ''); }, []); - useEffect(() => { - if (draft) { - setTitle(draft.title); - setSummary(draft.summary); - setIsPaidResource(draft.price ? true : false); - setPrice(draft.price || 0); - setContent(draft.content); - setCoverImage(draft.image); - setTopics(draft.topics || []); - setAdditionalLinks(draft.additionalLinks || []); - } - }, [draft]); - - const buildEvent = async (draft) => { - const dTag = draft.d - const event = new NDKEvent(ndk); - let encryptedContent; - - if (draft?.price) { - encryptedContent = await encryptContent(draft.content); - } - - event.kind = draft?.price ? 30402 : 30023; // Determine kind based on if price is present - event.content = draft?.price ? encryptedContent : draft.content; - event.created_at = Math.floor(Date.now() / 1000); - event.pubkey = user.pubkey; - event.tags = [ - ['d', dTag], - ['title', draft.title], - ['summary', draft.summary], - ['image', draft.image], - ...draft.topics.map(topic => ['t', topic]), - ['published_at', Math.floor(Date.now() / 1000).toString()], - ...(draft?.price ? [['price', draft.price.toString()], ['location', `https://plebdevs.com/details/${draft.id}`]] : []), - ]; - - return event; - }; - - const handlePublishedResource = async (e) => { - e.preventDefault(); - - // create new object with state fields - const updatedDraft = { - title, - summary, - price, - content, - d: draft.d, - image: coverImage, - topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])], - additionalLinks: additionalLinks.filter(link => link.trim() !== '') - } - - const event = await buildEvent(updatedDraft); - - try { - if (!ndk.signer) { - await addSigner(); - } - - await ndk.connect(); - - const published = await ndk.publish(event); - - if (published) { - // update the resource with new noteId - const response = await axios.put(`/api/resources/${draft.d}`, { noteId: event.id }); - showToast('success', 'Success', 'Document published successfully.'); - router.push(`/details/${event.id}`); - } else { - showToast('error', 'Error', 'Failed to publish document. Please try again.'); - } - } catch (error) { - console.error(error); - showToast('error', 'Error', 'Failed to publish document. Please try again.'); - } - } - const handleSubmit = async (e) => { e.preventDefault(); - const userResponse = await axios.get(`/api/users/${user.pubkey}`); + try { + const userResponse = await axios.get(`/api/users/${user.pubkey}`); + if (!userResponse.data) { + showToast('error', 'Error', 'User not found', 'Please try again.'); + return; + } - if (!userResponse.data) { - showToast('error', 'Error', 'User not found', 'Please try again.'); - return; - } + const payload = { + title, + summary, + type: 'document', + price: isPaidResource ? price : null, + content, + image: coverImage, + topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])], + additionalLinks: additionalLinks.filter(link => link.trim() !== ''), + user: userResponse.data.id + }; - const payload = { - title, - summary, - type: 'document', - price: isPaidResource ? price : null, - content, - image: coverImage, - topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])], - additionalLinks: additionalLinks.filter(link => link.trim() !== '') - }; + const response = await 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 === 200 || response.status === 201) { - showToast('success', 'Success', draft ? 'Document updated successfully.' : 'Document saved as draft.'); - - if (response.data?.id) { - router.push(`/draft/${response.data.id}`); - } - } - }) - .catch(error => { - console.error(error); - showToast('error', 'Error', 'Failed to save document. Please try again.'); - }); + if (response.status === 201) { + showToast('success', 'Success', 'Document saved as draft.'); + if (response.data?.id) { + router.push(`/draft/${response.data.id}`); + } + } + } catch (error) { + console.error(error); + showToast('error', 'Error', 'Failed to save document. Please try again.'); } }; @@ -209,7 +114,7 @@ const DocumentForm = ({ draft = null, isPublished = false }) => { }; return ( -
+
setTitle(e.target.value)} placeholder="Title" />
@@ -277,7 +182,7 @@ const DocumentForm = ({ draft = null, isPublished = false }) => {
- +
); diff --git a/src/components/forms/document/EditDraftDocumentForm.js b/src/components/forms/document/EditDraftDocumentForm.js new file mode 100644 index 0000000..7f4ec98 --- /dev/null +++ b/src/components/forms/document/EditDraftDocumentForm.js @@ -0,0 +1,184 @@ +import React, { useState, useEffect, useCallback } from "react"; +import axios from "axios"; +import { InputText } from "primereact/inputtext"; +import { InputTextarea } from "primereact/inputtextarea"; +import { InputNumber } from "primereact/inputnumber"; +import { InputSwitch } from "primereact/inputswitch"; +import GenericButton from "@/components/buttons/GenericButton"; +import { useRouter } from "next/router"; +import { useSession } from "next-auth/react"; +import { useToast } from "@/hooks/useToast"; +import dynamic from 'next/dynamic'; +import { Tooltip } from 'primereact/tooltip'; + +const MDEditor = dynamic(() => import("@uiw/react-md-editor"), { ssr: false }); + +const EditDraftDocumentForm = ({ draft }) => { + 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 [coverImage, setCoverImage] = useState(draft?.image || ''); + const [topics, setTopics] = useState(draft?.topics || ['']); + const [content, setContent] = useState(draft?.content || ''); + const [user, setUser] = useState(null); + const [additionalLinks, setAdditionalLinks] = useState(draft?.additionalLinks || ['']); + + const { data: session } = useSession(); + const { showToast } = useToast(); + const router = useRouter(); + + useEffect(() => { + if (session) { + setUser(session.user); + } + }, [session]); + + const handleContentChange = useCallback((value) => { + setContent(value || ''); + }, []); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + 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, + type: 'document', + price: isPaidResource ? price : null, + content, + image: coverImage, + topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'document'])], + additionalLinks: additionalLinks.filter(link => link.trim() !== '') + }; + + const response = await axios.put(`/api/drafts/${draft.id}`, payload); + + if (response.status === 200) { + showToast('success', 'Success', 'Document updated successfully.'); + if (response.data?.id) { + router.push(`/draft/${response.data.id}`); + } + } + } catch (error) { + console.error(error); + showToast('error', 'Error', 'Failed to update document. Please try again.'); + } + }; + + const handleTopicChange = (index, value) => { + const updatedTopics = topics.map((topic, i) => i === index ? value : topic); + setTopics(updatedTopics); + }; + + const addTopic = (e) => { + e.preventDefault(); + setTopics([...topics, '']); + }; + + const removeTopic = (e, index) => { + e.preventDefault(); + const updatedTopics = topics.filter((_, i) => i !== index); + setTopics(updatedTopics); + }; + + const handleLinkChange = (index, value) => { + const updatedLinks = additionalLinks.map((link, i) => i === index ? value : link); + setAdditionalLinks(updatedLinks); + }; + + const addLink = (e) => { + e.preventDefault(); + setAdditionalLinks([...additionalLinks, '']); + }; + + const removeLink = (e, index) => { + e.preventDefault(); + const updatedLinks = additionalLinks.filter((_, i) => i !== index); + setAdditionalLinks(updatedLinks); + }; + + return ( +
+
+ setTitle(e.target.value)} placeholder="Title" /> +
+
+ setSummary(e.target.value)} placeholder="Summary" rows={5} cols={30} /> +
+ +
+

Paid Document

+ setIsPaidResource(e.value)} /> + {isPaidResource && ( +
+ + setPrice(e.value)} placeholder="Price (sats)" /> +
+ )} +
+
+ setCoverImage(e.target.value)} placeholder="Cover Image URL" /> +
+
+ Content +
+ +
+
+
+ + External Links + + + {additionalLinks.map((link, index) => ( +
+ handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> + {index > 0 && ( + removeLink(e, index)} /> + )} +
+ ))} +
+ +
+ +
+
+ {topics.map((topic, index) => ( +
+ handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> + {index > 0 && ( + removeTopic(e, index)} /> + )} +
+ ))} +
+ +
+
+
+ +
+ + ); +}; + +export default EditDraftDocumentForm; \ No newline at end of file diff --git a/src/components/forms/document/EditPublishedDocumentForm.js b/src/components/forms/document/EditPublishedDocumentForm.js new file mode 100644 index 0000000..c299687 --- /dev/null +++ b/src/components/forms/document/EditPublishedDocumentForm.js @@ -0,0 +1,229 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; +import { useRouter } from 'next/router'; +import { useToast } from '@/hooks/useToast'; +import { useSession } from 'next-auth/react'; +import { useNDKContext } from '@/context/NDKContext'; +import { useEncryptContent } from '@/hooks/encryption/useEncryptContent'; +import GenericButton from '@/components/buttons/GenericButton'; +import { NDKEvent } from '@nostr-dev-kit/ndk'; +import { validateEvent } from '@/utils/nostr'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import { InputNumber } from 'primereact/inputnumber'; +import { InputSwitch } from 'primereact/inputswitch'; +import { Tooltip } from 'primereact/tooltip'; +import dynamic from 'next/dynamic'; + +const MDEditor = dynamic(() => import("@uiw/react-md-editor"), { ssr: false }); + +const EditPublishedDocumentForm = ({ event }) => { + const router = useRouter(); + const { data: session } = useSession(); + const { showToast } = useToast(); + const { ndk, addSigner } = useNDKContext(); + const { encryptContent } = useEncryptContent(); + const [user, setUser] = useState(null); + const [title, setTitle] = useState(event.title); + const [summary, setSummary] = useState(event.summary); + const [price, setPrice] = useState(event.price); + const [isPaidResource, setIsPaidResource] = useState(event.price ? true : false); + const [content, setContent] = useState(event.content); + const [coverImage, setCoverImage] = useState(event.image); + const [additionalLinks, setAdditionalLinks] = useState(event.additionalLinks); + const [topics, setTopics] = useState(event.topics); + + useEffect(() => { + if (session) { + setUser(session.user); + } + }, [session]); + + const handleContentChange = useCallback((value) => { + setContent(value || ''); + }, []); + + const addLink = () => { + setAdditionalLinks([...additionalLinks, '']); + } + + const removeLink = (e, index) => { + setAdditionalLinks(additionalLinks.filter((_, i) => i !== index)); + } + + const addTopic = () => { + setTopics([...topics, '']); + } + + const removeTopic = (e, index) => { + setTopics(topics.filter((_, i) => i !== index)); + } + + const handleLinkChange = (index, value) => { + const newLinks = [...additionalLinks]; + newLinks[index] = value; + setAdditionalLinks(newLinks); + }; + + const handleTopicChange = (index, value) => { + const newTopics = [...topics]; + newTopics[index] = value; + setTopics(newTopics); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + if (!ndk.signer) { + await addSigner(); + } + + // Encrypt content if it's a paid resource + let finalContent = content; + if (isPaidResource && price > 0) { + finalContent = await encryptContent(content); + if (!finalContent) { + showToast('error', 'Error', 'Failed to encrypt content'); + return; + } + } + + const ndkEvent = new NDKEvent(ndk); + ndkEvent.kind = event.kind; + ndkEvent.content = finalContent; + ndkEvent.created_at = Math.floor(Date.now() / 1000); + ndkEvent.pubkey = event.pubkey; + ndkEvent.tags = [ + ['title', title], + ['summary', summary], + ['image', coverImage], + ['t', 'document'], + ['d', event.d], + ]; + + // Add topics + topics.forEach(topic => { + if (topic && topic !== 'document') { + ndkEvent.tags.push(['t', topic]); + } + }); + + // Add additional links + additionalLinks.forEach(link => { + if (link) { + ndkEvent.tags.push(['r', link]); + } + }); + + // Add price if it exists + if (price) { + ndkEvent.tags.push(['price', price.toString()]); + } + + // Validate the event + const validationResult = validateEvent(ndkEvent); + if (validationResult !== true) { + console.log("validationResult", validationResult); + showToast('error', 'Error', validationResult); + return; + } + + // Publish the event + const signedEvent = await ndk.publish(ndkEvent); + + if (signedEvent) { + // update updated_at for resource in db + const updatedResource = await axios.put(`/api/resources/${event.d}`, { + updatedAt: new Date().toISOString(), + }); + + if (updatedResource && updatedResource.status === 200) { + showToast('success', 'Success', 'Document updated successfully'); + router.push(`/details/${updatedResource.data.noteId}`); + } else { + showToast('error', 'Error', 'Failed to update document'); + } + } + } catch (error) { + console.error('Error updating document:', error); + showToast('error', 'Error', 'Failed to update document'); + } + }; + + return ( +
+
+ setTitle(e.target.value)} placeholder="Title" /> +
+
+ setSummary(e.target.value)} placeholder="Summary" rows={5} cols={30} /> +
+ +
+

Paid Document

+ setIsPaidResource(e.value)} /> + {isPaidResource && ( +
+ + setPrice(e.value)} placeholder="Price (sats)" /> +
+ )} +
+
+ setCoverImage(e.target.value)} placeholder="Cover Image URL" /> +
+
+ Content +
+ +
+
+
+ + External Links + + + {additionalLinks.map((link, index) => ( +
+ handleLinkChange(index, e.target.value)} placeholder="https://example.com" className="w-full mt-2" /> + {index > 0 && ( + removeLink(e, index)} /> + )} +
+ ))} +
+ +
+ +
+
+ {topics.map((topic, index) => ( +
+ handleTopicChange(index, e.target.value)} placeholder="Topic" className="w-full mt-2" /> + {index > 0 && ( + removeTopic(e, index)} /> + )} +
+ ))} +
+ +
+
+
+ +
+ + ); +}; + +export default EditPublishedDocumentForm; diff --git a/src/components/forms/video/EditPublishedVideoForm.js b/src/components/forms/video/EditPublishedVideoForm.js index 515ac5b..7d79e4d 100644 --- a/src/components/forms/video/EditPublishedVideoForm.js +++ b/src/components/forms/video/EditPublishedVideoForm.js @@ -12,6 +12,7 @@ import { InputTextarea } from 'primereact/inputtextarea'; import { InputNumber } from 'primereact/inputnumber'; import { InputSwitch } from 'primereact/inputswitch'; import { Tooltip } from 'primereact/tooltip'; +import { useEncryptContent } from '@/hooks/encryption/useEncryptContent'; const EditPublishedVideoForm = ({ event }) => { const router = useRouter(); @@ -28,6 +29,8 @@ const EditPublishedVideoForm = ({ event }) => { const [additionalLinks, setAdditionalLinks] = useState(event.additionalLinks); const [topics, setTopics] = useState(event.topics); + const { encryptContent } = useEncryptContent(); + useEffect(() => { if (session) { setUser(session.user); @@ -74,7 +77,7 @@ const EditPublishedVideoForm = ({ event }) => { } let embedCode = ''; - + // Generate embed code based on video URL if (videoUrl.includes('youtube.com') || videoUrl.includes('youtu.be')) { const videoId = videoUrl.split('v=')[1] || videoUrl.split('/').pop(); @@ -82,11 +85,21 @@ const EditPublishedVideoForm = ({ event }) => { } else if (videoUrl.includes('vimeo.com')) { const videoId = videoUrl.split('/').pop(); embedCode = `
`; - } else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.webm')) { + } else if (!price || !price > 0 && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { + embedCode = `
`; + } else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm')) { const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"; - embedCode = price - ? `
` - : `
`; + const videoEmbed = `
`; + embedCode = videoEmbed; + } + + // Encrypt content if it's a paid resource + if (isPaidResource && price > 0) { + embedCode = await encryptContent(embedCode); + if (!embedCode) { + showToast('error', 'Error', 'Failed to encrypt content'); + return; + } } const ndkEvent = new NDKEvent(ndk); @@ -138,9 +151,11 @@ const EditPublishedVideoForm = ({ event }) => { updatedAt: new Date().toISOString(), }); - if (updatedResource) { + if (updatedResource && updatedResource.status === 200) { showToast('success', 'Success', 'Video updated successfully'); - router.push(`/details/${signedEvent.id}`); + router.push(`/details/${updatedResource.data.noteId}`); + } else { + showToast('error', 'Error', 'Failed to update video'); } } } catch (error) { @@ -177,12 +192,12 @@ const EditPublishedVideoForm = ({ event }) => {
External Links - {additionalLinks.map((link, index) => ( diff --git a/src/pages/create.js b/src/pages/create.js index 747cc14..20f6793 100644 --- a/src/pages/create.js +++ b/src/pages/create.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import MenuTab from "@/components/menutab/MenuTab"; -import DocumentForm from "@/components/forms/DocumentForm"; +import DocumentForm from "@/components/forms/document/DocumentForm"; import VideoForm from "@/components/forms/video/VideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; diff --git a/src/pages/details/[slug]/edit.js b/src/pages/details/[slug]/edit.js index 24edf8c..1a7d58b 100644 --- a/src/pages/details/[slug]/edit.js +++ b/src/pages/details/[slug]/edit.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import { parseEvent } from "@/utils/nostr"; -import DocumentForm from "@/components/forms/DocumentForm"; +import EditPublishedDocumentForm from "@/components/forms/document/EditPublishedDocumentForm"; import EditPublishedVideoForm from "@/components/forms/video/EditPublishedVideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; @@ -40,7 +40,7 @@ export default function Edit() {

Edit Published Event

{event?.topics.includes('course') && } {event?.topics.includes('video') && !event?.topics.includes('document') && } - {event?.topics.includes('document') && !event?.topics.includes('video') && } + {event?.topics.includes('document') && !event?.topics.includes('video') && } {event?.topics.includes('video') && event?.topics.includes('document') && }
); diff --git a/src/pages/draft/[slug]/edit.js b/src/pages/draft/[slug]/edit.js index cb96dca..518d06e 100644 --- a/src/pages/draft/[slug]/edit.js +++ b/src/pages/draft/[slug]/edit.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import axios from "axios"; -import DocumentForm from "@/components/forms/DocumentForm"; +import EditDraftDocumentForm from "@/components/forms/document/EditDraftDocumentForm"; import EditDraftVideoForm from "@/components/forms/video/EditDraftVideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import CombinedResourceForm from "@/components/forms/CombinedResourceForm"; @@ -38,7 +38,7 @@ const Edit = () => {

Edit Draft

{draft?.type === 'course' && } {draft?.type === 'video' && } - {draft?.type === 'document' && } + {draft?.type === 'document' && } {draft?.type === 'combined' && } ); From 0c5ac0d942b32fe38a91595c495b0e39e5126bad Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sat, 22 Feb 2025 17:25:29 -0600 Subject: [PATCH 03/10] Fix spacing on markdown documents --- .../content/combined/CombinedDetails.js | 20 +++++++++---------- .../content/courses/CombinedLesson.js | 4 ++-- .../content/courses/DocumentLesson.js | 4 ++-- .../content/documents/DocumentDetails.js | 4 ++-- .../feeds/messages/CommunityMessage.js | 14 ++++++------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/content/combined/CombinedDetails.js b/src/components/content/combined/CombinedDetails.js index fb4e616..f28124f 100644 --- a/src/components/content/combined/CombinedDetails.js +++ b/src/components/content/combined/CombinedDetails.js @@ -118,7 +118,7 @@ const CombinedDetails = ({ processedEvent, topics, title, summary, image, price, } if (processedEvent?.content) { - return ; + return ; } return null; @@ -222,14 +222,14 @@ const CombinedDetails = ({ processedEvent, topics, title, summary, image, price, {renderPaymentMessage()}
{course && ( - window.open(`/course/${course}`, '_blank')} - label={isMobileView ? "Course" : "Open Course"} - tooltip="This is a lesson in a course" - tooltipOptions={{ position: 'top' }} + window.open(`/course/${course}`, '_blank')} + label={isMobileView ? "Course" : "Open Course"} + tooltip="This is a lesson in a course" + tooltipOptions={{ position: 'top' }} /> )}
+ {renderContent()} - {renderContent()} ); }; diff --git a/src/components/content/courses/CombinedLesson.js b/src/components/content/courses/CombinedLesson.js index ce06358..3428395 100644 --- a/src/components/content/courses/CombinedLesson.js +++ b/src/components/content/courses/CombinedLesson.js @@ -116,7 +116,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple if (isPaid && decryptionPerformed) { return (
- +
); } @@ -158,7 +158,7 @@ const CombinedLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple if (lesson?.content) { return (
- +
); } diff --git a/src/components/content/courses/DocumentLesson.js b/src/components/content/courses/DocumentLesson.js index bd98df4..5efecab 100644 --- a/src/components/content/courses/DocumentLesson.js +++ b/src/components/content/courses/DocumentLesson.js @@ -64,7 +64,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple const renderContent = () => { if (isPaid && decryptionPerformed) { - return ; + return ; } if (isPaid && !decryptionPerformed) { return ( @@ -79,7 +79,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple ); } if (lesson?.content) { - return ; + return ; } return null; } diff --git a/src/components/content/documents/DocumentDetails.js b/src/components/content/documents/DocumentDetails.js index ca067e8..6bc2617 100644 --- a/src/components/content/documents/DocumentDetails.js +++ b/src/components/content/documents/DocumentDetails.js @@ -120,7 +120,7 @@ const DocumentDetails = ({ processedEvent, topics, title, summary, image, price, ); } if (processedEvent?.content) { - return ; + return ; } return null; } @@ -235,8 +235,8 @@ const DocumentDetails = ({ processedEvent, topics, title, summary, image, price, )} + {renderContent()} - {renderContent()} ) } diff --git a/src/components/feeds/messages/CommunityMessage.js b/src/components/feeds/messages/CommunityMessage.js index 6236b2f..bdb39dc 100644 --- a/src/components/feeds/messages/CommunityMessage.js +++ b/src/components/feeds/messages/CommunityMessage.js @@ -20,8 +20,7 @@ const StackerNewsIconComponent = () => ( const headerTemplate = (options, windowWidth, platform, id) => { return ( -
- +
768 ? `View ${platform === 'nostr' ? 'on' : 'in'} ${platform}` : null} icon="pi pi-external-link" @@ -31,6 +30,7 @@ const headerTemplate = (options, windowWidth, platform, id) => { tooltip={windowWidth < 768 ? `View ${platform === 'nostr' ? 'on' : 'in'} ${platform}` : null} tooltipOptions={{ position: 'left' }} /> +
); }; @@ -104,11 +104,6 @@ const CommunityMessage = ({ message, searchQuery, windowWidth, platform }) => { ) : (
- {platform !== "nostr" ? ( -

- {new Date(message.timestamp).toLocaleString()} -

- ) :
} 768 ? `View in ${platform}` : null} icon="pi pi-external-link" @@ -118,6 +113,11 @@ const CommunityMessage = ({ message, searchQuery, windowWidth, platform }) => { tooltip={windowWidth < 768 ? `View in ${platform}` : null} tooltipOptions={{ position: 'left' }} /> + {platform !== "nostr" ? ( +

+ {new Date(message.timestamp).toLocaleString()} +

+ ) :
}
) } From eaa2b77e064111fbadc2988783f2f03b9894c774 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Thu, 27 Feb 2025 11:26:32 -0600 Subject: [PATCH 04/10] Fixed published course editing --- src/components/forms/course/PublishedCourseForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/forms/course/PublishedCourseForm.js b/src/components/forms/course/PublishedCourseForm.js index 6f639d7..f2e847b 100644 --- a/src/components/forms/course/PublishedCourseForm.js +++ b/src/components/forms/course/PublishedCourseForm.js @@ -97,7 +97,7 @@ const PublishedCourseForm = ({ course }) => { // Update course in database await axios.put(`/api/courses/${course.d}`, { - price: isPaidCourse ? price : 0, + price: isPaidCourse ? Number(price) : 0, lessons: lessons.map(lesson => ({ resourceId: lesson.d, draftId: null, From b6830899346d81e2acb4da9bb5e615198b2049b6 Mon Sep 17 00:00:00 2001 From: Austin Kelsay <53542748+AustinKelsay@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:46:33 -0600 Subject: [PATCH 05/10] Update src/components/forms/document/EditPublishedDocumentForm.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../forms/document/EditPublishedDocumentForm.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/forms/document/EditPublishedDocumentForm.js b/src/components/forms/document/EditPublishedDocumentForm.js index c299687..6a1647f 100644 --- a/src/components/forms/document/EditPublishedDocumentForm.js +++ b/src/components/forms/document/EditPublishedDocumentForm.js @@ -81,9 +81,15 @@ const EditPublishedDocumentForm = ({ event }) => { // Encrypt content if it's a paid resource let finalContent = content; if (isPaidResource && price > 0) { - finalContent = await encryptContent(content); - if (!finalContent) { - showToast('error', 'Error', 'Failed to encrypt content'); + try { + finalContent = await encryptContent(content); + if (!finalContent) { + showToast('error', 'Error', 'Failed to encrypt content'); + return; + } + } catch (error) { + console.error('Encryption error:', error); + showToast('error', 'Error', 'Failed to encrypt content: ' + error.message); return; } } From 7f5f4f954784decb26f3f1062722befb3e97af73 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Thu, 27 Feb 2025 11:47:19 -0600 Subject: [PATCH 06/10] Add CDN endpoint env var --- src/components/forms/video/EditDraftVideoForm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/forms/video/EditDraftVideoForm.js b/src/components/forms/video/EditDraftVideoForm.js index 878e3e6..401b097 100644 --- a/src/components/forms/video/EditDraftVideoForm.js +++ b/src/components/forms/video/EditDraftVideoForm.js @@ -11,6 +11,8 @@ import { useSession } from 'next-auth/react'; import { Tooltip } from 'primereact/tooltip'; import 'primeicons/primeicons.css'; +const CDN_ENDPOINT = process.env.NEXT_PUBLIC_CDN_ENDPOINT; + const EditDraftVideoForm = ({ draft }) => { const [title, setTitle] = useState(draft?.title || ''); const [summary, setSummary] = useState(draft?.summary || ''); From fc7af5383a42d965e43f4d5b4e750f7db0607ad2 Mon Sep 17 00:00:00 2001 From: Austin Kelsay <53542748+AustinKelsay@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:48:15 -0600 Subject: [PATCH 07/10] Update src/components/forms/video/EditDraftVideoForm.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/forms/video/EditDraftVideoForm.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/forms/video/EditDraftVideoForm.js b/src/components/forms/video/EditDraftVideoForm.js index 401b097..893d01a 100644 --- a/src/components/forms/video/EditDraftVideoForm.js +++ b/src/components/forms/video/EditDraftVideoForm.js @@ -57,8 +57,9 @@ const EditDraftVideoForm = ({ draft }) => { const videoId = videoUrl.split('/').pop(); embedCode = `
`; } - else if (!price || !price > 0 && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { - embedCode = `
`; + else if ((!price || price <= 0) && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"; + embedCode = `
`; } else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm')) { const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000"; From 4e7a3a32aa18317bc124a1163c3a831f92632623 Mon Sep 17 00:00:00 2001 From: Austin Kelsay <53542748+AustinKelsay@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:49:03 -0600 Subject: [PATCH 08/10] Update src/components/forms/document/EditPublishedDocumentForm.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/forms/document/EditPublishedDocumentForm.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/forms/document/EditPublishedDocumentForm.js b/src/components/forms/document/EditPublishedDocumentForm.js index 6a1647f..b486467 100644 --- a/src/components/forms/document/EditPublishedDocumentForm.js +++ b/src/components/forms/document/EditPublishedDocumentForm.js @@ -51,11 +51,13 @@ const EditPublishedDocumentForm = ({ event }) => { setAdditionalLinks(additionalLinks.filter((_, i) => i !== index)); } - const addTopic = () => { + const addTopic = (e) => { + e.preventDefault(); setTopics([...topics, '']); } const removeTopic = (e, index) => { + e.preventDefault(); setTopics(topics.filter((_, i) => i !== index)); } From d1a8289e2a5fa54551e3d064d71ef74e2b07a277 Mon Sep 17 00:00:00 2001 From: Austin Kelsay <53542748+AustinKelsay@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:51:30 -0600 Subject: [PATCH 09/10] Update src/components/forms/document/EditPublishedDocumentForm.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/forms/document/EditPublishedDocumentForm.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/forms/document/EditPublishedDocumentForm.js b/src/components/forms/document/EditPublishedDocumentForm.js index b486467..b72eb42 100644 --- a/src/components/forms/document/EditPublishedDocumentForm.js +++ b/src/components/forms/document/EditPublishedDocumentForm.js @@ -43,11 +43,13 @@ const EditPublishedDocumentForm = ({ event }) => { setContent(value || ''); }, []); - const addLink = () => { + const addLink = (e) => { + e.preventDefault(); setAdditionalLinks([...additionalLinks, '']); } const removeLink = (e, index) => { + e.preventDefault(); setAdditionalLinks(additionalLinks.filter((_, i) => i !== index)); } From e342fc83a25f7152b7156bef0cae9deaa6adb3fd Mon Sep 17 00:00:00 2001 From: Austin Kelsay <53542748+AustinKelsay@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:54:27 -0600 Subject: [PATCH 10/10] Update src/components/forms/video/EditPublishedVideoForm.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/forms/video/EditPublishedVideoForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/forms/video/EditPublishedVideoForm.js b/src/components/forms/video/EditPublishedVideoForm.js index 7d79e4d..2bc1963 100644 --- a/src/components/forms/video/EditPublishedVideoForm.js +++ b/src/components/forms/video/EditPublishedVideoForm.js @@ -85,7 +85,7 @@ const EditPublishedVideoForm = ({ event }) => { } else if (videoUrl.includes('vimeo.com')) { const videoId = videoUrl.split('/').pop(); embedCode = `
`; - } else if (!price || !price > 0 && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { + } else if ((price === undefined || price <= 0) && (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm'))) { embedCode = `
`; } else if (videoUrl.includes('.mp4') || videoUrl.includes('.mov') || videoUrl.includes('.avi') || videoUrl.includes('.wmv') || videoUrl.includes('.flv') || videoUrl.includes('.webm')) { const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000";