"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { getSetupStatus, setSetupIdentity } from "@/lib/api"; import { SetupIdentityMode } from "./setup-identity-mode"; import { SetupConfigure } from "./setup-configure"; import { SetupAttachAuth } from "./setup-attach-auth"; import { SetupVerify } from "./setup-verify"; import { SetupTelemetry } from "./setup-telemetry"; import { SetupComplete } from "./setup-complete"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Stepper, StepperItem, StepperList, StepperIndicator, StepperSeparator, StepperTitle, StepperTrigger, } from "@/components/ui/stepper"; type SetupStep = | "mode" | "configure" | "attach-auth" | "verify" | "telemetry" | "complete"; export function SetupWizard() { const [currentStep, setCurrentStep] = useState("mode"); const [identityMode, setIdentityMode] = useState(null); const [attachedDid, setAttachedDid] = useState(null); const [attachedHandle, setAttachedHandle] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [lastFailedMode, setLastFailedMode] = useState(null); const stepContentRef = useRef(null); const initialLoadRef = useRef(true); useEffect(() => { if (initialLoadRef.current) { initialLoadRef.current = false; return; } stepContentRef.current?.focus(); }, [currentStep]); useEffect(() => { getSetupStatus() .then((status) => { if (status.setup_complete) { setCurrentStep("complete"); } else if (status.plc_verified) { setCurrentStep("complete"); } else if (status.identity_configured) { setIdentityMode(status.identity_mode); setCurrentStep("verify"); } else if ( status.identity_mode && status.identity_mode !== "not_exposed" ) { setIdentityMode(status.identity_mode); const pendingAuth = localStorage.getItem("happyview_attach_auth"); if (status.identity_mode === "attach_account" && pendingAuth) { try { const payload = JSON.parse(pendingAuth) as { attachedDid: string; }; setAttachedDid(payload.attachedDid); setCurrentStep("attach-auth"); } catch { setCurrentStep("configure"); } } else { setCurrentStep("configure"); } } }) .catch(() => {}) .finally(() => setLoading(false)); }, []); const handleModeSelected = useCallback(async (mode: string) => { setIdentityMode(mode); setError(null); setLastFailedMode(null); if (mode === "not_exposed") { try { await setSetupIdentity({ mode: "not_exposed" }); // Route through telemetry too: the ask is the feature, and skipping // identity setup is no reason to skip the only prompt an operator // will ever see for it. setCurrentStep("telemetry"); } catch (e) { setLastFailedMode(mode); setError( e instanceof Error ? e.message : "Failed to save configuration. Check that your backend is running and try again.", ); } } else if (mode === "attach_account") { setCurrentStep("configure"); } else { try { await setSetupIdentity({ mode }); setCurrentStep("verify"); } catch (e) { setLastFailedMode(mode); setError( e instanceof Error ? e.message : "Failed to configure identity. Check that your backend is running and try again.", ); } } }, []); const handleGoBack = useCallback(() => { setError(null); switch (currentStep) { case "configure": setCurrentStep("mode"); break; case "attach-auth": setCurrentStep("configure"); break; case "verify": setCurrentStep( identityMode === "attach_account" ? "configure" : "mode", ); break; } }, [currentStep, identityMode]); const handleConfigureComplete = useCallback( (opts?: { attachedDid?: string; attachedHandle?: string | null }) => { if (opts?.attachedDid) { setAttachedDid(opts.attachedDid); setAttachedHandle(opts.attachedHandle ?? null); setCurrentStep("attach-auth"); } else { setCurrentStep("verify"); } }, [], ); const handleAttachAuthComplete = useCallback(() => { setCurrentStep("verify"); }, []); const handleVerifyComplete = useCallback(() => { setCurrentStep("telemetry"); }, []); const handleTelemetryComplete = useCallback(() => { setCurrentStep("complete"); }, []); const stepOrder = useMemo( () => identityMode === "attach_account" ? ["mode", "configure", "attach-auth", "verify", "telemetry", "complete"] : ["mode", "verify", "telemetry", "complete"], [identityMode], ); const handleStepperNav = useCallback( (value: string) => { const target = value as SetupStep; const currentIndex = stepOrder.indexOf(currentStep); const targetIndex = stepOrder.indexOf(target); if (targetIndex < 0 || targetIndex > currentIndex) return; if (target === "mode") { setIdentityMode(null); setAttachedDid(null); setAttachedHandle(null); setError(null); } setCurrentStep(target); }, [currentStep, stepOrder], ); if (loading) { return (
); } return ( Identity {identityMode === "attach_account" && ( <> Account Sign In )} {identityMode === "did_plc" ? "Key Backup" : identityMode === "attach_account" ? "Verify" : "Review"} Telemetry Done {error && (
{error} {lastFailedMode && ( )}
)}
{currentStep === "mode" && ( )} {currentStep === "configure" && identityMode && ( )} {currentStep === "attach-auth" && attachedDid && ( )} {currentStep === "verify" && identityMode && ( )} {currentStep === "telemetry" && ( )} {currentStep === "complete" && ( )}
); }