diff --git a/src/components/Course/Chapter.tsx b/src/components/Course/Chapter.tsx
index 13f1e43ce..cc5ffc349 100644
--- a/src/components/Course/Chapter.tsx
+++ b/src/components/Course/Chapter.tsx
@@ -12,9 +12,9 @@ type ChapterProps = ChapterFileType & {
isActive?: boolean;
isCompleted?: boolean;
- courseId: string;
- chapterId: string;
- lessonId?: string;
+ currentCourseId: string;
+ currentChapterId?: string;
+ currentLessonId?: string;
onChapterClick?: () => void;
};
@@ -26,30 +26,30 @@ export function Chapter(props: ChapterProps) {
isActive = false,
onChapterClick,
- courseId,
- chapterId,
- lessonId,
+ currentCourseId,
+ currentChapterId,
+ currentLessonId,
} = props;
const { title } = frontmatter;
- const { data: courseProgress } = useCourseProgress(courseId);
+ const { data: courseProgress } = useCourseProgress(currentCourseId);
const completeLessonSet = useMemo(
() =>
new Set(
(courseProgress?.completed || [])
- .filter((l) => l.chapterId === chapterId)
+ .filter((l) => l.chapterId === currentChapterId)
.map((l) => `${l.chapterId}/${l.lessonId}`),
),
[courseProgress],
);
const isChapterCompleted = lessons.every((lesson) =>
- completeLessonSet.has(`${chapterId}/${lesson.id}`),
+ completeLessonSet.has(`${currentChapterId}/${lesson.id}`),
);
const completedPercentage = useMemo(() => {
const completedCount = lessons.filter((lesson) =>
- completeLessonSet.has(`${chapterId}/${lesson.id}`),
+ completeLessonSet.has(`${currentChapterId}/${lesson.id}`),
).length;
return getPercentage(completedCount, lessons.length);
@@ -103,17 +103,17 @@ export function Chapter(props: ChapterProps) {
<>
{filteredLessons?.map((lesson) => {
- const isActive = lessonId === lesson.id;
+ const isActive = currentLessonId === lesson.id;
const isCompleted = completeLessonSet.has(
- `${chapterId}/${lesson.id}`,
+ `${currentChapterId}/${lesson.id}`,
);
return (
@@ -131,17 +131,17 @@ export function Chapter(props: ChapterProps) {
{exercises?.map((exercise) => {
- const isActive = lessonId === exercise.id;
+ const isActive = currentLessonId === exercise.id;
const isCompleted = completeLessonSet.has(
- `${chapterId}/${exercise.id}`,
+ `${currentChapterId}/${exercise.id}`,
);
return (
@@ -161,8 +161,8 @@ export function Chapter(props: ChapterProps) {
}
type LessonProps = LessonFileType & {
- courseId: string;
- chapterId: string;
+ currentCourseId: string;
+ currentChapterId?: string;
isActive?: boolean;
isCompleted?: boolean;
@@ -172,16 +172,16 @@ export function Lesson(props: LessonProps) {
const {
frontmatter,
isActive,
- courseId,
- chapterId,
+ currentCourseId,
+ currentChapterId,
id: lessonId,
isCompleted,
} = props;
const { title } = frontmatter;
const isMounted = useIsMounted();
- const { isLoading } = useCourseProgress(courseId);
- const href = `/learn/${courseId}/${chapterId}/${lessonId}`;
+ const { isLoading } = useCourseProgress(currentCourseId);
+ const href = `/learn/${currentCourseId}/${currentChapterId}/${lessonId}`;
return (
;
export function CourseLayout(props: CourseLayoutProps) {
const { children, ...sidebarProps } = props;
- const { chapters, courseId, chapterId, lessonId, lesson } = sidebarProps;
+ const {
+ chapters,
+ currentCourseId,
+ currentChapterId,
+ currentLessonId,
+ lesson,
+ } = sidebarProps;
const $currentLesson = useStore(currentLesson);
const [showNextWarning, setShowNextWarning] = useState(false);
- const { data: courseProgress } = useCourseProgress(courseId);
- const completeLesson = useCompleteLessonMutation(courseId);
+ const { data: courseProgress } = useCourseProgress(currentCourseId);
+ const completeLesson = useCompleteLessonMutation(currentCourseId);
const completeLessonSet = useMemo(
() =>
new Set(
(courseProgress?.completed || []).map(
- (l) => `/learn/${courseId}/${l.chapterId}/${l.lessonId}`,
+ (l) => `/learn/${currentCourseId}/${l.chapterId}/${l.lessonId}`,
),
),
[courseProgress],
@@ -38,7 +45,7 @@ export function CourseLayout(props: CourseLayoutProps) {
const lessons: string[] = [];
for (const chapter of chapters) {
for (const lesson of chapter.lessons) {
- lessons.push(`/learn/${courseId}/${chapter.id}/${lesson.id}`);
+ lessons.push(`/learn/${currentCourseId}/${chapter.id}/${lesson.id}`);
}
}
@@ -53,7 +60,7 @@ export function CourseLayout(props: CourseLayoutProps) {
return getPercentage(completedCount, allLessonLinks.length);
}, [allLessonLinks, completeLessonSet]);
- const currentLessonUrl = `/learn/${courseId}/${chapterId}/${lessonId}`;
+ const currentLessonUrl = `/learn/${currentCourseId}/${currentChapterId}/${currentLessonId}`;
const isCurrentLessonCompleted = completeLessonSet.has(currentLessonUrl);
const currentLessonIndex = allLessonLinks.indexOf(currentLessonUrl);
@@ -68,10 +75,14 @@ export function CourseLayout(props: CourseLayoutProps) {
return;
}
+ if (!currentChapterId || !currentLessonId) {
+ return;
+ }
+
completeLesson.mutate(
{
- chapterId,
- lessonId,
+ chapterId: currentChapterId,
+ lessonId: currentLessonId,
},
{
onSuccess: () => {
@@ -91,10 +102,10 @@ export function CourseLayout(props: CourseLayoutProps) {
}
currentLesson.set({
- courseId,
- chapterId,
- lessonId,
- lessonType: lesson.frontmatter.type,
+ courseId: currentCourseId,
+ chapterId: currentChapterId,
+ lessonId: currentLessonId,
+ lessonType: lesson?.frontmatter?.type,
challengeStatus: 'pending',
quizStatus: 'pending',
});
@@ -112,7 +123,14 @@ export function CourseLayout(props: CourseLayoutProps) {
/>
)}
-
+
-
+ {currentChapterId && currentLessonId && (
+
+ )}
>
);
diff --git a/src/components/Course/CourseSidebar.tsx b/src/components/Course/CourseSidebar.tsx
index 45e9247fc..4137d55f4 100644
--- a/src/components/Course/CourseSidebar.tsx
+++ b/src/components/Course/CourseSidebar.tsx
@@ -1,15 +1,16 @@
import { useState } from 'react';
import type { ChapterFileType, LessonFileType } from '../../lib/course';
import { Chapter } from './Chapter';
+import { StickyNote } from 'lucide-react';
export type CourseSidebarProps = {
- courseId: string;
- chapterId: string;
- lessonId: string;
+ currentCourseId: string;
+ currentChapterId?: string;
+ currentLessonId?: string;
title: string;
chapters: ChapterFileType[];
- lesson: LessonFileType;
+ lesson?: LessonFileType;
completedPercentage: number;
};
@@ -19,12 +20,14 @@ export function CourseSidebar(props: CourseSidebarProps) {
title,
chapters,
completedPercentage,
- chapterId,
- lessonId,
- courseId,
+ currentCourseId,
+ currentChapterId,
+ currentLessonId,
} = props;
- const [activeChapterId, setActiveChapterId] = useState(chapterId);
+ const [activeChapterId, setActiveChapterId] = useState(currentChapterId);
+
+ const ceritificateUrl = `/learn/${currentCourseId}/certificate`;
return (
diff --git a/src/pages/learn/[courseId]/certificate.astro b/src/pages/learn/[courseId]/certificate.astro
new file mode 100644
index 000000000..fcbc2f674
--- /dev/null
+++ b/src/pages/learn/[courseId]/certificate.astro
@@ -0,0 +1,65 @@
+---
+import { CourseLayout } from '../../../components/Course/CourseLayout';
+import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
+import {
+ getAllCourses,
+ getChaptersByCourseId,
+ type CourseFileType,
+ type ChapterFileType,
+} from '../../../lib/course';
+
+interface Params extends Record
{
+ courseId: string;
+}
+
+interface Props {
+ course: CourseFileType & { chapters: ChapterFileType[] };
+}
+
+export async function getStaticPaths() {
+ const courses = await getAllCourses();
+ const coursesWithChapters = await Promise.all(
+ courses.map(async (course) => {
+ const chapters = await getChaptersByCourseId(course.id);
+ return {
+ ...course,
+ chapters,
+ };
+ }),
+ );
+
+ const paths: {
+ params: Params;
+ props: Props;
+ }[] = [];
+
+ for (const course of coursesWithChapters) {
+ const courseId = course.id;
+
+ paths.push({
+ params: {
+ courseId,
+ },
+ props: {
+ course,
+ },
+ });
+ }
+
+ return paths;
+}
+
+const { courseId } = Astro.params;
+const { course } = Astro.props;
+---
+
+
+
+ Hello
+
+
diff --git a/src/stores/course.ts b/src/stores/course.ts
index 05b7ed2a5..37f8c7a05 100644
--- a/src/stores/course.ts
+++ b/src/stores/course.ts
@@ -3,9 +3,9 @@ import type { AllowedLessonType } from '../lib/course';
export type CurrentLessonType = {
courseId: string;
- chapterId: string;
- lessonId: string;
- lessonType: AllowedLessonType;
+ chapterId?: string;
+ lessonId?: string;
+ lessonType?: AllowedLessonType;
challengeStatus?: 'pending' | 'wrong' | 'correct';
quizStatus?: 'pending' | 'wrong' | 'correct';
};