Open History to see the last commit
1import {
2 ANOTHER_ADDRESS,
3 type LinkableMachine,
4 linkDestination,
5} from "@t3tools/client-runtime/link-request";
6import { useId } from "react";
7
8import { EnvironmentMachineIcon } from "../EnvironmentMachineIcon";
9import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
10import { Badge } from "../ui/badge";
11import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
12
13/**
14 * Shown wherever a link goes to an address this app does not already have for a
15 * machine, with where that address leads.
16 */
17export function LinkAddressWarning({ address }: { readonly address: string }) {
18 return (
19 <Alert variant="warning">
20 <AlertTitle>Check this address</AlertTitle>
21 <AlertDescription>
22 This app cannot confirm it is one of your machines. The pairing code you paste is sent
23 there.
24 <span className="block break-all">{linkDestination(address)}</span>
25 </AlertDescription>
26 </Alert>
27 );
28}
29
30/** Whether a machine is linked already, or how this app knows it when not connected to it. */
31export function LinkMachineBadge({ machine }: { readonly machine: LinkableMachine }) {
32 if (machine.linked) {
33 return (
34 <Badge variant="success" size="sm">
35 Linked
36 </Badge>
37 );
38 }
39 if (machine.connected) return null;
40 return (
41 <Badge variant="outline" size="sm">
42 {machine.connectOnly ? "T3 Connect" : "Not connected"}
43 </Badge>
44 );
45}
46
47function MachineName({ machine }: { readonly machine: LinkableMachine }) {
48 return (
49 <span className="flex min-w-0 items-center gap-2">
50 <EnvironmentMachineIcon kind={machine.machine} className="size-4 shrink-0 opacity-80" />
51 <span className="min-w-0 truncate">{machine.label}</span>
52 </span>
53 );
54}
55
56/** The user's other machines, including those on their T3 Connect account, then an address. */
57export function LinkMachineSelect({
58 machines,
59 value,
60 onChange,
61 disabled,
62}: {
63 readonly machines: ReadonlyArray<LinkableMachine>;
64 readonly value: string | null;
65 readonly onChange: (value: string) => void;
66 readonly disabled: boolean;
67}) {
68 const labelId = useId();
69 return (
70 <div className="space-y-1.5">
71 <span id={labelId} className="block text-xs font-medium text-foreground">
72 Machine
73 </span>
74 <Select
75 value={value}
76 disabled={disabled}
77 onValueChange={(next) => {
78 if (next !== null) onChange(next as string);
79 }}
80 >
81 <SelectTrigger aria-labelledby={labelId}>
82 <SelectValue>
83 {(selected: string | null) => {
84 if (selected === ANOTHER_ADDRESS) return "Another address…";
85 const machine = machines.find((candidate) => candidate.environmentId === selected);
86 return machine === undefined ? "Choose a machine" : <MachineName machine={machine} />;
87 }}
88 </SelectValue>
89 </SelectTrigger>
90 <SelectPopup alignItemWithTrigger={false}>
91 {machines.map((machine) => (
92 <SelectItem key={machine.environmentId} value={machine.environmentId} hideIndicator>
93 <span className="flex min-w-0 items-center justify-between gap-3">
94 <MachineName machine={machine} />
95 <LinkMachineBadge machine={machine} />
96 </span>
97 </SelectItem>
98 ))}
99 <SelectItem value={ANOTHER_ADDRESS} hideIndicator>
100 Another address…
101 </SelectItem>
102 </SelectPopup>
103 </Select>
104 </div>
105 );
106}