"use client"; import { invoke } from "@tauri-apps/api/core"; import { useCallback, useEffect, useMemo, useState } from "react"; import { LuChevronDown, LuChevronRight, LuCookie, LuSearch, } from "react-icons/lu"; import { toast } from "sonner"; import { LoadingButton } from "@/components/loading-button"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { getBrowserIcon } from "@/lib/browser-utils"; import type { BrowserProfile, CookieCopyRequest, CookieCopyResult, CookieReadResult, DomainCookies, SelectedCookie, UnifiedCookie, } from "@/types"; import { RippleButton } from "./ui/ripple"; interface CookieCopyDialogProps { isOpen: boolean; onClose: () => void; selectedProfiles: string[]; profiles: BrowserProfile[]; runningProfiles: Set; onCopyComplete?: () => void; } type SelectionState = { [domain: string]: { allSelected: boolean; cookies: Set; }; }; export function CookieCopyDialog({ isOpen, onClose, selectedProfiles, profiles, runningProfiles, onCopyComplete, }: CookieCopyDialogProps) { const [sourceProfileId, setSourceProfileId] = useState(null); const [cookieData, setCookieData] = useState(null); const [isLoadingCookies, setIsLoadingCookies] = useState(false); const [isCopying, setIsCopying] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [selection, setSelection] = useState({}); const [expandedDomains, setExpandedDomains] = useState>( new Set(), ); const [error, setError] = useState(null); const eligibleSourceProfiles = useMemo(() => { return profiles.filter( (p) => p.browser === "wayfern" || p.browser === "camoufox", ); }, [profiles]); const targetProfiles = useMemo(() => { return profiles.filter( (p) => selectedProfiles.includes(p.id) && p.id !== sourceProfileId && (p.browser === "wayfern" || p.browser === "camoufox"), ); }, [profiles, selectedProfiles, sourceProfileId]); const filteredDomains = useMemo(() => { if (!cookieData) return []; if (!searchQuery.trim()) return cookieData.domains; const query = searchQuery.toLowerCase(); return cookieData.domains.filter( (d) => d.domain.toLowerCase().includes(query) || d.cookies.some((c) => c.name.toLowerCase().includes(query)), ); }, [cookieData, searchQuery]); const selectedCookieCount = useMemo(() => { let count = 0; for (const domain of Object.keys(selection)) { const domainSelection = selection[domain]; if (domainSelection.allSelected) { const domainData = cookieData?.domains.find((d) => d.domain === domain); count += domainData?.cookie_count || 0; } else { count += domainSelection.cookies.size; } } return count; }, [selection, cookieData]); const loadCookies = useCallback(async (profileId: string) => { setIsLoadingCookies(true); setError(null); setCookieData(null); setSelection({}); try { const result = await invoke("read_profile_cookies", { profileId, }); setCookieData(result); } catch (err) { console.error("Failed to load cookies:", err); setError(err instanceof Error ? err.message : String(err)); } finally { setIsLoadingCookies(false); } }, []); const handleSourceChange = useCallback( (profileId: string) => { setSourceProfileId(profileId); void loadCookies(profileId); }, [loadCookies], ); const toggleDomain = useCallback( (domain: string, cookies: UnifiedCookie[]) => { setSelection((prev) => { const current = prev[domain]; const allSelected = current?.allSelected || false; if (allSelected) { const newSelection = { ...prev }; delete newSelection[domain]; return newSelection; } else { return { ...prev, [domain]: { allSelected: true, cookies: new Set(cookies.map((c) => c.name)), }, }; } }); }, [], ); const toggleCookie = useCallback( (domain: string, cookieName: string, totalCookies: number) => { setSelection((prev) => { const current = prev[domain] || { allSelected: false, cookies: new Set(), }; const newCookies = new Set(current.cookies); if (newCookies.has(cookieName)) { newCookies.delete(cookieName); } else { newCookies.add(cookieName); } const allSelected = newCookies.size === totalCookies; if (newCookies.size === 0) { const newSelection = { ...prev }; delete newSelection[domain]; return newSelection; } return { ...prev, [domain]: { allSelected, cookies: newCookies, }, }; }); }, [], ); const toggleExpand = useCallback((domain: string) => { setExpandedDomains((prev) => { const next = new Set(prev); if (next.has(domain)) { next.delete(domain); } else { next.add(domain); } return next; }); }, []); const buildSelectedCookies = useCallback((): SelectedCookie[] => { const result: SelectedCookie[] = []; for (const [domain, domainSelection] of Object.entries(selection)) { if (domainSelection.allSelected) { result.push({ domain, name: "" }); } else { for (const cookieName of domainSelection.cookies) { result.push({ domain, name: cookieName }); } } } return result; }, [selection]); const handleCopy = useCallback(async () => { if (!sourceProfileId || targetProfiles.length === 0) return; const runningTargets = targetProfiles.filter((p) => runningProfiles.has(p.id), ); if (runningTargets.length > 0) { toast.error( `Cannot copy cookies: ${runningTargets.map((p) => p.name).join(", ")} ${ runningTargets.length === 1 ? "is" : "are" } still running`, ); return; } setIsCopying(true); setError(null); try { const selectedCookies = buildSelectedCookies(); const request: CookieCopyRequest = { source_profile_id: sourceProfileId, target_profile_ids: targetProfiles.map((p) => p.id), selected_cookies: selectedCookies, }; const results = await invoke("copy_profile_cookies", { request, }); let totalCopied = 0; let totalReplaced = 0; const errors: string[] = []; for (const result of results) { totalCopied += result.cookies_copied; totalReplaced += result.cookies_replaced; errors.push(...result.errors); } if (errors.length > 0) { toast.error(`Some errors occurred: ${errors.join(", ")}`); } else { toast.success( `Successfully copied ${totalCopied + totalReplaced} cookies (${totalReplaced} replaced)`, ); onCopyComplete?.(); onClose(); } } catch (err) { console.error("Failed to copy cookies:", err); toast.error( `Failed to copy cookies: ${err instanceof Error ? err.message : String(err)}`, ); } finally { setIsCopying(false); } }, [ sourceProfileId, targetProfiles, runningProfiles, buildSelectedCookies, onCopyComplete, onClose, ]); useEffect(() => { if (isOpen) { setSourceProfileId(null); setCookieData(null); setSelection({}); setSearchQuery(""); setExpandedDomains(new Set()); setError(null); } }, [isOpen]); const canCopy = sourceProfileId && targetProfiles.length > 0 && selectedCookieCount > 0 && !isCopying; return ( Copy Cookies Copy cookies from a source profile to {selectedProfiles.length}{" "} selected profile{selectedProfiles.length !== 1 ? "s" : ""}.
{targetProfiles.length === 0 ? (

{sourceProfileId ? "No other Wayfern/Camoufox profiles selected" : "Select a source profile first"}

) : (
{targetProfiles.map((p) => ( {p.name} {runningProfiles.has(p.id) && ( (running) )} ))}
)}
{sourceProfileId && (
setSearchQuery(e.target.value)} className="pl-8" />
{isLoadingCookies ? (
) : error ? (
{error}
) : filteredDomains.length === 0 ? (
{searchQuery ? "No matching cookies found" : "No cookies found"}
) : (
{filteredDomains.map((domain) => ( ))}
)}

Existing cookies with the same name and domain will be replaced. Other cookies will be kept.

)}
Cancel void handleCopy()} disabled={!canCopy} > Copy {selectedCookieCount > 0 ? `${selectedCookieCount} ` : ""} Cookie{selectedCookieCount !== 1 ? "s" : ""}
); } interface DomainRowProps { domain: DomainCookies; selection: SelectionState; isExpanded: boolean; onToggleDomain: (domain: string, cookies: UnifiedCookie[]) => void; onToggleCookie: ( domain: string, cookieName: string, totalCookies: number, ) => void; onToggleExpand: (domain: string) => void; } function DomainRow({ domain, selection, isExpanded, onToggleDomain, onToggleCookie, onToggleExpand, }: DomainRowProps) { const domainSelection = selection[domain.domain]; const isAllSelected = domainSelection?.allSelected || false; const selectedCount = domainSelection?.cookies.size || 0; const isPartial = selectedCount > 0 && selectedCount < domain.cookie_count && !isAllSelected; return (
onToggleDomain(domain.domain, domain.cookies)} className={isPartial ? "opacity-70" : ""} />
{isExpanded && (
{domain.cookies.map((cookie) => { const isSelected = domainSelection?.cookies.has(cookie.name) || false; return (
onToggleCookie( domain.domain, cookie.name, domain.cookie_count, ) } /> {cookie.name}
); })}
)}
); }