'use client'; import { useEffect, useState, type MouseEvent, type ReactNode } from 'react'; import { ArrowLeft, ArrowRight, RotateCcw } from 'lucide-react'; import type { Lesson, ReviewItem, ReviewLesson } from './lesson-types'; import { getModule } from './module-catalog'; import { courseHref } from './course-navigation'; import { emptyReviewState, restoreReviewState, reviewProgress, reviewSignature, type ReviewState, } from './review-state'; import './review.css'; type Navigate = (module: string, lesson?: string) => void; function ReviewLink({ module, lesson, navigate, children, className, }: { module: string; lesson?: string; navigate: Navigate; children: ReactNode; className?: string; }) { return ( ) => { if ( event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) return; event.preventDefault(); navigate(module, lesson); }} > {children} ); } export function ReviewPage({ lesson, lessons, navigate, }: { lesson: ReviewLesson; lessons: Lesson[]; navigate: Navigate; }) { const [state, setState] = useState(emptyReviewState); const [loaded, setLoaded] = useState(false); const [storageUnavailable, setStorageUnavailable] = useState(false); const [attempt, setAttempt] = useState(0); const storageKey = `shuhe:module-review:v1:${lesson.sourceModule}`; useEffect(() => { try { // oxlint-disable-next-line react/react-compiler -- Restore this browser's saved answers after hydration; server rendering cannot read localStorage. setState(restoreReviewState(localStorage.getItem(storageKey), lesson)); } catch { setStorageUnavailable(true); } setLoaded(true); }, [lesson, storageKey]); function update(next: ReviewState) { setState(next); try { localStorage.setItem( storageKey, JSON.stringify({ sourceModule: lesson.sourceModule, signature: reviewSignature(lesson), state: next, }), ); } catch { setStorageUnavailable(true); } } const progress = reviewProgress(lesson, state); const source = getModule(lesson.sourceModule); const index = lessons.findIndex((item) => item.id === lesson.id); function question(item: ReviewItem, order: number, challenge = false) { const prefix = challenge ? '情境' : '问题'; return (
{prefix} {String(order + 1).padStart(2, '0')}

{item.title}

{item.prompt}

记下我的回答(可选)