mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-05 00:32:03 +00:00
start/continue lesson button on course overview
This commit is contained in:
parent
d906625168
commit
724e7aa642
@ -31,6 +31,9 @@ export default function CourseDetails({
|
|||||||
handlePaymentError,
|
handlePaymentError,
|
||||||
isMobileView,
|
isMobileView,
|
||||||
showCompletedTag = true,
|
showCompletedTag = true,
|
||||||
|
completedLessons,
|
||||||
|
onLessonSelect,
|
||||||
|
toggleToContentTab
|
||||||
}) {
|
}) {
|
||||||
const [zapAmount, setZapAmount] = useState(0);
|
const [zapAmount, setZapAmount] = useState(0);
|
||||||
const [author, setAuthor] = useState(null);
|
const [author, setAuthor] = useState(null);
|
||||||
@ -213,7 +216,10 @@ export default function CourseDetails({
|
|||||||
returnImageProxy,
|
returnImageProxy,
|
||||||
renderPaymentMessage,
|
renderPaymentMessage,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
showCompletedTag
|
showCompletedTag,
|
||||||
|
completedLessons,
|
||||||
|
onLessonSelect,
|
||||||
|
toggleToContentTab
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,6 +4,7 @@ import { Tag } from 'primereact/tag';
|
|||||||
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
||||||
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
||||||
import { Divider } from 'primereact/divider';
|
import { Divider } from 'primereact/divider';
|
||||||
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
|
|
||||||
export default function DesktopCourseDetails({
|
export default function DesktopCourseDetails({
|
||||||
processedEvent,
|
processedEvent,
|
||||||
@ -17,9 +18,46 @@ export default function DesktopCourseDetails({
|
|||||||
returnImageProxy,
|
returnImageProxy,
|
||||||
renderPaymentMessage,
|
renderPaymentMessage,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
showCompletedTag
|
showCompletedTag,
|
||||||
|
completedLessons,
|
||||||
|
onLessonSelect,
|
||||||
|
toggleToContentTab
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
// Calculate next lesson to start/continue
|
||||||
|
const getNextLessonIndex = () => {
|
||||||
|
if (!lessons || lessons.length === 0) return 0;
|
||||||
|
|
||||||
|
// If no completed lessons, start with the first one
|
||||||
|
if (completedLessons.length === 0) return 0;
|
||||||
|
|
||||||
|
// Find the highest completed lesson index
|
||||||
|
const lessonIndices = lessons.map((lesson, index) => {
|
||||||
|
return { id: lesson.id, index };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get indices of completed lessons
|
||||||
|
const completedIndices = lessonIndices
|
||||||
|
.filter(item => completedLessons.includes(item.id))
|
||||||
|
.map(item => item.index);
|
||||||
|
|
||||||
|
// Get the max completed index
|
||||||
|
const maxCompletedIndex = Math.max(...completedIndices);
|
||||||
|
|
||||||
|
// Return the next lesson index (or the last one if all completed)
|
||||||
|
return Math.min(maxCompletedIndex + 1, lessons.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextLessonIndex = getNextLessonIndex();
|
||||||
|
const buttonLabel = completedLessons.length === 0
|
||||||
|
? "Start Lesson 1"
|
||||||
|
: `Continue to Lesson ${nextLessonIndex + 1}`;
|
||||||
|
|
||||||
|
const handleContinueClick = () => {
|
||||||
|
onLessonSelect(nextLessonIndex);
|
||||||
|
toggleToContentTab();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Header with course image, title and options */}
|
{/* Header with course image, title and options */}
|
||||||
@ -33,7 +71,7 @@ export default function DesktopCourseDetails({
|
|||||||
className="object-cover"
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title and options */}
|
{/* Title and options */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-start justify-between mb-2">
|
<div className="flex items-start justify-between mb-2">
|
||||||
@ -56,7 +94,7 @@ export default function DesktopCourseDetails({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Topics/tags */}
|
{/* Topics/tags */}
|
||||||
<div className="flex flex-wrap gap-2 mb-3">
|
<div className="flex flex-wrap gap-2 mb-3">
|
||||||
{processedEvent.topics &&
|
{processedEvent.topics &&
|
||||||
@ -65,7 +103,7 @@ export default function DesktopCourseDetails({
|
|||||||
<Tag className="text-white" key={index} value={topic}></Tag>
|
<Tag className="text-white" key={index} value={topic}></Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Author info */}
|
{/* Author info */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Image
|
<Image
|
||||||
@ -89,9 +127,9 @@ export default function DesktopCourseDetails({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider className="my-4" />
|
<Divider className="my-4" />
|
||||||
|
|
||||||
{/* Course details */}
|
{/* Course details */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
{/* Left column: Description */}
|
{/* Left column: Description */}
|
||||||
@ -99,19 +137,19 @@ export default function DesktopCourseDetails({
|
|||||||
<h2 className="text-xl font-semibold mb-3 text-white">About This Course</h2>
|
<h2 className="text-xl font-semibold mb-3 text-white">About This Course</h2>
|
||||||
<div className="text-gray-300 mb-4">
|
<div className="text-gray-300 mb-4">
|
||||||
{processedEvent?.description?.split('\n')
|
{processedEvent?.description?.split('\n')
|
||||||
.map((line, index) => <p key={index} className="mb-2">{line}</p>)}
|
.map((line, index) => <p key={index} className="mb-2">{line}</p>)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment section */}
|
{/* Payment section */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{renderPaymentMessage()}
|
{renderPaymentMessage()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right column: Course details */}
|
{/* Right column: Course details */}
|
||||||
<div className="bg-gray-800 rounded-lg h-fit p-4">
|
<div className="bg-gray-800 rounded-lg h-fit p-4">
|
||||||
<h2 className="text-xl font-semibold mb-3 text-white">Course Information</h2>
|
<h2 className="text-xl font-semibold mb-3 text-white">Course Information</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-gray-300 font-medium mb-2">Course Content</h3>
|
<h3 className="text-gray-300 font-medium mb-2">Course Content</h3>
|
||||||
@ -128,7 +166,7 @@ export default function DesktopCourseDetails({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{processedEvent.published && (
|
{processedEvent.published && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-gray-300 font-medium mb-2">Details</h3>
|
<h3 className="text-gray-300 font-medium mb-2">Details</h3>
|
||||||
@ -141,6 +179,18 @@ export default function DesktopCourseDetails({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Continue/Start button */}
|
||||||
|
{lessons && lessons.length > 0 && !isCompleted && (
|
||||||
|
<div className="mt-4 flex justify-start">
|
||||||
|
<GenericButton
|
||||||
|
label={buttonLabel}
|
||||||
|
icon="pi pi-play"
|
||||||
|
onClick={handleContinueClick}
|
||||||
|
outlined={true}
|
||||||
|
disabled={paidCourse && !decryptionPerformed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { Tag } from 'primereact/tag';
|
|||||||
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
import ZapDisplay from '@/components/zaps/ZapDisplay';
|
||||||
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
||||||
import { Divider } from 'primereact/divider';
|
import { Divider } from 'primereact/divider';
|
||||||
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
|
|
||||||
export default function MobileCourseDetails({
|
export default function MobileCourseDetails({
|
||||||
processedEvent,
|
processedEvent,
|
||||||
@ -17,8 +18,45 @@ export default function MobileCourseDetails({
|
|||||||
returnImageProxy,
|
returnImageProxy,
|
||||||
renderPaymentMessage,
|
renderPaymentMessage,
|
||||||
isCompleted,
|
isCompleted,
|
||||||
showCompletedTag
|
showCompletedTag,
|
||||||
|
completedLessons,
|
||||||
|
onLessonSelect,
|
||||||
|
toggleToContentTab
|
||||||
}) {
|
}) {
|
||||||
|
// Calculate next lesson to start/continue
|
||||||
|
const getNextLessonIndex = () => {
|
||||||
|
if (!lessons || lessons.length === 0) return 0;
|
||||||
|
|
||||||
|
// If no completed lessons, start with the first one
|
||||||
|
if (completedLessons.length === 0) return 0;
|
||||||
|
|
||||||
|
// Find the highest completed lesson index
|
||||||
|
const lessonIndices = lessons.map((lesson, index) => {
|
||||||
|
return { id: lesson.id, index };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get indices of completed lessons
|
||||||
|
const completedIndices = lessonIndices
|
||||||
|
.filter(item => completedLessons.includes(item.id))
|
||||||
|
.map(item => item.index);
|
||||||
|
|
||||||
|
// Get the max completed index
|
||||||
|
const maxCompletedIndex = Math.max(...completedIndices);
|
||||||
|
|
||||||
|
// Return the next lesson index (or the last one if all completed)
|
||||||
|
return Math.min(maxCompletedIndex + 1, lessons.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextLessonIndex = getNextLessonIndex();
|
||||||
|
const buttonLabel = completedLessons.length === 0
|
||||||
|
? "Start Lesson 1"
|
||||||
|
: `Continue to Lesson ${nextLessonIndex + 1}`;
|
||||||
|
|
||||||
|
const handleContinueClick = () => {
|
||||||
|
onLessonSelect(nextLessonIndex);
|
||||||
|
toggleToContentTab();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Mobile-specific layout */}
|
{/* Mobile-specific layout */}
|
||||||
@ -128,6 +166,19 @@ export default function MobileCourseDetails({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Continue/Start button */}
|
||||||
|
{lessons && lessons.length > 0 && !isCompleted && (
|
||||||
|
<div className="mt-4 flex justify-start">
|
||||||
|
<GenericButton
|
||||||
|
label={buttonLabel}
|
||||||
|
icon="pi pi-play"
|
||||||
|
onClick={handleContinueClick}
|
||||||
|
outlined={true}
|
||||||
|
disabled={paidCourse && !decryptionPerformed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -120,7 +120,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPaid && !decryptionPerformed) {
|
if (isPaid && !decryptionPerformed) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-8 rounded-lg flex flex-col items-center justify-center">
|
<div className="w-full p-8 rounded-lg flex flex-col items-center justify-center">
|
||||||
@ -133,7 +133,7 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
|
return <MarkdownDisplay content={lesson.content} className="p-4 rounded-lg w-full" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,13 +141,15 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Toast ref={toastRef} />
|
<Toast ref={toastRef} />
|
||||||
<div className="relative w-[80%] h-[200px] mx-auto mb-24">
|
<div className="relative w-[80%] h-[200px] mx-auto mb-24">
|
||||||
<Image
|
<div className="relative w-full h-full mt-2 rounded-lg">
|
||||||
alt="lesson background image"
|
<Image
|
||||||
src={returnImageProxy(lesson.image)}
|
alt="lesson background image"
|
||||||
fill
|
src={returnImageProxy(lesson.image)}
|
||||||
className="object-cover"
|
fill
|
||||||
/>
|
className="object-cover rounded-lg"
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-20"></div>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-0 bg-gray-800 bg-opacity-20"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mx-auto px-4 py-8 -mt-32 relative z-10">
|
<div className="w-full mx-auto px-4 py-8 -mt-32 relative z-10">
|
||||||
<div className="mb-8 bg-gray-800/70 rounded-lg p-4">
|
<div className="mb-8 bg-gray-800/70 rounded-lg p-4">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tag } from 'primereact/tag';
|
import { Tag } from 'primereact/tag';
|
||||||
import CourseDetails from '../details/CourseDetails';
|
import CourseDetails from '../details/CourseDetails';
|
||||||
|
import GenericButton from '@/components/buttons/GenericButton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display course overview with details
|
* Component to display course overview with details
|
||||||
@ -13,12 +14,15 @@ const CourseOverview = ({
|
|||||||
handlePaymentSuccess,
|
handlePaymentSuccess,
|
||||||
handlePaymentError,
|
handlePaymentError,
|
||||||
isMobileView,
|
isMobileView,
|
||||||
completedLessons
|
completedLessons,
|
||||||
|
onLessonSelect,
|
||||||
|
toggleToContentTab
|
||||||
}) => {
|
}) => {
|
||||||
// Determine if course is completed
|
// Determine if course is completed
|
||||||
const isCompleted = lessons && lessons.length > 0 && completedLessons.length === lessons.length;
|
const isCompleted = lessons && lessons.length > 0 && completedLessons.length === lessons.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className={`bg-gray-800 rounded-lg border border-gray-800 shadow-md ${isMobileView ? 'p-4' : 'p-6'}`}>
|
<div className={`bg-gray-800 rounded-lg border border-gray-800 shadow-md ${isMobileView ? 'p-4' : 'p-6'}`}>
|
||||||
{isMobileView && course && (
|
{isMobileView && course && (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
@ -50,8 +54,12 @@ const CourseOverview = ({
|
|||||||
handlePaymentError={handlePaymentError}
|
handlePaymentError={handlePaymentError}
|
||||||
isMobileView={isMobileView}
|
isMobileView={isMobileView}
|
||||||
showCompletedTag={!isMobileView}
|
showCompletedTag={!isMobileView}
|
||||||
|
completedLessons={completedLessons}
|
||||||
|
onLessonSelect={onLessonSelect}
|
||||||
|
toggleToContentTab={toggleToContentTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -242,6 +242,14 @@ const Course = () => {
|
|||||||
handlePaymentError={handlePaymentError}
|
handlePaymentError={handlePaymentError}
|
||||||
isMobileView={isMobileView}
|
isMobileView={isMobileView}
|
||||||
completedLessons={completedLessons}
|
completedLessons={completedLessons}
|
||||||
|
onLessonSelect={(index) => {
|
||||||
|
handleLessonSelect(index);
|
||||||
|
// Update URL with active parameter
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('active', index);
|
||||||
|
router.push(url, undefined, { shallow: true });
|
||||||
|
}}
|
||||||
|
toggleToContentTab={() => toggleTab(1)} // Assuming content tab is at index 1
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user