From e3317f870aca299ea9455ecd35db596aa175c245 Mon Sep 17 00:00:00 2001 From: austinkelsay Date: Sun, 11 May 2025 16:14:15 -0500 Subject: [PATCH] smoother course navigation --- .../content/courses/layout/CourseSidebar.js | 4 +- .../content/courses/tabs/CourseContent.js | 54 ++++++++++++++++--- src/hooks/courses/useCourseNavigation.js | 5 +- src/hooks/encryption/useCourseDecryption.js | 19 +++---- src/pages/course/[slug]/index.js | 44 +++++++++++---- 5 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/components/content/courses/layout/CourseSidebar.js b/src/components/content/courses/layout/CourseSidebar.js index 189e7f1..778a377 100644 --- a/src/components/content/courses/layout/CourseSidebar.js +++ b/src/components/content/courses/layout/CourseSidebar.js @@ -37,8 +37,8 @@ const CourseSidebar = ({ ${isMobileView ? 'mb-3' : 'mb-2'} `} onClick={() => { - // Force full page refresh to trigger proper decryption - window.location.href = `/course/${window.location.pathname.split('/').pop()}?active=${index}`; + // Use smooth navigation function instead of forcing page refresh + onLessonSelect(index); }} >
diff --git a/src/components/content/courses/tabs/CourseContent.js b/src/components/content/courses/tabs/CourseContent.js index e4e7953..79b37ac 100644 --- a/src/components/content/courses/tabs/CourseContent.js +++ b/src/components/content/courses/tabs/CourseContent.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import VideoLesson from '@/components/content/courses/lessons/VideoLesson'; import DocumentLesson from '@/components/content/courses/lessons/DocumentLesson'; import CombinedLesson from '@/components/content/courses/lessons/CombinedLesson'; @@ -15,6 +15,40 @@ const CourseContent = ({ decryptedLessonIds, setCompleted }) => { + const [lastActiveIndex, setLastActiveIndex] = useState(activeIndex); + const [isTransitioning, setIsTransitioning] = useState(false); + const [currentLesson, setCurrentLesson] = useState(null); + + // Initialize current lesson and handle updates when lessons or activeIndex change + useEffect(() => { + if (lessons.length > 0 && activeIndex < lessons.length) { + setCurrentLesson(lessons[activeIndex]); + } else { + setCurrentLesson(null); + } + }, [lessons, activeIndex]); + + // Handle smooth transitions between lessons + useEffect(() => { + if (activeIndex !== lastActiveIndex) { + // Start transition + setIsTransitioning(true); + + // After a short delay, update the current lesson + const timer = setTimeout(() => { + setCurrentLesson(lessons[activeIndex] || null); + setLastActiveIndex(activeIndex); + + // End transition with a slight delay to ensure content is ready + setTimeout(() => { + setIsTransitioning(false); + }, 50); + }, 300); // Match this with CSS transition duration + + return () => clearTimeout(timer); + } + }, [activeIndex, lastActiveIndex, lessons]); + const renderLesson = (lesson) => { if (!lesson) return null; @@ -24,6 +58,7 @@ const CourseContent = ({ if (lesson.topics?.includes('video') && lesson.topics?.includes('document')) { return ( ); - } else if (lesson.type === 'video' && !lesson.topics?.includes('document')) { + } else if (lesson.type === 'video' || lesson.topics?.includes('video')) { return ( ); - } else if (lesson.type === 'document' && !lesson.topics?.includes('video')) { + } else if (lesson.type === 'document' || lesson.topics?.includes('document')) { return ( - {lessons.length > 0 && lessons[activeIndex] ? ( + {lessons.length > 0 && currentLesson ? (
-
- {renderLesson(lessons[activeIndex])} +
+ {renderLesson(currentLesson)}
) : ( -
+

Select a lesson from the sidebar to begin learning.

)} diff --git a/src/hooks/courses/useCourseNavigation.js b/src/hooks/courses/useCourseNavigation.js index 8f502b4..7484c74 100644 --- a/src/hooks/courses/useCourseNavigation.js +++ b/src/hooks/courses/useCourseNavigation.js @@ -44,7 +44,10 @@ const useCourseNavigation = (router, isMobileView) => { // Function to handle lesson selection const handleLessonSelect = (index) => { setActiveIndex(index); - router.push(`/course/${router.query.slug}?active=${index}`, undefined, { shallow: true }); + + // Update URL without causing a page reload (for bookmarking purposes) + const newUrl = `/course/${router.query.slug}?active=${index}`; + window.history.replaceState({ url: newUrl, as: newUrl, options: { shallow: true } }, '', newUrl); // On mobile, switch to content tab after selection if (isMobileView) { diff --git a/src/hooks/encryption/useCourseDecryption.js b/src/hooks/encryption/useCourseDecryption.js index 6e9046f..e7a06f6 100644 --- a/src/hooks/encryption/useCourseDecryption.js +++ b/src/hooks/encryption/useCourseDecryption.js @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useDecryptContent } from './useDecryptContent'; -const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, router) => { +const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, router, activeIndex = 0) => { const [decryptedLessonIds, setDecryptedLessonIds] = useState({}); const [loading, setLoading] = useState(false); const { decryptContent } = useDecryptContent(); @@ -10,8 +10,8 @@ const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, r const retryCountRef = useRef({}); const MAX_RETRIES = 3; - // Get the current active lesson - const currentLessonIndex = router.query.active ? parseInt(router.query.active, 10) : 0; + // Get the current active lesson using the activeIndex prop instead of router.query + const currentLessonIndex = activeIndex; const currentLesson = lessons.length > 0 ? lessons[currentLessonIndex] : null; const currentLessonId = currentLesson?.id; @@ -35,8 +35,9 @@ const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, r useEffect(() => { if (currentLessonId && lastLessonIdRef.current !== currentLessonId) { retryCountRef.current[currentLessonId] = 0; + lastLessonIdRef.current = currentLessonId; } - }, [currentLessonId]); + }, [currentLessonId, activeIndex]); // Simplified decrypt function const decryptCurrentLesson = useCallback(async () => { @@ -120,6 +121,7 @@ const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, r retryCountRef.current[currentLesson.id] = 0; } catch (error) { // Silent error handling to prevent UI disruption + console.error('Decryption error:', error); } finally { setLoading(false); processingRef.current = false; @@ -130,16 +132,11 @@ const useCourseDecryption = (session, paidCourse, course, lessons, setLessons, r useEffect(() => { if (!currentLessonId) return; - // Skip if the lesson hasn't changed, unless it failed decryption previously - if (lastLessonIdRef.current === currentLessonId && decryptedLessonIds[currentLessonId]) return; - - // Update the last processed lesson id - lastLessonIdRef.current = currentLessonId; - + // Always attempt decryption when activeIndex changes if (hasAccess && paidCourse && !decryptedLessonIds[currentLessonId]) { decryptCurrentLesson(); } - }, [currentLessonId, hasAccess, paidCourse, decryptedLessonIds, decryptCurrentLesson]); + }, [currentLessonId, hasAccess, paidCourse, decryptedLessonIds, decryptCurrentLesson, activeIndex]); return { decryptionPerformed: isCurrentLessonDecrypted, diff --git a/src/pages/course/[slug]/index.js b/src/pages/course/[slug]/index.js index eb7fe99..fa7ac71 100644 --- a/src/pages/course/[slug]/index.js +++ b/src/pages/course/[slug]/index.js @@ -34,6 +34,7 @@ const Course = () => { const [nsec, setNsec] = useState(null); const [npub, setNpub] = useState(null); const [nAddress, setNAddress] = useState(null); + const [isDecrypting, setIsDecrypting] = useState(false); const windowWidth = useWindowWidth(); const isMobileView = windowWidth <= 968; const navbarHeight = 60; // Match the height from Navbar component @@ -100,9 +101,23 @@ const Course = () => { course, lessons, setLessons, - router + router, + activeIndex ); + useEffect(() => { + if (paidCourse && uniqueLessons.length > 0) { + const currentLesson = uniqueLessons[activeIndex]; + if (currentLesson && !decryptedLessonIds[currentLesson.id]) { + setIsDecrypting(true); + } else { + setIsDecrypting(false); + } + } else { + setIsDecrypting(false); + } + }, [activeIndex, uniqueLessons, decryptedLessonIds, paidCourse]); + useEffect(() => { if (uniqueLessons.length > 0) { const addresses = {}; @@ -156,7 +171,7 @@ const Course = () => { ); }; - if (courseLoading || decryptionLoading) { + if (courseLoading) { return (
@@ -204,14 +219,23 @@ const Course = () => { {/* Content tab content */}
- + {isDecrypting || decryptionLoading ? ( +
+
+ +

Decrypting lesson content...

+
+
+ ) : ( + + )}
{/* QA tab content */}