diff --git a/frontend/src/OnboardingWizard.tsx b/frontend/src/OnboardingWizard.tsx index 8366e62..c4b74af 100644 --- a/frontend/src/OnboardingWizard.tsx +++ b/frontend/src/OnboardingWizard.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { api } from "./api/client"; +import { showError } from "./banner"; import "./OnboardingWizard.css"; import type { AuthResponse } from "./types/api"; @@ -21,31 +22,35 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin const [code, setCode] = useState(""); const [auth, setAuth] = useState(null); const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); + // Field-validation messages only ("please fill this in") -- real + // API/Garmin failures go through the shared banner system instead (see + // completeFailed below for why the Retry button no longer keys off this). + const [validationError, setValidationError] = useState(null); + const [completeFailed, setCompleteFailed] = useState(false); const completeTriggered = useRef(false); function submitName(e: React.FormEvent) { e.preventDefault(); if (!displayName.trim()) { - setError("Please enter a display name."); + setValidationError("Please enter a display name."); return; } - setError(null); + setValidationError(null); setStep("garmin"); } async function submitGarmin(e: React.FormEvent) { e.preventDefault(); if (!email.trim() || !password) { - setError("Please enter your Garmin email and password."); + setValidationError("Please enter your Garmin email and password."); return; } setBusy(true); - setError(null); + setValidationError(null); try { setAuth(await api.setupGarminLogin(email.trim(), password)); } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong, please try again."); + showError(err instanceof Error ? err.message : "Something went wrong, please try again."); } finally { setBusy(false); } @@ -54,12 +59,11 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin async function submitMFA() { if (!code.trim()) return; setBusy(true); - setError(null); try { setAuth(await api.setupGarminMFA(code.trim())); setCode(""); } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong, please try again."); + showError(err instanceof Error ? err.message : "Something went wrong, please try again."); } finally { setBusy(false); } @@ -67,12 +71,13 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin async function complete() { setBusy(true); - setError(null); + setCompleteFailed(false); try { const result = await api.setupComplete(displayName.trim()); onCreated(result.display_name); } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong, please try again."); + showError(err instanceof Error ? err.message : "Something went wrong, please try again."); + setCompleteFailed(true); setBusy(false); } } @@ -108,7 +113,7 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin ✓ - {error &&

{error}

} + {validationError &&

{validationError}

} ); @@ -122,13 +127,10 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin {auth?.status === "authenticated" ? ( <>

Connected to Garmin — finishing setup…

- {error && ( - <> -

{error}

- - + {completeFailed && ( + )} ) : auth?.status === "mfa_required" ? ( @@ -142,7 +144,6 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin autoFocus /> {auth.message &&

{auth.message}

} - {error &&

{error}

} @@ -165,7 +166,7 @@ export function OnboardingWizard({ onCreated }: { onCreated: (displayName: strin disabled={busy} /> {auth?.message &&

{auth.message}

} - {error &&

{error}

} + {validationError &&

{validationError}

}