Files
pragativeer-v2-prelaunch/src/components/AI/MistakeReviewCard.tsx

199 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Flame } from "lucide-react"
import Link from "next/link"
import ReactMarkdown from 'react-markdown'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css'
export interface MistakePayload {
mistake: {
id: string
user_id: string
question_id: string
error_type: string
topic: string
subtopic: string
review_count: number
consecutive_correct: number
is_mastered: boolean
}
question: {
id: string
subject: string
question_text: string
options: Record<string, string>
correct_answer: { correct: string }
solution_text: string
}
}
interface MistakeReviewCardProps {
data: MistakePayload
onNext: () => void
onSubmitReview: (mistakeId: string, selectedAnswer: string) => Promise<{is_correct: boolean, correct_answer: string}>
}
export function MistakeReviewCard({ data, onNext, onSubmitReview }: MistakeReviewCardProps) {
const [selected, setSelected] = useState<string | null>(null)
const [result, setResult] = useState<'success' | 'error' | null>(null)
const [correctAnswer, setCorrectAnswer] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const handleCheck = async () => {
if (!selected) return;
setIsSubmitting(true)
try {
const res = await onSubmitReview(data.mistake.id, selected)
setResult(res.is_correct ? 'success' : 'error')
setCorrectAnswer(res.correct_answer)
} catch (err) {
console.error(err)
} finally {
setIsSubmitting(false)
}
}
const handleNext = () => {
setSelected(null)
setResult(null)
setCorrectAnswer(null)
onNext()
}
const { mistake, question } = data;
return (
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center pb-1">
<h2 className="text-base font-bold text-primary flex items-center gap-2">
♻️ Spaced Repetition Queue
</h2>
</div>
{/* Spaced Repetition Timeline */}
<div className="flex justify-between items-center relative py-4 px-2">
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-secondary -translate-y-1/2 z-0" />
<div className="absolute top-1/2 left-0 w-1/2 h-0.5 bg-[#00F0FF] -translate-y-1/2 z-0" />
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-xs font-bold text-[#00F0FF]">Attempt 1</span>
<span className="text-[11px] text-muted-foreground font-semibold">10 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-60">
<div className="w-3.5 h-3.5 rounded-full bg-[#00F0FF] border-2 border-background" />
<span className="text-xs font-bold text-[#00F0FF]">Attempt 2</span>
<span className="text-[11px] text-muted-foreground font-semibold">14 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 scale-110">
<div className="w-4 h-4 rounded-full border-[3px] border-[#00F0FF] bg-background shadow-[0_0_10px_rgba(0,240,255,0.5)]" />
<span className="text-sm font-bold text-[#00F0FF]">Today</span>
<span className="text-xs text-muted-foreground font-semibold">19 Jul</span>
</div>
<div className="relative z-10 flex flex-col items-center gap-1.5 opacity-50">
<div className="w-3.5 h-3.5 rounded-full bg-secondary border-2 border-background" />
<span className="text-xs font-bold text-muted-foreground">Mastery</span>
<span className="text-[11px] text-muted-foreground font-semibold">26 Jul</span>
</div>
</div>
{/* Mistake Card */}
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] min-h-[540px] h-full relative overflow-hidden flex flex-col justify-between group hover:border-[#00F0FF]/30 transition-colors">
<CardContent className="p-6 flex-1 flex flex-col justify-between">
<div className="flex items-center gap-2 mb-4">
<span className="text-xs font-bold px-3 py-1 rounded-md bg-white/5 border border-border text-muted-foreground uppercase tracking-widest">
{mistake.topic}
</span>
<span className="text-xs font-bold text-red-500 bg-red-500/10 px-2.5 py-1 rounded-md">
{mistake.error_type}
</span>
</div>
<div className="prose prose-sm dark:prose-invert max-w-none text-base md:text-lg mb-6 font-medium text-foreground leading-relaxed">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.question_text}
</ReactMarkdown>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-auto">
{Object.entries(question.options).map(([letter, text]) => {
const isSelected = selected === letter
let btnClass = "border-border bg-white/5 hover:bg-white/10 hover:border-border text-muted-foreground"
if (result) {
if (letter === correctAnswer) {
btnClass = "border-green-500 bg-green-500/10 text-green-500 shadow-[0_0_15px_rgba(34,197,94,0.15)]"
} else if (isSelected && letter !== correctAnswer) {
btnClass = "border-red-500 bg-red-500/10 text-red-500"
}
} else if (isSelected) {
btnClass = "border-[#00F0FF] bg-[#00F0FF]/10 text-[#00F0FF] shadow-[0_0_15px_rgba(0,240,255,0.15)]"
}
return (
<button
key={letter}
onClick={() => !result && setSelected(letter)}
disabled={!!result || isSubmitting}
className={`text-left p-4 rounded-xl border-[1.5px] transition-all text-base md:text-lg font-bold flex gap-3.5 items-center ${btnClass}`}
>
<span className="font-bold opacity-70">{letter})</span>
<span className="flex-1">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>{text}</ReactMarkdown>
</span>
</button>
)
})}
</div>
{/* Action Button */}
{!result ? (
<div className="mt-5 flex justify-end">
<button
onClick={handleCheck}
disabled={!selected || isSubmitting}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isSubmitting ? "Checking..." : "Submit Answer"}
</button>
</div>
) : (
<>
{/* Solution Block */}
<div className="mt-5 p-4 rounded-xl border border-[#00F0FF]/20 bg-[#00F0FF]/5">
<h4 className="text-[13px] font-bold text-[#00F0FF] mb-2 flex items-center gap-2">
<Flame size={16} className="fill-[#00F0FF]/20" />
AI Coach Explanation
</h4>
<div className="prose prose-sm dark:prose-invert max-w-none text-[13px] text-muted-foreground leading-relaxed [&>p]:m-0">
<ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{question.solution_text}
</ReactMarkdown>
</div>
</div>
<div className="mt-5 flex justify-end">
<button
onClick={handleNext}
className="px-6 py-2.5 rounded-md font-bold text-sm bg-gradient-to-r from-[#00F0FF] to-teal-400 text-background hover:opacity-90 transition-opacity"
>
Got It, Next Question →
</button>
</div>
</>
)}
</CardContent>
</Card>
</div>
)
}