fix(ui): change ui theme color
feat(calc): add geogebra based graph calculator for tests
This commit is contained in:
@ -6,7 +6,7 @@ import {
|
||||
TabsContent,
|
||||
} from "../../components/ui/tabs";
|
||||
import { useAuthStore } from "../../stores/authStore";
|
||||
import { CheckCircle, Search } from "lucide-react";
|
||||
import { CheckCircle, Flame, Search, Zap } from "lucide-react";
|
||||
import { api } from "../../utils/api";
|
||||
import {
|
||||
Card,
|
||||
@ -22,10 +22,18 @@ import type { PracticeSheet } from "../../types/sheet";
|
||||
import { formatStatus } from "../../lib/utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SearchOverlay } from "../../components/SearchOverlay";
|
||||
import { PredictedScoreCard } from "../../components/PredictedScoreCard";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "../../components/ui/avatar";
|
||||
import { useExamConfigStore } from "../../stores/useExamConfigStore";
|
||||
|
||||
export const Home = () => {
|
||||
const user = useAuthStore((state) => state.user);
|
||||
const navigate = useNavigate();
|
||||
const userXp = useExamConfigStore.getState().userXp;
|
||||
|
||||
const [practiceSheets, setPracticeSheets] = useState<PracticeSheet[]>([]);
|
||||
const [notStartedSheets, setNotStartedSheets] = useState<PracticeSheet[]>([]);
|
||||
@ -84,10 +92,42 @@ export const Home = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-gray-50 space-y-6 mx-auto px-8 sm:px-6 lg:px-90 py-12">
|
||||
<h1 className="text-4xl font-satoshi-bold tracking-tight text-gray-800 text-center">
|
||||
Welcome, {user?.name || "Student"}
|
||||
</h1>
|
||||
<main className="min-h-screen space-y-6 mx-auto px-8 sm:px-6 lg:px-90 py-12">
|
||||
<header className="flex items-center gap-3 justify-between">
|
||||
<div className="flex gap-3">
|
||||
<Avatar className="w-12 h-12">
|
||||
<AvatarImage src={user?.avatar_url} />
|
||||
<AvatarFallback className="font-satoshi-bold bg-linear-to-br from-indigo-400 to-indigo-500 uppercase text-lg text-white">
|
||||
{user?.name.slice(0, 1)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-xl font-satoshi-bold tracking-tight text-gray-800 text-center">
|
||||
Welcome, {user?.name || "Student"}
|
||||
</h1>
|
||||
<h4 className="text-sm font-satoshi-bold text-indigo-500 ">
|
||||
{user?.role === "STUDENT"
|
||||
? "Student"
|
||||
: user?.role === "ADMIN"
|
||||
? "Admin"
|
||||
: "Taecher"}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="rounded-full w-fit flex items-center gap-2">
|
||||
<Flame size={20} className="text-red-500 fill-amber-200" />
|
||||
|
||||
<span className="font-satoshi-bold text-md">5</span>
|
||||
</div>
|
||||
<div className="rounded-full w-fit flex items-center gap-2">
|
||||
<Zap size={20} className="text-lime-500 fill-lime-200" />
|
||||
|
||||
<span className="font-satoshi-bold text-md">{userXp}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<PredictedScoreCard />
|
||||
<h1 className="font-satoshi-bold text-2xl tracking-tight">
|
||||
What are you looking for?
|
||||
</h1>
|
||||
@ -111,7 +151,7 @@ export const Home = () => {
|
||||
inProgressSheets.map((sheet) => (
|
||||
<Card
|
||||
key={sheet?.id}
|
||||
className="rounded-4xl border bg-purple-50/70 border-purple-500"
|
||||
className="rounded-4xl border bg-indigo-50/70 border-indigo-500"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="font-satoshi-medium text-xl">
|
||||
@ -122,7 +162,7 @@ export const Home = () => {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex justify-between">
|
||||
<p className="font-satoshi text-sm border px-2 rounded-full bg-purple-500 text-white py-1">
|
||||
<p className="font-satoshi text-sm border px-2 rounded-full bg-indigo-500 text-white py-1">
|
||||
{formatStatus(sheet?.user_status)}
|
||||
</p>
|
||||
<Badge
|
||||
@ -141,7 +181,7 @@ export const Home = () => {
|
||||
<Button
|
||||
onClick={() => handleStartPracticeSheet(sheet?.id)}
|
||||
variant="outline"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-purple-500 to-purple-600 text-white"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
|
||||
>
|
||||
Resume
|
||||
</Button>
|
||||
@ -161,19 +201,19 @@ export const Home = () => {
|
||||
<TabsList className="bg-transparent p-0 w-full">
|
||||
<TabsTrigger
|
||||
value="all"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:font-satoshi-medium data-[state=active]:border-b-purple-800 data-[state=active]:text-purple-800"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:font-satoshi-medium data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
|
||||
>
|
||||
All
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="NOT_STARTED"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-purple-800 data-[state=active]:text-purple-800"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
|
||||
>
|
||||
Not Started
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="COMPLETED"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-purple-800 data-[state=active]:text-purple-800"
|
||||
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
|
||||
>
|
||||
Completed
|
||||
</TabsTrigger>
|
||||
@ -211,7 +251,7 @@ export const Home = () => {
|
||||
<Button
|
||||
onClick={() => handleStartPracticeSheet(sheet?.id)}
|
||||
variant="outline"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-purple-500 to-purple-600 text-white"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
@ -256,7 +296,7 @@ export const Home = () => {
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-purple-500 to-purple-600 rounded-3xl text-white"
|
||||
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 rounded-3xl text-white"
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
@ -297,7 +337,7 @@ export const Home = () => {
|
||||
<CardFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-purple-500 to-purple-600 rounded-3xl text-white"
|
||||
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 rounded-3xl text-white"
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
@ -322,29 +362,29 @@ export const Home = () => {
|
||||
</h1>
|
||||
<section className="space-y-4 ">
|
||||
<div className="flex gap-2">
|
||||
<CheckCircle size={24} color="#AD45FF" />
|
||||
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
|
||||
<p className="font-satoshi text-md">
|
||||
Practice regularly with official SAT materials
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle size={24} color="#AD45FF" />
|
||||
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
|
||||
<p className="font-satoshi text-md">
|
||||
Review your mistakes and learn from them
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle size={24} color="#AD45FF" />
|
||||
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
|
||||
<p className="font-satoshi text-md">Focus on your weak areas</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle size={24} color="#AD45FF" />
|
||||
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
|
||||
<p className="font-satoshi text-md">
|
||||
Take full-length practice tests
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle size={24} color="#AD45FF" />
|
||||
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
|
||||
<p className="font-satoshi text-md">
|
||||
Get plenty of rest before the test day
|
||||
</p>
|
||||
|
||||
@ -27,17 +27,17 @@ export const Practice = () => {
|
||||
return (
|
||||
<div className="px-8 py-8 space-y-4">
|
||||
<header className="flex justify-between items-center ">
|
||||
<div className="w-fit bg-linear-to-br from-purple-500 to-purple-600 p-3 rounded-2xl">
|
||||
<div className="w-fit bg-linear-to-br from-indigo-500 to-indigo-600 p-3 rounded-2xl">
|
||||
<BookOpen size={20} color="white" />
|
||||
</div>
|
||||
<div className="bg-purple-100 rounded-full w-fit py-2 px-4 flex items-center gap-2">
|
||||
<div className="h-2 w-2 bg-linear-to-br from-purple-400 to-purple-500 rounded-full"></div>
|
||||
<div className="bg-indigo-100 rounded-full w-fit py-2 px-4 flex items-center gap-2">
|
||||
<div className="h-2 w-2 bg-linear-to-br from-indigo-400 to-indigo-500 rounded-full"></div>
|
||||
<span className="font-satoshi-bold text-md">{userXp}</span>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<Card
|
||||
className="relative bg-linear-to-br from-purple-500 to-purple-600 rounded-4xl
|
||||
className="relative bg-linear-to-br from-indigo-500 to-indigo-600 rounded-4xl
|
||||
flex-row"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
|
||||
@ -63,7 +63,7 @@ export const Profile = () => {
|
||||
</section>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full border rounded-4xl bg-purple-500 py-4 px-4 flex justify-center items-center active:bg-purple-600 font-satoshi-medium text-white"
|
||||
className="w-full border rounded-4xl bg-linear-to-br from-indigo-400 to-indigo-600 py-4 px-4 flex justify-center items-center active:bg-purple-600 font-satoshi-medium text-white"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
|
||||
@ -32,7 +32,7 @@ import {
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "../../components/ui/avatar";
|
||||
import { Zap } from "lucide-react";
|
||||
import { Flame, LucideBadgeQuestionMark, Zap } from "lucide-react";
|
||||
import type { Leaderboard } from "../../types/leaderboard";
|
||||
import { api } from "../../utils/api";
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
@ -42,6 +42,7 @@ import { useExamConfigStore } from "../../stores/useExamConfigStore";
|
||||
export const Rewards = () => {
|
||||
const user = useAuthStore((state) => state.user);
|
||||
const [time, setTime] = useState("bottom");
|
||||
const [activeTab, setActiveTab] = useState("xp");
|
||||
|
||||
const [leaderboard, setLeaderboard] = useState<Leaderboard>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@ -94,7 +95,7 @@ export const Rewards = () => {
|
||||
) : (
|
||||
<p className="font-satoshi-medium text-md text-gray-500">
|
||||
Don't stop now! You're{" "}
|
||||
<span className="text-purple-400">
|
||||
<span className="text-indigo-400">
|
||||
#{leaderboard?.user_rank.rank}
|
||||
</span>{" "}
|
||||
in XP.
|
||||
@ -103,6 +104,8 @@ export const Rewards = () => {
|
||||
</header>
|
||||
<section className="w-full px-7">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
defaultValue="xp"
|
||||
className="space-y-6 h-[calc(100vh-250px)] flex flex-col"
|
||||
>
|
||||
@ -198,7 +201,7 @@ export const Rewards = () => {
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="font-satoshi-medium">{user.total_xp}</p>
|
||||
<Zap size={20} color="darkgreen" />
|
||||
<Zap size={20} className="text-lime-500 fill-lime-200" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -295,7 +298,7 @@ export const Rewards = () => {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</section>
|
||||
<Card className="absolute mx-auto w-full left-0 md:-bottom-12 bottom-0 bg-linear-to-br from-purple-500 to-purple-600 rounded-full py-4">
|
||||
<Card className="absolute mx-auto w-full left-0 md:-bottom-12 bottom-0 bg-linear-to-br from-indigo-500 to-indigo-600 rounded-full py-4">
|
||||
<CardContent className="flex justify-between items-center">
|
||||
{loading ? (
|
||||
<div className="flex justify-between items-center animate-pulse w-full">
|
||||
@ -332,9 +335,9 @@ export const Rewards = () => {
|
||||
{(leaderboard?.user_rank?.rank ?? Infinity) - 1}
|
||||
</span>
|
||||
)}
|
||||
<Avatar className={`p-6 ${getRandomColor()}`}>
|
||||
<Avatar className={`p-6 bg-white`}>
|
||||
<AvatarImage src={leaderboard?.user_rank.avatar_url} />
|
||||
<AvatarFallback className="text-white font-satoshi-bold">
|
||||
<AvatarFallback className=" font-satoshi-bold">
|
||||
{leaderboard?.user_rank.name.slice(0, 1).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@ -345,9 +348,20 @@ export const Rewards = () => {
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<p className="font-satoshi-medium text-white">
|
||||
{leaderboard?.user_rank.total_xp}
|
||||
{activeTab === "xp"
|
||||
? leaderboard?.user_rank.total_xp
|
||||
: activeTab === "questions"
|
||||
? "23"
|
||||
: "5"}
|
||||
</p>
|
||||
<Zap size={20} color="white" />
|
||||
|
||||
{activeTab === "xp" ? (
|
||||
<Zap size={20} color="white" />
|
||||
) : activeTab === "questions" ? (
|
||||
<LucideBadgeQuestionMark size={20} color="white" />
|
||||
) : (
|
||||
<Flame size={20} color="white" />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -14,14 +14,18 @@ export function StudentLayout() {
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<div className="min-h-screen flex w-full overflow-x-hidden">
|
||||
<div className="flex min-h-screen w-full overflow-x-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
<AppSidebar />
|
||||
<main className="flex-1 pb-20 ">
|
||||
<SidebarTrigger className="hidden md:block" />
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
<SidebarTrigger className="hidden md:block" />
|
||||
<main className="flex-1 pb-24 md:pb-0">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Mobile bottom nav */}
|
||||
<nav className="fixed bottom-0 left-0 right-0 rounded-t-4xl pt-2 bg-white border-t border-gray-200 shadow-4xl z-20 md:hidden">
|
||||
<div className="max-w-7xl mx-auto px-2">
|
||||
<div className="flex justify-around items-center">
|
||||
@ -32,7 +36,7 @@ export function StudentLayout() {
|
||||
className={({ isActive }) =>
|
||||
`flex flex-col items-center justify-center py-3 px-4 flex-1 transition-all duration-200 font-satoshi tracking-wide ${
|
||||
isActive
|
||||
? "text-purple-600"
|
||||
? "text-indigo-600"
|
||||
: "text-gray-500 hover:text-gray-700"
|
||||
}`
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ export const Pretest = () => {
|
||||
<div
|
||||
key={index}
|
||||
className={`w-2 h-2 mx-1 rounded-full ${
|
||||
index + 1 === current ? "bg-purple-500" : "bg-gray-300"
|
||||
index + 1 === current ? "bg-indigo-500" : "bg-gray-300"
|
||||
}`}
|
||||
></div>
|
||||
))}
|
||||
@ -216,7 +216,7 @@ export const Pretest = () => {
|
||||
<Button
|
||||
onClick={() => handleStartTest(practiceSheet?.id!)}
|
||||
variant="outline"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-8 bg-linear-to-br from-purple-500 to-purple-600 text-white active:bg-linear-to-br active:from-purple-600 active:to-purple-700 active:translate-y-1"
|
||||
className="font-satoshi rounded-3xl w-full text-lg py-8 bg-linear-to-br from-indigo-500 to-indigo-600 text-white active:bg-linear-to-br active:from-indigo-600 active:to-indigo-700 active:translate-y-1"
|
||||
disabled={!practiceSheet}
|
||||
>
|
||||
{practiceSheet ? (
|
||||
|
||||
@ -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}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -126,7 +126,7 @@ export const TargetedPractice = () => {
|
||||
<div>
|
||||
<Loader2
|
||||
size={30}
|
||||
color="purple"
|
||||
color="indigo"
|
||||
className="animate-spin"
|
||||
/>
|
||||
</div>
|
||||
@ -162,7 +162,7 @@ export const TargetedPractice = () => {
|
||||
${
|
||||
selectedTopics.length === 0
|
||||
? "bg-gray-300 text-gray-500 cursor-not-allowed"
|
||||
: "bg-linear-to-br from-purple-500 to-purple-600 text-white"
|
||||
: "bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
|
||||
}`}
|
||||
>
|
||||
Next
|
||||
@ -249,7 +249,7 @@ export const TargetedPractice = () => {
|
||||
${
|
||||
step !== "review"
|
||||
? "opacity-0 pointer-events-none"
|
||||
: "bg-linear-to-br from-purple-500 to-purple-600 text-white"
|
||||
: "bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
|
||||
}`}
|
||||
onClick={() => {
|
||||
handleStartTargetedPractice();
|
||||
|
||||
Reference in New Issue
Block a user