|
| 1 | +import { motion } from "framer-motion"; |
| 2 | +import { Check, Pencil, Plus, Server, Trash2 } from "lucide-react"; |
| 3 | +import { useState } from "react"; |
| 4 | +import { SettingsForm } from "@/components/settings/SettingsForm"; |
| 5 | +import { Button } from "@/components/ui/button"; |
| 6 | +import { Muted } from "@/components/ui/typography"; |
| 7 | +import { useInstances } from "@/hooks/useInstances"; |
| 8 | +import type { Instance } from "@/lib/config"; |
| 9 | +import { COLOR } from "@/lib/constants"; |
| 10 | + |
| 11 | +type Mode = { kind: "list" } | { kind: "create" } | { kind: "edit"; id: string }; |
| 12 | + |
| 13 | +interface InstancesManagerProps { |
| 14 | + onActivated?: () => void; |
| 15 | +} |
| 16 | + |
| 17 | +export function InstancesManager({ onActivated }: InstancesManagerProps) { |
| 18 | + const { instances, activeId, activate, remove } = useInstances(); |
| 19 | + const [mode, setMode] = useState<Mode>({ kind: "list" }); |
| 20 | + |
| 21 | + if (mode.kind === "create") { |
| 22 | + return ( |
| 23 | + <SettingsForm |
| 24 | + instance={null} |
| 25 | + onSaved={() => { |
| 26 | + setMode({ kind: "list" }); |
| 27 | + onActivated?.(); |
| 28 | + }} |
| 29 | + onCancel={instances.length > 0 ? () => setMode({ kind: "list" }) : undefined} |
| 30 | + hideCancel={instances.length === 0} |
| 31 | + /> |
| 32 | + ); |
| 33 | + } |
| 34 | + |
| 35 | + if (mode.kind === "edit") { |
| 36 | + const target = instances.find((i) => i.id === mode.id); |
| 37 | + if (!target) return null; |
| 38 | + return ( |
| 39 | + <SettingsForm |
| 40 | + instance={target} |
| 41 | + onSaved={() => setMode({ kind: "list" })} |
| 42 | + onCancel={() => setMode({ kind: "list" })} |
| 43 | + /> |
| 44 | + ); |
| 45 | + } |
| 46 | + |
| 47 | + if (instances.length === 0) { |
| 48 | + return ( |
| 49 | + <SettingsForm |
| 50 | + instance={null} |
| 51 | + onSaved={() => onActivated?.()} |
| 52 | + hideCancel |
| 53 | + submitLabel="Save Connection" |
| 54 | + /> |
| 55 | + ); |
| 56 | + } |
| 57 | + |
| 58 | + return ( |
| 59 | + <div className="space-y-3"> |
| 60 | + <div className="space-y-2"> |
| 61 | + {instances.map((inst) => ( |
| 62 | + <InstanceRow |
| 63 | + key={inst.id} |
| 64 | + instance={inst} |
| 65 | + active={inst.id === activeId} |
| 66 | + onActivate={() => { |
| 67 | + activate(inst.id); |
| 68 | + onActivated?.(); |
| 69 | + }} |
| 70 | + onEdit={() => setMode({ kind: "edit", id: inst.id })} |
| 71 | + onDelete={() => remove(inst.id)} |
| 72 | + /> |
| 73 | + ))} |
| 74 | + </div> |
| 75 | + |
| 76 | + <Button |
| 77 | + type="button" |
| 78 | + variant="ghost" |
| 79 | + onClick={() => setMode({ kind: "create" })} |
| 80 | + className="w-full py-2.5 px-4 rounded-xl flex items-center justify-center gap-2" |
| 81 | + > |
| 82 | + <Plus className="w-4 h-4" strokeWidth={1.5} /> |
| 83 | + Add another instance |
| 84 | + </Button> |
| 85 | + </div> |
| 86 | + ); |
| 87 | +} |
| 88 | + |
| 89 | +interface InstanceRowProps { |
| 90 | + instance: Instance; |
| 91 | + active: boolean; |
| 92 | + onActivate: () => void; |
| 93 | + onEdit: () => void; |
| 94 | + onDelete: () => void; |
| 95 | +} |
| 96 | + |
| 97 | +function InstanceRow({ instance, active, onActivate, onEdit, onDelete }: InstanceRowProps) { |
| 98 | + const [confirmingDelete, setConfirmingDelete] = useState(false); |
| 99 | + |
| 100 | + return ( |
| 101 | + <motion.div |
| 102 | + layout |
| 103 | + className="rounded-xl p-3 flex items-center gap-3" |
| 104 | + style={{ |
| 105 | + background: active ? "var(--accent-dim)" : "var(--bg-2)", |
| 106 | + border: `1px solid ${active ? "var(--accent-border)" : "var(--border)"}`, |
| 107 | + }} |
| 108 | + > |
| 109 | + <button |
| 110 | + type="button" |
| 111 | + onClick={onActivate} |
| 112 | + className="flex-1 flex items-center gap-3 text-left" |
| 113 | + disabled={active} |
| 114 | + title={active ? "Active instance" : "Switch to this instance"} |
| 115 | + > |
| 116 | + <div |
| 117 | + className="w-9 h-9 rounded-lg flex items-center justify-center shrink-0" |
| 118 | + style={{ |
| 119 | + background: active ? "var(--accent)" : "var(--surface)", |
| 120 | + color: active ? "white" : "var(--text-3)", |
| 121 | + }} |
| 122 | + > |
| 123 | + {active ? ( |
| 124 | + <Check className="w-4 h-4" strokeWidth={2} /> |
| 125 | + ) : ( |
| 126 | + <Server className="w-4 h-4" strokeWidth={1.5} /> |
| 127 | + )} |
| 128 | + </div> |
| 129 | + <div className="min-w-0 flex-1"> |
| 130 | + <p |
| 131 | + className="text-sm font-medium truncate" |
| 132 | + style={{ color: active ? "var(--accent-text)" : "var(--text-1)" }} |
| 133 | + > |
| 134 | + {instance.name} |
| 135 | + </p> |
| 136 | + <Muted className="text-xs font-mono truncate"> |
| 137 | + {instance.baseUrl.replace(/^https?:\/\//, "")} |
| 138 | + </Muted> |
| 139 | + </div> |
| 140 | + </button> |
| 141 | + |
| 142 | + <div className="flex items-center gap-1 shrink-0"> |
| 143 | + <button |
| 144 | + type="button" |
| 145 | + onClick={onEdit} |
| 146 | + className="w-7 h-7 rounded-md flex items-center justify-center transition-colors" |
| 147 | + style={{ color: "var(--text-3)" }} |
| 148 | + title="Edit" |
| 149 | + > |
| 150 | + <Pencil className="w-3.5 h-3.5" strokeWidth={1.5} /> |
| 151 | + </button> |
| 152 | + {confirmingDelete ? ( |
| 153 | + <button |
| 154 | + type="button" |
| 155 | + onClick={() => { |
| 156 | + onDelete(); |
| 157 | + setConfirmingDelete(false); |
| 158 | + }} |
| 159 | + className="text-xs font-medium px-2 py-1 rounded-md" |
| 160 | + style={{ color: COLOR.destructive, border: `1px solid ${COLOR.destructive}` }} |
| 161 | + > |
| 162 | + Confirm |
| 163 | + </button> |
| 164 | + ) : ( |
| 165 | + <button |
| 166 | + type="button" |
| 167 | + onClick={() => setConfirmingDelete(true)} |
| 168 | + className="w-7 h-7 rounded-md flex items-center justify-center transition-colors" |
| 169 | + style={{ color: "var(--text-3)" }} |
| 170 | + title="Delete" |
| 171 | + > |
| 172 | + <Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} /> |
| 173 | + </button> |
| 174 | + )} |
| 175 | + </div> |
| 176 | + </motion.div> |
| 177 | + ); |
| 178 | +} |
0 commit comments