+
+ ✏️
+ Done
+
+ Section
+
-
-
🏆
-
Nice!
-
Results await
+
+ 🏆
+ Nice!
+
+ Results
+
-
-
🔥
-
100%
-
Effort
+
+ 🔥
+ 100%
+
+ Effort
+
-
-
- Redirecting to results...
+
+
+
+ Redirecting to results...
+
-
+
);
diff --git a/src/pages/student/targeted-practice/page.tsx b/src/pages/student/targeted-practice/page.tsx
index 50af2aa..82db57b 100644
--- a/src/pages/student/targeted-practice/page.tsx
+++ b/src/pages/student/targeted-practice/page.tsx
@@ -2,16 +2,404 @@ import { useEffect, useState } from "react";
import { api } from "../../../utils/api";
import { type Topic } from "../../../types/topic";
import { useAuthStore } from "../../../stores/authStore";
-import { Loader2 } from "lucide-react";
+import { Loader2, Search, ArrowLeft, ChevronRight, Target } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { slideVariants } from "../../../lib/utils";
-import { ChoiceCard } from "../../../components/ChoiceCard";
import { useAuthToken } from "../../../hooks/useAuthToken";
-
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
import { useNavigate } from "react-router-dom";
-type Step = "topic" | "difficulty" | "duration" | "review";
+type Step = "topic" | "difficulty" | "review";
+
+const DOTS = [
+ { size: 10, color: "#f97316", top: "6%", left: "4%", delay: "0s" },
+ { size: 7, color: "#a855f7", top: "28%", left: "2%", delay: "1.2s" },
+ { size: 9, color: "#22c55e", top: "62%", left: "3%", delay: "0.6s" },
+ { size: 12, color: "#3b82f6", top: "10%", right: "4%", delay: "1.8s" },
+ { size: 7, color: "#f43f5e", top: "48%", right: "2%", delay: "0.9s" },
+ { size: 9, color: "#eab308", top: "76%", right: "5%", delay: "0.4s" },
+];
+
+const DIFFICULTY_META = {
+ EASY: {
+ emoji: "🌱",
+ label: "Easy",
+ desc: "Foundational questions to build confidence",
+ color: "#16a34a",
+ bg: "#f0fdf4",
+ border: "#bbf7d0",
+ },
+ MEDIUM: {
+ emoji: "🔥",
+ label: "Medium",
+ desc: "Balanced mix to solidify your understanding",
+ color: "#f97316",
+ bg: "#fff7ed",
+ border: "#fed7aa",
+ },
+ HARD: {
+ emoji: "⚡",
+ label: "Hard",
+ desc: "Challenging questions to push your limits",
+ color: "#7c3aed",
+ bg: "#fdf4ff",
+ border: "#e9d5ff",
+ },
+} as const;
+
+const STEPS: Step[] = ["topic", "difficulty", "review"];
+
+const SECTION_META: Record<
+ string,
+ { color: string; bg: string; border: string; emoji: string }
+> = {
+ "Reading & Writing": {
+ color: "#0891b2",
+ bg: "#ecfeff",
+ border: "#a5f3fc",
+ emoji: "📖",
+ },
+ Math: { color: "#16a34a", bg: "#f0fdf4", border: "#bbf7d0", emoji: "📐" },
+ default: { color: "#a855f7", bg: "#fdf4ff", border: "#e9d5ff", emoji: "📚" },
+};
+const getSectionMeta = (section?: string) =>
+ SECTION_META[section ?? ""] ?? SECTION_META["default"];
+
+const STYLES = `
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
+
+ .tp-screen {
+ min-height: 100vh;
+ background: #fffbf4;
+ font-family: 'Nunito', sans-serif;
+ position: relative;
+ overflow-x: hidden;
+ }
+
+ .tp-blob { position:fixed;pointer-events:none;z-index:0;filter:blur(48px);opacity:0.35; }
+ .tp-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:tpWobble1 14s ease-in-out infinite; }
+ .tp-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:tpWobble2 16s ease-in-out infinite; }
+ .tp-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:tpWobble1 18s ease-in-out infinite reverse; }
+ .tp-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:tpWobble2 12s ease-in-out infinite; }
+
+ @keyframes tpWobble1 {
+ 0%,100%{border-radius:60% 40% 70% 30%/50% 60% 40% 50%;transform:translate(0,0) rotate(0deg);}
+ 50%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(12px,16px) rotate(8deg);}
+ }
+ @keyframes tpWobble2 {
+ 0%,100%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(0,0) rotate(0deg);}
+ 50%{border-radius:60% 40% 70% 30%/40% 60% 40% 60%;transform:translate(-10px,12px) rotate(-6deg);}
+ }
+
+ .tp-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:tpFloat 7s ease-in-out infinite; }
+ @keyframes tpFloat {
+ 0%,100%{transform:translateY(0) rotate(0deg);}
+ 50%{transform:translateY(-12px) rotate(180deg);}
+ }
+
+ .tp-inner {
+ position: relative; z-index: 1;
+ max-width: 560px; margin: 0 auto;
+ padding: 2rem 1.25rem 8rem;
+ display: flex; flex-direction: column; gap: 1.5rem;
+ }
+
+ @keyframes tpPopIn {
+ from { opacity:0; transform:scale(0.92) translateY(12px); }
+ to { opacity:1; transform:scale(1) translateY(0); }
+ }
+ .tp-anim { animation: tpPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; }
+ .tp-anim-1 { animation-delay: 0.05s; }
+ .tp-anim-2 { animation-delay: 0.1s; }
+
+ /* ── Header ── */
+ .tp-back-btn {
+ width: 40px; height: 40px; border-radius: 50%;
+ background: white; border: 2.5px solid #f3f4f6;
+ display: flex; align-items: center; justify-content: center;
+ cursor: pointer; box-shadow: 0 3px 10px rgba(0,0,0,0.05);
+ transition: all 0.15s ease; flex-shrink: 0;
+ }
+ .tp-back-btn:hover { border-color: #e9d5ff; background: #fdf4ff; }
+ .tp-back-btn:active { transform: scale(0.9); }
+ .tp-back-btn.hidden { opacity:0; pointer-events:none; }
+
+ .tp-header-row {
+ display: flex; align-items: center; gap: 0.75rem;
+ }
+ .tp-header-text { flex: 1; }
+ .tp-eyebrow {
+ font-size: 0.62rem; font-weight: 800; letter-spacing: 0.16em;
+ text-transform: uppercase; color: #ef4444;
+ display: flex; align-items: center; gap: 0.35rem;
+ }
+ .tp-title {
+ font-size: 1.75rem; font-weight: 900; color: #1e1b4b;
+ letter-spacing: -0.02em; line-height: 1.15;
+ }
+ .tp-sub {
+ font-family: 'Nunito Sans', sans-serif;
+ font-size: 0.82rem; font-weight: 600; color: #9ca3af;
+ margin-top: 0.2rem; line-height: 1.5;
+ }
+
+ /* ── Progress bar ── */
+ .tp-progress-wrap {
+ background: white; border: 2.5px solid #f3f4f6;
+ border-radius: 100px; overflow: hidden;
+ height: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+ }
+ .tp-progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #f97316, #ef4444);
+ border-radius: 100px;
+ transition: width 0.5s cubic-bezier(0.34,1.56,0.64,1);
+ }
+ .tp-progress-labels {
+ display: flex; justify-content: space-between;
+ font-size: 0.6rem; font-weight: 800; letter-spacing: 0.1em;
+ text-transform: uppercase; color: #d1d5db;
+ margin-top: 0.35rem;
+ padding: 0 0.1rem;
+ }
+ .tp-progress-labels span.done { color: #f97316; }
+
+ /* ── Step content card ── */
+ .tp-step-card {
+ background: white; border: 2.5px solid #f3f4f6;
+ border-radius: 24px; padding: 1.25rem;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.05);
+ display: flex; flex-direction: column; gap: 1rem;
+ }
+ .tp-step-title {
+ font-size: 1rem; font-weight: 900; color: #1e1b4b;
+ display: flex; align-items: center; gap: 0.5rem;
+ }
+ .tp-step-badge {
+ font-size: 0.58rem; font-weight: 800; letter-spacing: 0.1em;
+ text-transform: uppercase; padding: 0.2rem 0.55rem;
+ border-radius: 100px; background: #fff7ed;
+ border: 2px solid #fed7aa; color: #f97316;
+ }
+
+ /* ── Search bar ── */
+ .tp-search-wrap {
+ position: relative;
+ }
+ .tp-search-icon {
+ position: absolute; left: 0.85rem; top: 50%;
+ transform: translateY(-50%); pointer-events: none;
+ color: #9ca3af;
+ }
+ .tp-search-input {
+ width: 100%; padding: 0.7rem 1rem 0.7rem 2.5rem;
+ background: #f9fafb; border: 2.5px solid #f3f4f6;
+ border-radius: 14px; font-family: 'Nunito Sans', sans-serif;
+ font-size: 0.85rem; font-weight: 600; color: #1e1b4b;
+ outline: none; transition: border-color 0.2s ease, box-shadow 0.2s ease;
+ box-sizing: border-box;
+ }
+ .tp-search-input:focus {
+ border-color: #c4b5fd; background: white;
+ box-shadow: 0 0 0 3px rgba(168,85,247,0.1);
+ }
+ .tp-search-input::placeholder { color: #9ca3af; }
+
+ /* ── Topic grid ── */
+ .tp-topic-grid {
+ display: grid; grid-template-columns: 1fr;
+ gap: 0.55rem; max-height: 380px; overflow-y: auto;
+ padding-right: 0.25rem;
+ }
+ @media(min-width: 460px) { .tp-topic-grid { grid-template-columns: 1fr 1fr; } }
+
+ /* ── Topic card ── */
+ .tp-topic-card {
+ display: flex; align-items: center; gap: 0.75rem;
+ background: white; border: 2.5px solid #f3f4f6;
+ border-radius: 16px; padding: 0.75rem 0.9rem;
+ cursor: pointer;
+ transition: transform 0.15s cubic-bezier(0.34,1.56,0.64,1),
+ box-shadow 0.15s ease,
+ border-color 0.15s ease,
+ background 0.15s ease;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+ user-select: none; -webkit-tap-highlight-color: transparent;
+ }
+ .tp-topic-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(0,0,0,0.07);
+ }
+ .tp-topic-card:active { transform: scale(0.96); }
+ .tp-topic-card.selected {
+ box-shadow: 0 4px 0 var(--tc-shadow), 0 6px 16px rgba(0,0,0,0.07);
+ }
+
+ .tp-topic-icon {
+ width: 36px; height: 36px; border-radius: 11px; flex-shrink: 0;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 1rem;
+ transition: transform 0.2s cubic-bezier(0.34,1.56,0.64,1);
+ }
+ .tp-topic-card.selected .tp-topic-icon { transform: scale(1.1); }
+
+ .tp-topic-body { flex: 1; min-width: 0; }
+ .tp-topic-name {
+ font-size: 0.82rem; font-weight: 900; color: #1e1b4b;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ line-height: 1.2;
+ }
+ .tp-topic-card.selected .tp-topic-name { color: var(--tc-color); }
+ .tp-topic-sub {
+ font-family: 'Nunito Sans', sans-serif;
+ font-size: 0.68rem; font-weight: 600; color: #9ca3af;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ margin-top: 0.1rem;
+ }
+
+ .tp-topic-section-pill {
+ font-size: 0.58rem; font-weight: 800; letter-spacing: 0.08em;
+ text-transform: uppercase; padding: 0.15rem 0.5rem;
+ border-radius: 100px; flex-shrink: 0;
+ display: none;
+ }
+ @media(min-width: 460px) { .tp-topic-section-pill { display: block; } }
+
+ .tp-topic-check {
+ width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
+ border: 2.5px solid #e5e7eb;
+ display: flex; align-items: center; justify-content: center;
+ transition: all 0.2s cubic-bezier(0.34,1.56,0.64,1);
+ }
+ .tp-topic-card.selected .tp-topic-check {
+ border-color: var(--tc-color);
+ background: var(--tc-color);
+ transform: scale(1.1);
+ }
+
+ /* ── Difficulty cards ── */
+ .tp-diff-card {
+ display: flex; align-items: center; gap: 1rem;
+ background: white; border: 2.5px solid #f3f4f6;
+ border-radius: 18px; padding: 1rem 1.1rem;
+ cursor: pointer; transition: all 0.18s ease;
+ box-shadow: 0 3px 10px rgba(0,0,0,0.04);
+ }
+ .tp-diff-card:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,0.07); }
+ .tp-diff-card:active { transform: translateY(1px); }
+ .tp-diff-card.selected { box-shadow: 0 6px 0 var(--d-shadow), 0 8px 20px rgba(0,0,0,0.08); }
+
+ .tp-diff-emoji {
+ width: 44px; height: 44px; border-radius: 14px;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 1.3rem; flex-shrink: 0;
+ }
+ .tp-diff-label { font-size: 0.95rem; font-weight: 900; color: #1e1b4b; }
+ .tp-diff-desc {
+ font-family: 'Nunito Sans', sans-serif;
+ font-size: 0.75rem; font-weight: 600; color: #9ca3af; margin-top: 0.1rem;
+ }
+ .tp-diff-check {
+ margin-left: auto; width: 24px; height: 24px; border-radius: 50%;
+ border: 2.5px solid #e5e7eb;
+ display: flex; align-items: center; justify-content: center;
+ transition: all 0.2s ease; flex-shrink: 0;
+ }
+ .tp-diff-card.selected .tp-diff-check {
+ border-color: var(--d-color);
+ background: var(--d-color);
+ }
+
+ /* ── Review card ── */
+ .tp-review-row {
+ display: flex; align-items: flex-start; gap: 0.75rem;
+ padding: 0.85rem 0;
+ border-bottom: 2px solid #f9fafb;
+ }
+ .tp-review-row:last-child { border-bottom: none; padding-bottom: 0; }
+ .tp-review-icon {
+ width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 0.95rem;
+ }
+ .tp-review-label {
+ font-size: 0.62rem; font-weight: 800; letter-spacing: 0.12em;
+ text-transform: uppercase; color: #9ca3af;
+ }
+ .tp-review-value {
+ font-size: 0.9rem; font-weight: 800; color: #1e1b4b;
+ margin-top: 0.1rem; line-height: 1.4;
+ }
+
+ /* ── Topic chips in review ── */
+ .tp-chip-wrap { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.35rem; }
+ .tp-chip {
+ background: #fdf4ff; border: 2px solid #e9d5ff;
+ border-radius: 100px; padding: 0.2rem 0.65rem;
+ font-size: 0.72rem; font-weight: 800; color: #9333ea;
+ }
+
+ /* ── Loading ── */
+ .tp-loading {
+ display: flex; align-items: center; justify-content: center;
+ gap: 0.6rem; padding: 2rem;
+ font-size: 0.85rem; font-weight: 700; color: #9ca3af;
+ }
+ .tp-spinner { animation: tpSpin 0.8s linear infinite; }
+ @keyframes tpSpin { to { transform: rotate(360deg); } }
+
+ /* ── Bottom CTA bar ── */
+ .tp-cta-bar {
+ position: fixed; bottom: 96px; left: 0; right: 0; z-index: 10;
+ padding: 0.85rem 1.25rem calc(0.85rem + env(safe-area-inset-bottom));
+ background: rgba(255,251,244,0.9);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border-top: 2px solid #f3f4f6;
+ }
+ .tp-cta-inner {
+ max-width: 560px; margin: 0 auto;
+ display: flex; gap: 0.75rem; align-items: center;
+ }
+
+ /* Next / Start button */
+ .tp-next-btn {
+ flex: 1; padding: 0.9rem 1.5rem;
+ background: #f97316; color: white; border: none;
+ border-radius: 100px; cursor: pointer;
+ font-family: 'Nunito', sans-serif; font-size: 0.92rem; font-weight: 900;
+ display: flex; align-items: center; justify-content: center; gap: 0.4rem;
+ box-shadow: 0 6px 0 #c2560e, 0 8px 20px rgba(249,115,22,0.25);
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
+ }
+ .tp-next-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #c2560e,0 12px 24px rgba(249,115,22,0.3); }
+ .tp-next-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #c2560e; }
+ .tp-next-btn:disabled {
+ background: #e5e7eb; color: #9ca3af; cursor: not-allowed;
+ box-shadow: 0 4px 0 #d1d5db;
+ }
+ .tp-next-btn:disabled:hover { transform: none; box-shadow: 0 4px 0 #d1d5db; }
+
+ /* Start button variant */
+ .tp-start-btn {
+ flex: 1; padding: 0.9rem 1.5rem;
+ background: linear-gradient(135deg, #7c3aed, #a855f7); color: white; border: none;
+ border-radius: 100px; cursor: pointer;
+ font-family: 'Nunito', sans-serif; font-size: 0.92rem; font-weight: 900;
+ display: flex; align-items: center; justify-content: center; gap: 0.4rem;
+ box-shadow: 0 6px 0 #5b21b6, 0 8px 20px rgba(124,58,237,0.3);
+ transition: transform 0.1s ease, box-shadow 0.1s ease;
+ }
+ .tp-start-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #5b21b6,0 12px 24px rgba(124,58,237,0.35); }
+ .tp-start-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #5b21b6; }
+
+ /* Empty state */
+ .tp-empty {
+ text-align: center; padding: 2rem; color: #9ca3af;
+ font-size: 0.85rem; font-weight: 700;
+ }
+`;
export const TargetedPractice = () => {
const navigate = useNavigate();
@@ -25,238 +413,480 @@ export const TargetedPractice = () => {
const user = useAuthStore((state) => state.user);
const token = useAuthToken();
- const [direction, setDirection] = useState<1 | -1>(1);
+ const [direction, setDirection] = useState<1 | -1>(1);
const [step, setStep] = useState
("topic");
const [selectedTopics, setSelectedTopics] = useState([]);
-
const [difficulty, setDifficulty] = useState<
"EASY" | "MEDIUM" | "HARD" | null
>(null);
-
const [search, setSearch] = useState("");
- const [loading, setLoading] = useState(false);
-
+ const [loading, setLoading] = useState(false);
const [topics, setTopics] = useState([]);
const difficulties = ["EASY", "MEDIUM", "HARD"] as const;
+ const stepIndex = STEPS.indexOf(step);
+ const progressPct = ((stepIndex + 1) / STEPS.length) * 100;
+
const toggleTopic = (topic: Topic) => {
- setSelectedTopics((prev) => {
- const exists = prev.some((t) => t.id === topic.id);
-
- if (exists) {
- return prev.filter((t) => t.id !== topic.id);
- }
-
- return [...prev, topic];
- });
+ setSelectedTopics((prev) =>
+ prev.some((t) => t.id === topic.id)
+ ? prev.filter((t) => t.id !== topic.id)
+ : [...prev, topic],
+ );
};
- async function handleStartTargetedPractice() {
+ const goNext = (nextStep: Step) => {
+ setDirection(1);
+ setStep(nextStep);
+ };
+
+ const goBack = () => {
+ const prev = STEPS[stepIndex - 1];
+ if (prev) {
+ setDirection(-1);
+ setStep(prev);
+ }
+ };
+
+ async function handleStart() {
if (!user || !token || !topics || !difficulty) return;
-
storeDuration(10);
-
navigate(`/student/practice/${topics[0].id}/test`, { replace: true });
}
useEffect(() => {
const fetchAllTopics = async () => {
if (!user) return;
-
try {
setLoading(true);
const authStorage = localStorage.getItem("auth-storage");
if (!authStorage) return;
-
- const parsed = JSON.parse(authStorage) as {
- state?: { token?: string };
- };
-
- const token = parsed.state?.token;
+ const {
+ state: { token },
+ } = JSON.parse(authStorage) as { state?: { token?: string } };
if (!token) return;
-
const response = await api.fetchAllTopics(token);
setTopics(response);
- setLoading(false);
} catch (error) {
- console.error("Failed to load topics. Reason: " + error);
+ console.error("Failed to load topics:", error);
+ } finally {
+ setLoading(false);
}
};
-
fetchAllTopics();
}, [user]);
+ const filteredTopics = topics.filter((t) =>
+ t.name.toLowerCase().includes(search.toLowerCase()),
+ );
+
return (
-
-
+
+
-
-
- {step === "topic" && (
-
- Choose a topic
+ {/* Blobs */}
+
+
+
+
- setSearch(e.target.value)}
- className="w-full rounded-xl border px-4 py-2"
- />
+ {/* Dots */}
+ {DOTS.map((d, i) => (
+
+ ))}
-
- {loading ? (
- <>
-
-
-
- >
- ) : (
- topics
- .filter((t) =>
- t.name.toLowerCase().includes(search.toLowerCase()),
- )
- .map((t) => (
-
st.id === t.id)}
- onClick={() => toggleTopic(t)}
- />
- ))
- )}
-
- {
- // ✅ STORE
- storeTopics(selectedTopics.map((t) => t.id)); // ✅ STORE
- setMode("TARGETED"); // ✅ STORE
- setQuestionCount(7); // ✅ STORE
- setDirection(1);
- setStep("difficulty");
- }}
- className={`rounded-2xl py-3 px-6 font-satoshi-bold transition
- ${
- selectedTopics.length === 0
- ? "bg-gray-300 text-gray-500 cursor-not-allowed"
- : "bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
- }`}
+
+ {/* Header */}
+
+
+
+
+
+
+ Targeted Practice
+
+
+ {step === "topic"
+ ? "Pick your topics"
+ : step === "difficulty"
+ ? "Set the difficulty"
+ : "Review & launch"}
+
+
+ {step === "topic"
+ ? "Select one or more topics you want to drill."
+ : step === "difficulty"
+ ? "How hard do you want to push yourself today?"
+ : "Everything look good? Let's go."}
+
+
+
+
+ {/* Progress */}
+
+
+
+ {STEPS.map((s, i) => (
+
+ {s === "topic"
+ ? "Topics"
+ : s === "difficulty"
+ ? "Difficulty"
+ : "Review"}
+
+ ))}
+
+
+
+ {/* Step content */}
+
+
+ {/* ── Step 1: Topic ── */}
+ {step === "topic" && (
+
- Next
-
-
+
+
+ Choose topics
+ {selectedTopics.length > 0 && (
+
+ {selectedTopics.length} selected
+
+ )}
+
+
+
+ setSearch(e.target.value)}
+ />
+
+ {loading ? (
+
+
+ Loading topics...
+
+ ) : filteredTopics.length === 0 ? (
+
No topics match "{search}"
+ ) : (
+
+ {filteredTopics.map((t) => {
+ const meta = getSectionMeta(t.section);
+ const isSelected = selectedTopics.some(
+ (st) => st.id === t.id,
+ );
+ return (
+
toggleTopic(t)}
+ >
+ {/* Section icon */}
+
+ {meta.emoji}
+
+
+ {/* Name + parent */}
+
+
{t.name}
+ {t.parent_name && (
+
{t.parent_name}
+ )}
+
+
+ {/* Section pill */}
+ {t.section && (
+
+ {t.section === "Reading & Writing"
+ ? "R&W"
+ : t.section}
+
+ )}
+
+ {/* Checkmark */}
+
+ {isSelected && (
+
+
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+
+ )}
+
+ {/* ── Step 2: Difficulty ── */}
+ {step === "difficulty" && (
+
+
+
How tough?
+
+ {difficulties.map((d) => {
+ const meta = DIFFICULTY_META[d];
+ const isSelected = difficulty === d;
+ return (
+
{
+ setDifficulty(d);
+ storeDifficulty(d);
+ goNext("review");
+ }}
+ >
+
+ {meta.emoji}
+
+
+
+ {meta.label}
+
+
{meta.desc}
+
+
+ {isSelected && (
+
+
+
+ )}
+
+
+ );
+ })}
+
+
+
+ )}
+
+ {/* ── Step 3: Review ── */}
+ {step === "review" && (
+
+
+
Your session setup
+
+
+
+ 📚
+
+
+
Topics
+
+ {selectedTopics.map((t) => (
+
+ {t.name}
+
+ ))}
+
+
+
+
+
+
+ {DIFFICULTY_META[difficulty!]?.emoji}
+
+
+
Difficulty
+
+ {DIFFICULTY_META[difficulty!]?.label}
+
+
+
+
+
+
+ ⏱️
+
+
+
Questions
+
7 questions · ~10 min
+
+
+
+
+ )}
+
+
+
+
+ {/* ── Bottom CTA ── */}
+
+
+ {step === "topic" && (
+
{
+ storeTopics(selectedTopics.map((t) => t.id));
+ setMode("TARGETED");
+ setQuestionCount(7);
+ goNext("difficulty");
+ }}
+ >
+ Next — Difficulty
+
)}
{step === "difficulty" && (
-
- Select difficulty
-
-
- {difficulties.map((d) => (
- {
- setDifficulty(d); // local UI
- storeDifficulty(d); // ✅ STORE
- setDirection(1);
- setStep("review");
- }}
- />
- ))}
-
-
+
+ Select a difficulty to continue
+
)}
{step === "review" && (
-
- Review your choices
-
-
-
- Topics: {" "}
- {selectedTopics.map((t) => t.name).join(", ")}
-
-
-
- Difficulty: {difficulty}
-
-
-
+
+ 🚀 Start Practice
+
)}
-
+
- {
- const order: Step[] = ["topic", "difficulty", "review"];
- setDirection(-1);
- setStep(order[order.indexOf(step) - 1]);
- }}
- className={`absolute bottom-24 left-10 rounded-2xl py-3 px-6 font-satoshi-bold transition
- ${
- step === "topic"
- ? "opacity-0 pointer-events-none"
- : "bg-linear-to-br from-slate-500 to-slate-600 text-white"
- }`}
- >
- ← Back
-
-
- {
- handleStartTargetedPractice();
- }}
- >
- Start Test
-
-
+
);
};