diff --git a/src/app/courses/[courseId]/quiz/page.tsx b/src/app/courses/[courseId]/quiz/page.tsx
index 8a3c8c8..77a5218 100644
--- a/src/app/courses/[courseId]/quiz/page.tsx
+++ b/src/app/courses/[courseId]/quiz/page.tsx
@@ -93,6 +93,7 @@ export default function QuizPage({
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 19fe9bb..5e70728 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -8,6 +8,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { CourseCard } from "@/components/course/course-card";
import { CourseProgressCard } from "@/components/course/course-progress-card";
import { BalanceDisplay } from "@/components/wallet/balance-display";
+import { RewardHistory } from "@/components/rewards/reward-history";
import { CredentialCard } from "@/components/credentials/credential-card";
import { DashboardSkeleton } from "@/components/shared/loading-skeleton";
import { EmptyState } from "@/components/shared/empty-state";
@@ -28,7 +29,7 @@ export default function DashboardPage() {
const { ready } = useRequireAuth();
const { courses, enrollments, loading: coursesLoading } = useCourses();
const { recommended, loading: recommendedLoading, error: recommendedError } = useRecommendedCourses();
- const { balances, loading: rewardsLoading } = useRewards();
+ const { balances, history, loading: rewardsLoading } = useRewards();
const { credentials, loading: credentialsLoading } = useCredentials();
const progress = useCourseStore((s) => s.progress);
@@ -202,6 +203,17 @@ export default function DashboardPage() {
+
+
+
+ Reward History
+
+
+
+
+
+
+
diff --git a/src/components/course/module-list.tsx b/src/components/course/module-list.tsx
index a421126..1745cc0 100644
--- a/src/components/course/module-list.tsx
+++ b/src/components/course/module-list.tsx
@@ -2,7 +2,7 @@
import Link from "next/link";
import { cn } from "@/lib/utils/cn";
-import { CheckCircle, Circle, PlayCircle, Lock } from "lucide-react";
+import { CheckCircle, Circle, PlayCircle, Lock, Video, Zap, Clock } from "lucide-react";
import type { Module } from "@/types/course";
import { formatDuration } from "@/lib/utils/format";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -44,33 +44,84 @@ export function ModuleList({
index === 0 ||
completedIds.has(sorted[index - 1]?.id);
+ const ContentTypeIcon =
+ mod.contentType === "video"
+ ? Video
+ : mod.contentType === "interactive"
+ ? Zap
+ : null;
+
const content = (
<>
+ {/* Status icon */}
{isCompleted ? (
-
+
) : isCurrent ? (
-
+
) : isAccessible ? (
-
+
) : (
-
+
)}
+
+ {/* Title + description + badges */}
{index + 1}. {mod.title}
-
{mod.description}
+
+
+ {mod.description}
+
+ {/* Content-type badge */}
+ {mod.contentType === "interactive" && (
+
+
+ Quiz
+
+ )}
+ {mod.contentType === "video" && (
+
+
+ Video
+
+ )}
+
+
+
+ {/* Duration + content-type icon */}
+
+
+
+ {formatDuration(mod.estimatedMinutes)}
+
+ {ContentTypeIcon && (
+
+ )}
-
- {formatDuration(mod.estimatedMinutes)}
-
>
);
diff --git a/src/components/course/quiz-interface.tsx b/src/components/course/quiz-interface.tsx
index 31a2368..454f1b8 100644
--- a/src/components/course/quiz-interface.tsx
+++ b/src/components/course/quiz-interface.tsx
@@ -5,25 +5,36 @@ import { cn } from "@/lib/utils/cn";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { EmptyState } from "@/components/shared/empty-state";
-import { CheckCircle, XCircle, ChevronRight, Trophy } from "lucide-react";
-import type { Quiz, QuizAttempt, QuizQuestion } from "@/types/quiz";
+import { ResultsScreen } from "@/components/course/results-screen";
+import {
+ ChevronLeft,
+ ChevronRight,
+ Clock,
+ Loader2,
+} from "lucide-react";
+import type { Quiz, QuizAttempt } from "@/types/quiz";
interface QuizInterfaceProps {
quiz: Quiz;
+ courseId: string;
onSubmit: (answers: Record) => Promise;
onRetry?: () => void;
className?: string;
}
-export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterfaceProps) {
+export function QuizInterface({ quiz, courseId, onSubmit, onRetry, className }: QuizInterfaceProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const [answers, setAnswers] = useState>({});
const [attempt, setAttempt] = useState(null);
const [submitting, setSubmitting] = useState(false);
+ const [submitError, setSubmitError] = useState(null);
- const question = quiz.questions[currentIndex];
- const isLastQuestion = currentIndex === quiz.questions.length - 1;
- const allAnswered = quiz.questions.every((q) => answers[q.id]);
+ const questions = quiz.questions;
+ const question = questions[currentIndex];
+ const isLastQuestion = currentIndex === questions.length - 1;
+ const answeredCount = Object.keys(answers).length;
+ const allAnswered = answeredCount === questions.length;
+ const progressPercent = Math.round(((currentIndex + 1) / questions.length) * 100);
if (!question) {
return (
@@ -41,11 +52,14 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf
const handleSubmit = async () => {
setSubmitting(true);
+ setSubmitError(null);
try {
const result = await onSubmit(answers);
setAttempt(result);
} catch (err) {
- console.error("Quiz submission failed:", err);
+ setSubmitError(
+ err instanceof Error ? err.message : "Submission failed. Please try again."
+ );
} finally {
setSubmitting(false);
}
@@ -55,115 +69,103 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf
setAnswers({});
setAttempt(null);
setCurrentIndex(0);
+ setSubmitError(null);
+ // Delegate to parent if provided (remounts via key); otherwise local reset suffices.
+ onRetry?.();
};
- // Results view
+ // ── Results view ──────────────────────────────────────────────────────────
if (attempt) {
return (
-
-
-
-
-
-
- {attempt.passed ? "Congratulations!" : "Keep Trying!"}
-
-
- You scored {attempt.score}% on this quiz
-
-
-
- {quiz.questions.map((q, i) => {
- const answer = attempt.answers.find((a) => a.questionId === q.id);
- return (
-
-
- {i + 1}. {q.text}
-
-
- {answer?.isCorrect ? (
-
- ) : (
-
- )}
-
- {answer?.isCorrect ? "Correct" : "Incorrect"}
-
-
- {!answer?.isCorrect && (
-
{q.explanation}
- )}
-
- );
- })}
-
- {attempt.passed && attempt.rewardClaimed && (
-
-
- +{quiz.rewardTokenAmount} LEARN tokens have been sent to your
- wallet!
-
-
- )}
-
- {!attempt.passed && (
-
-
-
- You need {quiz.passingScore}% to pass.
-
-
- )}
- {!attempt.passed && onRetry && (
-
-
-
- )}
-
-
+
);
}
- // Question view
+ // ── Question view ─────────────────────────────────────────────────────────
return (
-
-
-
-
- Question {currentIndex + 1} of {quiz.questions.length}
-
-
+
+
+ {/* Title row */}
+
+
{quiz.title}
-
+
+
+ {quiz.timeLimitMinutes && (
+
+
+ {quiz.timeLimitMinutes}m
+
+ )}
+
+ {answeredCount}/{questions.length} answered
+
+
-
+
+ {/* Progress bar */}
+
+
+ {/* Question dot navigation */}
+
+ {questions.map((q, i) => {
+ const isAnswered = Boolean(answers[q.id]);
+ const isCurrent = i === currentIndex;
+ return (
+
-
- {question.text}
+
+ {/* Question counter + text */}
+
+
+ Question {currentIndex + 1} of {questions.length}
+
+
+ {question.text}
+
+
+
+ {/* Answer options */}
{question.options.map((option) => {
const isSelected = answers[question.id] === option.id;
@@ -174,10 +176,10 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf
aria-checked={isSelected}
onClick={() => selectOption(question.id, option.id)}
className={cn(
- "w-full text-left rounded-lg border p-4 transition-all",
+ "w-full text-left rounded-lg border px-4 py-3 transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
isSelected
- ? "border-primary-500 bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-200"
- : "border-gray-200 hover:border-gray-300 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-800"
+ ? "border-primary-500 bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:border-primary-500 dark:text-primary-200"
+ : "border-gray-200 dark:border-gray-700 hover:border-gray-300 hover:bg-gray-50 dark:hover:border-gray-600 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300"
)}
>
{option.text}
@@ -186,12 +188,23 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf
})}
-
+ {/* Submission error */}
+ {submitError && (
+
+ {submitError}
+
+ )}
+
+ {/* Navigation */}
+
@@ -199,14 +212,24 @@ export function QuizInterface({ quiz, onSubmit, onRetry, className }: QuizInterf
) : (