import { useState } from "react"; import { useTranslation } from "react-i18next"; import * as Popover from "@radix-ui/react-popover"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { Command } from "cmdk"; import { Dialogs } from "@wailsio/runtime"; import { ChevronDown, MoreVertical, PlusCircle, Search, Trash2, UserMinus } from "lucide-react"; import type { Profile } from "@bindings/services/models.js"; import { cn } from "@/lib/cn"; import { generateColorFromString } from "@/lib/color"; import { NewProfileModal } from "@/components/NewProfileModal"; import { useProfile } from "@/modules/profile/ProfileContext.tsx"; const DEFAULT_PROFILE = "default"; export const ProfileSelector = () => { const { t } = useTranslation(); const { profiles, activeProfile, loaded, switchProfile, addProfile, removeProfile, logoutProfile, } = useProfile(); const [open, setOpen] = useState(false); const [newOpen, setNewOpen] = useState(false); const [busy, setBusy] = useState(false); const selected = profiles.find((p) => p.name === activeProfile) ?? profiles.find((p) => p.isActive) ?? profiles[0]; const sorted = [...profiles].sort((a, b) => a.name.localeCompare(b.name)); const guarded = async (title: string, fn: () => Promise) => { if (busy) return; setBusy(true); try { await fn(); } catch (e) { await Dialogs.Error({ Title: title, Message: e instanceof Error ? e.message : String(e), }); } finally { setBusy(false); } }; const handleSelect = (name: string) => { setOpen(false); if (name === activeProfile) return; void guarded(t("profile.error.switchTitle"), () => switchProfile(name)); }; const handleDeregister = async (name: string) => { const cancelLabel = t("common.cancel"); const confirmLabel = t("profile.deregister.confirm"); const result = await Dialogs.Warning({ Title: t("profile.deregister.title"), Message: t("profile.deregister.message", { name }), Buttons: [ { Label: cancelLabel, IsCancel: true }, { Label: confirmLabel, IsDefault: true }, ], }); if (result !== confirmLabel) return; void guarded(t("profile.error.deregisterTitle"), () => logoutProfile(name)); }; const handleDelete = async (name: string) => { if (name === DEFAULT_PROFILE) return; const cancelLabel = t("common.cancel"); const confirmLabel = t("common.delete"); const result = await Dialogs.Warning({ Title: t("profile.delete.title"), Message: t("profile.delete.message", { name }), Buttons: [ { Label: cancelLabel, IsCancel: true }, { Label: confirmLabel, IsDefault: true }, ], }); if (result !== confirmLabel) return; void guarded(t("profile.error.deleteTitle"), () => removeProfile(name)); }; const handleNewProfile = () => { setOpen(false); setNewOpen(true); }; const handleCreateProfile = (name: string) => { void guarded(t("profile.error.createTitle"), () => addProfile(name)); }; const displayName = selected?.name ?? (loaded ? t("profile.selector.noProfile") : t("profile.selector.loading")); const initial = (selected?.name ?? "?").charAt(0).toUpperCase(); const initialColor = generateColorFromString(selected?.name); return ( <> e.preventDefault()} >

{t("profile.selector.emptyTitle")}

{t("profile.selector.emptyDescription")}

{sorted.map((profile) => ( handleSelect(profile.name)} onDeregister={() => handleDeregister(profile.name)} onDelete={() => handleDelete(profile.name)} deletable={profile.name !== DEFAULT_PROFILE} /> ))}
); }; type ProfileRowProps = { profile: Profile; selected: boolean; onSelect: () => void; onDeregister: () => void; onDelete: () => void; deletable: boolean; }; const ProfileRow = ({ profile, selected, onSelect, onDeregister, onDelete, deletable, }: ProfileRowProps) => { const { t } = useTranslation(); const [menuOpen, setMenuOpen] = useState(false); const initial = profile.name.charAt(0).toUpperCase(); const initialColor = generateColorFromString(profile.name); return ( onSelect()} className={cn( "group flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-md cursor-default outline-none", "data-[selected=true]:bg-nb-gray-910", selected && "bg-nb-gray-910", )} >
{initial}
{profile.name} e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()} className={cn( "w-44 rounded-md border border-nb-gray-850 bg-nb-gray-910 shadow-lg p-1 z-50", )} > { e.preventDefault(); onDeregister(); setMenuOpen(false); }} className={cn( "flex items-center gap-2 px-2 py-1.5 rounded-md cursor-default outline-none font-medium", "text-xs text-nb-gray-200 data-[highlighted]:bg-nb-gray-850", )} > {t("profile.selector.deregister")} { e.preventDefault(); if (!deletable) return; onDelete(); setMenuOpen(false); }} className={cn( "flex items-center gap-2 px-2 py-1.5 rounded-md cursor-default outline-none font-medium", "text-xs data-[highlighted]:bg-nb-gray-850", deletable ? "text-red-500" : "text-nb-gray-500 cursor-not-allowed", )} > {t("profile.selector.delete")}
); };