import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Server, RefreshCw, Loader2 } from "lucide-react"; import { Button } from "@/components/button.tsx"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/dialog.tsx"; import { RemoteSyncServerPicker } from "./RemoteSyncServerPicker.tsx"; import { ElectronLoginForm } from "@/auth/ElectronLoginForm.tsx"; import { invalidateServerStatusCache } from "@/lib/hosts-request-cache.ts"; import { toast } from "sonner"; interface RemoteSyncConfig { serverUrl: string; connectedAt: string; lastSyncedAt?: string | null; lastSyncStatus?: "ok" | "error" | "never"; lastSyncError?: string | null; } interface RemoteSyncStatus { connected: boolean; syncing: boolean; lastSyncedAt: string | null; lastError: string | null; needsReauth: boolean; } type DesktopSettings = { defaultConnectionOrigin: "local" | "remote" }; interface RemoteSyncPanelProps { initialServerUrl?: string; reconnectRequested?: boolean; onReconnectRequestHandled?: () => void; } export function RemoteSyncPanel({ initialServerUrl, reconnectRequested = false, onReconnectRequestHandled, }: RemoteSyncPanelProps) { const { t } = useTranslation(); const [config, setConfig] = useState(null); const [status, setStatus] = useState(null); const [desktopSettings, setDesktopSettings] = useState({ defaultConnectionOrigin: "local", }); const [step, setStep] = useState<"idle" | "picker" | "login">("idle"); const [pendingServerUrl, setPendingServerUrl] = useState(""); const [syncingNow, setSyncingNow] = useState(false); const consumedInitialServerUrl = useRef(false); const refresh = useCallback(async () => { const [cfg, st, settings] = await Promise.all([ window.electronAPI?.invoke?.( "get-remote-sync-config", ) as Promise, window.electronAPI?.invoke?.( "get-remote-sync-status", ) as Promise, window.electronAPI?.invoke?.( "get-desktop-settings", ) as Promise, ]); setConfig(cfg ?? null); setStatus(st ?? null); if (settings) setDesktopSettings(settings); }, []); useEffect(() => { refresh(); const unsubscribe = window.electronAPI?.onRemoteSyncStatusChanged?.( (nextStatus: RemoteSyncStatus) => setStatus(nextStatus), ); return () => unsubscribe?.(); }, [refresh]); useEffect(() => { if (consumedInitialServerUrl.current) return; if (!initialServerUrl || config?.serverUrl) return; consumedInitialServerUrl.current = true; setPendingServerUrl(initialServerUrl); setStep("picker"); }, [initialServerUrl, config]); useEffect(() => { if (!reconnectRequested || !config?.serverUrl) return; setPendingServerUrl(config.serverUrl); setStep("login"); onReconnectRequestHandled?.(); }, [config?.serverUrl, onReconnectRequestHandled, reconnectRequested]); const handleConnectClick = () => setStep("picker"); const handleReconnectClick = () => { if (!config?.serverUrl) return; setPendingServerUrl(config.serverUrl); setStep("login"); }; const handleServerConfigured = (serverUrl: string) => { setPendingServerUrl(serverUrl); setStep("login"); }; const handleAuthSuccess = async () => { setStep("idle"); await refresh(); }; const handleDisconnect = async () => { await window.electronAPI?.invoke?.("clear-remote-sync-config"); await refresh(); }; const handleSyncNow = async () => { setSyncingNow(true); try { const result = (await window.electronAPI?.invoke?.("remote-sync-now")) as RemoteSyncStatus | null | undefined; if (!result || result.needsReauth || result.lastError) { toast.error(result?.lastError || t("remoteSync.syncUnavailable")); } else { toast.success(t("remoteSync.syncSuccess")); } } catch (error) { toast.error( error instanceof Error && error.message ? error.message : t("remoteSync.syncUnavailable"), ); } finally { setSyncingNow(false); await refresh(); } }; const handleOriginChange = async (origin: "local" | "remote") => { const next = { ...desktopSettings, defaultConnectionOrigin: origin }; setDesktopSettings(next); await window.electronAPI?.invoke?.("save-desktop-settings", next); invalidateServerStatusCache(); window.dispatchEvent(new CustomEvent("hosts:refresh")); }; const isConnected = !!config?.serverUrl; const connectedToText = config?.serverUrl ? t("remoteSync.connectedTo", { url: config.serverUrl }) : ""; const connectionSubtitleText = status?.needsReauth ? t("remoteSync.needsReauth") : status?.lastError ? t("remoteSync.syncError", { message: status.lastError }) : status?.lastSyncedAt ? t("remoteSync.lastSynced", { time: new Date(status.lastSyncedAt).toLocaleString(), }) : t("remoteSync.neverSynced"); return (

{t("remoteSync.title")}

{t("remoteSync.description")}

{isConnected ? ( <> {connectedToText} {connectionSubtitleText} ) : ( {t("remoteSync.notConnected")} )}
{isConnected ? ( <> {status?.needsReauth && ( )} ) : ( )}

{t("remoteSync.originTitle")}

{t("remoteSync.originDescription")}

!open && setStep("idle")} > {t("remoteSync.title")} setStep("idle")} initialServerUrl={initialServerUrl} /> !open && setStep("idle")} > e.preventDefault()} onInteractOutside={(e) => e.preventDefault()} > {t("remoteSync.signInTitle", { url: pendingServerUrl })}
setStep("picker")} />
); }