feat(ui): add new ui

This commit is contained in:
shafin-r
2026-02-20 19:10:13 +06:00
parent 3c8f945539
commit 76d2108aec
16 changed files with 4263 additions and 1702 deletions

View File

@ -1,23 +1,8 @@
import { useEffect, useState } from "react";
import {
Tabs,
TabsTrigger,
TabsList,
TabsContent,
} from "../../components/ui/tabs";
import { useAuthStore } from "../../stores/authStore";
import { CheckCircle, Flame, Search, Zap } from "lucide-react";
import { CheckCircle, Flame, Gauge, Play, Search } from "lucide-react";
import { api } from "../../utils/api";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../components/ui/card";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import type { PracticeSheet } from "../../types/sheet";
import { formatStatus } from "../../lib/utils";
import { useNavigate } from "react-router-dom";
@ -29,7 +14,297 @@ import {
AvatarImage,
} from "../../components/ui/avatar";
import { useExamConfigStore } from "../../stores/useExamConfigStore";
import {
Drawer,
DrawerContent,
DrawerTrigger,
} from "../../components/ui/drawer";
// ─── 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: 'Satoshi', 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; }
@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 (
<div className="h-sheet-card">
<p className="h-sheet-title">{sheet.title}</p>
{sheet.description && <p className="h-sheet-desc">{sheet.description}</p>}
<div className="h-sheet-meta">
<span className={`h-status-pill ${statusClass}`}>
{formatStatus(sheet.user_status)}
</span>
<span className="h-modules-badge">
📚 {sheet.modules_count} modules
</span>
</div>
<p className="h-time-badge"> {sheet.time_limit} min</p>
<button className="h-start-btn" onClick={() => onStart(sheet.id)}>
{sheet.user_status === "COMPLETED" ? "Retry →" : "Start →"}
</button>
</div>
);
};
// ─── 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 ───────────────────────────────────────────────────────────
export const Home = () => {
const user = useAuthStore((state) => state.user);
const navigate = useNavigate();
@ -39,358 +314,244 @@ export const Home = () => {
const [notStartedSheets, setNotStartedSheets] = useState<PracticeSheet[]>([]);
const [inProgressSheets, setInProgressSheets] = useState<PracticeSheet[]>([]);
const [completedSheets, setCompletedSheets] = useState<PracticeSheet[]>([]);
const [activeTab, setActiveTab] = useState<
"all" | "NOT_STARTED" | "COMPLETED"
>("all");
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
useEffect(() => {
const sortPracticeSheets = (sheets: PracticeSheet[]) => {
const notStarted = sheets.filter(
(sheet) => sheet.user_status === "NOT_STARTED",
const sort = (sheets: PracticeSheet[]) => {
setNotStartedSheets(
sheets.filter((s) => s.user_status === "NOT_STARTED"),
);
const inProgress = sheets.filter(
(sheet) => sheet.user_status === "IN_PROGRESS",
setInProgressSheets(
sheets.filter((s) => s.user_status === "IN_PROGRESS"),
);
const completed = sheets.filter(
(sheet) => sheet.user_status === "COMPLETED",
);
setNotStartedSheets(notStarted);
setInProgressSheets(inProgress);
setCompletedSheets(completed);
setCompletedSheets(sheets.filter((s) => s.user_status === "COMPLETED"));
};
const fetchPracticeSheets = async () => {
const fetch = async () => {
if (!user) return;
try {
const authStorage = localStorage.getItem("auth-storage");
if (!authStorage) {
console.error("authStorage not found in local storage");
return;
}
if (!authStorage) return;
const {
state: { token },
} = JSON.parse(authStorage);
if (!token) {
console.error("Token not found in authStorage");
return;
}
if (!token) return;
const sheets = await api.getPracticeSheets(token, 1, 10);
setPracticeSheets(sheets.data);
sortPracticeSheets(sheets.data);
} catch (error) {
console.error("Error fetching practice sheets:", error);
sort(sheets.data);
} catch (e) {
console.error(e);
}
};
fetchPracticeSheets();
fetch();
}, [user]);
const handleStartPracticeSheet = (sheetId: string) => {
navigate(`/student/practice/${sheetId}`);
};
const handleStart = (id: string) => navigate(`/student/practice/${id}`);
const tabSheets =
activeTab === "all"
? practiceSheets
: activeTab === "NOT_STARTED"
? notStartedSheets
: completedSheets;
const greeting =
new Date().getHours() < 12
? "Good morning"
: new Date().getHours() < 17
? "Good afternoon"
: "Good evening";
return (
<main className="min-h-screen space-y-6 mx-auto px-8 sm:px-6 lg:px-90 py-12">
<header className="flex items-center gap-3 justify-between">
<div className="flex gap-3">
<Avatar className="w-12 h-12">
<AvatarImage src={user?.avatar_url} />
<AvatarFallback className="font-satoshi-bold bg-linear-to-br from-indigo-400 to-indigo-500 uppercase text-lg text-white">
{user?.name.slice(0, 1)}
</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<h1 className="text-xl font-satoshi-bold tracking-tight text-gray-800 text-center">
Welcome, {user?.name || "Student"}
</h1>
<h4 className="text-sm font-satoshi-bold text-indigo-500 ">
{user?.role === "STUDENT"
? "Student"
: user?.role === "ADMIN"
? "Admin"
: "Taecher"}
</h4>
</div>
</div>
<div className="flex gap-3">
<div className="rounded-full w-fit flex items-center gap-2">
<Flame size={20} className="text-red-500 fill-amber-200" />
<div className="home-screen">
<style>{STYLES}</style>
<span className="font-satoshi-bold text-md">5</span>
</div>
<div className="rounded-full w-fit flex items-center gap-2">
<Zap size={20} className="text-lime-500 fill-lime-200" />
{/* Blobs */}
<div className="h-blob h-blob-1" />
<div className="h-blob h-blob-2" />
<div className="h-blob h-blob-3" />
<div className="h-blob h-blob-4" />
<span className="font-satoshi-bold text-md">{userXp}</span>
</div>
</div>
</header>
<PredictedScoreCard />
<h1 className="font-satoshi-bold text-2xl tracking-tight">
What are you looking for?
</h1>
<section className="relative w-full">
<input
onFocus={() => setIsSearchOpen(true)}
placeholder="Search practice sheets..."
readOnly
className="font-satoshi w-full pl-10 pr-4 py-3 border border-gray-300 rounded-2xl shadow-sm cursor-pointer"
{/* Dots */}
{DOTS.map((d, i) => (
<div
key={i}
className="h-dot"
style={
{
width: d.size,
height: d.size,
background: d.color,
top: d.top,
left: d.left,
right: d.right,
animationDelay: d.delay,
animationDuration: `${3.5 + i * 0.4}s`,
} as React.CSSProperties
}
/>
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Search size={22} color="gray" />
</div>
</section>
))}
<section className="space-y-4">
<h1 className="font-satoshi-bold text-2xl tracking-tight">
Pick up where you left off
</h1>
{inProgressSheets.length > 0 ? (
inProgressSheets.map((sheet) => (
<Card
key={sheet?.id}
className="rounded-4xl border bg-indigo-50/70 border-indigo-500"
>
<CardHeader>
<CardTitle className="font-satoshi-medium text-xl">
{sheet?.title}
</CardTitle>
<CardDescription className="font-satoshi">
{sheet?.description}
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between">
<p className="font-satoshi text-sm border px-2 rounded-full bg-indigo-500 text-white py-1">
{formatStatus(sheet?.user_status)}
</p>
<Badge
variant="secondary"
className="bg-indigo-100 text-indigo-500 font-satoshi tracking-wide"
>
{sheet?.modules_count} modules
</Badge>
</CardContent>
<CardContent>
<p className="font-satoshi text-gray-700">
{sheet?.time_limit} minutes
</p>
</CardContent>
<CardFooter>
<Button
onClick={() => handleStartPracticeSheet(sheet?.id)}
variant="outline"
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
>
Resume
</Button>
</CardFooter>
</Card>
))
) : (
<Card className="flex items-center justify-center py-4 rounded-4xl">
<h2 className="text-center font-satoshi text-lg text-gray-800">
You don't have any practice sheets in progress. Why not start one?
</h2>
</Card>
)}
</section>
<section className="w-full">
<Tabs defaultValue="all" className="w-full">
<TabsList className="bg-transparent p-0 w-full">
<TabsTrigger
value="all"
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:font-satoshi-medium data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
>
All
</TabsTrigger>
<TabsTrigger
value="NOT_STARTED"
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
>
Not Started
</TabsTrigger>
<TabsTrigger
value="COMPLETED"
className="font-satoshi-regular tracking-wide text-md rounded-none border-b-3 data-[state=active]:border-b-indigo-800 data-[state=active]:text-indigo-800"
>
Completed
</TabsTrigger>
</TabsList>
<TabsContent value="all" className="pt-6">
<div className="gap-6 flex flex-col md:grid md:grid-cols-2">
{practiceSheets.length > 0 ? (
practiceSheets.map((sheet) => (
<Card key={sheet?.id} className="rounded-4xl">
<CardHeader>
<CardTitle className="font-satoshi-medium text-xl">
{sheet?.title}
</CardTitle>
<CardDescription className="font-satoshi">
{sheet?.description}
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between">
<p className="font-satoshi text-gray-500">
{formatStatus(sheet?.user_status)}
</p>
<Badge
variant="secondary"
className="bg-indigo-100 text-indigo-500 font-satoshi tracking-wide"
>
{sheet?.modules_count} modules
</Badge>
</CardContent>
<CardContent>
<p className="font-satoshi text-gray-700">
{sheet?.time_limit} minutes
</p>
</CardContent>
<CardFooter>
<Button
onClick={() => handleStartPracticeSheet(sheet?.id)}
variant="outline"
className="font-satoshi rounded-3xl w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 text-white"
>
Start
</Button>
</CardFooter>
</Card>
))
) : (
<div className="flex items-center justify-center py-4 rounded-full">
<h2 className="text-center font-satoshi text-lg text-gray-500">
No Practice Sheets available.
</h2>
</div>
)}
<div className="home-inner">
{/* ── Header ── */}
<header className="home-header">
<div className="home-header-left">
<Avatar style={{ width: 48, height: 48 }}>
<AvatarImage src={user?.avatar_url} />
<AvatarFallback
style={{
fontWeight: 900,
fontSize: "1.1rem",
color: "white",
textTransform: "uppercase",
background: "linear-gradient(135deg,#a855f7,#7c3aed)",
}}
>
{user?.name?.slice(0, 1)}
</AvatarFallback>
</Avatar>
<div className="space-y-1">
<p className="home-user-name">
{greeting}, {user?.name?.split(" ")[0] || "Student"}
</p>
<p className="home-user-role">
{user?.role === "STUDENT"
? "Student"
: user?.role === "ADMIN"
? "Admin"
: "Teacher"}
</p>
</div>
</TabsContent>
<TabsContent value="NOT_STARTED" className="pt-6">
<div className="gap-6 flex flex-col md:grid md:grid-cols-2">
{notStartedSheets.map((sheet) => (
<Card key={sheet?.id} className="rounded-4xl">
<CardHeader>
<CardTitle className="font-satoshi-medium text-xl">
{sheet?.title}
</CardTitle>
<CardDescription className="font-satoshi">
{sheet?.description}
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between">
<p className="font-satoshi text-gray-700">Not Started</p>
<Badge
variant="secondary"
className="bg-indigo-100 text-indigo-500 font-satoshi tracking-wide "
>
{sheet?.modules_count} modules
</Badge>
</CardContent>
<CardContent>
<p className="font-satoshi text-gray-700">
{sheet?.time_limit} minutes
</p>
</CardContent>
<CardFooter>
<Button
variant="outline"
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 rounded-3xl text-white"
>
Start
</Button>
</CardFooter>
</Card>
</div>
<div className="home-header-right">
{/* Streak chip */}
<div className="h-chip streak">
<Flame size={18} style={{ fill: "#fca5a5" }} />
<span>5</span>
</div>
{/* Score chip */}
<Drawer direction="top">
<DrawerTrigger asChild>
<div className="h-chip score">
<Gauge size={18} />
</div>
</DrawerTrigger>
<DrawerContent>
<PredictedScoreCard />
</DrawerContent>
</Drawer>
</div>
</header>
{/* ── Search ── */}
<div className="h-search-wrap h-anim h-anim-1">
<span className="h-search-icon">
<Search size={18} color="#9ca3af" />
</span>
<input
className="h-search-input"
placeholder="Search practice sheets..."
readOnly
onFocus={() => setIsSearchOpen(true)}
/>
</div>
{/* ── In progress ── */}
<section className="h-anim h-anim-2">
<p className="h-section-title">📌 Pick up where you left off</p>
{inProgressSheets.length > 0 ? (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "0.65rem",
}}
>
{inProgressSheets.map((sheet) => (
<div
key={sheet.id}
className="h-inprogress-card"
onClick={() => handleStart(sheet.id)}
>
<div className="h-inprogress-info">
<p className="h-inprogress-title">{sheet.title}</p>
<span className="h-inprogress-badge">In Progress</span>
</div>
<button
className="h-play-btn"
onClick={(e) => {
e.stopPropagation();
handleStart(sheet.id);
}}
>
<Play size={18} color="white" fill="white" />
</button>
</div>
))}
</div>
</TabsContent>
<TabsContent value="COMPLETED" className="pt-6">
<div className="gap-6 flex flex-col md:grid md:grid-cols-2">
{completedSheets.length > 0 ? (
completedSheets.map((sheet) => (
<Card key={sheet?.id} className="rounded-4xl">
<CardHeader>
<CardTitle className="font-satoshi-medium text-xl">
{sheet?.title}
</CardTitle>
<CardDescription className="font-satoshi">
{sheet?.description}
</CardDescription>
</CardHeader>
<CardContent className="flex justify-between">
<p className="font-satoshi text-gray-500">
{formatStatus(sheet?.user_status)}
</p>
<Badge
variant="secondary"
className="bg-indigo-100 text-indigo-500 font-satoshi tracking-wide"
>
{sheet?.modules_count} modules
</Badge>
</CardContent>
<CardContent>
<p className="font-satoshi text-gray-700">
{sheet?.time_limit} minutes
</p>
</CardContent>
<CardFooter>
<Button
variant="outline"
className="font-satoshi w-full text-lg py-6 bg-linear-to-br from-indigo-500 to-indigo-600 rounded-3xl text-white"
>
Start
</Button>
</CardFooter>
</Card>
))
) : (
<div className="flex items-center justify-center py-4 rounded-full">
<h2 className="text-center font-satoshi text-lg text-gray-500">
You have not completed any practice sheets.
</h2>
</div>
)}
) : (
<div className="h-empty">
<span className="h-empty-emoji">🎯</span>
No sheets in progress start one below!
</div>
</TabsContent>
</Tabs>
</section>
)}
</section>
<section className="space-y-4 ">
<h1 className="font-satoshi-bold text-2xl tracking-tight">
SAT Preparation Tips
</h1>
<section className="space-y-4 ">
<div className="flex gap-2">
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
<p className="font-satoshi text-md">
Practice regularly with official SAT materials
</p>
{/* ── All sheets with tabs ── */}
<section className="h-anim h-anim-3">
<p className="h-section-title">📋 Practice Sheets</p>
{/* Tab buttons */}
<div className="h-tabs-list">
{(["all", "NOT_STARTED", "COMPLETED"] as const).map((tab) => (
<button
key={tab}
className={`h-tab-btn${activeTab === tab ? " active" : ""}`}
onClick={() => setActiveTab(tab)}
>
{tab === "all"
? "All"
: tab === "NOT_STARTED"
? "Not Started"
: "Completed"}
</button>
))}
</div>
<div className="flex items-center gap-2">
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
<p className="font-satoshi text-md">
Review your mistakes and learn from them
</p>
</div>
<div className="flex items-center gap-2">
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
<p className="font-satoshi text-md">Focus on your weak areas</p>
</div>
<div className="flex items-center gap-2">
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
<p className="font-satoshi text-md">
Take full-length practice tests
</p>
</div>
<div className="flex items-center gap-2">
<CheckCircle size={24} color="oklch(58.5% 0.233 277.117)" />
<p className="font-satoshi text-md">
Get plenty of rest before the test day
</p>
{tabSheets.length > 0 ? (
<div className="h-sheet-grid">
{tabSheets.map((sheet) => (
<SheetCard key={sheet.id} sheet={sheet} onStart={handleStart} />
))}
</div>
) : (
<div className="h-empty">
<span className="h-empty-emoji">🔍</span>
Nothing here yet!
</div>
)}
</section>
{/* ── Tips ── */}
<section className="h-anim h-anim-4">
<p className="h-section-title">💡 SAT Prep Tips</p>
<div className="h-tips-list">
{TIPS.map((tip, i) => (
<div key={i} className="h-tip-row">
<CheckCircle size={18} color="#a855f7" className="h-tip-icon" />
<p className="h-tip-text">{tip}</p>
</div>
))}
</div>
</section>
</section>
</div>
{isSearchOpen && (
<SearchOverlay
sheets={practiceSheets}
@ -402,6 +563,6 @@ export const Home = () => {
setSearchQuery={setSearchQuery}
/>
)}
</main>
</div>
);
};