mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-03 07:42:03 +00:00
smoother course navigation
This commit is contained in:
parent
7a805f0988
commit
e3317f870a
@ -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'}`}>
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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 */}
|
||||
|
Loading…
x
Reference in New Issue
Block a user