"use client"; import { invoke } from "@tauri-apps/api/core"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { FaApple, FaLinux, FaWindows } from "react-icons/fa"; import { LuChevronRight, LuClipboard, LuClipboardCheck, LuCookie, LuCopy, LuFingerprint, LuGlobe, LuGroup, LuPlus, LuPuzzle, LuRefreshCw, LuSettings, LuShieldCheck, LuTrash2, LuUsers, LuX, } from "react-icons/lu"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { ProBadge } from "@/components/ui/pro-badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { getBrowserDisplayName, getOSDisplayName, getProfileIcon, isCrossOsProfile, } from "@/lib/browser-utils"; import { formatRelativeTime } from "@/lib/flag-utils"; import { cn } from "@/lib/utils"; import type { BrowserProfile, ProfileGroup, StoredProxy, VpnConfig, } from "@/types"; interface ProfileInfoDialogProps { isOpen: boolean; onClose: () => void; profile: BrowserProfile | null; storedProxies: StoredProxy[]; vpnConfigs: VpnConfig[]; onOpenTrafficDialog?: (profileId: string) => void; onOpenProfileSyncDialog?: (profile: BrowserProfile) => void; onAssignProfilesToGroup?: (profileIds: string[]) => void; onConfigureCamoufox?: (profile: BrowserProfile) => void; onCopyCookiesToProfile?: (profile: BrowserProfile) => void; onOpenCookieManagement?: (profile: BrowserProfile) => void; onAssignExtensionGroup?: (profileIds: string[]) => void; onOpenBypassRules?: (profile: BrowserProfile) => void; onCloneProfile?: (profile: BrowserProfile) => void; onDeleteProfile?: (profile: BrowserProfile) => void; onLaunchWithSync?: (profile: BrowserProfile) => void; crossOsUnlocked?: boolean; isRunning?: boolean; isDisabled?: boolean; isCrossOs?: boolean; syncStatuses: Record; } function OSIcon({ os }: { os: string }) { switch (os) { case "macos": return ; case "windows": return ; case "linux": return ; default: return null; } } function InfoCard({ label, value }: { label: string; value: string }) { return (

{label}

{value}

); } export function ProfileInfoDialog({ isOpen, onClose, profile, storedProxies, vpnConfigs, onOpenTrafficDialog, onOpenProfileSyncDialog, onAssignProfilesToGroup, onConfigureCamoufox, onCopyCookiesToProfile, onOpenCookieManagement, onAssignExtensionGroup, onOpenBypassRules, onCloneProfile, onDeleteProfile, onLaunchWithSync, crossOsUnlocked = false, isRunning = false, isDisabled = false, isCrossOs = false, syncStatuses, }: ProfileInfoDialogProps) { const { t } = useTranslation(); const [copied, setCopied] = React.useState(false); const [groupName, setGroupName] = React.useState(null); const [extensionGroupName, setExtensionGroupName] = React.useState< string | null >(null); React.useEffect(() => { if (!isOpen || !profile?.group_id) { setGroupName(null); return; } (async () => { try { const groups = await invoke("get_groups"); const group = groups.find((g) => g.id === profile.group_id); setGroupName(group?.name ?? null); } catch { setGroupName(null); } })(); }, [isOpen, profile?.group_id]); React.useEffect(() => { if (!isOpen || !profile?.extension_group_id) { setExtensionGroupName(null); return; } (async () => { try { const group = await invoke<{ name: string } | null>( "get_extension_group_for_profile", { profileId: profile.id }, ); setExtensionGroupName(group?.name ?? null); } catch { setExtensionGroupName(null); } })(); }, [isOpen, profile?.extension_group_id, profile?.id]); React.useEffect(() => { if (!isOpen) { setCopied(false); } }, [isOpen]); if (!profile) return null; const ProfileIcon = getProfileIcon(profile); const isCamoufoxOrWayfern = profile.browser === "camoufox" || profile.browser === "wayfern"; const isDeleteDisabled = isRunning; const proxyName = profile.proxy_id ? storedProxies.find((p) => p.id === profile.proxy_id)?.name : null; const vpnName = profile.vpn_id ? vpnConfigs.find((v) => v.id === profile.vpn_id)?.name : null; const networkLabel = vpnName ? `VPN: ${vpnName}` : proxyName ? `Proxy: ${proxyName}` : t("profileInfo.values.none"); const syncStatus = syncStatuses[profile.id]; const syncMode = profile.sync_mode ?? "Disabled"; const syncLabel = syncStatus ? `${syncMode} (${syncStatus.status})` : syncMode; const handleCopyId = async () => { try { await navigator.clipboard.writeText(profile.id); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { // ignore } }; const handleAction = (action: () => void) => { onClose(); action(); }; const releaseLabel = profile.release_type.charAt(0).toUpperCase() + profile.release_type.slice(1); const hasTags = profile.tags && profile.tags.length > 0; const hasNote = !!profile.note; const showCrossOs = isCrossOsProfile(profile); type ActionItem = { icon: React.ReactNode; label: string; onClick: () => void; disabled?: boolean; destructive?: boolean; proBadge?: boolean; runningBadge?: boolean; hidden?: boolean; }; const actions: ActionItem[] = [ { icon: , label: t("profiles.actions.viewNetwork"), onClick: () => handleAction(() => onOpenTrafficDialog?.(profile.id)), disabled: isCrossOs, }, { icon: , label: t("profiles.actions.syncSettings"), onClick: () => handleAction(() => onOpenProfileSyncDialog?.(profile)), disabled: isCrossOs, hidden: profile.ephemeral === true, }, { icon: , label: t("profiles.actions.assignToGroup"), onClick: () => handleAction(() => onAssignProfilesToGroup?.([profile.id])), disabled: isDisabled, runningBadge: isRunning, }, { icon: , label: t("profiles.actions.changeFingerprint"), onClick: () => handleAction(() => onConfigureCamoufox?.(profile)), disabled: isDisabled, runningBadge: isRunning, hidden: !isCamoufoxOrWayfern || !onConfigureCamoufox, }, { icon: , label: t("profiles.synchronizer.launchWithSync"), onClick: () => handleAction(() => onLaunchWithSync?.(profile)), disabled: isDisabled || isRunning || !crossOsUnlocked, proBadge: !crossOsUnlocked, hidden: profile.browser !== "wayfern" || !onLaunchWithSync, }, { icon: , label: t("profiles.actions.copyCookiesToProfile"), onClick: () => handleAction(() => onCopyCookiesToProfile?.(profile)), disabled: isDisabled, runningBadge: isRunning, hidden: !isCamoufoxOrWayfern || profile.ephemeral === true || !onCopyCookiesToProfile, }, { icon: , label: t("profileInfo.actions.manageCookies"), onClick: () => handleAction(() => onOpenCookieManagement?.(profile)), disabled: isDisabled, runningBadge: isRunning, hidden: !isCamoufoxOrWayfern || profile.ephemeral === true || !onOpenCookieManagement, }, { icon: , label: t("profiles.actions.clone"), onClick: () => handleAction(() => onCloneProfile?.(profile)), disabled: isDisabled, runningBadge: isRunning, hidden: profile.ephemeral === true, }, { icon: , label: t("profileInfo.actions.assignExtensionGroup"), onClick: () => handleAction(() => onAssignExtensionGroup?.([profile.id])), disabled: isDisabled || !crossOsUnlocked, proBadge: !crossOsUnlocked, runningBadge: isRunning && crossOsUnlocked, hidden: profile.ephemeral === true, }, { icon: , label: t("profileInfo.network.bypassRulesTitle"), onClick: () => handleAction(() => onOpenBypassRules?.(profile)), }, { icon: , label: t("profiles.actions.delete"), onClick: () => handleAction(() => onDeleteProfile?.(profile)), disabled: isDeleteDisabled, destructive: true, }, ]; const visibleActions = actions.filter((a) => !a.hidden); return ( !open && onClose()}> {t("profileInfo.title")} {t("profileInfo.tabs.info")} {t("profileInfo.tabs.settings")}
{/* Hero */}

{profile.name}

{getBrowserDisplayName(profile.browser)}{" "} {profile.version} {releaseLabel} {isRunning && ( {t("common.status.running")} )} {profile.ephemeral && ( {t("profiles.ephemeralBadge")} )} {showCrossOs && ( {getOSDisplayName( profile.host_os || profile.camoufox_config?.os || profile.wayfern_config?.os || "", )} )}
{/* Profile ID */}
ID {profile.id}
{/* Network & Organization */}
{/* Sync */}

{t("profileInfo.fields.syncStatus")}

{syncLabel}

{syncMode === "Disabled" ? t("sync.mode.disabled") : syncStatus?.status === "syncing" ? t("common.status.syncing") : t("common.status.synced")}
{/* Tags */} {hasTags && (
{t("profileInfo.fields.tags")}
{profile.tags?.map((tag) => ( {tag} ))}
)} {/* Note */} {hasNote && (
{t("profileInfo.fields.note")}

{profile.note}

)} {/* Team */} {profile.created_by_email && (

{t("sync.team.title")}

{t("sync.team.createdBy", { email: profile.created_by_email, })}

)}
{visibleActions.map((action) => ( ))}
); } interface ProfileBypassRulesDialogProps { isOpen: boolean; onClose: () => void; profileId: string | null; initialRules?: string[]; } export function ProfileBypassRulesDialog({ isOpen, onClose, profileId, initialRules, }: ProfileBypassRulesDialogProps) { const { t } = useTranslation(); const [bypassRules, setBypassRules] = React.useState([]); const [newRule, setNewRule] = React.useState(""); React.useEffect(() => { if (isOpen) { setBypassRules(initialRules ?? []); setNewRule(""); } }, [isOpen, initialRules]); const updateBypassRules = async (rules: string[]) => { if (!profileId) return; try { await invoke("update_profile_proxy_bypass_rules", { profileId, rules, }); setBypassRules(rules); } catch { // ignore } }; const handleAddRule = () => { const trimmed = newRule.trim(); if (!trimmed || bypassRules.includes(trimmed)) return; const updated = [...bypassRules, trimmed]; setNewRule(""); void updateBypassRules(updated); }; const handleRemoveRule = (rule: string) => { void updateBypassRules(bypassRules.filter((r) => r !== rule)); }; return ( !open && onClose()}> {t("profileInfo.network.bypassRulesTitle")}

{t("profileInfo.network.bypassRulesDescription")}

setNewRule(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleAddRule(); }} placeholder={t("profileInfo.network.rulePlaceholder")} className="flex-1 text-sm" />
{bypassRules.length === 0 ? (

{t("profileInfo.network.noRules")}

) : (
{bypassRules.map((rule) => (
{rule}
))}
)}

{t("profileInfo.network.ruleTypes")}

); }