diff --git a/src/components/content/carousels/CoursesCarousel.js b/src/components/content/carousels/CoursesCarousel.js index 4ff6269..68af421 100644 --- a/src/components/content/carousels/CoursesCarousel.js +++ b/src/components/content/carousels/CoursesCarousel.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, use } from 'react'; import { Carousel } from 'primereact/carousel'; import { parseCourseEvent } from '@/utils/nostr'; // import CourseTemplate from '@/components/content/carousels/templates/CourseTemplate'; -import { CourseTemplate } from '@/components/content/carousels/newTemplates/CourseTemplate'; +import { CourseTemplate } from '@/components/content/carousels/templates/CourseTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import { useCourses } from '@/hooks/nostr/useCourses'; diff --git a/src/components/content/carousels/GenericCarousel.js b/src/components/content/carousels/GenericCarousel.js index 2638511..3430459 100644 --- a/src/components/content/carousels/GenericCarousel.js +++ b/src/components/content/carousels/GenericCarousel.js @@ -1,9 +1,9 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Carousel } from 'primereact/carousel'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; -import { VideoTemplate } from '@/components/content/carousels/newTemplates/VideoTemplate'; -import { DocumentTemplate } from '@/components/content/carousels/newTemplates/DocumentTemplate'; -import { CourseTemplate } from '@/components/content/carousels/newTemplates/CourseTemplate'; +import { VideoTemplate } from '@/components/content/carousels/templates/VideoTemplate'; +import { DocumentTemplate } from '@/components/content/carousels/templates/DocumentTemplate'; +import { CourseTemplate } from '@/components/content/carousels/templates/CourseTemplate'; import debounce from 'lodash/debounce'; const responsiveOptions = [ @@ -66,7 +66,7 @@ export default function GenericCarousel({items, selectedTopic, title}) { if (carouselItems.length > 0) { if (item.type === 'resource') { return ; - } else if (item.type === 'workshop') { + } else if (item.type === 'video') { return ; } else if (item.type === 'course') { return ; diff --git a/src/components/content/carousels/InteractivePromotionalCarousel.js b/src/components/content/carousels/InteractivePromotionalCarousel.js index bfb8420..8b0a2c8 100644 --- a/src/components/content/carousels/InteractivePromotionalCarousel.js +++ b/src/components/content/carousels/InteractivePromotionalCarousel.js @@ -25,8 +25,8 @@ const promotions = [ }, { id: 3, - category: "WORKSHOPS", - title: "Hands-on Video Workshops", + category: "VIDEOS", + title: "Hands-on workshops and devleloper video content", description: "Watch and learn with our interactive video workshops. Get practical experience building real Bitcoin and Lightning applications.", icon: "pi pi-video", image: "https://newsroom.siliconslopes.com/content/images/2018/10/code.jpg", @@ -97,9 +97,9 @@ const InteractivePromotionalCarousel = () => { return ( router.push('/content?tag=courses')} icon={} label="View All Courses" className="w-fit py-2 font-semibold" size="small" outlined /> ); - case "WORKSHOPS": + case "VIDEOS": return ( - router.push('/content?tag=workshops')} icon={} label="View All Workshops" className="w-fit py-2 font-semibold" size="small" outlined /> + router.push('/content?tag=videos')} icon={} label="View All Videos" className="w-fit py-2 font-semibold" size="small" outlined /> ); case "RESOURCES": return ( @@ -145,9 +145,9 @@ const InteractivePromotionalCarousel = () => { return ( router.push('/content?tag=courses')} icon={} label="View All Courses" className="py-2 font-semibold" size="small" outlined /> ); - case "WORKSHOPS": + case "VIDEOS": return ( - router.push('/content?tag=workshops')} icon={} label="View All Workshops" className="py-2 font-semibold" size="small" outlined /> + router.push('/content?tag=videos')} icon={} label="View All Videos" className="py-2 font-semibold" size="small" outlined /> ); case "RESOURCES": return ( diff --git a/src/components/content/carousels/ResourcesCarousel.js b/src/components/content/carousels/ResourcesCarousel.js index 40e7bf4..a65915d 100644 --- a/src/components/content/carousels/ResourcesCarousel.js +++ b/src/components/content/carousels/ResourcesCarousel.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Carousel } from 'primereact/carousel'; import { parseEvent } from '@/utils/nostr'; // import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; -import { DocumentTemplate } from '@/components/content/carousels/newTemplates/DocumentTemplate'; +import { DocumentTemplate } from '@/components/content/carousels/templates/DocumentTemplate'; import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; import { useResources } from '@/hooks/nostr/useResources'; diff --git a/src/components/content/carousels/VideosCarousel.js b/src/components/content/carousels/VideosCarousel.js new file mode 100644 index 0000000..18b25e9 --- /dev/null +++ b/src/components/content/carousels/VideosCarousel.js @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import { Carousel } from 'primereact/carousel'; +import { parseEvent } from '@/utils/nostr'; +import {VideoTemplate} from '@/components/content/carousels/templates/VideoTemplate'; +import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; +import { useVideos } from '@/hooks/nostr/useVideos'; + +const responsiveOptions = [ + { + breakpoint: '3000px', + numVisible: 3, + numScroll: 1 + }, + { + breakpoint: '1462px', + numVisible: 2, + numScroll: 1 + }, + { + breakpoint: '575px', + numVisible: 1, + numScroll: 1 + } +]; + +export default function VideosCarousel() { + const [processedVideos, setProcessedVideos] = useState([]); + const { videos, videosLoading, videosError } = useVideos(); + + useEffect(() => { + const fetch = async () => { + try { + if (videos && videos.length > 0) { + const processedVideos = videos.map(video => parseEvent(video)); + + const sortedVideos = processedVideos.sort((a, b) => b.created_at - a.created_at); + + console.log('Sorted videos:', sortedVideos); + setProcessedVideos(sortedVideos); + } else { + console.log('No videos fetched or empty array returned'); + } + } catch (error) { + console.error('Error fetching videos:', error); + } + }; + fetch(); + }, [videos]); + + if (videosError) return
Error: {videosError}
; + + return ( + <> +

Videos

+ + !processedVideos.length ? + : + + } + responsiveOptions={responsiveOptions} + /> + + ); +} diff --git a/src/components/content/carousels/WorkshopsCarousel.js b/src/components/content/carousels/WorkshopsCarousel.js deleted file mode 100644 index 91193c8..0000000 --- a/src/components/content/carousels/WorkshopsCarousel.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Carousel } from 'primereact/carousel'; -import { parseEvent } from '@/utils/nostr'; -// import WorkshopTemplate from '@/components/content/carousels/templates/WorkshopTemplate'; -import {VideoTemplate} from '@/components/content/carousels/newTemplates/VideoTemplate'; -import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; -import { useWorkshops } from '@/hooks/nostr/useWorkshops'; - -const responsiveOptions = [ - { - breakpoint: '3000px', - numVisible: 3, - numScroll: 1 - }, - { - breakpoint: '1462px', - numVisible: 2, - numScroll: 1 - }, - { - breakpoint: '575px', - numVisible: 1, - numScroll: 1 - } -]; - -export default function WorkshopsCarousel() { - const [processedWorkshops, setProcessedWorkshops] = useState([]); - const { workshops, workshopsLoading, workshopsError } = useWorkshops(); - - useEffect(() => { - const fetch = async () => { - try { - if (workshops && workshops.length > 0) { - const processedWorkshops = workshops.map(workshop => parseEvent(workshop)); - - // Sort workshops by created_at in descending order (most recent first) - const sortedWorkshops = processedWorkshops.sort((a, b) => b.created_at - a.created_at); - - console.log('Sorted workshops:', sortedWorkshops); - setProcessedWorkshops(sortedWorkshops); - } else { - console.log('No workshops fetched or empty array returned'); - } - } catch (error) { - console.error('Error fetching workshops:', error); - } - }; - fetch(); - }, [workshops]); - - if (workshopsError) return
Error: {workshopsError}
; - - return ( - <> -

Workshops

- - !processedWorkshops.length ? - : - - } - responsiveOptions={responsiveOptions} - /> - - ); -} diff --git a/src/components/content/carousels/newTemplates/CourseTemplate.js b/src/components/content/carousels/newTemplates/CourseTemplate.js deleted file mode 100644 index 770b356..0000000 --- a/src/components/content/carousels/newTemplates/CourseTemplate.js +++ /dev/null @@ -1,106 +0,0 @@ -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { Tag } from "primereact/tag"; -import ZapDisplay from "@/components/zaps/ZapDisplay"; -import { nip19 } from "nostr-tools"; -import Image from "next/image" -import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; -import { getTotalFromZaps } from "@/utils/lightning"; -import { useImageProxy } from "@/hooks/useImageProxy"; -import { useRouter } from "next/router"; -import { formatTimestampToHowLongAgo } from "@/utils/time"; -import { ProgressSpinner } from "primereact/progressspinner"; -import GenericButton from "@/components/buttons/GenericButton"; -import { defaultRelayUrls } from "@/context/NDKContext"; - -export function CourseTemplate({ course }) { - const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: course }); - const [zapAmount, setZapAmount] = useState(0); - const [lessonCount, setLessonCount] = useState(0); - const [nAddress, setNAddress] = useState(null); - const router = useRouter(); - const { returnImageProxy } = useImageProxy(); - - useEffect(() => { - if (zaps.length > 0) { - const total = getTotalFromZaps(zaps, course); - setZapAmount(total); - } - }, [zaps, course]); - - useEffect(() => { - if (course && course?.tags) { - const lessons = course.tags.filter(tag => tag[0] === "a"); - setLessonCount(lessons.length); - } - }, [course]); - - useEffect(() => { - if (course && course?.id) { - const nAddress = nip19.naddrEncode({ - pubkey: course.pubkey, - kind: course.kind, - identifier: course.id, - relayUrls: defaultRelayUrls - }); - setNAddress(nAddress); - } - }, [course]); - - if (!nAddress) return ; - - if (zapsError) return
Error: {zapsError}
; - - return ( - -
- Course background -
-
- -
- -
- -
- {course.name || course.title} -
-
-
-
- -
- {course && course.topics && course.topics.map((topic, index) => ( - - {topic} - - ))} -
-

{lessonCount} lessons

-
- - {course.description || course.summary} - - -

{course?.published_at && course.published_at !== "" ? ( - formatTimestampToHowLongAgo(course.published_at) - ) : ( - formatTimestampToHowLongAgo(course.created_at) - )}

- router.push(`/course/${nAddress}`)} size="small" label="Start Learning" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" /> -
- - ) -} \ No newline at end of file diff --git a/src/components/content/carousels/templates/CourseTemplate.js b/src/components/content/carousels/templates/CourseTemplate.js index 82ee2ed..a005dcb 100644 --- a/src/components/content/carousels/templates/CourseTemplate.js +++ b/src/components/content/carousels/templates/CourseTemplate.js @@ -1,16 +1,23 @@ -import React, { useEffect, useState } from "react"; -import Image from "next/image"; +import { useState, useEffect } from "react"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Tag } from "primereact/tag"; +import ZapDisplay from "@/components/zaps/ZapDisplay"; +import { nip19 } from "nostr-tools"; +import Image from "next/image" +import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; +import { getTotalFromZaps } from "@/utils/lightning"; +import { useImageProxy } from "@/hooks/useImageProxy"; import { useRouter } from "next/router"; import { formatTimestampToHowLongAgo } from "@/utils/time"; -import { useImageProxy } from "@/hooks/useImageProxy"; -import { getTotalFromZaps } from "@/utils/lightning"; -import ZapDisplay from "@/components/zaps/ZapDisplay"; -import { Tag } from "primereact/tag"; -import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; +import { ProgressSpinner } from "primereact/progressspinner"; +import GenericButton from "@/components/buttons/GenericButton"; +import { defaultRelayUrls } from "@/context/NDKContext"; -const CourseTemplate = ({ course }) => { +export function CourseTemplate({ course }) { const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: course }); const [zapAmount, setZapAmount] = useState(0); + const [lessonCount, setLessonCount] = useState(0); + const [nAddress, setNAddress] = useState(null); const router = useRouter(); const { returnImageProxy } = useImageProxy(); @@ -21,61 +28,96 @@ const CourseTemplate = ({ course }) => { } }, [zaps, course]); + useEffect(() => { + if (course && course?.tags) { + const lessons = course.tags.filter(tag => tag[0] === "a"); + setLessonCount(lessons.length); + } + }, [course]); + + useEffect(() => { + if (course && course?.id) { + const nAddress = nip19.naddrEncode({ + pubkey: course.pubkey, + kind: course.kind, + identifier: course.id, + relayUrls: defaultRelayUrls + }); + setNAddress(nAddress); + } + }, [course]); + + if (!nAddress) return ; + if (zapsError) return
Error: {zapsError}
; return ( -
- {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} -
router.replace(`/course/${course.id}`)} - className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" - style={{ paddingBottom: "56.25%" }} - > + +
course thumbnail -
-
-

- {course.name || course.title} -

-

{course.description || course.summary}

- {course.price && course.price > 0 ? ( -

Price: {course.price} sats

- ) : ( -

Free

- )} -
-

- {course?.published_at && course.published_at !== "" ? ( - formatTimestampToHowLongAgo(course.published_at) - ) : ( - formatTimestampToHowLongAgo(course.created_at) - )} -

- +
+
+
- {course?.topics && course?.topics.length > 0 && ( -
- {course && course.topics && course.topics.map((topic, index) => ( - - ))} + +
+ +
+ {course.name || course.title} +
- )} +
-
- ); -}; - -export default CourseTemplate; \ No newline at end of file + +
+ {course && course.topics && course.topics.map((topic, index) => ( + + {topic} + + ))} +
+

{lessonCount} lessons

+
+ + {course.description || course.summary && ( + <> + {course.description && ( +
+ {course.description.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + {course.summary && ( +
+ {course.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + + )} +
+ +

{course?.published_at && course.published_at !== "" ? ( + formatTimestampToHowLongAgo(course.published_at) + ) : ( + formatTimestampToHowLongAgo(course.created_at) + )}

+ router.push(`/course/${nAddress}`)} size="small" label="Start Learning" icon="pi pi-chevron-right" iconPos="right" outlined className="items-center py-2" /> +
+ + ) +} \ No newline at end of file diff --git a/src/components/content/carousels/newTemplates/DocumentTemplate.js b/src/components/content/carousels/templates/DocumentTemplate.js similarity index 86% rename from src/components/content/carousels/newTemplates/DocumentTemplate.js rename to src/components/content/carousels/templates/DocumentTemplate.js index 563c243..7864a75 100644 --- a/src/components/content/carousels/newTemplates/DocumentTemplate.js +++ b/src/components/content/carousels/templates/DocumentTemplate.js @@ -79,7 +79,24 @@ export function DocumentTemplate({ document }) { WebkitBoxOrient: "vertical", WebkitLineClamp: "2" }}> - {document.description || document.summary} + {document.description || document.summary && ( + <> + {document.description && ( +
+ {document.description.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + {document.summary && ( +
+ {document.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + + )}

{document?.published_at && document.published_at !== "" ? ( diff --git a/src/components/content/carousels/templates/ResourceTemplate.js b/src/components/content/carousels/templates/ResourceTemplate.js deleted file mode 100644 index fa7324d..0000000 --- a/src/components/content/carousels/templates/ResourceTemplate.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useEffect, useState } from "react"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import { formatTimestampToHowLongAgo } from "@/utils/time"; -import { useImageProxy } from "@/hooks/useImageProxy"; -import { getTotalFromZaps } from "@/utils/lightning"; -import { Tag } from "primereact/tag"; -import ZapDisplay from "@/components/zaps/ZapDisplay"; -import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; - -const ResourceTemplate = ({ resource }) => { - const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: resource }); - const [zapAmount, setZapAmount] = useState(0); - - const router = useRouter(); - const { returnImageProxy } = useImageProxy(); - - useEffect(() => { - if (zaps.length > 0) { - const total = getTotalFromZaps(zaps, resource); - setZapAmount(total); - } - }, [zaps, resource]); - - if (zapsError) return

Error: {zapsError}
; - - return ( -
- {/* Wrap the image in a div with a relative class with a padding-bottom of 56.25% representing the aspect ratio of 16:9 */} -
router.replace(`/details/${resource.id}`)} - className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" - style={{ paddingBottom: "56.25%" }} - > - resource thumbnail -
-
-

- {resource.title} -

-

{resource.summary}

- {resource.price && resource.price > 0 ? ( -

Price: {resource.price} sats

- ) : ( -

Free

- )} -
-

- {formatTimestampToHowLongAgo(resource.published_at)} -

- -
- {resource?.topics && resource?.topics.length > 0 && ( -
- {resource.topics.map((topic, index) => ( - - ))} -
- )} -
-
- ); -}; - -export default ResourceTemplate; \ No newline at end of file diff --git a/src/components/content/carousels/newTemplates/VideoTemplate.js b/src/components/content/carousels/templates/VideoTemplate.js similarity index 83% rename from src/components/content/carousels/newTemplates/VideoTemplate.js rename to src/components/content/carousels/templates/VideoTemplate.js index 3e9a92a..aeee764 100644 --- a/src/components/content/carousels/newTemplates/VideoTemplate.js +++ b/src/components/content/carousels/templates/VideoTemplate.js @@ -21,13 +21,15 @@ export function VideoTemplate({ video }) { const { returnImageProxy } = useImageProxy(); useEffect(() => { + if (video && video?.pubkey && video?.kind && video?.id) { const addr = nip19.naddrEncode({ pubkey: video.pubkey, kind: video.kind, identifier: video.id, relayUrls: defaultRelayUrls }) - setNAddress(addr); + setNAddress(addr); + } }, [video]); useEffect(() => { @@ -78,7 +80,24 @@ export function VideoTemplate({ video }) { WebkitBoxOrient: "vertical", WebkitLineClamp: "2" }}> - {video.description || video.summary} + {video.description || video.summary && ( + <> + {video.description && ( +
+ {video.description.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + {video.summary && ( +
+ {video.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} + + )}

{video?.published_at && video.published_at !== "" ? ( diff --git a/src/components/content/carousels/templates/WorkshopTemplate.js b/src/components/content/carousels/templates/WorkshopTemplate.js deleted file mode 100644 index 3ff425d..0000000 --- a/src/components/content/carousels/templates/WorkshopTemplate.js +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect, useState } from "react"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import { formatTimestampToHowLongAgo } from "@/utils/time"; -import { useImageProxy } from "@/hooks/useImageProxy"; -import { getTotalFromZaps } from "@/utils/lightning"; -import { useZapsSubscription } from "@/hooks/nostrQueries/zaps/useZapsSubscription"; -import ZapDisplay from "@/components/zaps/ZapDisplay"; -import { Tag } from "primereact/tag"; - -const WorkshopTemplate = ({ workshop }) => { - const { zaps, zapsLoading, zapsError } = useZapsSubscription({ event: workshop }); - const [zapAmount, setZapAmount] = useState(0); - const router = useRouter(); - const { returnImageProxy } = useImageProxy(); - - useEffect(() => { - if (zaps.length > 0) { - const total = getTotalFromZaps(zaps, workshop); - setZapAmount(total); - } - }, [zaps, workshop]); - - if (zapsError) return

Error: {zapsError}
; - - return ( -
-
router.replace(`/details/${workshop.id}`)} - className="relative w-full h-0 hover:opacity-80 transition-opacity duration-300 cursor-pointer" - style={{ paddingBottom: "56.25%" }} - > - workshop thumbnail -
-
-

- {workshop.title} -

-

{workshop.summary}

- {workshop.price && workshop.price > 0 ? ( -

Price: {workshop.price} sats

- ) : ( -

Free

- )} -
-

- {formatTimestampToHowLongAgo(workshop.published_at)} -

- -
- {workshop?.topics && workshop?.topics.length > 0 && ( -
- {workshop.topics.map((topic, index) => ( - - ))} -
- )} -
-
- ); -}; - -export default WorkshopTemplate; \ No newline at end of file diff --git a/src/components/content/courses/CourseLesson.js b/src/components/content/courses/CourseLesson.js index b1f5643..418479b 100644 --- a/src/components/content/courses/CourseLesson.js +++ b/src/components/content/courses/CourseLesson.js @@ -53,7 +53,14 @@ const CourseLesson = ({ lesson, course, decryptionPerformed, isPaid }) => { )}

{lesson?.title}

-

{lesson?.summary}

+

{lesson?.summary && ( +

+ {lesson.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (

External links:

diff --git a/src/components/content/courses/DocumentLesson.js b/src/components/content/courses/DocumentLesson.js index 7d8257d..cfa6044 100644 --- a/src/components/content/courses/DocumentLesson.js +++ b/src/components/content/courses/DocumentLesson.js @@ -87,7 +87,14 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid }) => { )}
-

{lesson.summary}

+

{lesson.summary && ( +

+ {lesson.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

['r', link]) : []), ]; - type = 'workshop'; + type = 'video'; break; default: return null; diff --git a/src/components/content/courses/DraftCourseLesson.js b/src/components/content/courses/DraftCourseLesson.js index f25ce1c..f639aa2 100644 --- a/src/components/content/courses/DraftCourseLesson.js +++ b/src/components/content/courses/DraftCourseLesson.js @@ -43,7 +43,14 @@ const DraftCourseLesson = ({ lesson, course }) => { )}

{lesson?.title}

-

{lesson?.summary}

+

{lesson?.summary && ( +

+ {lesson.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (

External links:

diff --git a/src/components/content/courses/VideoLesson.js b/src/components/content/courses/VideoLesson.js index e4e2619..947e3ca 100644 --- a/src/components/content/courses/VideoLesson.js +++ b/src/components/content/courses/VideoLesson.js @@ -97,7 +97,14 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid }) => { )}
-

{lesson.summary}

+

{lesson.summary && ( +

+ {lesson.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

-

{summary}

+

{summary && ( +

+ {summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

- router.push(`/details/${processedEvent.id}/edit`)} label="Edit" severity='warning' outlined /> + router.push(`/details/${nAddress}/edit`)} label="Edit" severity='warning' outlined /> { />
{content.title || content.name}
-
{content.summary || content.description}
+
{content.summary || content.description && ( +
+ {content.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +
{content.price &&
Price: {content.price}
} {content?.topics?.length > 0 && (
diff --git a/src/components/content/lists/ContentListItem.js b/src/components/content/lists/ContentListItem.js index e17f4f9..66d63db 100644 --- a/src/components/content/lists/ContentListItem.js +++ b/src/components/content/lists/ContentListItem.js @@ -3,7 +3,9 @@ import Image from "next/image"; import GenericButton from "@/components/buttons/GenericButton"; import { useImageProxy } from "@/hooks/useImageProxy"; import { useRouter } from "next/router"; +import { nip19 } from "nostr-tools"; import { Divider } from 'primereact/divider'; +import { defaultRelayUrls } from "@/context/NDKContext"; const ContentListItem = (content) => { @@ -11,17 +13,30 @@ const ContentListItem = (content) => { const router = useRouter(); const isPublishedCourse = content?.kind === 30004; const isDraftCourse = !content?.kind && content?.draftLessons; - const isResource = content?.kind && content?.kind === 30023; + const isResource = content?.kind && content?.kind === 30023 || content?.kind === 30402; const isDraft = !content?.kind && !content?.draftLessons; const handleClick = () => { - console.log(content); + console.log(content, "isDraftCourse", isDraftCourse, "isDraft", isDraft, "isResource", isResource, "isPublishedCourse", isPublishedCourse); + let nAddress; if (isPublishedCourse) { - router.push(`/course/${content.id}`); + nAddress = nip19.naddrEncode({ + identifier: content.id, + kind: content.kind, + pubkey: content.pubkey, + relayUrls: defaultRelayUrls + }); + router.push(`/course/${nAddress}`); } else if (isDraftCourse) { router.push(`/course/${content.id}/draft`); } else if (isResource) { - router.push(`/details/${content.id}`); + nAddress = nip19.naddrEncode({ + identifier: content.id, + kind: content.kind, + pubkey: content.pubkey, + relayUrls: defaultRelayUrls + }); + router.push(`/details/${nAddress}`); } else if (isDraft) { router.push(`/draft/${content.id}`); } diff --git a/src/components/content/videos/VideoDetails.js b/src/components/content/videos/VideoDetails.js index 5f8e4d2..5f2953e 100644 --- a/src/components/content/videos/VideoDetails.js +++ b/src/components/content/videos/VideoDetails.js @@ -129,7 +129,14 @@ const VideoDetails = ({ processedEvent, topics, title, summary, image, price, au }
-

{summary}

+

{summary && ( +

+ {summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

{authorView ? (
- router.push(`/details/${processedEvent.id}/edit`)} label="Edit" severity='warning' outlined /> + router.push(`/details/${nAddress}/edit`)} label="Edit" severity='warning' outlined /> window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: 'right' }} />
) : (
- window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: 'right' }} /> + window.open(`https://nostr.band/${nAddress}`, '_blank')} tooltip="View Nostr Event" tooltipOptions={{ position: paidResource ? 'left' : 'right' }} />
)}
+
+ {renderPaymentMessage()} +
) diff --git a/src/components/forms/WorkshopForm.js b/src/components/forms/VideoForm.js similarity index 95% rename from src/components/forms/WorkshopForm.js rename to src/components/forms/VideoForm.js index 9c4f4f2..129d42f 100644 --- a/src/components/forms/WorkshopForm.js +++ b/src/components/forms/VideoForm.js @@ -2,6 +2,7 @@ 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'; @@ -11,7 +12,7 @@ import 'primeicons/primeicons.css'; import { Tooltip } from 'primereact/tooltip'; import 'primereact/resources/primereact.min.css'; -const WorkshopForm = ({ draft = null }) => { +const VideoForm = ({ draft = null }) => { const [title, setTitle] = useState(draft?.title || ''); const [summary, setSummary] = useState(draft?.summary || ''); const [price, setPrice] = useState(draft?.price || 0); @@ -76,12 +77,12 @@ const WorkshopForm = ({ draft = null }) => { const payload = { title, summary, - type: 'workshop', + type: 'video', price: isPaidResource ? price : null, content: embedCode, image: coverImage, user: userResponse.data.id, - topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'workshop'])], + topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'video'])], additionalLinks: additionalLinks.filter(link => link.trim() !== ''), }; @@ -92,7 +93,7 @@ const WorkshopForm = ({ draft = null }) => { axios[method](url, payload) .then(response => { if (response.status === 200 || response.status === 201) { - showToast('success', 'Success', draft ? 'Workshop updated successfully.' : 'Workshop saved as draft.'); + showToast('success', 'Success', draft ? 'Video updated successfully.' : 'Video saved as draft.'); if (response.data?.id) { router.push(`/draft/${response.data.id}`); @@ -101,7 +102,7 @@ const WorkshopForm = ({ draft = null }) => { }) .catch(error => { console.error(error); - showToast('error', 'Error', 'Failed to save workshop. Please try again.'); + showToast('error', 'Error', 'Failed to save video. Please try again.'); }); } }; @@ -145,11 +146,11 @@ const WorkshopForm = ({ draft = null }) => { setTitle(e.target.value)} placeholder="Title" />
- setSummary(e.target.value)} placeholder="Summary" /> + setSummary(e.target.value)} placeholder="Summary" rows={5} cols={30} />
-

Paid Workshop

+

Paid Video

setIsPaidResource(e.value)} /> {isPaidResource && (
@@ -208,4 +209,4 @@ const WorkshopForm = ({ draft = null }) => { ); } -export default WorkshopForm; \ No newline at end of file +export default VideoForm; \ No newline at end of file diff --git a/src/components/forms/course/CourseForm.js b/src/components/forms/course/CourseForm.js index ae4d145..866a737 100644 --- a/src/components/forms/course/CourseForm.js +++ b/src/components/forms/course/CourseForm.js @@ -10,7 +10,7 @@ import { useToast } from '@/hooks/useToast'; import { parseEvent } from '@/utils/nostr'; import { useDraftsQuery } from '@/hooks/apiQueries/useDraftsQuery'; import { useResources } from '@/hooks/nostr/useResources'; -import { useWorkshops } from '@/hooks/nostr/useWorkshops'; +import { useVideos } from '@/hooks/nostr/useVideos'; import axios from 'axios'; import LessonSelector from './LessonSelector'; @@ -28,11 +28,11 @@ const CourseForm = ({ draft = null }) => { const router = useRouter(); const { showToast } = useToast(); const { resources, resourcesLoading, resourcesError } = useResources(); - const { workshops, workshopsLoading, workshopsError } = useWorkshops(); + const { videos, videosLoading, videosError } = useVideos(); const { drafts, draftsLoading, draftsError } = useDraftsQuery(); useEffect(() => { - if (draft && resources && workshops && drafts) { + if (draft && resources && videos && drafts) { const populatedLessons = draft.draftLessons.map((lesson, index) => { if (lesson?.resource) { const matchingResource = resources.find((resource) => resource.d === lesson.resource.d); @@ -46,32 +46,32 @@ const CourseForm = ({ draft = null }) => { setLessons(populatedLessons); } - }, [draft, resources, workshops, drafts]); + }, [draft, resources, videos, drafts]); useEffect(() => { console.log('allContent', allContent); }, [allContent]); useEffect(() => { - console.log('fasfsa', workshops) - }, [workshops]) + console.log('fasfsa', videos) + }, [videos]) useEffect(() => { - if (!resourcesLoading && !workshopsLoading && !draftsLoading) { + if (!resourcesLoading && !videosLoading && !draftsLoading) { let combinedContent = []; if (resources) { combinedContent = [...combinedContent, ...resources]; } - if (workshops) { - console.log('workssdfsdfdsf', workshops) - combinedContent = [...combinedContent, ...workshops]; + if (videos) { + console.log('workssdfsdfdsf', videos) + combinedContent = [...combinedContent, ...videos]; } if (drafts) { combinedContent = [...combinedContent, ...drafts]; } setAllContent(combinedContent); } - }, [resources, workshops, drafts, resourcesLoading, workshopsLoading, draftsLoading]); + }, [resources, videos, drafts, resourcesLoading, videosLoading, draftsLoading]); const handleSubmit = async (event) => { event.preventDefault(); @@ -156,22 +156,22 @@ const CourseForm = ({ draft = null }) => { } }; - const handleNewWorkshopCreate = async (newWorkshop) => { + const handleNewVideoCreate = async (newVideo) => { try { - console.log('newWorkshop', newWorkshop); - const response = await axios.post('/api/drafts', newWorkshop); + console.log('newVideo', newVideo); + const response = await axios.post('/api/drafts', newVideo); console.log('response', response); - const createdWorkshop = response.data; - setAllContent(prevContent => [...prevContent, createdWorkshop]); - return createdWorkshop; + const createdVideo = response.data; + setAllContent(prevContent => [...prevContent, createdVideo]); + return createdVideo; } catch (error) { - console.error('Error creating workshop draft:', error); - showToast('error', 'Error', 'Failed to create workshop draft'); + console.error('Error creating video draft:', error); + showToast('error', 'Error', 'Failed to create video draft'); return null; } }; - if (resourcesLoading || workshopsLoading || draftsLoading) { + if (resourcesLoading || videosLoading || draftsLoading) { return ; } @@ -206,7 +206,7 @@ const CourseForm = ({ draft = null }) => { setLessons={setLessons} allContent={allContent} onNewResourceCreate={handleNewResourceCreate} - onNewWorkshopCreate={handleNewWorkshopCreate} + onNewVideoCreate={handleNewVideoCreate} />
{topics.map((topic, index) => ( diff --git a/src/components/forms/course/LessonSelector.js b/src/components/forms/course/LessonSelector.js index 3e8db86..be2d19a 100644 --- a/src/components/forms/course/LessonSelector.js +++ b/src/components/forms/course/LessonSelector.js @@ -4,14 +4,14 @@ import GenericButton from '@/components/buttons/GenericButton'; import { Dialog } from 'primereact/dialog'; import { Accordion, AccordionTab } from 'primereact/accordion'; import EmbeddedResourceForm from '@/components/forms/course/embedded/EmbeddedResourceForm'; -import EmbeddedWorkshopForm from '@/components/forms/course/embedded/EmbeddedWorkshopForm'; +import EmbeddedVideoForm from '@/components/forms/course/embedded/EmbeddedVideoForm'; import ContentDropdownItem from '@/components/content/dropdowns/ContentDropdownItem'; import SelectedContentItem from '@/components/content/SelectedContentItem'; import { parseEvent } from '@/utils/nostr'; -const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewResourceCreate, onNewWorkshopCreate }) => { +const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewResourceCreate, onNewVideoCreate }) => { const [showResourceForm, setShowResourceForm] = useState(false); - const [showWorkshopForm, setShowWorkshopForm] = useState(false); + const [showVideoForm, setShowVideoForm] = useState(false); const [contentOptions, setContentOptions] = useState([]); const [openTabs, setOpenTabs] = useState([]); @@ -52,7 +52,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe value: content })); - const draftWorkshopOptions = filteredContent.filter(content => content?.topics.includes('workshop') && !content.kind).map(content => ({ + const draftVideoOptions = filteredContent.filter(content => content?.topics.includes('video') && !content.kind).map(content => ({ label: content.title, value: content })); @@ -62,7 +62,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe value: content })); - const workshopOptions = filteredContent.filter(content => content?.type === "workshop" && content.kind).map(content => ({ + const videoOptions = filteredContent.filter(content => content?.type === "video" && content.kind).map(content => ({ label: content.title, value: content })); @@ -73,16 +73,16 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe items: draftResourceOptions }, { - label: 'Draft Workshops', - items: draftWorkshopOptions + label: 'Draft Videos', + items: draftVideoOptions }, { label: 'Published Resources', items: resourceOptions }, { - label: 'Published Workshops', - items: workshopOptions + label: 'Published Videos', + items: videoOptions } ]); }; @@ -124,12 +124,12 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe } }; - const handleNewWorkshopSave = async (newWorkshop) => { - console.log('newWorkshop', newWorkshop); - const createdWorkshop = await onNewWorkshopCreate(newWorkshop); - if (createdWorkshop) { - handleContentSelect(createdWorkshop, lessons.length); - setShowWorkshopForm(false); + const handleNewVideoSave = async (newVideo) => { + console.log('newVideo', newVideo); + const createdVideo = await onNewVideoCreate(newVideo); + if (createdVideo) { + handleContentSelect(createdVideo, lessons.length); + setShowVideoForm(false); } }; @@ -168,7 +168,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe {lesson.id ? null : ( <> {e.preventDefault(); setShowResourceForm(true)}} className="mr-2" /> - {e.preventDefault(); setShowWorkshopForm(true)}} className="mr-2" /> + {e.preventDefault(); setShowVideoForm(true)}} className="mr-2" /> )}
@@ -194,8 +194,8 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent, onNewRe - setShowWorkshopForm(false)} header="Create New Workshop"> - + setShowVideoForm(false)} header="Create New Video"> +
); diff --git a/src/components/forms/course/embedded/EmbeddedWorkshopForm.js b/src/components/forms/course/embedded/EmbeddedVideoForm.js similarity index 94% rename from src/components/forms/course/embedded/EmbeddedWorkshopForm.js rename to src/components/forms/course/embedded/EmbeddedVideoForm.js index fdcf411..0e0ed5f 100644 --- a/src/components/forms/course/embedded/EmbeddedWorkshopForm.js +++ b/src/components/forms/course/embedded/EmbeddedVideoForm.js @@ -9,7 +9,7 @@ import 'primeicons/primeicons.css'; import { Tooltip } from 'primereact/tooltip'; import 'primereact/resources/primereact.min.css'; -const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => { +const EmbeddedVideoForm = ({ draft = null, onSave, isPaid }) => { const [title, setTitle] = useState(draft?.title || ''); const [summary, setSummary] = useState(draft?.summary || ''); const [price, setPrice] = useState(draft?.price || 0); @@ -62,11 +62,11 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => { const payload = { title, summary, - type: 'workshop', + type: 'vidoe', price: isPaidResource ? price : null, content: embedCode, image: coverImage, - topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'workshop'])], + topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'video'])], additionalLinks: additionalLinks.filter(link => link.trim() !== ''), user: user?.id || user?.pubkey }; @@ -74,10 +74,10 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => { if (onSave) { try { await onSave(payload); - showToast('success', 'Success', draft ? 'Workshop updated successfully.' : 'Workshop created successfully.'); + showToast('success', 'Success', draft ? 'Video updated successfully.' : 'Video created successfully.'); } catch (error) { console.error(error); - showToast('error', 'Error', 'Failed to save workshop. Please try again.'); + showToast('error', 'Error', 'Failed to save video. Please try again.'); } } }; @@ -125,7 +125,7 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => {
-

Paid Workshop

+

Paid Video

setIsPaidResource(e.value)} /> {isPaidResource && (
@@ -184,4 +184,4 @@ const EmbeddedWorkshopForm = ({ draft = null, onSave, isPaid }) => { ); } -export default EmbeddedWorkshopForm; \ No newline at end of file +export default EmbeddedVideoForm; \ No newline at end of file diff --git a/src/components/profile/UserContent.js b/src/components/profile/UserContent.js index f68813a..477233b 100644 --- a/src/components/profile/UserContent.js +++ b/src/components/profile/UserContent.js @@ -4,7 +4,7 @@ import GenericButton from "@/components/buttons/GenericButton"; import MenuTab from "@/components/menutab/MenuTab"; import { useCourses } from "@/hooks/nostr/useCourses"; import { useResources } from "@/hooks/nostr/useResources"; -import { useWorkshops } from "@/hooks/nostr/useWorkshops"; +import { useVideos } from "@/hooks/nostr/useVideos"; import { useDraftsQuery } from "@/hooks/apiQueries/useDraftsQuery"; import { useCourseDraftsQuery } from "@/hooks/apiQueries/useCourseDraftsQuery"; import { useContentIdsQuery } from "@/hooks/apiQueries/useContentIdsQuery"; @@ -32,7 +32,7 @@ const UserContent = () => { const {ndk, addSigner} = useNDKContext(); const { courses, coursesLoading, coursesError } = useCourses(); const { resources, resourcesLoading, resourcesError } = useResources(); - const { workshops, workshopsLoading, workshopsError } = useWorkshops(); + const { videos, videosLoading, videosError } = useVideos(); const { courseDrafts, courseDraftsLoading, courseDraftsError } = useCourseDraftsQuery(); const { drafts, draftsLoading, draftsError } = useDraftsQuery(); const { contentIds, contentIdsLoading, contentIdsError, refetchContentIds } = useContentIdsQuery(); @@ -52,7 +52,7 @@ const UserContent = () => { { label: "Drafts", icon: "pi pi-file-edit" }, { label: "Draft Courses", icon: "pi pi-book" }, { label: "Resources", icon: "pi pi-file" }, - { label: "Workshops", icon: "pi pi-video" }, + { label: "Videos", icon: "pi pi-video" }, { label: "Courses", icon: "pi pi-desktop" }, ]; @@ -73,7 +73,7 @@ const UserContent = () => { console.log('uniqueEvents', uniqueEvents) return Array.from(uniqueEvents); } catch (error) { - console.error('Error fetching workshops from NDK:', error); + console.error('Error fetching videos from NDK:', error); return []; } }; @@ -100,7 +100,7 @@ const UserContent = () => { case 3: return resources?.map(parseEvent) || []; case 3: - return workshops?.map(parseEvent) || []; + return videos?.map(parseEvent) || []; case 4: return courses?.map(parseEvent) || []; default: @@ -110,10 +110,10 @@ const UserContent = () => { setContent(getContentByIndex(activeIndex)); } - }, [activeIndex, isClient, drafts, resources, workshops, courses, publishedContent, courseDrafts]) + }, [activeIndex, isClient, drafts, resources, videos, courses, publishedContent, courseDrafts]) - const isLoading = coursesLoading || resourcesLoading || workshopsLoading || draftsLoading || contentIdsLoading || courseDraftsLoading; - const isError = coursesError || resourcesError || workshopsError || draftsError || contentIdsError || courseDraftsError; + const isLoading = coursesLoading || resourcesLoading || videosLoading || draftsLoading || contentIdsLoading || courseDraftsLoading; + const isError = coursesError || resourcesError || videosError || draftsError || contentIdsError || courseDraftsError; return (
diff --git a/src/components/sidebar/Sidebar.js b/src/components/sidebar/Sidebar.js index a99b649..b48eac0 100644 --- a/src/components/sidebar/Sidebar.js +++ b/src/components/sidebar/Sidebar.js @@ -133,8 +133,8 @@ const Sidebar = ({ course = false }) => {
router.push('/content?tag=courses')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=courses') ? 'bg-gray-700' : ''}`}>

Courses

-
router.push('/content?tag=workshops')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=workshops') ? 'bg-gray-700' : ''}`}> -

Workshops

+
router.push('/content?tag=videos')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=videos') ? 'bg-gray-700' : ''}`}> +

Videos

router.push('/content?tag=resources')} className={`w-full cursor-pointer py-2 my-2 hover:bg-gray-700 rounded-lg ${isActive('/content?tag=resources') ? 'bg-gray-700' : ''}`}>

Resources

diff --git a/src/hooks/nostr/useWorkshops.js b/src/hooks/nostr/useVideos.js similarity index 53% rename from src/hooks/nostr/useWorkshops.js rename to src/hooks/nostr/useVideos.js index 5d01b04..e1c119d 100644 --- a/src/hooks/nostr/useWorkshops.js +++ b/src/hooks/nostr/useVideos.js @@ -4,12 +4,12 @@ import { useContentIdsQuery } from '@/hooks/apiQueries/useContentIdsQuery'; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; -export function useWorkshops() { +export function useVideos() { const [isClient, setIsClient] = useState(false); - const [workshops, setWorkshops] = useState(); + const [videos, setVideos] = useState(); // Add new state variables for loading and error - const [workshopsLoading, setWorkshopsLoading] = useState(false); - const [workshopsError, setWorkshopsError] = useState(null); + const [videosLoading, setVideosLoading] = useState(false); + const [videosError, setVideosError] = useState(null); const { contentIds } = useContentIdsQuery() const {ndk, addSigner} = useNDKContext(); @@ -19,18 +19,18 @@ export function useWorkshops() { }, []); const hasRequiredProperties = (event, contentIds) => { - const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "workshop"); + const hasVideo = event.tags.some(([tag, value]) => tag === "t" && value === "video"); const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value)); - return hasWorkshop && hasId; + return hasVideo && hasId; }; - const fetchWorkshopsFromNDK = async () => { - setWorkshopsLoading(true); - setWorkshopsError(null); + const fetchVideosFromNDK = async () => { + setVideosLoading(true); + setVideosError(null); try { if (!contentIds || contentIds.length === 0) { console.log('No content IDs found'); - setWorkshopsLoading(false); + setVideosLoading(false); return []; // Return early if no content IDs are found } @@ -41,30 +41,30 @@ export function useWorkshops() { if (events && events.size > 0) { const eventsArray = Array.from(events); - const workshops = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); - setWorkshopsLoading(false); - return workshops; + const videos = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); + setVideosLoading(false); + return videos; } - setWorkshopsLoading(false); + setVideosLoading(false); return []; } catch (error) { - console.error('Error fetching workshops from NDK:', error); - setWorkshopsError(error); - setWorkshopsLoading(false); + console.error('Error fetching videos from NDK:', error); + setVideosError(error); + setVideosLoading(false); return []; } }; useEffect(() => { if (isClient && contentIds) { - fetchWorkshopsFromNDK().then(fetchedWorkshops => { - if (fetchedWorkshops && fetchedWorkshops.length > 0) { - console.log('fetchedworkshops', fetchedWorkshops) - setWorkshops(fetchedWorkshops); + fetchVideosFromNDK().then(fetchedVideos => { + if (fetchedVideos && fetchedVideos.length > 0) { + console.log('fetchedvideos', fetchedVideos) + setVideos(fetchedVideos); } }); } }, [isClient, contentIds]); - return { workshops, workshopsLoading, workshopsError }; + return { videos, videosLoading, videosError }; } \ No newline at end of file diff --git a/src/hooks/nostrQueries/content/useAllContentQuery.js b/src/hooks/nostrQueries/content/useAllContentQuery.js index 1526d07..6fc8597 100644 --- a/src/hooks/nostrQueries/content/useAllContentQuery.js +++ b/src/hooks/nostrQueries/content/useAllContentQuery.js @@ -24,7 +24,7 @@ const fetchAllContentFromNDK = async (ids) => { } return []; } catch (error) { - console.error('Error fetching workshops from NDK:', error); + console.error('Error fetching videos from NDK:', error); return []; } }; diff --git a/src/hooks/nostrQueries/content/useWorkshopsQuery.js b/src/hooks/nostrQueries/content/useVideosQuery.js similarity index 66% rename from src/hooks/nostrQueries/content/useWorkshopsQuery.js rename to src/hooks/nostrQueries/content/useVideosQuery.js index 13bd444..2ea60be 100644 --- a/src/hooks/nostrQueries/content/useWorkshopsQuery.js +++ b/src/hooks/nostrQueries/content/useVideosQuery.js @@ -5,7 +5,7 @@ import axios from 'axios'; const AUTHOR_PUBKEY = process.env.NEXT_PUBLIC_AUTHOR_PUBKEY; -export function useWorkshopsQuery() { +export function useVideosQuery() { const [isClient, setIsClient] = useState(false); const {ndk, addSigner} = useNDKContext(); @@ -14,12 +14,12 @@ export function useWorkshopsQuery() { }, []); const hasRequiredProperties = (event, contentIds) => { - const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "workshop"); + const hasVideo = event.tags.some(([tag, value]) => tag === "t" && value === "video"); const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value)); - return hasWorkshop && hasId; + return hasVideo && hasId; }; - const fetchWorkshopsFromNDK = async () => { + const fetchVideosFromNDK = async () => { try { const response = await axios.get(`/api/content/all`); const contentIds = response.data; @@ -36,23 +36,23 @@ export function useWorkshopsQuery() { if (events && events.size > 0) { const eventsArray = Array.from(events); - const workshops = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); - return workshops; + const videos = eventsArray.filter(event => hasRequiredProperties(event, contentIds)); + return videos; } return []; } catch (error) { - console.error('Error fetching workshops from NDK:', error); + console.error('Error fetching videos from NDK:', error); return []; } }; - const { data: workshops, isLoading: workshopsLoading, error: workshopsError, refetch: refetchWorkshops } = useQuery({ - queryKey: ['workshops', isClient], - queryFn: fetchWorkshopsFromNDK, + const { data: videos, isLoading: videosLoading, error: videosError, refetch: refetchVideos } = useQuery({ + queryKey: ['videos', isClient], + queryFn: fetchVideosFromNDK, // staleTime: 1000 * 60 * 30, // 30 minutes // refetchInterval: 1000 * 60 * 30, // 30 minutes enabled: isClient, }); - return { workshops, workshopsLoading, workshopsError, refetchWorkshops }; + return { videos, videosLoading, videosError, refetchVideos }; } diff --git a/src/pages/about.js b/src/pages/about.js index f475ff2..e9ee994 100644 --- a/src/pages/about.js +++ b/src/pages/about.js @@ -61,8 +61,8 @@ const AboutPage = () => { description={
  • Resources: Markdown documents posted as NIP-23 long-form events on Nostr.
  • -
  • Workshops: Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.
  • -
  • Courses: Nostr lists that combine multiple resources and workshops into a structured learning path.
  • +
  • Videos: Enhanced markdown files with rich media support, including embedded videos, also saved as NIP-23 events.
  • +
  • Courses: Nostr lists that combine multiple resources and videos into a structured learning path.
} /> diff --git a/src/pages/content/index.js b/src/pages/content/index.js index 9e49d42..aade97a 100644 --- a/src/pages/content/index.js +++ b/src/pages/content/index.js @@ -2,7 +2,7 @@ import React, { useEffect, useState, useMemo } from 'react'; import GenericCarousel from '@/components/content/carousels/GenericCarousel'; import { parseEvent, parseCourseEvent } from '@/utils/nostr'; import { useResources } from '@/hooks/nostr/useResources'; -import { useWorkshops } from '@/hooks/nostr/useWorkshops'; +import { useVideos } from '@/hooks/nostr/useVideos'; import { useCourses } from '@/hooks/nostr/useCourses'; import { TabMenu } from 'primereact/tabmenu'; import 'primeicons/primeicons.css'; @@ -18,7 +18,7 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => { let icon = 'pi pi-tag'; if (item === 'All') icon = 'pi pi-eye'; else if (item === 'Resources') icon = 'pi pi-file'; - else if (item === 'Workshops') icon = 'pi pi-video'; + else if (item === 'Videos') icon = 'pi pi-video'; else if (item === 'Courses') icon = 'pi pi-desktop'; const queryParam = item === 'all' ? '' : `?tag=${item.toLowerCase()}`; @@ -68,11 +68,11 @@ const MenuTab = ({ items, selectedTopic, onTabChange }) => { const ContentPage = () => { const router = useRouter(); const { resources, resourcesLoading } = useResources(); - const { workshops, workshopsLoading } = useWorkshops(); + const { videos, videosLoading } = useVideos(); const { courses, coursesLoading } = useCourses(); const [processedResources, setProcessedResources] = useState([]); - const [processedWorkshops, setProcessedWorkshops] = useState([]); + const [processedVideos, setProcessedVideos] = useState([]); const [processedCourses, setProcessedCourses] = useState([]); const [allContent, setAllContent] = useState([]); const [allTopics, setAllTopics] = useState([]); @@ -99,11 +99,11 @@ const ContentPage = () => { }, [resources, resourcesLoading]); useEffect(() => { - if (workshops && !workshopsLoading) { - const processedWorkshops = workshops.map(workshop => ({...parseEvent(workshop), type: 'workshop'})); - setProcessedWorkshops(processedWorkshops); + if (videos && !videosLoading) { + const processedVideos = videos.map(video => ({...parseEvent(video), type: 'video'})); + setProcessedVideos(processedVideos); } - }, [workshops, workshopsLoading]); + }, [videos, videosLoading]); useEffect(() => { if (courses && !coursesLoading) { @@ -113,11 +113,11 @@ const ContentPage = () => { }, [courses, coursesLoading]); useEffect(() => { - const allContent = [...processedResources, ...processedWorkshops, ...processedCourses]; + const allContent = [...processedResources, ...processedVideos, ...processedCourses]; setAllContent(allContent); const uniqueTopics = new Set(allContent.map(item => item.topics).flat()); - const priorityItems = ['All', 'Courses', 'Workshops', 'Resources']; + const priorityItems = ['All', 'Courses', 'Videos', 'Resources']; const otherTopics = Array.from(uniqueTopics).filter(topic => !priorityItems.includes(topic)); const combinedTopics = [...priorityItems.slice(1), ...otherTopics]; setAllTopics(combinedTopics); @@ -125,13 +125,13 @@ const ContentPage = () => { if (selectedTopic) { filterContent(selectedTopic, allContent); } - }, [processedResources, processedWorkshops, processedCourses]); + }, [processedResources, processedVideos, processedCourses]); const filterContent = (topic, content) => { let filtered = content; if (topic !== 'All') { const topicLower = topic.toLowerCase(); - if (['courses', 'workshops', 'resources'].includes(topicLower)) { + if (['courses', 'videos', 'resources'].includes(topicLower)) { filtered = content.filter(item => item.type === topicLower.slice(0, -1)); } else { filtered = content.filter(item => item.topics && item.topics.includes(topic.toLowerCase())); @@ -166,7 +166,7 @@ const ContentPage = () => {

All Content

!['Courses', 'Workshops', 'Resources'].includes(topic))]} + items={['Courses', 'Videos', 'Resources', ...allTopics.filter(topic => !['Courses', 'Videos', 'Resources'].includes(topic))]} selectedTopic={selectedTopic} onTabChange={handleTopicChange} className="max-w-[90%] mx-auto" diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js index 7218a4f..366137e 100644 --- a/src/pages/course/[slug]/index.js +++ b/src/pages/course/[slug]/index.js @@ -225,7 +225,7 @@ const Course = () => { } >
- {lesson.type === 'workshop' ? + {lesson.type === 'video' ? : } diff --git a/src/pages/create.js b/src/pages/create.js index ff609f0..654abab 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 ResourceForm from "@/components/forms/ResourceForm"; -import WorkshopForm from "@/components/forms/WorkshopForm"; +import VideoForm from "@/components/forms/VideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import { useIsAdmin } from "@/hooks/useIsAdmin"; import { useRouter } from "next/router"; @@ -13,7 +13,7 @@ const Create = () => { const router = useRouter(); const homeItems = [ { label: 'Resource', icon: 'pi pi-book' }, - { label: 'Workshop', icon: 'pi pi-video' }, + { label: 'Video', icon: 'pi pi-video' }, { label: 'Course', icon: 'pi pi-desktop' } ]; @@ -30,8 +30,8 @@ const Create = () => { switch (homeItems[activeIndex].label) { case 'Course': return ; - case 'Workshop': - return ; + case 'Video': + return ; case 'Resource': return ; default: diff --git a/src/pages/details/[slug]/edit.js b/src/pages/details/[slug]/edit.js index 84a3b89..6e3d7cc 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 ResourceForm from "@/components/forms/ResourceForm"; -import WorkshopForm from "@/components/forms/WorkshopForm"; +import VideoForm from "@/components/forms/VideoForm"; import CourseForm from "@/components/forms/course/CourseForm"; import { useNDKContext } from "@/context/NDKContext"; import { useToast } from "@/hooks/useToast"; @@ -40,7 +40,7 @@ export default function Edit() {

Edit Published Event

{event?.topics.includes('course') && } - {event?.topics.includes('workshop') && } + {!event?.topics.includes('video') && } {event?.topics.includes('resource') && }
); diff --git a/src/pages/details/[slug]/index.js b/src/pages/details/[slug]/index.js index e06a193..47e5a57 100644 --- a/src/pages/details/[slug]/index.js +++ b/src/pages/details/[slug]/index.js @@ -205,7 +205,7 @@ export default function Details() { return (
- {processedEvent && processedEvent.type !== "workshop" ? ( + {processedEvent && processedEvent.type !== "video" ? ( {

Edit Draft

{draft?.type === 'course' && } - {draft?.type === 'workshop' && } + {draft?.type === 'video' && } {draft?.type === 'resource' && }
); diff --git a/src/pages/draft/[slug]/index.js b/src/pages/draft/[slug]/index.js index 0fa9485..6563713 100644 --- a/src/pages/draft/[slug]/index.js +++ b/src/pages/draft/[slug]/index.js @@ -210,7 +210,7 @@ export default function Draft() { type = 'resource'; break; - case 'workshop': + case 'video': if (draft?.price) { // encrypt the content with NEXT_PUBLIC_APP_PRIV_KEY to NEXT_PUBLIC_APP_PUBLIC_KEY encryptedContent = await nip04.encrypt(process.env.NEXT_PUBLIC_APP_PRIV_KEY, process.env.NEXT_PUBLIC_APP_PUBLIC_KEY, draft.content); @@ -245,7 +245,7 @@ export default function Draft() { ...(draft?.additionalLinks ? draft.additionalLinks.map(link => ['r', link]) : []), ]; - type = 'workshop'; + type = 'video'; break; default: return null; @@ -269,7 +269,14 @@ export default function Draft() { })}

{draft?.title}

-

{draft?.summary}

+

{draft?.summary && ( +

+ {draft.summary.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +

{draft?.price && (

Price: {draft.price}

)} diff --git a/src/pages/index.js b/src/pages/index.js index e2898eb..3cc963c 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,7 +1,7 @@ import Head from 'next/head'; import React from 'react'; import CoursesCarousel from '@/components/content/carousels/CoursesCarousel'; -import WorkshopsCarousel from '@/components/content/carousels/WorkshopsCarousel'; +import VideosCarousel from '@/components/content/carousels/VideosCarousel'; import ResourcesCarousel from '@/components/content/carousels/ResourcesCarousel'; import InteractivePromotionalCarousel from '@/components/content/carousels/InteractivePromotionalCarousel'; @@ -17,7 +17,7 @@ export default function Home() {
- +
diff --git a/src/utils/nostr.js b/src/utils/nostr.js index 2473cf0..2108cf5 100644 --- a/src/utils/nostr.js +++ b/src/utils/nostr.js @@ -104,8 +104,8 @@ export const parseEvent = (event) => { eventData.d = tag[1]; break; case 't': - if (tag[1] === 'workshop') { - eventData.type = 'workshop'; + if (tag[1] === 'video') { + eventData.type = 'video'; } else if (tag[1] !== "plebdevs") { eventData.topics.push(tag[1]); }