1import { useEffect, useSyncExternalStore } from "react";
2
3import {
4 completeConfirmDialogClose,
5 readConfirmDialogState,
6 registerConfirmDialogHost,
7 respondToConfirmDialog,
8 subscribeConfirmDialog,
9} from "../confirmDialog";
10import {
11 AlertDialog,
12 AlertDialogClose,
13 AlertDialogDescription,
14 AlertDialogFooter,
15 AlertDialogHeader,
16 AlertDialogPopup,
17 AlertDialogTitle,
18} from "./ui/alert-dialog";
19import { Button } from "./ui/button";
20
21type ConfirmationCopy = {
22 readonly title: string;
23 readonly description: string | null;
24};
25
26function resolveConfirmDialogCopy(message: string): ConfirmationCopy {
27 const normalizedMessage = message.trim();
28 const lines = normalizedMessage.split("\n");
29 const questionLineIndex = lines.findIndex((line) => line.trim().endsWith("?"));
30
31 if (questionLineIndex >= 0) {
32 const title = lines[questionLineIndex]!.trim();
33 const description = lines
34 .filter((_, index) => index !== questionLineIndex)
35 .join("\n")
36 .trim();
37 return { title, description: description || null };
38 }
39
40 const questionMarkIndex = normalizedMessage.indexOf("?");
41 if (questionMarkIndex >= 0) {
42 return {
43 title: normalizedMessage.slice(0, questionMarkIndex + 1).trim(),
44 description: normalizedMessage.slice(questionMarkIndex + 1).trim() || null,
45 };
46 }
47
48 return {
49 title: "Confirm action",
50 description: normalizedMessage || "This action requires your confirmation.",
51 };
52}
53
54export function ConfirmDialogHost() {
55 const state = useSyncExternalStore(
56 subscribeConfirmDialog,
57 readConfirmDialogState,
58 readConfirmDialogState,
59 );
60
61 useEffect(() => registerConfirmDialogHost(), []);
62
63 const copy = resolveConfirmDialogCopy(state.status === "idle" ? "" : state.message);
64 const confirmVariant = state.status === "idle" ? "default" : state.variant;
65 const onCancel = () => respondToConfirmDialog(false);
66 const onConfirm = () => respondToConfirmDialog(true);
67
68 return (
69 <AlertDialog
70 open={state.status === "confirming"}
71 onOpenChange={(open) => {
72 if (!open) onCancel();
73 }}
74 onOpenChangeComplete={(open) => {
75 if (!open) completeConfirmDialogClose();
76 }}
77 >
78 <AlertDialogPopup>
79 <AlertDialogHeader>
80 <AlertDialogTitle>{copy.title}</AlertDialogTitle>
81 {copy.description ? (
82 <AlertDialogDescription className="whitespace-pre-line">
83 {copy.description}
84 </AlertDialogDescription>
85 ) : null}
86 </AlertDialogHeader>
87 <AlertDialogFooter>
88 <AlertDialogClose render={<Button variant="outline" />}>Cancel</AlertDialogClose>
89 <Button variant={confirmVariant} onClick={onConfirm}>
90 Confirm
91 </Button>
92 </AlertDialogFooter>
93 </AlertDialogPopup>
94 </AlertDialog>
95 );
96}