diff --git a/src/components/content/carousels/GenericCarousel.js b/src/components/content/carousels/GenericCarousel.js new file mode 100644 index 0000000..dae5f18 --- /dev/null +++ b/src/components/content/carousels/GenericCarousel.js @@ -0,0 +1,72 @@ +import React, { useState, useEffect } from 'react'; +import { Carousel } from 'primereact/carousel'; +import ResourceTemplate from '@/components/content/carousels/templates/ResourceTemplate'; +import TemplateSkeleton from '@/components/content/carousels/skeletons/TemplateSkeleton'; + +const responsiveOptions = [ + { + breakpoint: '3000px', + numVisible: 3, + }, + { + breakpoint: '1462px', + numVisible: 2, + }, + { + breakpoint: '575px', + numVisible: 1, + } +]; + +export default function GenericCarousel({items}) { + const [carousels, setCarousels] = useState([]); + + useEffect(() => { + const handleResize = () => { + const width = window.innerWidth; + let itemsPerCarousel = 3; + + if (width <= 1462) { + itemsPerCarousel = 2; + } + if (width <= 575) { + itemsPerCarousel = 1; + } + + const newCarousels = []; + for (let i = 0; i < items.length; i += itemsPerCarousel) { + newCarousels.push(items.slice(i, i + itemsPerCarousel)); + } + setCarousels(newCarousels); + }; + + handleResize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [items]); + + return ( + <> + {carousels.map((carouselItems, index) => ( + + carouselItems.length > 0 ? + : + + } + responsiveOptions={responsiveOptions} + className="mb-4" + pt={{ + previousButton: { className: 'hidden' }, + nextButton: { className: 'hidden' } + }} + /> + ))} + + ); +} diff --git a/src/components/content/courses/DraftCourseDetails.js b/src/components/content/courses/DraftCourseDetails.js index 2ded7c1..86ed114 100644 --- a/src/components/content/courses/DraftCourseDetails.js +++ b/src/components/content/courses/DraftCourseDetails.js @@ -237,6 +237,11 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons }) useEffect(() => { async function buildEvent(draft) { + if (!draft) { + console.error('Draft is null or undefined'); + return null; + } + const event = new NDKEvent(ndk); let type; let encryptedContent; @@ -298,24 +303,42 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons }) } async function buildDraftEvent(lesson) { - const { unsignedEvent, type } = await buildEvent(lesson); - return unsignedEvent + if (!lesson) { + console.error('Lesson is null or undefined'); + return null; + } + + const result = await buildEvent(lesson); + if (!result) { + console.error('Failed to build event'); + return null; + } + + const { unsignedEvent, type } = result; + return unsignedEvent; } if (!hasRunEffect.current && lessons.length > 0 && user && author) { hasRunEffect.current = true; lessons.forEach(async (lesson) => { + if (!lesson) { + console.error('Lesson is null or undefined'); + return; + } + const isDraft = !lesson?.pubkey; if (isDraft) { const unsignedEvent = await buildDraftEvent(lesson); - setProcessedLessons(prev => [...prev, { - d: lesson?.id, - kind: lesson?.price ? 30402 : 30023, - pubkey: unsignedEvent.pubkey, - index: lesson.index, - unpublished: unsignedEvent - }]); + if (unsignedEvent) { + setProcessedLessons(prev => [...prev, { + d: lesson?.id, + kind: lesson?.price ? 30402 : 30023, + pubkey: unsignedEvent.pubkey, + index: lesson.index, + unpublished: unsignedEvent + }]); + } } else { setProcessedLessons(prev => [...prev, { d: lesson?.d, diff --git a/src/pages/content.js b/src/pages/content.js new file mode 100644 index 0000000..1ae054b --- /dev/null +++ b/src/pages/content.js @@ -0,0 +1,115 @@ +import React, { useEffect, useState } 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 { useCourses } from '@/hooks/nostr/useCourses'; +import { TabMenu } from 'primereact/tabmenu'; +import 'primeicons/primeicons.css'; +import { InputText } from 'primereact/inputtext'; + +const MenuTab = ({ items, activeIndex, onTabChange }) => { + const menuItems = items.map((item, index) => ({ + label: item, + icon: 'pi pi-tag', + command: () => onTabChange(index) + })); + + return ( +
+ onTabChange(e.index)} + pt={{ + menu: { className: 'bg-transparent border-none' }, + action: { className: 'bg-transparent border-none' } + }} + /> +
+ ); +} + +const ContentPage = () => { + const { resources, resourcesLoading } = useResources(); + const { workshops, workshopsLoading } = useWorkshops(); + const { courses, coursesLoading } = useCourses(); + + const [processedResources, setProcessedResources] = useState([]); + const [processedWorkshops, setProcessedWorkshops] = useState([]); + const [processedCourses, setProcessedCourses] = useState([]); + const [allContent, setAllContent] = useState([]); + const [allTopics, setAllTopics] = useState([]); + const [activeIndex, setActiveIndex] = useState(0); + const [searchQuery, setSearchQuery] = useState(''); + + useEffect(() => { + if (resources && !resourcesLoading) { + const processedResources = resources.map(resource => parseEvent(resource)); + setProcessedResources(processedResources); + } + }, [resources, resourcesLoading]); + + useEffect(() => { + if (workshops && !workshopsLoading) { + const processedWorkshops = workshops.map(workshop => parseEvent(workshop)); + setProcessedWorkshops(processedWorkshops); + } + }, [workshops, workshopsLoading]); + + useEffect(() => { + if (courses && !coursesLoading) { + const processedCourses = courses.map(course => parseCourseEvent(course)); + setProcessedCourses(processedCourses); + } + }, [courses, coursesLoading]); + + useEffect(() => { + const allTopics = new Set([...processedResources, ...processedWorkshops, ...processedCourses].map(item => item.topics).flat()); + setAllTopics(Array.from(allTopics)); + setAllContent([...processedResources, ...processedWorkshops, ...processedCourses]); + }, [processedResources, processedWorkshops, processedCourses]); + + useEffect(() => { + console.log(allTopics); + }, [allTopics]); + + const renderCarousels = () => { + const carousels = []; + for (let i = 0; i < allContent.length; i += 3) { + const items = allContent.slice(i, i + 3); + carousels.push( + + ); + } + return carousels; + }; + + return ( +
+
+

All Content

+ setSearchQuery(e.target.value)} + placeholder="Search" + icon="pi pi-search" + /> +
+ + {renderCarousels()} +
+ ); +}; + +export default ContentPage; \ No newline at end of file diff --git a/src/pages/course/[slug]/draft/index.js b/src/pages/course/[slug]/draft/index.js index ffb1778..42513fd 100644 --- a/src/pages/course/[slug]/draft/index.js +++ b/src/pages/course/[slug]/draft/index.js @@ -8,7 +8,7 @@ import { useNDKContext } from "@/context/NDKContext"; import { useSession } from "next-auth/react"; const DraftCourse = () => { - const { data: session } = useSession(); + const { data: session, status } = useSession(); const [course, setCourse] = useState(null); const [lessons, setLessons] = useState([]); const [lessonsWithAuthors, setLessonsWithAuthors] = useState([]); @@ -46,7 +46,7 @@ const DraftCourse = () => { useEffect(() => { const fetchLessonDetails = async () => { - if (lessons.length > 0) { + if (lessons.length > 0 && status === "authenticated") { await ndk.connect(); const newLessonsWithAuthors = await Promise.all(lessons.map(async (lesson) => { @@ -56,7 +56,7 @@ const DraftCourse = () => { const parsedLessonObject = { ...lesson?.draft, index: lesson.index, - author: session.user + author: session?.user || null // Use optional chaining and provide a fallback } return parsedLessonObject; } else { @@ -83,7 +83,11 @@ const DraftCourse = () => { }; fetchLessonDetails(); - }, [lessons, ndk, fetchAuthor, session]); + }, [lessons, ndk, fetchAuthor, session, status]); + + if (status === "loading") { + return
Loading...
; + } return ( <>