feat(frontend): add progress tracking

This commit is contained in:
2025-07-31 13:23:11 +01:00
parent a7d29b7cce
commit ca989b6943
3 changed files with 71 additions and 1 deletions

View File

@@ -0,0 +1,26 @@
"use client";
import { useState, useEffect } from "react";
import { CheckCircle } from "lucide-react";
import { isQuestionCompleted } from "@/lib/progress";
interface CompletionIndicatorProps {
slug: string;
}
export function CompletionIndicator({ slug }: CompletionIndicatorProps) {
const [completed, setCompleted] = useState(false);
useEffect(() => {
setCompleted(isQuestionCompleted(slug));
}, [slug]);
if (!completed) return null;
return (
<CheckCircle
className="h-4 w-4 text-green-500 flex-shrink-0"
aria-label="Completed"
/>
);
}

View File

@@ -1,6 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { getDifficultyVariant, getDifficultyLabel, capitalize } from "@/lib/utils"; import { getDifficultyVariant, getDifficultyLabel, capitalize } from "@/lib/utils";
import { CompletionIndicator } from "./completion-indicator";
import type { QuestionListItem } from "@/types"; import type { QuestionListItem } from "@/types";
interface QuestionCardProps { interface QuestionCardProps {
@@ -14,7 +15,10 @@ export function QuestionCard({ question }: QuestionCardProps) {
className="block p-4 rounded-lg border border-[var(--border)] bg-[var(--card)] hover:border-[var(--primary)] transition-colors" className="block p-4 rounded-lg border border-[var(--border)] bg-[var(--card)] hover:border-[var(--primary)] transition-colors"
> >
<div className="flex items-start justify-between gap-4 mb-3"> <div className="flex items-start justify-between gap-4 mb-3">
<h3 className="font-semibold">{question.title}</h3> <div className="flex items-center gap-2">
<CompletionIndicator slug={question.slug} />
<h3 className="font-semibold">{question.title}</h3>
</div>
<Badge <Badge
variant={getDifficultyVariant(question.difficulty)} variant={getDifficultyVariant(question.difficulty)}
aria-label={getDifficultyLabel(question.difficulty)} aria-label={getDifficultyLabel(question.difficulty)}

View File

@@ -0,0 +1,40 @@
const PROGRESS_KEY = "codetutor-progress";
const SOLUTIONS_KEY = "codetutor-solutions";
export function getCompletedQuestions(): Set<string> {
if (typeof window === "undefined") return new Set();
const stored = localStorage.getItem(PROGRESS_KEY);
return stored ? new Set(JSON.parse(stored)) : new Set();
}
export function markQuestionCompleted(slug: string, code?: string): void {
const completed = getCompletedQuestions();
completed.add(slug);
localStorage.setItem(PROGRESS_KEY, JSON.stringify([...completed]));
if (code) {
saveSolution(slug, code);
}
}
export function isQuestionCompleted(slug: string): boolean {
return getCompletedQuestions().has(slug);
}
export function saveSolution(slug: string, code: string): void {
if (typeof window === "undefined") return;
const solutions = getSavedSolutions();
solutions[slug] = code;
localStorage.setItem(SOLUTIONS_KEY, JSON.stringify(solutions));
}
export function getSavedSolution(slug: string): string | null {
if (typeof window === "undefined") return null;
const solutions = getSavedSolutions();
return solutions[slug] || null;
}
function getSavedSolutions(): Record<string, string> {
const stored = localStorage.getItem(SOLUTIONS_KEY);
return stored ? JSON.parse(stored) : {};
}