fix(ui): change ui theme color
feat(calc): add geogebra based graph calculator for tests
This commit is contained in:
@ -1,7 +1,5 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { useResults } from "../../../stores/useResults";
|
||||
import { useSatExam } from "../../../stores/useSatExam";
|
||||
import { LucideArrowLeft } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
@ -11,7 +9,6 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Progress } from "../../../components/ui/progress";
|
||||
import { CircularLevelProgress } from "../../../components/CircularLevelProgress";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
|
||||
@ -33,7 +30,7 @@ const XPGainedCard = ({
|
||||
if (!results?.xp_gained) return;
|
||||
|
||||
let startTime: number | null = null;
|
||||
const duration = 800; // ms
|
||||
const duration = 800;
|
||||
|
||||
const animate = (time: number) => {
|
||||
if (!startTime) startTime = time;
|
||||
@ -58,39 +55,139 @@ const XPGainedCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Targeted static results ──────────────────────────────────────────────────
|
||||
const TARGETED_XP = 15;
|
||||
const TARGETED_SCORE = 15;
|
||||
|
||||
const TargetedResults = ({ onFinish }: { onFinish: () => void }) => {
|
||||
const { userXp, setUserXp } = useExamConfigStore();
|
||||
|
||||
// previousXP is whatever the user had before; we add 15 on top
|
||||
const previousXP = userXp ?? 0;
|
||||
const gainedXP = TARGETED_XP;
|
||||
const totalXP = previousXP;
|
||||
|
||||
// Sync updated XP back into the store
|
||||
useEffect(() => {
|
||||
setUserXp(totalXP);
|
||||
}, []);
|
||||
|
||||
// Simple level bounds — 0–100 per level so progress is visible
|
||||
// Adjust these to match your real level thresholds if needed
|
||||
const levelMinXP = Math.floor(previousXP / 100) * 100;
|
||||
const levelMaxXP = levelMinXP + 100;
|
||||
const currentLevel = Math.floor(previousXP / 100) + 1;
|
||||
|
||||
const [displayXP, setDisplayXP] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let startTime: number | null = null;
|
||||
const duration = 800;
|
||||
const animate = (time: number) => {
|
||||
if (!startTime) startTime = time;
|
||||
const t = Math.min((time - startTime) / duration, 1);
|
||||
setDisplayXP(Math.floor(t * gainedXP));
|
||||
if (t < 1) requestAnimationFrame(animate);
|
||||
};
|
||||
requestAnimationFrame(animate);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-gray-50 space-y-6 mx-auto px-8 sm:px-6 lg:px-90 py-10">
|
||||
<header className="flex gap-4">
|
||||
<button
|
||||
onClick={onFinish}
|
||||
className="p-2 rounded-full border border-purple-400 bg-linear-to-br from-purple-400 to-purple-500"
|
||||
>
|
||||
<LucideArrowLeft size={20} color="white" />
|
||||
</button>
|
||||
<h1 className="text-3xl font-satoshi-bold">Results</h1>
|
||||
</header>
|
||||
|
||||
{/* Targeted mode badge */}
|
||||
<div className="flex items-center gap-2 bg-purple-50 border border-purple-200 rounded-2xl px-4 py-3">
|
||||
<span className="text-xl">🎯</span>
|
||||
<p className="font-satoshi text-purple-700 text-sm">
|
||||
<strong>Targeted Mode Complete!</strong> You answered all questions
|
||||
correctly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="w-full flex items-center justify-center">
|
||||
<CircularLevelProgress
|
||||
previousXP={previousXP}
|
||||
gainedXP={gainedXP}
|
||||
levelMinXP={levelMinXP}
|
||||
levelMaxXP={levelMaxXP}
|
||||
level={currentLevel}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>XP</CardTitle>
|
||||
<CardDescription>How much did you improve?</CardDescription>
|
||||
<CardAction>
|
||||
<p className="font-satoshi-medium">+{displayXP} XP</p>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Score</CardTitle>
|
||||
<CardDescription>Total score you achieved.</CardDescription>
|
||||
<CardAction>
|
||||
<p className="font-satoshi-medium">{TARGETED_SCORE}</p>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Keep it up! 🚀</CardTitle>
|
||||
<CardDescription>
|
||||
Great work getting every question right. Keep practicing to level up
|
||||
faster!
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
<button
|
||||
onClick={onFinish}
|
||||
className="w-full font-satoshi rounded-3xl text-lg py-4 bg-linear-to-br from-purple-500 to-purple-600 text-white"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Main Results ─────────────────────────────────────────────────────────────
|
||||
export const Results = () => {
|
||||
const navigate = useNavigate();
|
||||
const results = useResults((s) => s.results);
|
||||
const clearResults = useResults((s) => s.clearResults);
|
||||
|
||||
const { setUserXp } = useExamConfigStore();
|
||||
const { setUserXp, payload } = useExamConfigStore();
|
||||
const isTargeted = payload?.mode === "TARGETED";
|
||||
|
||||
useEffect(() => {
|
||||
if (results) setUserXp(results?.total_xp);
|
||||
}, [results]);
|
||||
|
||||
function handleFinishExam() {
|
||||
useExamConfigStore.getState().clearPayload();
|
||||
clearResults();
|
||||
navigate(`/student/home`);
|
||||
}
|
||||
|
||||
// const [displayXP, setDisplayXP] = useState(0);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!results?.score) return;
|
||||
// let start = 0;
|
||||
// const duration = 600;
|
||||
// const startTime = performance.now();
|
||||
|
||||
// const animate = (time: number) => {
|
||||
// const t = Math.min((time - startTime) / duration, 1);
|
||||
// setDisplayXP(Math.floor(t * results.score));
|
||||
// if (t < 1) requestAnimationFrame(animate);
|
||||
// };
|
||||
|
||||
// requestAnimationFrame(animate);
|
||||
// }, [results?.score]);
|
||||
// ── Targeted mode: show static screen ──────────────────────────────────────
|
||||
if (isTargeted) {
|
||||
return <TargetedResults onFinish={handleFinishExam} />;
|
||||
}
|
||||
|
||||
// ── Standard mode ──────────────────────────────────────────────────────────
|
||||
const previousXP = results ? results.total_xp - results.xp_gained : 0;
|
||||
|
||||
return (
|
||||
@ -107,11 +204,6 @@ export const Results = () => {
|
||||
<section className="w-full flex items-center justify-center">
|
||||
{results && (
|
||||
<CircularLevelProgress
|
||||
// previousXP={505}
|
||||
// gainedXP={605}
|
||||
// levelMinXP={500}
|
||||
// levelMaxXP={1000}
|
||||
// level={3}
|
||||
previousXP={previousXP}
|
||||
gainedXP={results.xp_gained}
|
||||
levelMinXP={results.current_level_start}
|
||||
|
||||
Reference in New Issue
Block a user