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 && (