1import { DownloadIcon } from "lucide-react";
2import { useSyncExternalStore } from "react";
3import type { RelayClientInstallProgressStage } from "@t3tools/contracts";
4
5import {
6 completeRelayClientInstallDialogClose,
7 readRelayClientInstallDialogState,
8 respondToRelayClientInstallConfirmation,
9 subscribeRelayClientInstallDialog,
10} from "../../cloud/relayClientInstallDialog";
11import { Button } from "../ui/button";
12import {
13 Dialog,
14 DialogDescription,
15 DialogFooter,
16 DialogHeader,
17 DialogPanel,
18 DialogPopup,
19 DialogTitle,
20} from "../ui/dialog";
21const installSteps: ReadonlyArray<{
22 readonly stage: RelayClientInstallProgressStage;
23 readonly label: string;
24}> = [
25 { stage: "checking", label: "Checking current installation" },
26 { stage: "waiting_for_lock", label: "Waiting for installer" },
27 { stage: "downloading", label: "Downloading relay client" },
28 { stage: "verifying", label: "Verifying download" },
29 { stage: "installing", label: "Installing relay client" },
30 { stage: "validating", label: "Validating executable" },
31 { stage: "activating", label: "Activating installation" },
32];
33
34export function RelayClientInstallDialog() {
35 const state = useSyncExternalStore(
36 subscribeRelayClientInstallDialog,
37 readRelayClientInstallDialogState,
38 readRelayClientInstallDialogState,
39 );
40 const view = state.status === "closing" ? state.view : state;
41 const isConfirming = view.status === "confirming";
42 const isInstalling = view.status === "installing";
43 const activeStepIndex = isInstalling
44 ? installSteps.findIndex(({ stage }) => stage === view.stage)
45 : -1;
46 const activeStep = installSteps[activeStepIndex];
47
48 return (
49 <Dialog
50 open={state.status === "confirming" || state.status === "installing"}
51 onOpenChange={(open) => {
52 if (!open && isConfirming) {
53 respondToRelayClientInstallConfirmation(false);
54 }
55 }}
56 onOpenChangeComplete={(open) => {
57 if (!open) {
58 completeRelayClientInstallDialogClose();
59 }
60 }}
61 >
62 <DialogPopup className="max-w-md" showCloseButton={isConfirming}>
63 <DialogHeader>
64 <div className="flex size-9 items-center justify-center rounded-lg border border-border/70 bg-muted/60">
65 <DownloadIcon aria-hidden className="size-4.5 text-muted-foreground" />
66 </div>
67 <DialogTitle>
68 {isInstalling ? "Installing relay client" : "Install relay client?"}
69 </DialogTitle>
70 <DialogDescription>
71 {isInstalling
72 ? "The Knife is preparing this environment for secure access through T3 Connect."
73 : "The Knife needs the relay client to make this environment available through T3 Connect."}
74 </DialogDescription>
75 </DialogHeader>
76 <DialogPanel scrollFade={false}>
77 {isInstalling ? (
78 <div className="space-y-2.5">
79 <div className="flex items-center justify-between gap-3 text-sm">
80 <p aria-live="polite" className="font-medium text-foreground">
81 {activeStep?.label}
82 </p>
83 <p className="shrink-0 tabular-nums text-muted-foreground">
84 {activeStepIndex + 1} of {installSteps.length}
85 </p>
86 </div>
87 <progress
88 aria-label="Relay client installation progress"
89 className="h-2 w-full appearance-none overflow-hidden rounded-full bg-muted [&::-moz-progress-bar]:rounded-full [&::-moz-progress-bar]:bg-primary [&::-webkit-progress-bar]:rounded-full [&::-webkit-progress-bar]:bg-muted [&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:bg-primary"
90 max={installSteps.length}
91 value={activeStepIndex + 1}
92 />
93 <p className="text-xs leading-relaxed text-muted-foreground">
94 Keep The Knife open while the relay client is installed.
95 </p>
96 </div>
97 ) : (
98 <div className="rounded-xl border border-border/70 bg-muted/35 p-3">
99 <p className="text-sm font-medium text-foreground">Managed relay client</p>
100 <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
101 The Knife will download and install version{" "}
102 {view.status === "confirming" ? view.version : ""} locally.
103 </p>
104 </div>
105 )}
106 </DialogPanel>
107 {isConfirming ? (
108 <DialogFooter>
109 <Button
110 variant="outline"
111 onClick={() => respondToRelayClientInstallConfirmation(false)}
112 >
113 Cancel
114 </Button>
115 <Button onClick={() => respondToRelayClientInstallConfirmation(true)}>
116 Download and install
117 </Button>
118 </DialogFooter>
119 ) : null}
120 </DialogPopup>
121 </Dialog>
122 );
123}