import { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { api } from "../../../utils/api"; import { useAuthStore } from "../../../stores/authStore"; import type { PracticeSheet } from "../../../types/sheet"; import { CircleQuestionMark, Clock, Layers, Loader, Tag } from "lucide-react"; import { Carousel, CarouselContent, CarouselItem, type CarouselApi, } from "../../../components/ui/carousel"; import { useExamConfigStore } from "../../../stores/useExamConfigStore"; // ─── Shared background dots (same subtle config as rest of app) ─────────────── const DOTS = [ { size: 10, color: "#f97316", top: "8%", left: "5%", delay: "0s" }, { size: 7, color: "#a855f7", top: "28%", left: "2%", delay: "1.2s" }, { size: 9, color: "#22c55e", top: "60%", left: "4%", delay: "0.6s" }, { size: 12, color: "#3b82f6", top: "12%", right: "4%", delay: "1.8s" }, { size: 7, color: "#f43f5e", top: "45%", right: "3%", delay: "0.9s" }, { size: 9, color: "#eab308", top: "75%", right: "6%", delay: "0.4s" }, ]; 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'); .pt-screen { min-height: 100vh; background: #fffbf4; font-family: 'Nunito', sans-serif; position: relative; overflow-x: hidden; } /* ── Blobs ── */ .pt-blob { position: fixed; pointer-events: none; z-index: 0; filter: blur(48px); opacity: 0.35; } .pt-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:ptWobble1 14s ease-in-out infinite; } .pt-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:ptWobble2 16s ease-in-out infinite; } .pt-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:ptWobble1 18s ease-in-out infinite reverse; } .pt-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:ptWobble2 12s ease-in-out infinite; } @keyframes ptWobble1 { 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 ptWobble2 { 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);} } /* ── Floating dots ── */ .pt-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:ptFloat 7s ease-in-out infinite; } @keyframes ptFloat { 0%,100%{transform:translateY(0) rotate(0deg);} 50%{transform:translateY(-12px) rotate(180deg);} } /* ── Inner scroll container ── */ .pt-inner { position: relative; z-index: 1; max-width: 580px; margin: 0 auto; padding: 2rem 1.25rem 4rem; display: flex; flex-direction: column; gap: 1.25rem; } /* ── Pop-in animation ── */ @keyframes ptPopIn { from { opacity:0; transform: scale(0.92) translateY(12px); } to { opacity:1; transform: scale(1) translateY(0); } } .pt-anim { animation: ptPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; } .pt-anim-1 { animation-delay: 0.05s; } .pt-anim-2 { animation-delay: 0.1s; } .pt-anim-3 { animation-delay: 0.15s; } .pt-anim-4 { animation-delay: 0.2s; } .pt-anim-5 { animation-delay: 0.25s; } /* ── Header ── */ .pt-header { animation: ptPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; } .pt-eyebrow { font-size: 0.65rem; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase; color: #a855f7; margin-bottom: 0.3rem; } .pt-title { font-size: clamp(1.6rem, 5vw, 2.2rem); font-weight: 900; color: #1e1b4b; letter-spacing: -0.02em; line-height: 1.15; } .pt-desc { font-family: 'Nunito Sans', sans-serif; font-size: 0.9rem; font-weight: 600; color: #9ca3af; margin-top: 0.3rem; } /* ── White card base ── */ .pt-card { background: white; border: 2.5px solid #f3f4f6; border-radius: 24px; padding: 1.25rem 1.5rem; box-shadow: 0 6px 20px rgba(0,0,0,0.04); box-sizing: border-box; } /* ── Stats row ── */ .pt-stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; } .pt-stat { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; padding: 0.85rem 0.5rem; background: white; border: 2.5px solid #f3f4f6; border-radius: 20px; box-shadow: 0 3px 10px rgba(0,0,0,0.04); } .pt-stat-icon { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .pt-stat-icon.cyan { background: #cffafe; } .pt-stat-icon.purple { background: #f3e8ff; } .pt-stat-icon.amber { background: #fef3c7; } .pt-stat-value { font-size: 1.4rem; font-weight: 900; color: #1e1b4b; line-height: 1; } .pt-stat-label { font-size: 0.65rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #9ca3af; } /* ── Loading skeleton ── */ .pt-loading { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; padding: 2rem; background: white; border: 2.5px solid #f3f4f6; border-radius: 24px; box-shadow: 0 6px 20px rgba(0,0,0,0.04); } .pt-spinner { animation: ptSpin 0.8s linear infinite; } @keyframes ptSpin { to { transform: rotate(360deg); } } .pt-loading-text { font-size: 0.85rem; font-weight: 700; color: #9ca3af; } /* ── Module carousel card ── */ .pt-module-card { background: white; border: 2.5px solid #f3f4f6; border-radius: 24px; padding: 1.25rem 1.5rem; box-shadow: 0 6px 20px rgba(0,0,0,0.04); display: flex; flex-direction: column; gap: 1rem; } .pt-module-header { display: flex; align-items: center; gap: 0.6rem; } .pt-section-badge { font-size: 0.65rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #a855f7; background: #f3e8ff; border-radius: 100px; padding: 0.25rem 0.7rem; } .pt-module-title { font-size: 0.95rem; font-weight: 900; color: #1e1b4b; line-height: 1.3; } .pt-module-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; } .pt-module-stat { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; padding: 0.7rem 0.4rem; border: 2px solid #f3f4f6; border-radius: 16px; background: #fafafa; } .pt-module-stat-icon { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .pt-module-stat-icon.cyan { background: #cffafe; } .pt-module-stat-icon.lime { background: #d9f99d; } .pt-module-stat-icon.amber { background: #fef3c7; } .pt-module-stat-value { font-size: 1rem; font-weight: 900; color: #1e1b4b; line-height: 1; } .pt-module-stat-label { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #9ca3af; } /* ── Dot indicator ── */ .pt-dots { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin-top: 0.75rem; } .pt-dot-ind { border-radius: 100px; height: 7px; transition: width 0.3s ease, background 0.3s ease; } .pt-dot-ind.active { width: 20px; background: #a855f7; } .pt-dot-ind.inactive { width: 7px; background: #e5e7eb; } /* ── Tip card ── */ .pt-tip { display: flex; align-items: flex-start; gap: 0.75rem; } .pt-tip-emoji { font-size: 1.4rem; flex-shrink: 0; margin-top: 2px; } .pt-tip-text { font-size: 0.88rem; font-weight: 700; color: #374151; line-height: 1.5; } /* ── Start button ── */ .pt-start-btn { width: 100%; background: #f97316; color: white; border: none; border-radius: 100px; padding: 1.1rem; font-family: 'Nunito', sans-serif; font-size: 1rem; font-weight: 800; cursor: pointer; 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; display: flex; align-items: center; justify-content: center; gap: 0.5rem; } .pt-start-btn:hover:not(:disabled) { transform:translateY(-2px); box-shadow:0 8px 0 #c2560e,0 12px 24px rgba(249,115,22,0.3); } .pt-start-btn:active:not(:disabled) { transform:translateY(3px); box-shadow:0 3px 0 #c2560e,0 4px 12px rgba(249,115,22,0.2); } .pt-start-btn:disabled { opacity:0.6; cursor:not-allowed; box-shadow:none; } /* ── Error card ── */ .pt-error { background: #fff5f5; border: 2.5px solid #fecaca; border-radius: 24px; padding: 1.5rem; text-align: center; font-size: 0.9rem; font-weight: 700; color: #ef4444; } `; export const Pretest = () => { const { setSheetId, setMode, storeDuration, setQuestionCount } = useExamConfigStore(); const user = useAuthStore((state) => state.user); const { sheetId } = useParams<{ sheetId: string }>(); const navigate = useNavigate(); const [carouselApi, setCarouselApi] = useState(); const [current, setCurrent] = useState(0); const [practiceSheet, setPracticeSheet] = useState( null, ); function handleStartTest(id: string) { if (!id) return; setSheetId(id); setMode("SIMULATION"); storeDuration(practiceSheet?.time_limit ?? 0); setQuestionCount(2); navigate(`/student/practice/${id}/test`, { replace: true }); } useEffect(() => { if (!user) return; async function fetchSheet(id: string) { const authStorage = localStorage.getItem("auth-storage"); if (!authStorage) return; const { state: { token }, } = JSON.parse(authStorage); if (!token) return; const data = await api.getPracticeSheetById(token, id); setPracticeSheet(data); } fetchSheet(sheetId!); }, [sheetId, user]); useEffect(() => { if (!carouselApi) return; setCurrent(carouselApi.selectedScrollSnap() + 1); carouselApi.on("select", () => setCurrent(carouselApi.selectedScrollSnap() + 1), ); }, [carouselApi]); return (
{/* Blobs */}
{/* Floating dots */} {DOTS.map((d, i) => (
))}
{/* ── Header ── */}

📋 Practice Sheet

{practiceSheet ? ( <>

{practiceSheet.title}

{practiceSheet.description}

) : ( <>
)}
{/* ── At-a-glance stats ── */} {practiceSheet ? (
{practiceSheet.time_limit} Minutes
{practiceSheet.modules.length} Modules
{practiceSheet.questions_count} Questions
) : (

Loading sheet details...

)} {/* ── Module carousel ── */}
{practiceSheet ? ( practiceSheet.modules.length > 0 ? ( practiceSheet.modules.map((module, index) => (
Section {Math.floor(index / 2) + 1}

{module.title}

{module.duration} Min
{module.questions.length} Questions
{module.section} Type
)) ) : (
😕 No modules available for this sheet.
) ) : (

Loading modules...

)}
{/* Dot indicator */} {practiceSheet && practiceSheet.modules.length > 1 && (
{practiceSheet.modules.map((_, i) => (
))}
)}
{/* ── Encouragement tip ── */}
💪

Take your time, read each question carefully, and do your best. Every practice run brings you closer to your goal!

{/* ── Start button ── */}
); };