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...

); } const inputStyle = { width: "100%", padding: "0.6rem 0.85rem", borderRadius: 8, border: "1px solid #d1d5db", fontSize: 15, outline: "none", boxSizing: "border-box", marginBottom: error ? "0.5rem" : 0, }; return (
{/* Progress dots */}
{STEPS.map((_, i) => (
))}
{/* Step indicator */}

Step {step + 1} of {STEPS.length}

{/* Title */}

{current?.title}

{/* Description */}

{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" && (
{/* Provider ID */}
setAuthForm((f) => ({ ...f, providerId: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Display Name */}
setAuthForm((f) => ({ ...f, displayName: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Issuer URL */}
setAuthForm((f) => ({ ...f, issuerUrl: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Internal Base URL (optional) */}
setAuthForm((f) => ({ ...f, internalBaseUrl: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Client ID */}
setAuthForm((f) => ({ ...f, clientId: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Client Secret */}
setAuthForm((f) => ({ ...f, clientSecret: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Scopes */}
setAuthForm((f) => ({ ...f, scopes: e.target.value }))} style={{ ...inputStyle, fontSize: 14 }} />
{/* Test Connection button */} {/* Test result */} {testResult && (
{testResult.ok ? "Connection successful!" : `Connection failed: ${testResult.error}`}
)}
)} {/* Step: Super user info */} {current?.id === "superuser" && (
As a Super User, you can manage all settings, staff, and appointments.
)} {/* Step: Second admin info */} {current?.id === "admin" && (
You can add additional Super Users from the Staff management page after setup.
)} {/* Error message */} {error && (

{error}

)} {/* Navigation buttons */}
{canGoBack && ( )}
); }