TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import { useRouter } from "next/navigation";4import { Button, type ButtonProps } from "@/components/ui/button";5import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";6import type { AdminActionResult } from "@/actions/admin";78/**9 * Runs a server action inside a transition and refreshes the router. When `confirm` is given, a10 * dialog gates the (usually destructive) action.11 */12export function ActionButton({13 action,14 children,15 confirm,16 onDone,17 ...btn18}: {19 action: () => Promise<AdminActionResult<unknown>>;20 children: React.ReactNode;21 confirm?: { title: string; description?: React.ReactNode; confirmLabel?: string; variant?: ButtonProps["variant"] };22 onDone?: (r: AdminActionResult<unknown>) => void;23} & Omit<ButtonProps, "onClick" | "children">) {24 const router = useRouter();25 const [pending, start] = React.useTransition();26 const [open, setOpen] = React.useState(false);27 const [msg, setMsg] = React.useState<{ kind: "error" | "warning"; text: string } | null>(null);2829 const run = () =>30 start(async () => {31 setMsg(null);32 const r = await action();33 if (!r.ok) setMsg({ kind: "error", text: r.error });34 else if (r.warning) setMsg({ kind: "warning", text: r.warning });35 onDone?.(r);36 if (r.ok) {37 setOpen(false);38 router.refresh();39 }40 });4142 return (43 <>44 <span className="inline-flex flex-col items-start gap-1">45 <Button type="button" {...btn} loading={pending} onClick={() => (confirm ? setOpen(true) : run())}>46 {children}47 </Button>48 {msg && !confirm ? (49 <span role="alert" className={msg.kind === "error" ? "text-[12px] text-danger" : "text-[12px] text-warning"}>50 {msg.text}51 </span>52 ) : null}53 </span>54 {confirm ? (55 <Dialog open={open} onOpenChange={setOpen}>56 <DialogContent size="sm">57 <DialogHeader>58 <DialogTitle>{confirm.title}</DialogTitle>59 {confirm.description ? <DialogDescription>{confirm.description}</DialogDescription> : null}60 </DialogHeader>61 {msg ? (62 <p role="alert" className={msg.kind === "error" ? "text-[13px] text-danger" : "text-[13px] text-warning"}>63 {msg.text}64 </p>65 ) : null}66 <DialogFooter>67 <Button type="button" variant="outline" onClick={() => setOpen(false)}>68 Cancel69 </Button>70 <Button type="button" variant={confirm.variant ?? "danger"} loading={pending} onClick={run}>71 {confirm.confirmLabel ?? "Confirm"}72 </Button>73 </DialogFooter>74 </DialogContent>75 </Dialog>76 ) : null}77 </>78 );79}8081export function ResultMessage({ result }: { result: AdminActionResult<unknown> | null }) {82 if (!result) return null;83 if (!result.ok)84 return (85 <p role="alert" className="text-[12.5px] text-danger">86 {result.error}87 </p>88 );89 if (result.warning)90 return (91 <p role="status" className="text-[12.5px] text-warning">92 {result.warning}93 </p>94 );95 return (96 <p role="status" className="text-[12.5px] text-success">97 Saved.98 </p>99 );100}101