feat: quiz

This commit is contained in:
gosangam
2026-10-15 19:40:28 +05:30
parent 3f3fa9a720
commit 111485a43e
20 changed files with 2178 additions and 224 deletions
+84 -77
View File
@@ -14,6 +14,7 @@
*/
import { useEffect, useState } from "react";
import { Can } from "@/components/staff-context";
import { toast } from "sonner";
import {
vani,
@@ -142,80 +143,84 @@ export default function VaniPage() {
</p>
</header>
<section className="space-y-3 rounded-xl border border-border p-4">
<div className="flex flex-wrap items-center gap-2">
<input
value={link}
onChange={(e) => setLink(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && void pinByLink()}
placeholder="Paste a link — /transcriptions/720720sb-london"
className={`${field} min-w-0 flex-1`}
/>
<button
type="button"
disabled={busy || !link.trim()}
onClick={pinByLink}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
Use this link
</button>
</div>
<p className="text-xs text-muted-foreground">
Or search by words. A slug only matches its own words, so paste the
link when you know exactly which item you want.
</p>
<div className="flex flex-wrap items-center gap-3 border-t border-border pt-3">
<label className="text-sm text-muted-foreground">
Date
<Can perm="vani:write">
<section className="space-y-3 rounded-xl border border-border p-4">
<div className="flex flex-wrap items-center gap-2">
<input
type="date"
value={day}
min={todayUTC()}
onChange={(e) => setDay(e.target.value)}
className={`${field} ml-2`}
value={link}
onChange={(e) => setLink(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && void pinByLink()}
placeholder="Paste a link — /transcriptions/720720sb-london"
className={`${field} min-w-0 flex-1`}
/>
</label>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search verses, chapters and lectures"
className={`${field} min-w-0 flex-1`}
/>
</div>
{!searchable ? (
<button
type="button"
disabled={busy || !link.trim()}
onClick={pinByLink}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
Use this link
</button>
</div>
<p className="text-xs text-muted-foreground">
Type at least two characters to search.
Or search by words. A slug only matches its own words, so paste the
link when you know exactly which item you want.
</p>
) : visible.length === 0 ? (
<p className="text-xs text-muted-foreground">Nothing found.</p>
) : (
<ul className="divide-y divide-border rounded-lg border border-border">
{visible.map((c) => (
<li
key={c.doc_id}
className="flex items-center gap-3 px-3 py-2.5"
>
<div className="min-w-0 flex-1">
<p className="truncate text-sm text-foreground">{c.title}</p>
<p className="truncate text-xs text-muted-foreground">
{vaniKindFor(c.collection)} · {c.snippet}
</p>
</div>
<button
type="button"
disabled={busy}
onClick={() => pin(c)}
className="shrink-0 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
<div className="flex flex-wrap items-center gap-3 border-t border-border pt-3">
<label className="text-sm text-muted-foreground">
Date
<input
type="date"
value={day}
min={todayUTC()}
onChange={(e) => setDay(e.target.value)}
className={`${field} ml-2`}
/>
</label>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search verses, chapters and lectures"
className={`${field} min-w-0 flex-1`}
/>
</div>
{!searchable ? (
<p className="text-xs text-muted-foreground">
Type at least two characters to search.
</p>
) : visible.length === 0 ? (
<p className="text-xs text-muted-foreground">Nothing found.</p>
) : (
<ul className="divide-y divide-border rounded-lg border border-border">
{visible.map((c) => (
<li
key={c.doc_id}
className="flex items-center gap-3 px-3 py-2.5"
>
Set for {day}
</button>
</li>
))}
</ul>
)}
</section>
<div className="min-w-0 flex-1">
<p className="truncate text-sm text-foreground">
{c.title}
</p>
<p className="truncate text-xs text-muted-foreground">
{vaniKindFor(c.collection)} · {c.snippet}
</p>
</div>
<button
type="button"
disabled={busy}
onClick={() => pin(c)}
className="shrink-0 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
Set for {day}
</button>
</li>
))}
</ul>
)}
</section>
</Can>
<section className="space-y-2">
<h2 className="text-sm font-medium text-foreground">
@@ -243,14 +248,16 @@ export default function VaniPage() {
{p.payload.book_title ? ` · ${p.payload.book_title}` : ""}
</p>
</div>
<button
type="button"
disabled={busy}
onClick={() => clear(p)}
className="shrink-0 rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted disabled:opacity-50"
>
Clear
</button>
<Can perm="vani:write">
<button
type="button"
disabled={busy}
onClick={() => clear(p)}
className="shrink-0 rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted disabled:opacity-50"
>
Clear
</button>
</Can>
</li>
))}
</ul>