diff --git a/src/components/content/courses/DraftCourseDetails.js b/src/components/content/courses/DraftCourseDetails.js index 0b4125b..dfa8f11 100644 --- a/src/components/content/courses/DraftCourseDetails.js +++ b/src/components/content/courses/DraftCourseDetails.js @@ -21,7 +21,7 @@ const MDDisplay = dynamic( } ); -export default function DraftCourseDetails({ processedEvent, lessons }) { +export default function DraftCourseDetails({ processedEvent, draftId, lessons }) { const [author, setAuthor] = useState(null); const [user, setUser] = useState(null); @@ -181,7 +181,7 @@ export default function DraftCourseDetails({ processedEvent, lessons }) {
diff --git a/src/components/content/dropdowns/ContentDropdownItem.js b/src/components/content/dropdowns/ContentDropdownItem.js index 718445e..ce3da18 100644 --- a/src/components/content/dropdowns/ContentDropdownItem.js +++ b/src/components/content/dropdowns/ContentDropdownItem.js @@ -6,6 +6,7 @@ import { formatUnixTimestamp } from "@/utils/time"; const ContentDropdownItem = ({ content, onSelect, selected }) => { const { returnImageProxy } = useImageProxy(); + console.log('selected:', content); if (selected) { return ( diff --git a/src/components/content/resources/ResourceDetails.js b/src/components/content/resources/ResourceDetails.js index d102eb8..54c680e 100644 --- a/src/components/content/resources/ResourceDetails.js +++ b/src/components/content/resources/ResourceDetails.js @@ -7,14 +7,15 @@ import ZapDisplay from "@/components/zaps/ZapDisplay"; import { useImageProxy } from "@/hooks/useImageProxy"; import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; import { getTotalFromZaps } from "@/utils/lightning"; +import { useSession } from "next-auth/react"; const ResourceDetails = ({processedEvent, topics, title, summary, image, price, author, paidResource, decryptedContent, handlePaymentSuccess, handlePaymentError}) => { const [zapAmount, setZapAmount] = useState(null); const router = useRouter(); - const { slug } = router.query; const { returnImageProxy } = useImageProxy(); const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: processedEvent }); + const { data: session, status } = useSession(); useEffect(() => { if (!zaps) return; @@ -75,7 +76,10 @@ const ResourceDetails = ({processedEvent, topics, title, summary, image, price, />} {/* if the resource has been paid for show a green paid x sats text */} - {paidResource && decryptedContent &&

Paid {processedEvent.price} sats

} + {paidResource && decryptedContent && author && !processedEvent?.pubkey === session?.user?.pubkey &&

Paid {processedEvent.price} sats

} + + {/* if this is the author of the resource show a zap button */} + {paidResource && author && processedEvent?.pubkey === session?.user?.pubkey &&

Price {processedEvent.price} sats

} diff --git a/src/components/forms/CourseForm.js b/src/components/forms/CourseForm.js index b1c1c64..618fc71 100644 --- a/src/components/forms/CourseForm.js +++ b/src/components/forms/CourseForm.js @@ -6,7 +6,7 @@ import { InputSwitch } from "primereact/inputswitch"; import { Button } from "primereact/button"; import { Dropdown } from "primereact/dropdown"; import { ProgressSpinner } from "primereact/progressspinner"; -import { v4 as uuidv4, v4 } from 'uuid'; +import { v4 as uuidv4 } from 'uuid'; import { useSession } from 'next-auth/react'; import { useNDKContext } from "@/context/NDKContext"; import { useRouter } from "next/router"; @@ -19,13 +19,14 @@ import { parseEvent } from "@/utils/nostr"; import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem"; import 'primeicons/primeicons.css'; -const CourseForm = () => { +const CourseForm = ({ draft = null, isPublished = false }) => { const [title, setTitle] = useState(''); const [summary, setSummary] = useState(''); const [checked, setChecked] = useState(false); const [price, setPrice] = useState(0); const [coverImage, setCoverImage] = useState(''); const [lessons, setLessons] = useState([{ id: uuidv4(), title: 'Select a lesson' }]); + const [loadingLessons, setLoadingLessons] = useState(true); const [selectedLessons, setSelectedLessons] = useState([]); const [topics, setTopics] = useState(['']); @@ -45,10 +46,53 @@ const CourseForm = () => { }, [session]); useEffect(() => { - console.log('selectedLessons:', selectedLessons); - }, [selectedLessons]); + const fetchLessons = async () => { + if (draft && draft.resources) { + const parsedLessons = await Promise.all( + draft.resources.map(async (lesson) => { + const parsedLesson = await fetchLessonEventFromNostr(lesson.noteId); + return parsedLesson; + }) + ); + setLessons(parsedLessons); + setLoadingLessons(false); // Data is loaded + } else { + setLoadingLessons(false); // No draft means no lessons to load + } + }; - const handleDraftSubmit = async (e) => { + fetchLessons(); + }, [draft]); + + const fetchLessonEventFromNostr = async (eventId) => { + try { + await ndk.connect(); + + const fetchedEvent = await ndk.fetchEvent(eventId); + + if (fetchedEvent) { + const parsedEvent = parseEvent(fetchedEvent); + return parsedEvent; + } + } catch (error) { + showToast('error', 'Error', `Failed to fetch lesson: ${eventId}`); + } + } + + useEffect(() => { + if (draft) { + console.log('draft:', draft); + setTitle(draft.title); + setSummary(draft.summary); + setChecked(draft.price > 0); + setPrice(draft.price || 0); + setCoverImage(draft.image); + setSelectedLessons(draft.resources || []); + setTopics(draft.topics || ['']); + } + }, [draft]); + + const handleSubmit = async (e) => { e.preventDefault(); if (!ndk.signer) { @@ -57,7 +101,6 @@ const CourseForm = () => { // Prepare the lessons from selected lessons const resources = await Promise.all(selectedLessons.map(async (lesson) => { - // if .type is present than this lesson is a draft we need to publish if (lesson?.type) { const event = createLessonEvent(lesson); const published = await event.publish(); @@ -66,7 +109,6 @@ const CourseForm = () => { throw new Error(`Failed to publish lesson: ${lesson.title}`); } - // Now post to resources const resource = await axios.post('/api/resources', { id: event.tags.find(tag => tag[0] === 'd')[1], userId: user.id, @@ -78,7 +120,6 @@ const CourseForm = () => { throw new Error(`Failed to post resource: ${lesson.title}`); } - // now delete the draft const deleted = await axios.delete(`/api/drafts/${lesson.id}`); if (deleted.status !== 204) { @@ -101,27 +142,25 @@ const CourseForm = () => { } })); - console.log('resources:', resources); - const payload = { userId: user.id, title, summary, image: coverImage, price: price || 0, - resources, // Send the array of lesson/resource IDs + resources, + topics, }; - console.log('payload:', payload); - try { - // Post the course draft to the API - const response = await axios.post('/api/courses/drafts', payload); - - console.log('response:', response); - - // If successful, navigate to the course page - showToast('success', 'Course draft saved successfully'); + let response; + if (draft) { + response = await axios.put(`/api/courses/drafts/${draft.id}`, payload); + showToast('success', 'Success', 'Course draft updated successfully'); + } else { + response = await axios.post('/api/courses/drafts', payload); + showToast('success', 'Success', 'Course draft saved successfully'); + } router.push(`/course/${response.data.id}/draft`); } catch (error) { console.error('Error saving course draft:', error); @@ -129,6 +168,47 @@ const CourseForm = () => { } }; + const handlePublishedCourse = async (e) => { + e.preventDefault(); + + if (!ndk.signer) { + await addSigner(); + } + + const event = new NDKEvent(ndk); + event.kind = price > 0 ? 30402 : 30023; + event.content = JSON.stringify({ + title, + summary, + image: coverImage, + resources: selectedLessons.map(lesson => lesson.id), + }); + event.tags = [ + ['d', draft.id], + ['title', title], + ['summary', summary], + ['image', coverImage], + ...topics.map(topic => ['t', topic]), + ['published_at', Math.floor(Date.now() / 1000).toString()], + ['price', price.toString()], + ]; + + try { + const published = await ndk.publish(event); + + if (published) { + const response = await axios.put(`/api/courses/${draft.id}`, { noteId: event.id }); + showToast('success', 'Success', 'Course published successfully'); + router.push(`/course/${event.id}`); + } else { + showToast('error', 'Error', 'Failed to publish course. Please try again.'); + } + } catch (error) { + console.error('Error publishing course:', error); + showToast('error', 'Failed to publish course. Please try again.'); + } + }; + const createLessonEvent = (lesson) => { const event = new NDKEvent(ndk); event.kind = lesson.price ? 30402 : 30023; @@ -231,12 +311,12 @@ const CourseForm = () => { }; // const lessonOptions = getContentOptions(); - if (resourcesLoading || workshopsLoading || draftsLoading) { + if (loadingLessons || resourcesLoading || workshopsLoading || draftsLoading) { return ; } return ( -
+
setTitle(e.target.value)} placeholder="Title" />
@@ -257,12 +337,15 @@ const CourseForm = () => {
- {selectedLessons.map((lesson, index) => ( + {selectedLessons.map(async (lesson, index) => { + return (
- ))} + ) + }) + } {lessons.map((lesson, index) => (
{
-
); diff --git a/src/hooks/nostrQueries/zaps/useZapsSubscription.js b/src/hooks/nostrQueries/zaps/useZapsSubscription.js index 60d4052..3cf5c06 100644 --- a/src/hooks/nostrQueries/zaps/useZapsSubscription.js +++ b/src/hooks/nostrQueries/zaps/useZapsSubscription.js @@ -35,7 +35,6 @@ export function useZapsSubscription({event}) { }); subscription.on('eose', () => { - console.log("eose"); // Only set loading to false if no zaps have been received yet if (isFirstZap) { setZapsLoading(false); diff --git a/src/pages/api/courses/drafts/[slug].js b/src/pages/api/courses/drafts/[slug].js index 2ffbc52..48dc0d8 100644 --- a/src/pages/api/courses/drafts/[slug].js +++ b/src/pages/api/courses/drafts/[slug].js @@ -20,7 +20,6 @@ export default async function handler(req, res) { } } else if (userId) { try { - console.log('INHEEEERE:', userId); const courseDrafts = await getAllCourseDraftsByUserId(userId); res.status(200).json(courseDrafts); } catch (error) { diff --git a/src/pages/course/[slug]/draft/edit.js b/src/pages/course/[slug]/draft/edit.js new file mode 100644 index 0000000..9deefed --- /dev/null +++ b/src/pages/course/[slug]/draft/edit.js @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { useRouter } from "next/router"; +import { parseEvent } from "@/utils/nostr"; +import ResourceForm from "@/components/forms/ResourceForm"; +import WorkshopForm from "@/components/forms/WorkshopForm"; +import CourseForm from "@/components/forms/CourseForm"; +import { useNDKContext } from "@/context/NDKContext"; +import { useToast } from "@/hooks/useToast"; + +export default function Edit() { + const [event, setEvent] = useState(null); + const [draft, setDraft] = useState(null); + const { ndk, addSigner } = useNDKContext(); + const router = useRouter(); + const { showToast } = useToast(); + + useEffect(() => { + if (router.isReady) { + const { slug } = router.query; + const fetchEvent = async () => { + // await ndk.connect(); + // const fetchedEvent = await ndk.fetchEvent(slug); + // if (fetchedEvent) { + // const parsedEvent = parseEvent(fetchedEvent); + // console.log('parsedEvent:', parsedEvent); + // setEvent(parsedEvent); + // } else { + // If no event found, try to fetch draft + try { + console.log('fetching draft:', slug); + const response = await axios.get(`/api/courses/drafts/${slug}`); + console.log('response:', response); + if (response.status === 200) { + setDraft(response.data); + } else { + showToast('error', 'Error', 'Draft not found.'); + } + } catch (error) { + console.error('Error fetching draft:', error); + showToast('error', 'Error', 'Failed to fetch draft.'); + } + // } + } + fetchEvent(); + } + }, [router.isReady, router.query, ndk, showToast]); + + return ( +
+

Edit {event ? 'Published Event' : 'Draft'}

+ {draft && + + } +
+ ); +} \ No newline at end of file diff --git a/src/pages/course/[slug]/draft.js b/src/pages/course/[slug]/draft/index.js similarity index 94% rename from src/pages/course/[slug]/draft.js rename to src/pages/course/[slug]/draft/index.js index 5188069..05f460c 100644 --- a/src/pages/course/[slug]/draft.js +++ b/src/pages/course/[slug]/draft/index.js @@ -21,6 +21,7 @@ const DraftCourse = () => { const router = useRouter(); const {ndk, addSigner} = useNDKContext(); + const { slug } = router.query; const fetchAuthor = useCallback(async (pubkey) => { if (!pubkey) return; @@ -67,6 +68,7 @@ const DraftCourse = () => { author }; } + return lesson; // Fallback to the original lesson if no event found })); @@ -79,7 +81,7 @@ const DraftCourse = () => { return ( <> - + {lessonsWithAuthors.length > 0 && lessonsWithAuthors.map((lesson, index) => ( ))}