diff --git a/src/components/ChoiceCard.tsx b/src/components/ChoiceCard.tsx index e69de29..0055a72 100644 --- a/src/components/ChoiceCard.tsx +++ b/src/components/ChoiceCard.tsx @@ -0,0 +1,34 @@ +import { Badge } from "./ui/badge"; + +export const ChoiceCard = ({ + label, + selected, + subLabel, + section, + onClick, +}: { + label: string; + selected?: boolean; + subLabel?: string; + section?: string; + onClick: () => void; +}) => ( + +); diff --git a/src/pages/student/Home.tsx b/src/pages/student/Home.tsx index ecf7cad..20cf011 100644 --- a/src/pages/student/Home.tsx +++ b/src/pages/student/Home.tsx @@ -103,19 +103,16 @@ export const Home = () => { your scores now!

*/} - +
- + Your score is low! - - + + Score diff --git a/src/pages/student/drills/page.tsx b/src/pages/student/drills/page.tsx index 9c0234e..803709c 100644 --- a/src/pages/student/drills/page.tsx +++ b/src/pages/student/drills/page.tsx @@ -1,7 +1,212 @@ +import { useEffect, useState } from "react"; +import { useAuthStore } from "../../../stores/authStore"; +import type { Topic } from "../../../types/topic"; +import { api } from "../../../utils/api"; +import { ChoiceCard } from "../../../components/ChoiceCard"; +import { AnimatePresence, motion } from "framer-motion"; +import { slideVariants } from "../../../lib/utils"; +import { Loader2 } from "lucide-react"; +import { useExamConfigStore } from "../../../stores/useExamConfigStore"; +import { useNavigate } from "react-router-dom"; + +type Step = "topic" | "review"; + export const Drills = () => { + const user = useAuthStore((state) => state.user); + const navigate = useNavigate(); + + const [direction, setDirection] = useState<1 | -1>(1); + + const [topics, setTopics] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedTopics, setSelectedTopics] = useState([]); + + const [search, setSearch] = useState(""); + const [step, setStep] = useState("topic"); + + const { storeTopics, setMode, setQuestionCount } = useExamConfigStore(); + + 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]; + }); + }; + + function handleStartDrill() { + if (!user || !topics) return; + + 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; + if (!token) return; + + const response = await api.fetchAllTopics(token); + setTopics(response); + setLoading(false); + } catch (error) { + console.error("Failed to load topics. Reason: " + error); + } + }; + + fetchAllTopics(); + }, [user]); + return (
- Drills +
+

Drills

+

+ Train your speed and accuracy with our drill-based testing system. +

+
+
+
+ + {step === "topic" && ( + +

Choose a topic

+ + setSearch(e.target.value)} + className="w-full rounded-xl border px-4 py-2" + /> + +
+ {loading ? ( + <> +
+ +
+ + ) : ( + topics + .filter((t) => + t.name.toLowerCase().includes(search.toLowerCase()), + ) + .map((t) => ( + st.id === t.id)} + onClick={() => toggleTopic(t)} + /> + )) + )} +
+ +
+ )} + + {step === "review" && ( + +

+ Review your choices +

+ +
+

+ Topics:{" "} + {selectedTopics.map((t) => t.name).join(", ")} +

+
+
+ )} +
+
+ + + +
); }; diff --git a/src/pages/student/hard-test-modules/page.tsx b/src/pages/student/hard-test-modules/page.tsx index 82f2651..3199623 100644 --- a/src/pages/student/hard-test-modules/page.tsx +++ b/src/pages/student/hard-test-modules/page.tsx @@ -1,7 +1,119 @@ +import { + DecimalsArrowRight, + Languages, + Percent, + Pilcrow, + Superscript, + WholeWord, +} from "lucide-react"; +import { Card, CardContent } from "../../../components/ui/card"; +import { useState } from "react"; +import { useAuthStore } from "../../../stores/authStore"; +import { useNavigate } from "react-router-dom"; +import { useExamConfigStore } from "../../../stores/useExamConfigStore"; + +type Module = "EBRW" | "MATH" | null; + export const HardTestModules = () => { + const user = useAuthStore((state) => state.user); + const navigate = useNavigate(); + const [selected, setSelected] = useState(null); + + const { setMode, storeDuration, setSection } = useExamConfigStore(); + + function handleStartModule() { + if (!user) return; + + (setMode("MODULE"), storeDuration(7), setSection(selected)); + + navigate(`/student/practice/${selected}/test`, { replace: true }); + } return (
- HardTestModules +
+

Hard Test Modules

+

+ Tackle hard practice test modules by selecting a section. +

+
+
+ + setSelected((prev) => (prev === "EBRW" ? null : "EBRW")) + } + className={`relative cursor-pointer overflow-hidden transition + ${ + selected === "EBRW" + ? "ring-2 ring-blue-500 scale-[1.02]" + : "hover:scale-[1.01]" + } + bg-linear-to-br from-blue-400 to-blue-600 + `} + > + +

+ Reading & Writing +

+
+ + + +
+ + setSelected((prev) => (prev === "MATH" ? null : "MATH")) + } + className={`relative cursor-pointer overflow-hidden transition + ${ + selected === "MATH" + ? "ring-2 ring-rose-500 scale-[1.02]" + : "hover:scale-[1.01]" + } + bg-linear-to-br from-rose-400 to-rose-600 + `} + > + +

+ Mathematics +

+
+ + + +
+
+ {selected && ( +
+ +
+ )}
); }; diff --git a/src/pages/student/practice/Pretest.tsx b/src/pages/student/practice/Pretest.tsx index ab0c64e..d28a07d 100644 --- a/src/pages/student/practice/Pretest.tsx +++ b/src/pages/student/practice/Pretest.tsx @@ -36,7 +36,7 @@ export const Pretest = () => { } setSheetId(sheetId); - setMode("MODULE"); + setMode("SIMULATION"); storeDuration(practiceSheet?.time_limit ?? 0); setQuestionCount(2); diff --git a/src/pages/student/practice/Test.tsx b/src/pages/student/practice/Test.tsx index 60b998f..9a37229 100644 --- a/src/pages/student/practice/Test.tsx +++ b/src/pages/student/practice/Test.tsx @@ -187,6 +187,7 @@ export const Test = () => { }; const handleQuitExam = () => { + useExamConfigStore.getState().clearPayload(); finishExam(); navigate("/student/home"); }; diff --git a/src/pages/student/targeted-practice/page.tsx b/src/pages/student/targeted-practice/page.tsx index 0916ee5..da56d5a 100644 --- a/src/pages/student/targeted-practice/page.tsx +++ b/src/pages/student/targeted-practice/page.tsx @@ -5,51 +5,14 @@ import { useAuthStore } from "../../../stores/authStore"; import { Loader2 } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { slideVariants } from "../../../lib/utils"; -import { Badge } from "../../../components/ui/badge"; +import { ChoiceCard } from "../../../components/ChoiceCard"; import { useAuthToken } from "../../../hooks/useAuthToken"; -import type { - TargetedSessionRequest, - TargetedSessionResponse, -} from "../../../types/session"; import { useExamConfigStore } from "../../../stores/useExamConfigStore"; -import { replace, useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; type Step = "topic" | "difficulty" | "duration" | "review"; -const ChoiceCard = ({ - label, - selected, - subLabel, - section, - onClick, -}: { - label: string; - selected?: boolean; - subLabel?: string; - section?: string; - onClick: () => void; -}) => ( - -); - export const TargetedPractice = () => { const navigate = useNavigate(); const { @@ -129,7 +92,13 @@ export const TargetedPractice = () => { return (
-

Targeted Practice

+
+

Targeted Practice

+

+ Focus on what really matters. Define your own test and get to + practicing what you really need. +

+
@@ -183,7 +152,7 @@ export const TargetedPractice = () => {