Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
6.7 kB ยท 206 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207"use client";
import { Check, Copy, type IconType, More, Delete } from "@openstatus/icons";import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger,} from "@openstatus/ui/components/ui/alert-dialog";import { Button } from "@openstatus/ui/components/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,} from "@openstatus/ui/components/ui/dropdown-menu";import { Input } from "@openstatus/ui/components/ui/input";import { useCopyToClipboard } from "@openstatus/ui/hooks/use-copy-to-clipboard";import type { DropdownMenuContentProps } from "@radix-ui/react-dropdown-menu";import { isTRPCClientError } from "@trpc/client";import type * as React from "react";import { useState, useTransition } from "react";import { toast } from "sonner";
interface QuickActionsProps extends React.ComponentProps<typeof Button> { align?: DropdownMenuContentProps["align"]; side?: DropdownMenuContentProps["side"]; actions?: { id: string; label: string; icon: IconType; variant: "default" | "destructive"; onClick?: () => Promise<void> | void; }[]; deleteAction?: { /** * The value that must be typed to confirm deletion. Also used in the dialog title. * If omitted, no type-to-confirm step is shown. */ confirmationValue?: string; submitAction?: () => Promise<void>; };}
export function QuickActions({ align = "end", side, className, actions, deleteAction, children, ...props}: QuickActionsProps) { const [value, setValue] = useState(""); const [isPending, startTransition] = useTransition(); const [open, setOpen] = useState(false); const { copy, isCopied } = useCopyToClipboard();
const handleDelete = async () => { startTransition(async () => { if (!deleteAction?.submitAction) return; const promise = deleteAction.submitAction(); toast.promise(promise, { loading: "Deleting...", success: "Deleted", error: (error) => { if (isTRPCClientError(error)) { return error.message; } return "Failed to delete"; }, }); try { await promise; } catch (error) { console.error("Failed to delete:", error); } finally { setOpen(false); } }); };
return ( <AlertDialog open={open} onOpenChange={setOpen}> <DropdownMenu> <DropdownMenuTrigger asChild> {children ?? ( <Button variant="ghost" size="icon" className={className ?? "data-[state=open]:bg-accent h-7 w-7"} {...props} > <More /> </Button> )} </DropdownMenuTrigger> <DropdownMenuContent align={align} side={side} className="w-36"> <DropdownMenuLabel className="sr-only"> Quick Actions </DropdownMenuLabel> {actions ?.filter((item) => item.id !== "delete") .map((item) => ( <DropdownMenuGroup key={item.id}> <DropdownMenuItem variant={item.variant} disabled={!item.onClick} onClick={(e) => { e.stopPropagation(); item.onClick?.(); }} > <item.icon className="text-muted-foreground" /> <span className="truncate">{item.label}</span> </DropdownMenuItem> </DropdownMenuGroup> ))} {deleteAction && ( <> {/* NOTE: add a separator only if actions exist */} {actions?.length ? <DropdownMenuSeparator /> : null} <AlertDialogTrigger asChild> <DropdownMenuItem variant="destructive"> <Delete className="text-muted-foreground" /> Delete </DropdownMenuItem> </AlertDialogTrigger> </> )} </DropdownMenuContent> </DropdownMenu> <AlertDialogContent onCloseAutoFocus={(event) => { // NOTE: bug where the body is not clickable after closing the alert dialog event.preventDefault(); document.body.style.pointerEvents = ""; }} > <AlertDialogHeader> <AlertDialogTitle> {deleteAction?.confirmationValue ? `Are you sure about deleting \`${deleteAction.confirmationValue}\`?` : "Are you sure?"} </AlertDialogTitle> <AlertDialogDescription> This action cannot be undone. This will permanently remove the entry from the database. </AlertDialogDescription> </AlertDialogHeader> {deleteAction?.confirmationValue ? ( <form id="form-alert-dialog" className="space-y-1.5"> <p className="text-muted-foreground text-sm"> Type{" "} <Button variant="secondary" size="sm" type="button" className="font-normal [&_svg]:size-3" onClick={() => copy(deleteAction.confirmationValue || "", { withToast: false, }) } > {deleteAction.confirmationValue} {isCopied ? <Check /> : <Copy />} </Button>{" "} to confirm </p> <Input value={value} onChange={(e) => setValue(e.target.value)} /> </form> ) : null} <AlertDialogFooter> <AlertDialogCancel onClick={(e) => e.stopPropagation()}> Cancel </AlertDialogCancel> <AlertDialogAction className="bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 text-white shadow-xs" disabled={ (deleteAction?.confirmationValue && value !== deleteAction?.confirmationValue) || isPending } form="form-alert-dialog" type="submit" onClick={(e) => { e.preventDefault(); handleDelete(); }} > {isPending ? "Deleting..." : "Delete"} </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> );}