feat: quiz

This commit is contained in:
gosangam
2026-10-15 20:07:01 +05:30
parent 9f67da37a6
commit 6a6143ceb2
12 changed files with 982 additions and 16 deletions
+106
View File
@@ -0,0 +1,106 @@
"use client";
/**
* QuizzesCTA — the way into /quizzes, in two shapes:
*
* - "tile": a card that leads the search page's "Start here" row.
* - "banner": a full-width row for the homepage, under sādhana / vāṇī.
*
* Renders nothing until there is at least one published quiz; with
* `pendingOnly` it also hides once every quiz has been finished, so the
* homepage only nudges when there's something left to do.
*/
import { useEffect, useState } from "react";
import Link from "next/link";
import { ArrowUpRight, ChevronRight, ClipboardCheck } from "lucide-react";
import { useAuth } from "@/components/auth/auth-provider";
import { quiz, type QuizCard } from "@/lib/quiz";
import { cn } from "@/lib/utils";
export function QuizzesCTA({
variant = "banner",
pendingOnly = false,
className,
}: {
variant?: "banner" | "tile";
pendingOnly?: boolean;
className?: string;
}) {
const { user } = useAuth();
const [cards, setCards] = useState<QuizCard[] | null>(null);
useEffect(() => {
let cancelled = false;
quiz
.list()
.then((c) => {
if (!cancelled) setCards(c);
})
.catch(() => {
/* CTA simply doesn't render */
});
return () => {
cancelled = true;
};
// Refetch on sign-in/out: progress is per user.
}, [user?.id]);
if (!cards?.length) return null;
if (pendingOnly && cards.every((c) => c.my.state === "completed")) {
return null;
}
const tagline = "Test your understanding of chapters, lectures and more";
if (variant === "tile") {
// Matches the search page's FeaturedTile so it sits in that grid as an
// equal: same padding, icon chip, title scale and corner arrow.
return (
<Link
href="/quizzes"
className={cn(
"group relative flex flex-col h-full rounded-2xl bg-card ring-1 ring-foreground/10 hover:ring-foreground/25 hover:shadow-md transition-all p-6",
className,
)}
>
<div className="flex items-start justify-between">
<div className="flex items-center justify-center size-11 rounded-xl bg-primary/12 text-primary">
<ClipboardCheck className="size-5" />
</div>
<ArrowUpRight className="size-4 text-muted-foreground/60 group-hover:text-foreground transition-colors" />
</div>
<h3 className="mt-5 font-[family-name:var(--font-title)] font-semibold text-2xl sm:text-[1.6rem] lg:text-[1.35rem] leading-tight text-foreground">
Quizzes
</h3>
<p className="mt-2 text-sm text-muted-foreground leading-relaxed flex-1">
{tagline}
</p>
</Link>
);
}
return (
<Link
href="/quizzes"
className={cn(
"group flex items-center gap-3.5 rounded-2xl border border-border bg-card p-4 text-left transition-colors hover:border-primary/40 hover:bg-muted/40",
className,
)}
>
<span className="inline-flex size-12 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
<ClipboardCheck className="size-6" />
</span>
<div className="min-w-0 flex-1">
<p className="text-base font-semibold leading-snug text-foreground">
Quizzes
</p>
<p className="truncate text-sm leading-snug text-muted-foreground">
{tagline}
</p>
</div>
<ChevronRight className="size-5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5 group-hover:text-foreground" />
</Link>
);
}