chore(build): refactor codebase for production
This commit is contained in:
@ -1,32 +0,0 @@
|
||||
// src/pages/ErrorPage.tsx
|
||||
import { useRouteError, isRouteErrorResponse } from "react-router-dom";
|
||||
|
||||
export default function ErrorPage() {
|
||||
const error = useRouteError();
|
||||
|
||||
console.error(error);
|
||||
|
||||
let title = "Something went wrong";
|
||||
let message = "An unexpected error occurred.";
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
title = `${error.status} ${error.statusText}`;
|
||||
message = error.data?.message || message;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="bg-white shadow-xl rounded-2xl p-8 max-w-md text-center">
|
||||
<h1 className="text-2xl font-bold text-red-600 mb-4">{title}</h1>
|
||||
<p className="text-gray-600 mb-6">{message}</p>
|
||||
|
||||
<button
|
||||
onClick={() => (window.location.href = "/")}
|
||||
className="px-4 py-2 bg-black text-white rounded-lg"
|
||||
>
|
||||
Go Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -6,7 +6,6 @@ import {
|
||||
Loader2,
|
||||
Mail,
|
||||
Lock,
|
||||
User,
|
||||
ImageIcon,
|
||||
BookOpen,
|
||||
Star,
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { List, SquarePen, DecimalsArrowRight, MapPin } from "lucide-react";
|
||||
import { Progress } from "../../components/ui/progress";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
} from "../../components/ui/card";
|
||||
import { Field, FieldLabel } from "../../components/ui/field";
|
||||
import { CircularProgress } from "../../components/CircularProgress";
|
||||
|
||||
export const Analytics = () => {
|
||||
return (
|
||||
<main className="min-h-screen max-w-7xl mx-auto px-8 sm:px-6 lg:px-8 py-8 space-y-4 lg:pl-[calc(17rem+2rem)] lg:mr-0">
|
||||
<h1 className="font-satoshi-bold text-3xl text-center tracking-tight">
|
||||
Analytics
|
||||
</h1>
|
||||
<section className="flex w-full gap-3 justify-between">
|
||||
<Card className="w-1/3 relative bg-linear-to-br from-purple-600 to-purple-700 rounded-4xl">
|
||||
<div className="space-y-4">
|
||||
<CardContent className="md:w-full space-y-4 flex flex-col items-center justify-center h-50">
|
||||
<MapPin size={60} color="white" />
|
||||
<h1 className="text-4xl font-satoshi-bold text-white flex">
|
||||
<span>145</span> <span className="text-xl">th</span>
|
||||
</h1>
|
||||
</CardContent>
|
||||
</div>
|
||||
<div className="overflow-hidden opacity-0 -rotate-45 absolute -top-2 -right-30 ">
|
||||
<DecimalsArrowRight size={380} color="white" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
className="w-2/3 relative bg-linear-to-br from-gray-100 to-gray-300 rounded-4xl
|
||||
flex-row"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<CardHeader className="md:w-full">
|
||||
<CardTitle className="font-satoshi-bold tracking-tight text-3xl ">
|
||||
Details
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="md:w-full space-y-4"></CardContent>
|
||||
<CardFooter className="flex justify-between"></CardFooter>
|
||||
</div>
|
||||
<div className="overflow-hidden opacity-30 -rotate-45 absolute -top-2 -right-30 ">
|
||||
<DecimalsArrowRight size={380} color="white" />
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
<section>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Field className="w-full max-w-sm">
|
||||
<FieldLabel htmlFor="progress-upload">
|
||||
<span className="font-satoshi text-xl">Score</span>
|
||||
<span className="ml-auto font-satoshi">
|
||||
<span className="text-5xl">854</span>
|
||||
<span className="text-lg">/1600</span>
|
||||
</span>
|
||||
</FieldLabel>
|
||||
<Progress value={55} id="progress-upload" max={100} />
|
||||
</Field>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
@ -7,7 +7,6 @@ import { formatStatus } from "../../lib/utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SearchOverlay } from "../../components/SearchOverlay";
|
||||
import { InfoHeader } from "../../components/InfoHeader";
|
||||
import { InventoryButton } from "../../components/InventoryButton";
|
||||
|
||||
// ─── Shared blob/dot background (same as break/results screens) ────────────────
|
||||
const DOTS = [
|
||||
|
||||
@ -491,7 +491,9 @@ export const Lessons = () => {
|
||||
setLessonLoading(true);
|
||||
const authStorage = localStorage.getItem("auth-storage");
|
||||
if (!authStorage) return;
|
||||
|
||||
const {
|
||||
// @ts-ignore
|
||||
state: { token },
|
||||
} = JSON.parse(authStorage) as { state?: { token?: string } };
|
||||
if (!token) return;
|
||||
@ -631,7 +633,7 @@ export const Lessons = () => {
|
||||
lesson={lesson}
|
||||
index={i}
|
||||
searchQuery={searchQuery}
|
||||
onClick={() => handleLessonClick(lesson.id)}
|
||||
onClick={() => handleLessonClick(lesson.id, lesson.title)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -8,8 +8,6 @@ import {
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useExamConfigStore } from "../../stores/useExamConfigStore";
|
||||
import { LevelBar } from "../../components/LevelBar";
|
||||
import { InfoHeader } from "../../components/InfoHeader";
|
||||
|
||||
const DOTS = [
|
||||
|
||||
@ -756,9 +756,9 @@ const RouteSegment = ({
|
||||
isNext,
|
||||
accent,
|
||||
}: RouteSegmentProps) => {
|
||||
const lineRef = useRef<THREE.Line>(null!);
|
||||
const glowRef = useRef<THREE.Mesh>(null!);
|
||||
const shipRef = useRef<THREE.Group>(null!);
|
||||
const lineRef = useRef<THREE.Line | null>(null);
|
||||
const glowRef = useRef<THREE.Mesh | null>(null);
|
||||
const shipRef = useRef<THREE.Group | null>(null);
|
||||
const shipT = useRef(0);
|
||||
|
||||
// CatmullRom curve bowing sideways — alternate direction per segment
|
||||
@ -799,8 +799,10 @@ const RouteSegment = ({
|
||||
useFrame((_, dt) => {
|
||||
// Scroll dashes forward along the route
|
||||
if (lineRef.current) {
|
||||
const mat = lineRef.current.material as THREE.LineDashedMaterial;
|
||||
if (dashSpeed > 0) mat.dashOffset -= dt * dashSpeed;
|
||||
// material typings may not include dashOffset; use any and guard the value
|
||||
const lineMat = lineRef.current.material as any;
|
||||
if (dashSpeed > 0)
|
||||
lineMat.dashOffset = (lineMat.dashOffset ?? 0) - dt * dashSpeed;
|
||||
}
|
||||
// Pulse glow on active segments
|
||||
if (glowRef.current && (isActive || isNext)) {
|
||||
@ -837,9 +839,14 @@ const RouteSegment = ({
|
||||
|
||||
{/* Dashed route line */}
|
||||
<line
|
||||
ref={lineRef}
|
||||
ref={(r: any) => {
|
||||
// r may be an SVGLineElement in JSX DOM typings; treat as any to satisfy TS and assign to Line ref
|
||||
lineRef.current = r as THREE.Line | null;
|
||||
}}
|
||||
// @ts-ignore - geometry is a three.js prop, not an SVG attribute
|
||||
geometry={lineGeo}
|
||||
onUpdate={(self) => self.computeLineDistances()}
|
||||
// onUpdate receives a three.js Line; use any to avoid DOM typings
|
||||
onUpdate={(self: any) => self.computeLineDistances()}
|
||||
>
|
||||
<lineDashedMaterial
|
||||
color={color}
|
||||
@ -1275,7 +1282,7 @@ const LeftPanel = ({
|
||||
arcs: QuestArc[];
|
||||
activeArcId: string;
|
||||
onSelectArc: (id: string) => void;
|
||||
scrollRef: React.RefObject<HTMLDivElement>;
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
user: any;
|
||||
onClaim: (n: QuestNode) => void;
|
||||
}) => {
|
||||
@ -1557,7 +1564,7 @@ export const QuestMap = () => {
|
||||
const [claimResult, setClaimResult] = useState<ClaimedRewardResponse | null>(
|
||||
null,
|
||||
);
|
||||
const [claimLoading, setClaimLoading] = useState(false);
|
||||
|
||||
const [claimError, setClaimError] = useState<string | null>(null);
|
||||
const [selectedNode, setSelectedNode] = useState<QuestNode | null>(null);
|
||||
|
||||
@ -1597,14 +1604,12 @@ export const QuestMap = () => {
|
||||
setClaimingNode(node);
|
||||
setClaimResult(null);
|
||||
setClaimError(null);
|
||||
setClaimLoading(true);
|
||||
|
||||
try {
|
||||
const result = await api.claimReward(token, node.node_id);
|
||||
setClaimResult(result);
|
||||
} catch (err) {
|
||||
setClaimError(err instanceof Error ? err.message : "Claim failed");
|
||||
} finally {
|
||||
setClaimLoading(false);
|
||||
}
|
||||
},
|
||||
[token],
|
||||
|
||||
@ -434,9 +434,10 @@ export const Rewards = () => {
|
||||
if (!user) return;
|
||||
const authStorage = localStorage.getItem("auth-storage");
|
||||
if (!authStorage) return;
|
||||
const {
|
||||
state: { token },
|
||||
} = JSON.parse(authStorage) as { state?: { token?: string } };
|
||||
const parsed = JSON.parse(authStorage) as {
|
||||
state?: { token?: string };
|
||||
} | null;
|
||||
const token = parsed?.state?.token;
|
||||
if (!token) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
@ -481,7 +482,7 @@ export const Rewards = () => {
|
||||
|
||||
// ✅ FIX 2: Safely cast user_rank — null becomes undefined so all optional chaining works
|
||||
const ur = (leaderboard?.user_rank ?? undefined) as
|
||||
| Record<string, unknown>
|
||||
| Record<string, number>
|
||||
| undefined;
|
||||
|
||||
const islandStats = getIslandStats(ur, activeTab);
|
||||
|
||||
@ -324,9 +324,10 @@ export const Drills = () => {
|
||||
setLoading(true);
|
||||
const authStorage = localStorage.getItem("auth-storage");
|
||||
if (!authStorage) return;
|
||||
const {
|
||||
state: { token },
|
||||
} = JSON.parse(authStorage) as { state?: { token?: string } };
|
||||
const parsed = JSON.parse(authStorage) as {
|
||||
state?: { token?: string };
|
||||
} | null;
|
||||
const token = parsed?.state?.token;
|
||||
if (!token) return;
|
||||
const response = await api.fetchAllTopics(token);
|
||||
setTopics(response);
|
||||
|
||||
@ -47,9 +47,9 @@ function FormulaCard({
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`transition-all duration-300 ease-in-out ${open ? "max-h-[400px] opacity-100" : "max-h-0 opacity-0"}`}
|
||||
className={`transition-all duration-300 ease-in-out ${open ? "max-h-100 opacity-100" : "max-h-0 opacity-0"}`}
|
||||
>
|
||||
<div className="border-t border-emerald-100 px-5 py-4 flex flex-col sm:flex-row items-center gap-5 bg-gradient-to-br from-emerald-50/50 to-white/80">
|
||||
<div className="border-t border-emerald-100 px-5 py-4 flex flex-col sm:flex-row items-center gap-5 bg-linear-to-br from-emerald-50/50 to-white/80">
|
||||
<div className="shrink-0">{diagram}</div>
|
||||
<div className="text-sm text-slate-600 space-y-1 font-mono">
|
||||
{example}
|
||||
|
||||
@ -469,7 +469,7 @@ const CirclePropertiesLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{CIRCLE_PROP_QUIZ_DATA.map((quiz, idx) => (
|
||||
{CIRCLE_PROP_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Circle,
|
||||
Target,
|
||||
|
||||
@ -490,7 +490,7 @@ const CongruenceSimilarityLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{SIMILARITY_QUIZ_DATA.map((quiz, idx) => (
|
||||
{SIMILARITY_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -266,7 +266,7 @@ const DataAnalysisLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{DATA_ANALYSIS_QUIZ_DATA.map((quiz, idx) => (
|
||||
{DATA_ANALYSIS_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -296,7 +296,7 @@ const EBRWBoundariesLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -142,7 +142,7 @@ const EBRWCentralIdeasLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -158,7 +158,7 @@ const EBRWCommandEvidenceLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -209,7 +209,7 @@ const EBRWCommasLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -88,7 +88,7 @@ const EBRWCraftStructureLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -125,7 +125,7 @@ const EBRWCrossTextLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -195,7 +195,7 @@ const EBRWDashesApostrophesLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -105,6 +105,7 @@ const EBRWExplicitMeaningLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -105,6 +105,7 @@ const EBRWExpressionIdeasLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -220,6 +220,7 @@ const EBRWFormStructureSenseLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -273,6 +273,7 @@ const EBRWGraphicDisplaysLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -155,6 +155,7 @@ const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -102,6 +102,7 @@ const EBRWMainIdeaLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -208,7 +208,7 @@ const EBRWPronounsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -190,6 +190,7 @@ const EBRWRhetoricalSynthesisLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -189,7 +189,7 @@ const EBRWSemicolonsColonsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -214,7 +214,7 @@ const EBRWSentenceStructureLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -203,7 +203,7 @@ const EBRWSubjectVerbLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -270,7 +270,7 @@ const EBRWTextStructurePurposeLesson: React.FC<LessonProps> = ({
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -171,7 +171,7 @@ const EBRWTransitionsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -211,7 +211,7 @@ const EBRWVerbsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -45,7 +45,7 @@ const EBRWVocabMeaningLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -45,7 +45,7 @@ const EBRWVocabPreciseLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
|
||||
@ -275,7 +275,7 @@ const EBRWWordsInContextLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
}: {
|
||||
index: number;
|
||||
title: string;
|
||||
icon: React.ElementType;
|
||||
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
||||
}) => {
|
||||
const isActive = activeSection === index;
|
||||
const isPast = activeSection > index;
|
||||
@ -288,11 +288,7 @@ const EBRWWordsInContextLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
|
||||
${isActive ? "bg-fuchsia-600 text-white" : isPast ? "bg-fuchsia-400 text-white" : "bg-slate-200 text-slate-500"}`}
|
||||
>
|
||||
{isPast ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
<Icon className="w-4 h-4" />
|
||||
)}
|
||||
{isPast ? <Check className="w-4 h-4" /> : <Icon />}
|
||||
</div>
|
||||
<p
|
||||
className={`text-sm font-bold ${isActive ? "text-fuchsia-900" : "text-slate-600"}`}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
ArrowRight,
|
||||
Layers,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Search,
|
||||
GitBranch,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Scale,
|
||||
ArrowRight,
|
||||
@ -64,9 +64,9 @@ const BalanceWidget = () => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative h-32 flex justify-center items-end mb-4">
|
||||
<div className="absolute bottom-0 w-0 h-0 border-l-[16px] border-l-transparent border-r-[16px] border-r-transparent border-b-[32px] border-b-slate-800" />
|
||||
<div className="absolute bottom-0 w-0 h-0 border-l-16 border-l-transparent border-r-16 border-r-transparent border-b-32 border-b-slate-800" />
|
||||
<div
|
||||
className="w-52 h-1.5 bg-slate-600 absolute bottom-[32px] transition-transform duration-500"
|
||||
className="w-52 h-1.5 bg-slate-600 absolute bottom-8 transition-transform duration-500"
|
||||
style={{ transform: `rotate(${tilt}deg)` }}
|
||||
>
|
||||
<div className="absolute left-0 -top-12 w-16 h-12 border-b-2 border-l border-r border-slate-300 rounded-b-lg bg-white/80 flex items-center justify-center">
|
||||
|
||||
@ -1,12 +1,4 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Grid,
|
||||
TrendingUp,
|
||||
Layers,
|
||||
ArrowRight,
|
||||
Hash,
|
||||
BookOpen,
|
||||
} from "lucide-react";
|
||||
import { Grid, TrendingUp, Layers, Hash, BookOpen } from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
FormulaBox,
|
||||
|
||||
@ -57,9 +57,9 @@ const BalanceScaleWidget = () => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative h-40 w-full mb-8 flex justify-center items-end">
|
||||
<div className="absolute bottom-0 w-0 h-0 border-l-[20px] border-l-transparent border-r-[20px] border-r-transparent border-b-[40px] border-b-slate-800"></div>
|
||||
<div className="absolute bottom-0 w-0 h-0 border-l-20 border-l-transparent border-r-20 border-r-transparent border-b-40 border-b-slate-800"></div>
|
||||
<div
|
||||
className="w-64 h-2 bg-slate-600 absolute bottom-[40px] transition-transform duration-700"
|
||||
className="w-64 h-2 bg-slate-600 absolute bottom-10 transition-transform duration-700"
|
||||
style={{ transform: `rotate(${tilt}deg)` }}
|
||||
>
|
||||
<div
|
||||
@ -481,7 +481,7 @@ const LinearEquationsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{LINEAR_EQ_QUIZ_DATA.map((quiz, idx) => (
|
||||
{LINEAR_EQ_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
TrendingUp,
|
||||
Hash,
|
||||
@ -186,7 +185,7 @@ export default function LinearFunctionsLesson({ onFinish }: LessonProps) {
|
||||
key={c}
|
||||
className="flex gap-3 items-center bg-white/60 rounded-lg p-3 border border-blue-100"
|
||||
>
|
||||
<code className="font-mono text-blue-700 font-bold min-w-[110px]">
|
||||
<code className="font-mono text-blue-700 font-bold min-w-27.5">
|
||||
{c}
|
||||
</code>
|
||||
<span className="text-slate-600">{d}</span>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Scale,
|
||||
ArrowRight,
|
||||
@ -9,7 +8,6 @@ import {
|
||||
} from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
FormulaBox,
|
||||
ExampleCard,
|
||||
TipCard,
|
||||
PracticeFromDataset,
|
||||
|
||||
@ -327,7 +327,7 @@ const LinearParallelPerpendicularLesson: React.FC<LessonProps> = ({
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{LINEAR_PARALLEL_PERP_QUIZ_DATA.map((quiz, idx) => (
|
||||
{LINEAR_PARALLEL_PERP_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -303,7 +303,7 @@ const LinearTransformationsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{LINEAR_TRANSFORMATIONS_QUIZ_DATA.map((quiz, idx) => (
|
||||
{LINEAR_TRANSFORMATIONS_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
ArrowRight,
|
||||
Triangle,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Layers, Hash, Target, Zap, RotateCcw, BookOpen } from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
BarChart,
|
||||
Box,
|
||||
|
||||
@ -457,7 +457,7 @@ const PolynomialFunctionsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{ADV_POLYNOMIAL_QUIZ.map((quiz, idx) => (
|
||||
{ADV_POLYNOMIAL_QUIZ.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Target, Hash, GitBranch, Layers, Table, BookOpen } from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
|
||||
@ -618,7 +618,7 @@ const QuadraticEquationsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{QUADRATIC_EQ_QUIZ_DATA.map((quiz, idx) => (
|
||||
{QUADRATIC_EQ_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -450,7 +450,7 @@ const RationalRadicalLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{ADV_RATIONAL_QUIZ.map((quiz, idx) => (
|
||||
{ADV_RATIONAL_QUIZ.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -32,7 +32,7 @@ const RatiosLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{RATIOS_QUIZ_DATA.map((quiz, idx) => (
|
||||
{RATIOS_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Scale,
|
||||
ArrowRight,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Triangle,
|
||||
Ruler,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Scale, Target, BarChart, Layers, Hash, BookOpen } from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Target,
|
||||
ArrowRight,
|
||||
|
||||
@ -384,7 +384,7 @@ const SystemsEquationsLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{SYSTEMS_QUIZ_DATA.map((quiz, idx) => (
|
||||
{SYSTEMS_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
import { Layers, ArrowRight, Hash, Lightbulb, BookOpen } from "lucide-react";
|
||||
import LessonShell, {
|
||||
ConceptCard,
|
||||
FormulaBox,
|
||||
ExampleCard,
|
||||
TipCard,
|
||||
PracticeFromDataset,
|
||||
|
||||
@ -688,7 +688,7 @@ const TrigLesson: React.FC<LessonProps> = ({ onFinish }) => {
|
||||
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
|
||||
Practice Time
|
||||
</h2>
|
||||
{TRIG_QUIZ_DATA.map((quiz, idx) => (
|
||||
{TRIG_QUIZ_DATA.map((quiz) => (
|
||||
<div key={quiz.id} className="mb-12">
|
||||
<Quiz data={quiz} />
|
||||
</div>
|
||||
|
||||
@ -2,8 +2,9 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useResults } from "../../../stores/useResults";
|
||||
import { LucideArrowLeft } from "lucide-react";
|
||||
import { CircularLevelProgress } from "../../../components/CircularLevelProgress";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
|
||||
import { useAuthStore } from "../../../stores/authStore";
|
||||
|
||||
// ─── Shared styles injected once ─────────────────────────────────────────────
|
||||
const STYLES = `
|
||||
@ -404,13 +405,16 @@ export const Results = () => {
|
||||
const clearResults = useResults((s) => s.clearResults);
|
||||
const { payload } = useExamConfigStore();
|
||||
const isTargeted = payload?.mode === "TARGETED";
|
||||
const fetchUser = useAuthStore((s) => s.fetchUser);
|
||||
|
||||
function handleFinishExam() {
|
||||
const handleFinishExam = useCallback(async () => {
|
||||
useExamConfigStore.getState().clearPayload();
|
||||
|
||||
clearResults();
|
||||
|
||||
await fetchUser(); // ← refreshes user in authStore after exam completes
|
||||
|
||||
navigate("/student/home");
|
||||
}
|
||||
}, [clearResults, fetchUser, navigate]);
|
||||
|
||||
if (isTargeted) return <TargetedResults onFinish={handleFinishExam} />;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { Navigate, useNavigate } from "react-router-dom";
|
||||
// @ts-ignore
|
||||
import { BlockMath, InlineMath } from "react-katex";
|
||||
import {
|
||||
Binary,
|
||||
@ -931,6 +932,7 @@ export const Test = () => {
|
||||
if (!user) return;
|
||||
const payload = useExamConfigStore.getState().payload;
|
||||
try {
|
||||
// @ts-ignore
|
||||
const response = await api.startSession(token as string, payload);
|
||||
setSessionId(response.id);
|
||||
await loadSessionQuestions(response.id);
|
||||
@ -1067,6 +1069,7 @@ export const Test = () => {
|
||||
(e) => !correctedRef.current.has(e.questionId),
|
||||
);
|
||||
if (!remaining.length) {
|
||||
// @ts-ignore
|
||||
const next = await api.fetchNextModule(token!, sessionId);
|
||||
if (next.status === "COMPLETED") finishExam();
|
||||
return;
|
||||
@ -2152,7 +2155,7 @@ export const Test = () => {
|
||||
<p className="text-gray-500 font-semibold mb-8">
|
||||
Take a breather — next module coming up
|
||||
</p>
|
||||
<div className="t-card p-6 mb-8 text-center min-w-[200px]">
|
||||
<div className="t-card p-6 mb-8 text-center min-w-50">
|
||||
<p className="text-xs font-bold text-gray-400 uppercase tracking-widest mb-2">
|
||||
Next module in
|
||||
</p>
|
||||
@ -2198,7 +2201,7 @@ export const Test = () => {
|
||||
["🏆", "Nice!", "Results"],
|
||||
["🔥", "100%", "Effort"],
|
||||
].map(([e, v, l]) => (
|
||||
<div key={l} className="t-card p-4 text-center min-w-[100px]">
|
||||
<div key={l} className="t-card p-4 text-center min-w-25">
|
||||
<span className="text-2xl block mb-1">{e}</span>
|
||||
<span className="font-black text-lg text-[#1e1b4b]">{v}</span>
|
||||
<span className="text-xs font-bold text-gray-400 uppercase block">
|
||||
|
||||
@ -513,9 +513,10 @@ export const TargetedPractice = () => {
|
||||
setLoading(true);
|
||||
const authStorage = localStorage.getItem("auth-storage");
|
||||
if (!authStorage) return;
|
||||
const {
|
||||
state: { token },
|
||||
} = JSON.parse(authStorage) as { state?: { token?: string } };
|
||||
const parsed = JSON.parse(authStorage) as {
|
||||
state?: { token?: string };
|
||||
} | null;
|
||||
const token = parsed?.state?.token;
|
||||
if (!token) return;
|
||||
const response = await api.fetchAllTopics(token);
|
||||
setTopics(response);
|
||||
@ -707,9 +708,13 @@ export const TargetedPractice = () => {
|
||||
color: meta.color,
|
||||
}}
|
||||
>
|
||||
{t.section === "Reading & Writing"
|
||||
? "R&W"
|
||||
: t.section}
|
||||
{(() => {
|
||||
const s = String(t.section);
|
||||
return s === "EBRW" ||
|
||||
s === "Reading & Writing"
|
||||
? "R&W"
|
||||
: s;
|
||||
})()}
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user