Openstatus www.openstatus.dev
at main 151 lines 4.5 kB view raw
1"use client"; 2 3import type { Row } from "@tanstack/react-table"; 4 5import { QuickActions } from "@/components/dropdowns/quick-actions"; 6import { 7 AlertDialog, 8 AlertDialogAction, 9 AlertDialogCancel, 10 AlertDialogContent, 11 AlertDialogDescription, 12 AlertDialogFooter, 13 AlertDialogHeader, 14 AlertDialogTitle, 15} from "@/components/ui/alert-dialog"; 16import { getActions } from "@/data/incidents.client"; 17import { useTRPC } from "@/lib/trpc/client"; 18import type { RouterOutputs } from "@openstatus/api"; 19import { useMutation, useQueryClient } from "@tanstack/react-query"; 20import { isTRPCClientError } from "@trpc/client"; 21import { useMemo, useState, useTransition } from "react"; 22import { toast } from "sonner"; 23 24type Incident = RouterOutputs["incident"]["list"][number]; 25 26interface DataTableRowActionsProps { 27 row: Row<Incident>; 28} 29 30export function DataTableRowActions({ row }: DataTableRowActionsProps) { 31 const [isPending, startTransition] = useTransition(); 32 const trpc = useTRPC(); 33 const queryClient = useQueryClient(); 34 const acknowledgeIncidentMutation = useMutation( 35 trpc.incident.acknowledge.mutationOptions({ 36 onSuccess: () => { 37 queryClient.refetchQueries({ 38 queryKey: trpc.incident.list.queryKey({ 39 monitorId: row.original.monitorId, 40 }), 41 }); 42 }, 43 }), 44 ); 45 const resolveIncidentMutation = useMutation( 46 trpc.incident.resolve.mutationOptions({ 47 onSuccess: () => { 48 queryClient.refetchQueries({ 49 queryKey: trpc.incident.list.queryKey({ 50 monitorId: row.original.monitorId, 51 }), 52 }); 53 }, 54 }), 55 ); 56 const deleteIncidentMutation = useMutation( 57 trpc.incident.delete.mutationOptions({ 58 onSuccess: () => { 59 queryClient.refetchQueries({ 60 queryKey: trpc.incident.list.queryKey({ 61 monitorId: row.original.monitorId, 62 }), 63 }); 64 }, 65 }), 66 ); 67 68 const [type, setType] = useState<"acknowledge" | "resolve" | null>(null); 69 const open = useMemo(() => type !== null, [type]); 70 71 const actions = getActions({ 72 acknowledge: row.original.acknowledgedAt 73 ? undefined 74 : () => setType("acknowledge"), 75 resolve: row.original.resolvedAt ? undefined : () => setType("resolve"), 76 }); 77 78 const handleConfirm = async () => { 79 try { 80 startTransition(async () => { 81 const promise = 82 type === "acknowledge" 83 ? acknowledgeIncidentMutation.mutateAsync({ 84 id: row.original.id, 85 }) 86 : resolveIncidentMutation.mutateAsync({ 87 id: row.original.id, 88 }); 89 toast.promise(promise, { 90 loading: "Confirming...", 91 success: "Confirmed", 92 error: (error) => { 93 if (isTRPCClientError(error)) { 94 return error.message; 95 } 96 return "Failed to confirm"; 97 }, 98 }); 99 await promise; 100 setType(null); 101 }); 102 } catch (error) { 103 console.error("Failed to confirm:", error); 104 } 105 }; 106 107 return ( 108 <> 109 <QuickActions 110 actions={actions} 111 deleteAction={{ 112 confirmationValue: row.original.title || "incident", 113 submitAction: async () => { 114 await deleteIncidentMutation.mutateAsync({ 115 id: row.original.id, 116 }); 117 }, 118 }} 119 /> 120 <AlertDialog open={open} onOpenChange={() => setType(null)}> 121 <AlertDialogContent 122 onCloseAutoFocus={(event) => { 123 // NOTE: bug where the body is not clickable after closing the alert dialog 124 event.preventDefault(); 125 document.body.style.pointerEvents = ""; 126 }} 127 > 128 <AlertDialogHeader> 129 <AlertDialogTitle>Confirm your action</AlertDialogTitle> 130 <AlertDialogDescription> 131 You are about to <span className="font-semibold">{type}</span>{" "} 132 this incident. 133 </AlertDialogDescription> 134 </AlertDialogHeader> 135 <AlertDialogFooter> 136 <AlertDialogCancel>Cancel</AlertDialogCancel> 137 <AlertDialogAction 138 onClick={(e) => { 139 e.preventDefault(); 140 handleConfirm(); 141 }} 142 disabled={isPending} 143 > 144 {isPending ? "Confirming..." : "Confirm"} 145 </AlertDialogAction> 146 </AlertDialogFooter> 147 </AlertDialogContent> 148 </AlertDialog> 149 </> 150 ); 151}