75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* The quiz button on a content page.
|
|
*
|
|
* Asks the BFF whether this page has a published quiz and renders nothing if
|
|
* not, so it can sit on every content page without leaving an empty slot.
|
|
* Its label tracks progress: Take quiz → Continue quiz → View results.
|
|
*/
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { ClipboardCheck } from "lucide-react";
|
|
import { useAuth } from "@/components/auth/auth-provider";
|
|
import { quiz, quizActionLabel, type QuizCard } from "@/lib/quiz";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function QuizButton({
|
|
path,
|
|
className,
|
|
}: {
|
|
/** The content page's own path, e.g. /books/bg/2. */
|
|
path: string;
|
|
className?: string;
|
|
}) {
|
|
const { user } = useAuth();
|
|
const [card, setCard] = useState<QuizCard | null>(null);
|
|
|
|
// Re-asked when the user changes, so progress appears right after sign-in.
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
quiz
|
|
.forPath(path)
|
|
.then((c) => {
|
|
if (!cancelled) setCard(c);
|
|
})
|
|
.catch(() => {
|
|
/* no quiz button is the right failure */
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [path, user?.id]);
|
|
|
|
if (!card) return null;
|
|
|
|
const done = card.my.state === "completed";
|
|
return (
|
|
<Link
|
|
href={`/quizzes/${card.id}`}
|
|
title={card.title}
|
|
className={cn(
|
|
"inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium transition-colors",
|
|
done
|
|
? "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
: "border-primary/40 bg-primary/10 text-primary hover:bg-primary/15",
|
|
className,
|
|
)}
|
|
>
|
|
<ClipboardCheck className="size-3.5" />
|
|
{quizActionLabel(card.my.state)}
|
|
{card.my.state === "in_progress" && (
|
|
<span className="tabular-nums opacity-70">
|
|
{card.my.answered}/{card.my.total}
|
|
</span>
|
|
)}
|
|
{done && card.my.score !== undefined && (
|
|
<span className="tabular-nums opacity-70">
|
|
{card.my.score}/{card.my.total}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
);
|
|
}
|