import { useEffect, useState } from "react"; import { api } from "../api/client"; import { showError, showSuccess } from "../banner"; import type { AuthResponse } from "../types/api"; import { GarminMFAModal } from "./GarminMFAModal"; import { SyncModal } from "./SyncModal"; export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () => Promise }) { const [auth, setAuth] = useState(null); const [busy, setBusy] = useState(false); // There's no real Garmin logout -- the backend session just sits idle. // "Disconnect" only hides that and shows Connect again locally; a real // login (connect()) clears it. const [disconnected, setDisconnected] = useState(false); const [showSyncModal, setShowSyncModal] = useState(false); // Auth status alone -- sync status is polled by SyncModal itself while // it's open, so this no longer needs the "poll faster while syncing" // dynamic interval it used to have. useEffect(() => { api.garminStatus().then(setAuth).catch((e) => showError(String(e))); const interval = setInterval(() => { api.garminStatus().then(setAuth).catch(() => {}); }, 6000); return () => clearInterval(interval); }, []); async function connect() { setBusy(true); try { await onBeforeConnect?.(); const result = await api.garminLogin(); setAuth(result); setDisconnected(false); if (result.status === "authenticated") showSuccess("Connected to Garmin successfully."); } catch (e) { showError(String(e)); } finally { setBusy(false); } } function disconnect() { setDisconnected(true); } async function submitMFA(code: string) { if (!code.trim()) return; setBusy(true); try { const result = await api.garminMFA(code.trim()); setAuth(result); if (result.status === "authenticated") showSuccess("Connected to Garmin successfully."); } catch (e) { showError(String(e)); } finally { setBusy(false); } } async function sync() { setBusy(true); try { await api.garminSyncRun(); setShowSyncModal(true); } catch (e) { showError(String(e)); } finally { setBusy(false); } } async function resetAll() { if (!window.confirm("This deletes every synced activity, lap, and kind assignment, then starts a fresh pull from Garmin next sync. This cannot be undone. Continue?")) { return; } setBusy(true); try { await api.garminSyncReset(); // Reset runs as a background sync job; wait for it to actually finish // before reloading, otherwise other pages (e.g. Activities) would // still show the just-deleted activities from their own stale state. let status = await api.garminSyncStatus(); while (status.in_progress) { await new Promise((resolve) => setTimeout(resolve, 300)); status = await api.garminSyncStatus(); } window.location.reload(); } catch (e) { showError(String(e)); setBusy(false); } } const status = disconnected ? "unknown" : auth?.status ?? "unknown"; return (
{status !== "authenticated" && status !== "mfa_required" && ( )} {status === "authenticated" && ( <> )} {/* Reset all only wipes local DB state (see handleSyncReset) -- it doesn't touch the Garmin session, so it stays available even while disconnected/not-yet-connected. */}
{/* Connected/not-connected is already conveyed by the Connect/Disconnect button itself -- only MFA/failed need a label, since no button distinguishes those from "not connected". */} {(status === "mfa_required" || status === "failed") && ( {status === "mfa_required" ? "MFA code required" : "Connection failed"} )}
{status === "mfa_required" && ( )} {!disconnected && auth?.message && status !== "authenticated" && (

{auth.message}

)} {showSyncModal && setShowSyncModal(false)} />}
); }