feat: add four new student modules (rotation, skill-video, exam-prep, academic) with backend APIs and frontend pages; add exam-prep question history with DB persistence
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, ArrowUpRight } from "lucide-react";
|
||||
|
||||
import type { NavGroup, NavItem } from "@/lib/navigation";
|
||||
|
||||
const GROUP_THEME: Record<string, { gradient: string; accent: string; label: string }> = {
|
||||
core: { gradient: "from-primary/12 to-primary/4", accent: "bg-primary/10 text-primary group-hover:bg-primary group-hover:text-primary-foreground", label: "data" },
|
||||
practice: { gradient: "from-success/12 to-success/4", accent: "bg-success/10 text-success group-hover:bg-success group-hover:text-success-foreground", label: "practice" },
|
||||
advance: { gradient: "from-chart-4/12 to-chart-4/4", accent: "bg-chart-4/10 text-chart-4 group-hover:bg-chart-4 group-hover:text-white", label: "advance" },
|
||||
insight: { gradient: "from-chart-3/12 to-chart-3/4", accent: "bg-chart-3/10 text-chart-3 group-hover:bg-chart-3 group-hover:text-white", label: "insight" },
|
||||
};
|
||||
|
||||
function FeatureCard({ item, groupKey }: { item: NavItem; groupKey: string }) {
|
||||
const Icon = item.icon;
|
||||
const theme = GROUP_THEME[groupKey] ?? GROUP_THEME.core;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="group relative flex h-full flex-col gap-3 overflow-hidden rounded-2xl bg-card p-5 shadow-sm ring-1 ring-foreground/[0.06] transition-all duration-300 hover:-translate-y-1 hover:shadow-xl hover:ring-primary/30"
|
||||
>
|
||||
<div className={`pointer-events-none absolute inset-0 bg-gradient-to-br ${theme.gradient} opacity-0 transition-opacity duration-300 group-hover:opacity-100`} />
|
||||
|
||||
<div className="relative flex items-center justify-between">
|
||||
<span className={`flex size-11 items-center justify-center rounded-xl transition-colors duration-300 ${theme.accent}`}>
|
||||
<Icon className="size-5" />
|
||||
</span>
|
||||
<ArrowUpRight className="size-4 text-muted-foreground/40 transition-all duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 group-hover:text-primary" />
|
||||
</div>
|
||||
<div className="relative">
|
||||
<h3 className="text-base font-semibold text-foreground">{item.label}</h3>
|
||||
<p className="mt-1.5 text-sm leading-relaxed text-muted-foreground">
|
||||
{item.desc}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function FeatureGrid({ items }: { items: NavItem[] }) {
|
||||
const features = items.filter((i) => i.desc);
|
||||
return (
|
||||
<div className="stagger grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((f) => (
|
||||
<FeatureCard key={f.href} item={f} groupKey={f.group ?? "core"} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupedFeatureGrid({ groups }: { groups: NavGroup[] }) {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{groups.map((g) => (
|
||||
<section key={g.key} className="animate-fade-in-up">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<div className="h-5 w-1 rounded-full bg-primary" />
|
||||
<h2 className="text-sm font-bold tracking-wide text-foreground">{g.label}</h2>
|
||||
<span className="text-xs text-muted-foreground">({g.items.length})</span>
|
||||
<ArrowRight className="ml-auto size-3.5 text-muted-foreground/50" />
|
||||
</div>
|
||||
<div className="stagger grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{g.items.map((item) => (
|
||||
<FeatureCard key={item.href} item={item} groupKey={g.key} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user