SPB Git
2.2 KB · 27 lines tsx
1"use client";2import { useState } from "react";3import { useQuery } from "@tanstack/react-query";4import { LayoutTemplate } from "lucide-react";5import { Button } from "@/components/ui/button";6import { Spinner } from "@/components/ui/primitives";7import { toast } from "@/components/ui/toast";8import { api } from "@/lib/client/api";9import { cn } from "@/lib/utils";1011export function TemplateChooser({ dbId, onDone, onSkip, embedded }: { dbId: string; onDone: () => void; onSkip?: () => void; embedded?: boolean }) {12  const { data } = useQuery<{ id: string; name: string; icon: string; description: string; properties: string[] }[]>({ queryKey: ["db-templates"], queryFn: () => api("/api/databases/templates") });13  const [busy, setBusy] = useState<string | null>(null);14  const apply = async (t: string) => { setBusy(t); try { await api(`/api/databases/${dbId}/apply-template`, { method: "POST", json: { template: t } }); toast("Template applied", { tone: "success" }); onDone(); } catch (e: any) { toast("Could not apply template", { description: e?.message, tone: "danger" }); } finally { setBusy(null); } };15  return (16    <div className={cn(!embedded && "rounded-xl border border-dashed border-line p-5")}>17      <div className="mb-3 flex items-center gap-2 text-sm font-medium"><LayoutTemplate className="size-4 text-fg-muted" /> Start from a template</div>18      {!data ? <Spinner /> : <div className="grid gap-2 sm:grid-cols-3">{data.map((t) => (19        <button key={t.id} type="button" disabled={!!busy} onClick={() => apply(t.id)} className="flex min-h-[7rem] flex-col rounded-lg border border-line bg-bg-elev p-3 text-left transition-colors hover:border-accent/50 hover:bg-bg-sunk disabled:opacity-60">20          <span className="text-xl">{t.icon}</span><span className="mt-1 text-[13px] font-medium">{t.name}</span><span className="mt-0.5 line-clamp-2 text-[12px] text-fg-muted">{t.description}</span><span className="mt-auto pt-2 text-[11px] text-fg-faint">{t.properties.join(" · ")}</span>21          {busy === t.id && <Spinner className="mt-1" />}22        </button>))}</div>}23      {onSkip && <div className="mt-3 flex items-center gap-2 text-[12px] text-fg-muted">or <Button size="xs" variant="outline" onClick={onSkip}>Start empty</Button></div>}24    </div>25  );26}27
Files · 2c37cb5a3e2af18e5ba361996d453cab97da82c2
Menu

Theme