From fad187b862cad3756c4c097a4f7ba7bc6ab7e6f9 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Fri, 25 Oct 2024 17:33:34 +0600 Subject: [PATCH] wip --- src/components/Course/CertificateView.tsx | 133 ++++++++++++++---- src/components/Course/Chapter.tsx | 131 +++++++++-------- src/components/Course/CourseLayout.tsx | 35 ++--- src/components/Course/CourseSidebar.tsx | 28 ++-- src/components/Course/RateCourseForm.tsx | 72 ++++++++++ .../[courseId]/[chapterId]/[lessonId].astro | 7 +- src/pages/learn/[courseId]/certificate.astro | 8 +- 7 files changed, 292 insertions(+), 122 deletions(-) create mode 100644 src/components/Course/RateCourseForm.tsx diff --git a/src/components/Course/CertificateView.tsx b/src/components/Course/CertificateView.tsx index dedd6096c..d14bbab52 100644 --- a/src/components/Course/CertificateView.tsx +++ b/src/components/Course/CertificateView.tsx @@ -1,42 +1,121 @@ -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Rating } from '../Rating/Rating'; +import { RateCourseForm } from './RateCourseForm'; +import type { ChapterFileType } from '../../lib/course'; +import { useCourseProgress } from '../../hooks/use-course'; +import { Loader2 } from 'lucide-react'; type CertificateViewProps = { + chapters: ChapterFileType[]; currentCourseId: string; }; export function CertificateView(props: CertificateViewProps) { - const { currentCourseId } = props; + const { currentCourseId, chapters } = props; + + const [isLoading, setIsLoading] = useState(true); + + const { data: courseProgress, status } = useCourseProgress(currentCourseId); + + const completeLessonSet = useMemo( + () => + new Set( + (courseProgress?.completed || []).map( + (l) => `/learn/${currentCourseId}/${l.chapterId}/${l.lessonId}`, + ), + ), + [courseProgress], + ); + + const allLessonLinks = useMemo(() => { + const lessons: string[] = []; + for (const chapter of chapters) { + for (const lesson of chapter.lessons) { + lessons.push(`/learn/${currentCourseId}/${chapter.id}/${lesson.id}`); + } + } + + return lessons; + }, [chapters]); + + const isCourseCompleted = useMemo(() => { + return allLessonLinks.every((lessonLink) => + completeLessonSet.has(lessonLink), + ); + }, [allLessonLinks, completeLessonSet]); const [rating, setRating] = useState(0); + const [showRatingForm, setShowRatingForm] = useState(false); - return ( -
-
-

Congratulations!

-

- You finished the course. Download the completion certificate below and - share it with the world. -

- -
+ useEffect(() => { + if (!courseProgress) { + return; + } + + setIsLoading(false); + }, [courseProgress]); -
- setRating(rating)} - starSize={36} + return ( + <> + {showRatingForm && ( + { + setRating(0); + setShowRatingForm(false); + }} /> - Rate your experience + )} + +
+ {isLoading && ( + + )} + + {isCourseCompleted && !isLoading && ( + <> +
+

Congratulations!

+

+ You finished the course. Download the completion certificate + below and share it with the world. +

+ +
+ +
+ { + setRating(rating); + setShowRatingForm(true); + }} + starSize={36} + /> + Rate your experience +
+ + )} + + {!isCourseCompleted && !isLoading && ( +
+

Almost there!

+

+ Complete the course to download the certificate and rate your + experience. +

+
+ )}
-
+ ); } diff --git a/src/components/Course/Chapter.tsx b/src/components/Course/Chapter.tsx index cc5ffc349..7acd99870 100644 --- a/src/components/Course/Chapter.tsx +++ b/src/components/Course/Chapter.tsx @@ -12,44 +12,46 @@ type ChapterProps = ChapterFileType & { isActive?: boolean; isCompleted?: boolean; - currentCourseId: string; - currentChapterId?: string; - currentLessonId?: string; + activeCourseId: string; + activeChapterId?: string; + activeLessonId?: string; onChapterClick?: () => void; }; export function Chapter(props: ChapterProps) { const { + id: chapterId, index, frontmatter, lessons, isActive = false, onChapterClick, - currentCourseId, - currentChapterId, - currentLessonId, + activeCourseId, + activeChapterId, + activeLessonId, } = props; const { title } = frontmatter; - const { data: courseProgress } = useCourseProgress(currentCourseId); + const { data: courseProgress } = useCourseProgress(activeCourseId); const completeLessonSet = useMemo( () => new Set( (courseProgress?.completed || []) - .filter((l) => l.chapterId === currentChapterId) + .filter((l) => l.chapterId === chapterId) .map((l) => `${l.chapterId}/${l.lessonId}`), ), [courseProgress], ); + const isChapterCompleted = lessons.every((lesson) => - completeLessonSet.has(`${currentChapterId}/${lesson.id}`), + completeLessonSet.has(`${chapterId}/${lesson.id}`), ); const completedPercentage = useMemo(() => { const completedCount = lessons.filter((lesson) => - completeLessonSet.has(`${currentChapterId}/${lesson.id}`), + completeLessonSet.has(`${chapterId}/${lesson.id}`), ).length; return getPercentage(completedCount, lessons.length); @@ -101,25 +103,14 @@ export function Chapter(props: ChapterProps) {
{lessons.length > 0 && ( <> -
- {filteredLessons?.map((lesson) => { - const isActive = currentLessonId === lesson.id; - const isCompleted = completeLessonSet.has( - `${currentChapterId}/${lesson.id}`, - ); - - return ( - - ); - })} -
+
-
- {exercises?.map((exercise) => { - const isActive = currentLessonId === exercise.id; - const isCompleted = completeLessonSet.has( - `${currentChapterId}/${exercise.id}`, - ); - - return ( - - ); - })} -
+ )} @@ -160,28 +140,69 @@ export function Chapter(props: ChapterProps) { ); } -type LessonProps = LessonFileType & { - currentCourseId: string; - currentChapterId?: string; +type LessonListProps = { + activeCourseId: string; + activeChapterId?: string; + activeLessonId?: string; + chapterId: string; + lessons: LessonFileType[]; + completedLessonSet: Set; +}; + +function LessonList(props: LessonListProps) { + const { + activeCourseId, + activeChapterId, + activeLessonId, + chapterId, + lessons, + completedLessonSet, + } = props; + + return ( +
+ {lessons.map((lesson) => { + const isActive = + activeLessonId === lesson.id && chapterId === activeChapterId; + const isCompleted = completedLessonSet.has(`${chapterId}/${lesson.id}`); + + return ( + + ); + })} +
+ ); +} + +type LessonProps = LessonFileType & { isActive?: boolean; isCompleted?: boolean; + courseId: string; + chapterId: string; }; export function Lesson(props: LessonProps) { const { frontmatter, isActive, - currentCourseId, - currentChapterId, + courseId, + chapterId, id: lessonId, isCompleted, } = props; const { title } = frontmatter; const isMounted = useIsMounted(); - const { isLoading } = useCourseProgress(currentCourseId); - const href = `/learn/${currentCourseId}/${currentChapterId}/${lessonId}`; + const { isLoading } = useCourseProgress(courseId); + const href = `/learn/${courseId}/${chapterId}/${lessonId}`; return ( new Set( (courseProgress?.completed || []).map( - (l) => `/learn/${currentCourseId}/${l.chapterId}/${l.lessonId}`, + (l) => `/learn/${activeCourseId}/${l.chapterId}/${l.lessonId}`, ), ), [courseProgress], @@ -45,7 +40,7 @@ export function CourseLayout(props: CourseLayoutProps) { const lessons: string[] = []; for (const chapter of chapters) { for (const lesson of chapter.lessons) { - lessons.push(`/learn/${currentCourseId}/${chapter.id}/${lesson.id}`); + lessons.push(`/learn/${activeCourseId}/${chapter.id}/${lesson.id}`); } } @@ -60,7 +55,7 @@ export function CourseLayout(props: CourseLayoutProps) { return getPercentage(completedCount, allLessonLinks.length); }, [allLessonLinks, completeLessonSet]); - const currentLessonUrl = `/learn/${currentCourseId}/${currentChapterId}/${currentLessonId}`; + const currentLessonUrl = `/learn/${activeCourseId}/${activeChapterId}/${activeLessonId}`; const isCurrentLessonCompleted = completeLessonSet.has(currentLessonUrl); const currentLessonIndex = allLessonLinks.indexOf(currentLessonUrl); @@ -75,14 +70,14 @@ export function CourseLayout(props: CourseLayoutProps) { return; } - if (!currentChapterId || !currentLessonId) { + if (!activeChapterId || !activeLessonId) { return; } completeLesson.mutate( { - chapterId: currentChapterId, - lessonId: currentLessonId, + chapterId: activeChapterId, + lessonId: activeLessonId, }, { onSuccess: () => { @@ -102,9 +97,9 @@ export function CourseLayout(props: CourseLayoutProps) { } currentLesson.set({ - courseId: currentCourseId, - chapterId: currentChapterId, - lessonId: currentLessonId, + courseId: activeCourseId, + chapterId: activeChapterId, + lessonId: activeLessonId, lessonType: lesson?.frontmatter?.type, challengeStatus: 'pending', quizStatus: 'pending', @@ -126,7 +121,7 @@ export function CourseLayout(props: CourseLayoutProps) {
- {currentChapterId && currentLessonId && ( + {activeChapterId && activeLessonId && (