import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useBranding } from "../BrandingContext.js"; export function SetupWizard({ onSetupComplete }) { const navigate = useNavigate(); const { refresh: refreshBranding } = useBranding(); // Fetch setup status to determine if auth provider step is needed const [setupStatus, setSetupStatus] = useState(null); // null = loading const [loadingStatus, setLoadingStatus] = useState(true); // Auth provider form state const [authForm, setAuthForm] = useState({ providerId: "authentik", displayName: "", issuerUrl: "", internalBaseUrl: "", clientId: "", clientSecret: "", scopes: "openid profile email", }); const [testingConnection, setTestingConnection] = useState(false); const [testResult, setTestResult] = useState(null); // {ok: boolean, error?: string} const [step, setStep] = useState(0); const [businessName, setBusinessName] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { fetch("/api/setup/status") .then((r) => r.json()) .then((data) => { setSetupStatus(data); setLoadingStatus(false); }) .catch(() => { setLoadingStatus(false); }); }, []); // Build steps dynamically based on setup status const STEPS = setupStatus?.showAuthProviderStep ? [ { id: "welcome", title: "Welcome", description: "Welcome to GroomBook! Let's get your business set up." }, { id: "auth", title: "Auth Provider", description: "Configure your authentication provider to secure your GroomBook instance." }, { id: "business", title: "Business Name", description: "What is the name of your business?" }, { id: "superuser", title: "Super User", description: "You will be designated as a Super User with full administrative access." }, { id: "admin", title: "Add Another Admin", description: "Consider adding a second Super User as a backup. This is optional but recommended." }, { id: "done", title: "All Set!", description: "Your GroomBook instance is ready to use." }, ] : [ { id: "welcome", title: "Welcome", description: "Welcome to GroomBook! Let's get your business set up." }, { id: "business", title: "Business Name", description: "What is the name of your business?" }, { id: "superuser", title: "Super User", description: "You will be designated as a Super User with full administrative access." }, { id: "admin", title: "Add Another Admin", description: "Consider adding a second Super User as a backup. This is optional but recommended." }, { id: "done", title: "All Set!", description: "Your GroomBook instance is ready to use." }, ]; const current = STEPS[step]; const isLast = step === STEPS.length - 1; const isFirst = step === 0; const canGoBack = step > 0 && step < STEPS.length - 1; // Determine if we can proceed - depends on which step we're on const canGoNext = (() => { if (step === STEPS.length - 1) return true; // done step if (current?.id === "business") return businessName.trim().length > 0; if (current?.id === "auth") { return ( authForm.displayName.trim().length > 0 && authForm.issuerUrl.trim().length > 0 && authForm.clientId.trim().length > 0 && authForm.clientSecret.trim().length > 0 ); } return true; })(); const handleTestConnection = async () => { setTestingConnection(true); setTestResult(null); try { const res = await fetch("/api/setup/auth-provider/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId: authForm.providerId, displayName: authForm.displayName, issuerUrl: authForm.issuerUrl, internalBaseUrl: authForm.internalBaseUrl || null, clientId: authForm.clientId, scopes: authForm.scopes, }), }); const data = await res.json(); setTestResult(data); } catch (e) { setTestResult({ ok: false, error: "Network error. Please try again." }); } finally { setTestingConnection(false); } }; const handleNext = async () => { if (step === STEPS.length - 1) { // Done - redirect to admin navigate("/admin"); return; } // Submit auth provider config if (current?.id === "auth") { setLoading(true); setError(null); try { const res = await fetch("/api/setup/auth-provider", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerId: authForm.providerId, displayName: authForm.displayName, issuerUrl: authForm.issuerUrl, internalBaseUrl: authForm.internalBaseUrl || null, clientId: authForm.clientId, clientSecret: authForm.clientSecret, scopes: authForm.scopes, }), }); if (!res.ok) { const data = await res.json(); setError(data.error || "Failed to save auth provider configuration. Please try again."); setLoading(false); return; } } catch (e) { setError("Network error. Please try again."); setLoading(false); return; } setLoading(false); } // Submit business name and complete setup if (current?.id === "business" && businessName.trim()) { setLoading(true); setError(null); try { const res = await fetch("/api/setup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ businessName: businessName.trim() }), }); if (!res.ok) { const data = await res.json(); setError(data.error || "Setup failed. Please try again."); setLoading(false); return; } // Refresh branding so the nav bar shows the new business name refreshBranding(); // Clear needsSetup state in App so the redirect to /admin sticks if (onSetupComplete) onSetupComplete(); } catch (e) { setError("Network error. Please try again."); setLoading(false); return; } setLoading(false); } setStep((s) => s + 1); }; const handleBack = () => { if (step > 0) setStep((s) => s - 1); }; if (loadingStatus) { return (
Loading...
Step {step + 1} of {STEPS.length}
{/* Title */}{current?.description}
{/* Step: Business name input */} {current?.id === "business" && ( setBusinessName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && canGoNext && handleNext()} autoFocus style={inputStyle} /> )} {/* Step: Auth provider config form */} {current?.id === "auth" && ({error}
)} {/* Navigation buttons */}