import { useEffect, useState } from "react"; import { useAuthStore } from "../../stores/authStore"; import { CheckCircle, Flame, Gauge, Play, Search } from "lucide-react"; import { api } from "../../utils/api"; 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 { Drawer, DrawerContent, DrawerTrigger, } from "../../components/ui/drawer"; import { useExamConfigStore } from "../../stores/useExamConfigStore"; // ─── Shared blob/dot background (same as break/results screens) ──────────────── const DOTS = [ { size: 12, color: "#f97316", top: "8%", left: "6%", delay: "0s" }, { size: 8, color: "#a855f7", top: "22%", left: "2%", delay: "1s" }, { size: 10, color: "#22c55e", top: "55%", left: "4%", delay: "0.5s" }, { size: 14, color: "#3b82f6", top: "10%", right: "5%", delay: "1.5s" }, { size: 8, color: "#f43f5e", top: "40%", right: "3%", delay: "0.8s" }, { size: 10, color: "#eab308", top: "70%", right: "7%", delay: "0.3s" }, ]; 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'); .home-screen { min-height: 100vh; background: #fffbf4; font-family: 'Nunito', sans-serif; position: relative; overflow-x: hidden; } /* ── Blobs ── */ .h-blob { position: fixed; pointer-events: none; z-index: 0; filter: blur(48px); opacity: 0.35; } .h-blob-1 { width:240px;height:240px;background:#fde68a;top:-80px;left:-80px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:hWobble1 14s ease-in-out infinite; } .h-blob-2 { width:190px;height:190px;background:#a5f3c0;bottom:-50px;left:6%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:hWobble2 16s ease-in-out infinite; } .h-blob-3 { width:210px;height:210px;background:#fbcfe8;top:15%;right:-60px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:hWobble1 18s ease-in-out infinite reverse; } .h-blob-4 { width:150px;height:150px;background:#bfdbfe;bottom:12%;right:2%;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:hWobble2 12s ease-in-out infinite; } @keyframes hWobble1 { 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 hWobble2 { 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 ── */ .h-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.3;animation:hFloat 6s ease-in-out infinite; } @keyframes hFloat { 0%,100%{transform:translateY(0) rotate(0deg);} 50%{transform:translateY(-14px) rotate(180deg);} } /* ── Inner scroll container ── */ .home-inner { position: relative; z-index: 1; max-width: 580px; margin: 0 auto; padding: 2rem 1.25rem 4rem; display: flex; flex-direction: column; gap: 1.75rem; } /* ── Header ── */ .home-header { display: flex; align-items: center; justify-content: space-between; animation: hPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; } .home-header-left { display:flex;align-items:center;gap:0.75rem; } .home-user-name { font-size: 1.1rem; font-weight: 900; color: #1e1b4b; line-height:1.1; } .home-user-role { font-size: 0.72rem; font-weight: 700; letter-spacing:0.08em; text-transform: uppercase; color: #a855f7; } .home-header-right { display:flex;align-items:center;gap:0.6rem; } /* Header action chips */ .h-chip { display: flex; align-items: center; gap: 0.4rem; background: white; border: 2.5px solid #f3f4f6; border-radius: 100px; padding: 0.5rem 0.9rem; box-shadow: 0 3px 10px rgba(0,0,0,0.06); cursor: pointer; font-size:0.85rem; font-weight:800; transition: transform 0.15s ease, box-shadow 0.15s ease; } .h-chip:hover { transform:translateY(-2px);box-shadow:0 6px 14px rgba(0,0,0,0.08); } .h-chip.streak { border-color:#fecaca; background:#fff5f5; color:#ef4444; } .h-chip.score { border-color:#d9f99d; background:#f7ffe4; color:#65a30d; } /* ── Section titles ── */ .h-section-title { font-size: 1.2rem; font-weight: 900; color: #1e1b4b; letter-spacing: -0.01em; margin-bottom: 0.75rem; } /* ── Search bar ── */ .h-search-wrap { position: relative; animation: hPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) 0.05s both; } .h-search-input { width: 100%; box-sizing: border-box; padding: 0.85rem 1rem 0.85rem 2.8rem; background: white; border: 2.5px solid #f3f4f6; border-radius: 18px; font-family: 'Nunito', sans-serif; font-size: 0.9rem; font-weight: 700; color: #9ca3af; box-shadow: 0 4px 14px rgba(0,0,0,0.05); cursor: pointer; outline: none; transition: border-color 0.2s ease, box-shadow 0.2s ease; } .h-search-input:focus { border-color:#c084fc; box-shadow:0 4px 20px rgba(192,132,252,0.15); } .h-search-icon { position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%); pointer-events:none; } /* ── In-progress card ── */ .h-inprogress-card { background: white; border: 2.5px solid #c4b5fd; border-radius: 22px; padding: 1.1rem 1.25rem; box-shadow: 0 4px 16px rgba(167,139,250,0.12); display: flex; align-items: center; justify-content: space-between; gap: 1rem; cursor: pointer; transition: transform 0.15s ease, box-shadow 0.15s ease; } .h-inprogress-card:hover { transform:translateY(-2px);box-shadow:0 8px 24px rgba(167,139,250,0.2); } .h-inprogress-info { display:flex;flex-direction:column;gap:0.25rem;flex:1;min-width:0; } .h-inprogress-title { font-size: 0.95rem; font-weight: 900; color: #1e1b4b; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .h-inprogress-badge { font-size: 0.65rem; font-weight: 800; letter-spacing:0.1em; text-transform:uppercase; color:#a855f7; background:#f3e8ff; border-radius:100px; padding:0.2rem 0.6rem; width: fit-content; } .h-play-btn { width: 44px; height: 44px; border-radius: 50%; border: none; cursor: pointer; background: linear-gradient(135deg, #a855f7, #7c3aed); display:flex;align-items:center;justify-content:center; box-shadow: 0 4px 0 #5b21b6aa; transition: transform 0.1s ease, box-shadow 0.1s ease; flex-shrink:0; } .h-play-btn:hover { transform:translateY(-2px);box-shadow:0 6px 0 #5b21b6aa; } .h-play-btn:active { transform:translateY(2px);box-shadow:0 2px 0 #5b21b6aa; } /* ── Empty state ── */ .h-empty { background:white; border:2.5px dashed #e5e7eb; border-radius:22px; padding: 1.75rem; text-align:center; font-size:0.9rem; font-weight:700; color:#9ca3af; } .h-empty-emoji { font-size:2rem; display:block; margin-bottom:0.5rem; } /* ── Tabs ── */ .h-tabs-list { display:flex; border-bottom: 2px solid #f3f4f6; gap: 0; margin-bottom:1rem; } .h-tab-btn { flex:1; padding:0.65rem 0; text-align:center; font-family:'Nunito',sans-serif; font-size:0.82rem; font-weight:800; color:#9ca3af; background:transparent; border:none; border-bottom: 3px solid transparent; margin-bottom:-2px; cursor:pointer; transition: color 0.2s ease, border-color 0.2s ease; } .h-tab-btn.active { color:#1e1b4b; border-bottom-color:#a855f7; } /* ── Practice sheet card ── */ .h-sheet-grid { display:grid; gap:0.85rem; grid-template-columns: 1fr; } @media(min-width:520px){ .h-sheet-grid { grid-template-columns:1fr 1fr; } } .h-sheet-card { background:white; border:2.5px solid #f3f4f6; border-radius:22px; padding:1.1rem; box-shadow:0 4px 14px rgba(0,0,0,0.04); display:flex; flex-direction:column; gap:0.6rem; transition: transform 0.15s ease, box-shadow 0.15s ease; } .h-sheet-card:hover { transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,0.07); } .h-sheet-title { font-size:0.95rem;font-weight:900;color:#1e1b4b;line-height:1.2; } .h-sheet-desc { font-size:0.78rem;font-weight:600;color:#9ca3af;font-family:'Nunito Sans',sans-serif; } .h-sheet-meta { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.4rem; } .h-status-pill { font-size:0.65rem;font-weight:800;letter-spacing:0.08em;text-transform:uppercase; border-radius:100px;padding:0.25rem 0.65rem; } .h-status-pill.inprogress { background:#f3e8ff;color:#9333ea; } .h-status-pill.notstarted { background:#f3f4f6;color:#6b7280; } .h-status-pill.completed { background:#dcfce7;color:#16a34a; } .h-modules-badge { font-size:0.65rem;font-weight:800; background:#ede9fe;color:#7c3aed;border-radius:100px;padding:0.25rem 0.65rem; } .h-time-badge { font-size:0.72rem;font-weight:700;color:#9ca3af;display:flex;align-items:center;gap:0.3rem; } .h-start-btn { width:100%;margin-top:auto; background:#f97316;color:white;border:none;border-radius:100px; padding:0.75rem;font-family:'Nunito',sans-serif; font-size:0.9rem;font-weight:800;cursor:pointer; box-shadow:0 4px 0 #c2560e,0 6px 16px rgba(249,115,22,0.2); transition:transform 0.1s ease,box-shadow 0.1s ease; } .h-start-btn:hover { transform:translateY(-2px);box-shadow:0 6px 0 #c2560e,0 10px 20px rgba(249,115,22,0.25); } .h-start-btn:active { transform:translateY(2px); box-shadow:0 2px 0 #c2560e,0 3px 8px rgba(249,115,22,0.15); } /* ── Tips section ── */ .h-tips-list { display:flex;flex-direction:column;gap:0.6rem; } .h-tip-row { display:flex;align-items:flex-start;gap:0.65rem; background:white;border:2.5px solid #f3f4f6;border-radius:16px; padding:0.75rem 1rem;box-shadow:0 2px 8px rgba(0,0,0,0.03); } .h-tip-icon { flex-shrink:0;margin-top:1px; } .h-tip-text { font-size:0.85rem;font-weight:700;color:#374151;line-height:1.4; } /* ── Load more ── */ .h-load-more-btn { width: 100%; margin-top: 0.25rem; padding: 0.75rem; background: white; border: 2.5px solid #f3f4f6; border-radius: 100px; cursor: pointer; font-family: 'Nunito', sans-serif; font-size: 0.82rem; font-weight: 800; color: #9ca3af; display: flex; align-items: center; justify-content: center; gap: 0.4rem; box-shadow: 0 3px 10px rgba(0,0,0,0.04); transition: all 0.2s ease; } .h-load-more-btn:hover { border-color: #c4b5fd; color: #a855f7; background: #fdf4ff; transform: translateY(-1px); box-shadow: 0 6px 14px rgba(0,0,0,0.06); } .h-load-more-btn:active { transform: translateY(1px); } .h-sheet-count { text-align: center; font-family: 'Nunito Sans', sans-serif; font-size: 0.72rem; font-weight: 600; color: #d1d5db; margin-top: 0.5rem; } .h-sheet-count span { font-weight: 800; color: #9ca3af; } @keyframes hPopIn { from{opacity:0;transform:scale(0.92) translateY(10px);} to{opacity:1;transform:scale(1) translateY(0);} } .h-anim { animation: hPopIn 0.4s cubic-bezier(0.34,1.56,0.64,1) both; } .h-anim-1 { animation-delay:0.05s; } .h-anim-2 { animation-delay:0.1s; } .h-anim-3 { animation-delay:0.15s; } .h-anim-4 { animation-delay:0.2s; } .h-anim-5 { animation-delay:0.25s; } `; // ─── Sheet card ─────────────────────────────────────────────────────────────── const SheetCard = ({ sheet, onStart, }: { sheet: PracticeSheet; onStart: (id: string) => void; }) => { const statusClass = sheet.user_status === "IN_PROGRESS" ? "inprogress" : sheet.user_status === "COMPLETED" ? "completed" : "notstarted"; return (

{sheet.title}

{sheet.description &&

{sheet.description}

}
{formatStatus(sheet.user_status)} 📚 {sheet.modules_count} modules

⏱️ {sheet.time_limit} min

); }; // ─── Tips data ──────────────────────────────────────────────────────────────── const TIPS = [ "Practice regularly with official SAT materials", "Review your mistakes and learn from them", "Focus on your weak areas first", "Take full-length practice tests", "Get plenty of rest before test day", ]; // ─── Main component ─────────────────────────────────────────────────────────── const PAGE_SIZE = 2; export const Home = () => { const user = useAuthStore((state) => state.user); const navigate = useNavigate(); const { userMetrics } = useExamConfigStore(); const [practiceSheets, setPracticeSheets] = useState([]); const [notStartedSheets, setNotStartedSheets] = useState([]); const [inProgressSheets, setInProgressSheets] = useState([]); const [completedSheets, setCompletedSheets] = useState([]); const [activeTab, setActiveTab] = useState< "all" | "NOT_STARTED" | "COMPLETED" >("all"); const [isSearchOpen, setIsSearchOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [visibleCount, setVisibleCount] = useState(PAGE_SIZE); useEffect(() => { const sort = (sheets: PracticeSheet[]) => { setNotStartedSheets( sheets.filter((s) => s.user_status === "NOT_STARTED"), ); setInProgressSheets( sheets.filter((s) => s.user_status === "IN_PROGRESS"), ); setCompletedSheets(sheets.filter((s) => s.user_status === "COMPLETED")); }; const fetch = async () => { if (!user) return; try { const authStorage = localStorage.getItem("auth-storage"); if (!authStorage) return; const { state: { token }, } = JSON.parse(authStorage); if (!token) return; const sheets = await api.getPracticeSheets(token, 1, 10); setPracticeSheets(sheets.data); sort(sheets.data); } catch (e) { console.error(e); } }; fetch(); }, [user]); const handleStart = (id: string) => navigate(`/student/practice/${id}`); const allTabSheets = activeTab === "all" ? practiceSheets : activeTab === "NOT_STARTED" ? notStartedSheets : completedSheets; const tabSheets = allTabSheets.slice(0, visibleCount); const hasMore = visibleCount < allTabSheets.length; const remaining = allTabSheets.length - visibleCount; const handleTabChange = (tab: "all" | "NOT_STARTED" | "COMPLETED") => { setActiveTab(tab); setVisibleCount(PAGE_SIZE); }; const greeting = new Date().getHours() < 12 ? "Good morning" : new Date().getHours() < 17 ? "Good afternoon" : "Good evening"; return (
{/* Blobs */}
{/* Dots */} {DOTS.map((d, i) => (
))}
{/* ── Header ── */}
{user?.name?.slice(0, 1)}

{greeting}, {user?.name?.split(" ")[0] || "Student"}

{user?.role === "STUDENT" ? "Student" : user?.role === "ADMIN" ? "Admin" : "Teacher"}

{/* Streak chip */}
{userMetrics.streak}
{/* Score chip */}
{/* ── Search ── */}
setIsSearchOpen(true)} />
{/* ── In progress ── */}

📌 Pick up where you left off

{inProgressSheets.length > 0 ? (
{inProgressSheets.map((sheet) => (
handleStart(sheet.id)} >

{sheet.title}

In Progress
))}
) : (
🎯 No sheets in progress — start one below!
)}
{/* ── All sheets with tabs ── */}

📋 Practice Sheets

{/* Tab buttons */}
{(["all", "NOT_STARTED", "COMPLETED"] as const).map((tab) => ( ))}
{allTabSheets.length > 0 ? ( <>
{tabSheets.map((sheet) => ( ))}
{hasMore ? ( ) : allTabSheets.length > PAGE_SIZE ? (

Showing all {allTabSheets.length} sheets

) : null} ) : (
🔍 Nothing here yet!
)}
{/* ── Tips ── */}

💡 SAT Prep Tips

{TIPS.map((tip, i) => (

{tip}

))}
{isSearchOpen && ( { setIsSearchOpen(false); setSearchQuery(""); }} searchQuery={searchQuery} setSearchQuery={setSearchQuery} /> )}
); };