Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/components/CourseProgress/ChapterCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useCallback } from 'react';
import { useLocation } from '@docusaurus/router';
import styles from './styles.module.css';

const ChapterCard = ({
id,
title,
status,
progress,
timeEstimate,
onStart,
path,
isCurrent
}) => {
const isCompleted = status === 'completed';
const isInProgress = status === 'in-progress';
const location = useLocation();

const handleAction = useCallback((e) => {
e.preventDefault();
if (!isCompleted && !isInProgress) {
onStart?.();
}
// If the chapter has a path, let the link handle navigation
// The progress update will happen via the URL change detection
}, [isCompleted, isInProgress, onStart]);

return (
<div
className={`${styles.chapterCard}
${isCompleted ? styles.completed : ''}
${isCurrent ? styles.currentChapter : ''}`}
>
<div className={styles.chapterHeader}>
<span className={styles.chapterNumber}>Chapter {id}</span>
{isCompleted ? (
<span className={styles.statusBadge}>
<span className={styles.checkmark}>✓</span> Completed
</span>
) : isInProgress ? (
<span className={`${styles.statusBadge} ${styles.inProgressBadge}`}>
In Progress
</span>
) : null}
</div>

<h3 className={styles.chapterTitle}>{title}</h3>

<div className={styles.progressContainer}>
<div
className={styles.chapterProgress}
style={{ width: `${progress}%` }}
/>
</div>

<div className={styles.chapterFooter}>
<span className={styles.timeEstimate}>⏱️ {timeEstimate}</span>
{path ? (
<a
href={path}
className={`${styles.actionButton} ${isCompleted ? styles.completedButton : ''}`}
onClick={handleAction}
>
{isCompleted ? 'Review' : isInProgress ? 'Continue' : 'Start'}
</a>
) : (
<button
className={`${styles.actionButton} ${isCompleted ? styles.completedButton : ''}`}
onClick={handleAction}
disabled={isCompleted}
>
{isCompleted ? 'Completed' : isInProgress ? 'Continue' : 'Start'}
</button>
)}
</div>
</div>
);
};

export default ChapterCard;
21 changes: 21 additions & 0 deletions src/components/CourseProgress/ProgressBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import styles from './styles.module.css';

const ProgressBar = ({ percentage }) => {
return (
<div className={styles.progressBarContainer}>
<div
className={styles.progressBar}
style={{ width: `${percentage}%` }}
role="progressbar"
aria-valuenow={percentage}
aria-valuemin="0"
aria-valuemax="100"
>
<span className={styles.progressText}>{percentage}% Complete</span>
</div>
</div>
);
};

export default ProgressBar;
112 changes: 112 additions & 0 deletions src/components/CourseProgress/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from '@docusaurus/router';
import styles from './styles.module.css';
import ProgressBar from './ProgressBar';
import ChapterCard from './ChapterCard';

// Helper function to get/set progress from localStorage
const getStoredProgress = (courseId) => {
if (typeof window === 'undefined') return {};
const stored = localStorage.getItem(`course-progress-${courseId}`);
return stored ? JSON.parse(stored) : {};
};

const saveProgress = (courseId, progress) => {
if (typeof window !== 'undefined') {
localStorage.setItem(`course-progress-${courseId}`, JSON.stringify(progress));
}
};

const CourseProgress = ({ courseId, chapters: initialChapters }) => {
const location = useLocation();
const [chapters, setChapters] = useState(initialChapters);

// Initialize progress from localStorage
useEffect(() => {
const storedProgress = getStoredProgress(courseId);

setChapters(prevChapters =>
prevChapters.map(chapter => ({
...chapter,
status: storedProgress[chapter.id]?.status || 'not-started',
progress: storedProgress[chapter.id]?.progress || 0
}))
);
}, [courseId]);

// Update chapter status and save to localStorage
const updateChapterStatus = (chapterId, status, progress) => {
setChapters(prevChapters => {
const updatedChapters = prevChapters.map(chapter =>
chapter.id === chapterId
? { ...chapter, status, progress }
: chapter
);

// Save to localStorage
const progressToSave = {};
updatedChapters.forEach(chapter => {
progressToSave[chapter.id] = {
status: chapter.status,
progress: chapter.progress
};
});

saveProgress(courseId, progressToSave);

return updatedChapters;
});
};

// Mark chapter as started/continued
const handleChapterStart = (chapterId) => {
updateChapterStatus(chapterId, 'in-progress', 10); // Start with 10% progress
// You can add navigation logic here
};

// Calculate overall progress
const totalChapters = chapters.length;
const completedChapters = chapters.filter(chapter => chapter.status === 'completed').length;
const inProgressChapters = chapters.filter(chapter => chapter.status === 'in-progress').length;
const overallProgress = Math.round((completedChapters / totalChapters) * 100);

// Auto-detect current chapter based on URL
useEffect(() => {
if (!chapters.length) return;

// Get the current path and find the matching chapter
const currentPath = location.pathname;
const currentChapter = chapters.find(chapter =>
chapter.path && currentPath.includes(chapter.path)
);

if (currentChapter && currentChapter.status === 'not-started') {
updateChapterStatus(currentChapter.id, 'in-progress', 10);
}
}, [location.pathname, chapters]);

return (
<div className={styles.courseProgress}>
<h2>Course Progress</h2>
<ProgressBar percentage={overallProgress} />

<div className={styles.chapterList}>
{chapters.map((chapter) => (
<ChapterCard
key={chapter.id}
id={chapter.id}
title={chapter.title}
status={chapter.status}
progress={chapter.status === 'completed' ? 100 : chapter.progress || 0}
timeEstimate={chapter.timeEstimate}
onStart={() => handleChapterStart(chapter.id)}
path={chapter.path}
isCurrent={location.pathname.includes(chapter.path || '')}
/>
))}
</div>
</div>
);
};

export default CourseProgress;
Loading
Loading