diff --git a/src/hooks/courses/index.js b/src/hooks/courses/index.js index 9f3ab6c..f6438e0 100644 --- a/src/hooks/courses/index.js +++ b/src/hooks/courses/index.js @@ -1,9 +1,13 @@ import useCourseDecryption from '../encryption/useCourseDecryption'; import useCourseTabs from './useCourseTabs'; import useCoursePayment from './useCoursePayment'; +import useCourseData from './useCourseData'; +import useLessons from './useLessons'; export { useCourseDecryption, useCourseTabs, - useCoursePayment + useCoursePayment, + useCourseData, + useLessons }; \ No newline at end of file diff --git a/src/hooks/courses/useCourseData.js b/src/hooks/courses/useCourseData.js new file mode 100644 index 0000000..0140066 --- /dev/null +++ b/src/hooks/courses/useCourseData.js @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react'; +import { parseCourseEvent } from '@/utils/nostr'; +import { nip19 } from 'nostr-tools'; +import { useToast } from '../useToast'; + +/** + * Hook to fetch and manage course data + * @param {Object} ndk - NDK instance for Nostr data fetching + * @param {Function} fetchAuthor - Function to fetch author data + * @param {Object} router - Next.js router instance + * @returns {Object} Course data and related state + */ +const useCourseData = (ndk, fetchAuthor, router) => { + const [course, setCourse] = useState(null); + const [lessonIds, setLessonIds] = useState([]); + const [paidCourse, setPaidCourse] = useState(null); + const [loading, setLoading] = useState(true); + const { showToast } = useToast(); + + useEffect(() => { + if (!router.isReady) return; + + const { slug } = router.query; + + const fetchCourseId = async () => { + if (slug.includes('naddr')) { + const { data } = nip19.decode(slug); + if (!data?.identifier) { + showToast('error', 'Error', 'Resource not found'); + return null; + } + return data.identifier; + } else { + return slug; + } + }; + + const fetchCourse = async courseId => { + try { + await ndk.connect(); + const event = await ndk.fetchEvent({ '#d': [courseId] }); + if (!event) return null; + + const author = await fetchAuthor(event.pubkey); + const lessonIds = event.tags.filter(tag => tag[0] === 'a').map(tag => tag[1].split(':')[2]); + + const parsedCourse = { ...parseCourseEvent(event), author }; + return { parsedCourse, lessonIds }; + } catch (error) { + console.error('Error fetching event:', error); + return null; + } + }; + + const initializeCourse = async () => { + setLoading(true); + const id = await fetchCourseId(); + if (!id) { + setLoading(false); + return; + } + + const courseData = await fetchCourse(id); + if (courseData) { + const { parsedCourse, lessonIds } = courseData; + setCourse(parsedCourse); + setLessonIds(lessonIds); + setPaidCourse(parsedCourse.price && parsedCourse.price > 0); + } + setLoading(false); + }; + + initializeCourse(); + }, [router.isReady, router.query, ndk, fetchAuthor, showToast]); + + return { course, lessonIds, paidCourse, loading }; +}; + +export default useCourseData; \ No newline at end of file diff --git a/src/hooks/courses/useLessons.js b/src/hooks/courses/useLessons.js new file mode 100644 index 0000000..2c80706 --- /dev/null +++ b/src/hooks/courses/useLessons.js @@ -0,0 +1,61 @@ +import { useState, useEffect } from 'react'; +import { parseEvent } from '@/utils/nostr'; + +/** + * Hook to fetch and manage lesson data for a course + * @param {Object} ndk - NDK instance for Nostr data fetching + * @param {Function} fetchAuthor - Function to fetch author data + * @param {Array} lessonIds - Array of lesson IDs to fetch + * @param {String} pubkey - Public key of the course author + * @returns {Object} Lesson data and state + */ +const useLessons = (ndk, fetchAuthor, lessonIds, pubkey) => { + const [lessons, setLessons] = useState([]); + const [uniqueLessons, setUniqueLessons] = useState([]); + + // Fetch lessons when IDs or pubkey change + useEffect(() => { + if (lessonIds.length > 0 && pubkey) { + const fetchLessons = async () => { + try { + await ndk.connect(); + + // Create a single filter with all lesson IDs to avoid multiple calls + const filter = { + '#d': lessonIds, + kinds: [30023, 30402], + authors: [pubkey], + }; + + const events = await ndk.fetchEvents(filter); + const newLessons = []; + + // Process events + for (const event of events) { + const author = await fetchAuthor(event.pubkey); + const parsedLesson = { ...parseEvent(event), author }; + newLessons.push(parsedLesson); + } + + setLessons(newLessons); + } catch (error) { + console.error('Error fetching events:', error); + } + }; + + fetchLessons(); + } + }, [lessonIds, ndk, fetchAuthor, pubkey]); + + // Deduplicate lessons + useEffect(() => { + const newUniqueLessons = Array.from( + new Map(lessons.map(lesson => [lesson.id, lesson])).values() + ); + setUniqueLessons(newUniqueLessons); + }, [lessons]); + + return { lessons, uniqueLessons, setLessons }; +}; + +export default useLessons; \ No newline at end of file