smoother course navigation

This commit is contained in:
austinkelsay 2025-05-11 16:14:15 -05:00
parent 7a805f0988
commit e3317f870a
No known key found for this signature in database
GPG Key ID: 5A763922E5BA08EE
5 changed files with 95 additions and 31 deletions

View File

@ -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);
}}
>
<div className={`flex items-start p-3 cursor-pointer ${isMobileView ? 'p-4' : 'p-3'}`}>

View File

@ -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 (
<CombinedLesson
key={`combined-${lesson.id}`}
lesson={lesson}
course={course}
decryptionPerformed={lessonDecrypted}
@ -31,9 +66,10 @@ const CourseContent = ({
setCompleted={setCompleted}
/>
);
} else if (lesson.type === 'video' && !lesson.topics?.includes('document')) {
} else if (lesson.type === 'video' || lesson.topics?.includes('video')) {
return (
<VideoLesson
key={`video-${lesson.id}`}
lesson={lesson}
course={course}
decryptionPerformed={lessonDecrypted}
@ -41,9 +77,10 @@ const CourseContent = ({
setCompleted={setCompleted}
/>
);
} else if (lesson.type === 'document' && !lesson.topics?.includes('video')) {
} else if (lesson.type === 'document' || lesson.topics?.includes('document')) {
return (
<DocumentLesson
key={`doc-${lesson.id}`}
lesson={lesson}
course={course}
decryptionPerformed={lessonDecrypted}
@ -58,14 +95,17 @@ const CourseContent = ({
return (
<>
{lessons.length > 0 && lessons[activeIndex] ? (
{lessons.length > 0 && currentLesson ? (
<div className="bg-gray-800 rounded-lg shadow-sm overflow-hidden">
<div key={`lesson-${lessons[activeIndex].id}`}>
{renderLesson(lessons[activeIndex])}
<div
key={`lesson-container-${currentLesson.id}`}
className={`transition-opacity duration-300 ease-in-out ${isTransitioning ? 'opacity-0' : 'opacity-100'}`}
>
{renderLesson(currentLesson)}
</div>
</div>
) : (
<div className="text-center bg-gray-800 rounded-lg p-8">
<div className={`text-center bg-gray-800 rounded-lg p-8 transition-opacity duration-300 ease-in-out ${isTransitioning ? 'opacity-0' : 'opacity-100'}`}>
<p>Select a lesson from the sidebar to begin learning.</p>
</div>
)}

View File

@ -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) {

View File

@ -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,

View File

@ -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 (
<div className="w-full h-full flex items-center justify-center">
<ProgressSpinner />
@ -204,14 +219,23 @@ const Course = () => {
{/* Content tab content */}
<div className={`${activeTab === 'content' ? 'block' : 'hidden'}`}>
<CourseContent
lessons={uniqueLessons}
activeIndex={activeIndex}
course={course}
paidCourse={paidCourse}
decryptedLessonIds={decryptedLessonIds}
setCompleted={setCompleted}
/>
{isDecrypting || decryptionLoading ? (
<div className="w-full py-12 bg-gray-800 rounded-lg flex items-center justify-center">
<div className="text-center">
<ProgressSpinner style={{ width: '50px', height: '50px' }} />
<p className="mt-4 text-gray-300">Decrypting lesson content...</p>
</div>
</div>
) : (
<CourseContent
lessons={uniqueLessons}
activeIndex={activeIndex}
course={course}
paidCourse={paidCourse}
decryptedLessonIds={decryptedLessonIds}
setCompleted={setCompleted}
/>
)}
</div>
{/* QA tab content */}