import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEye, faEyeSlash } from '@fortawesome/pro-duotone-svg-icons'; import { useAuth } from '@/providers/auth-provider'; import { useData } from '@/providers/data-provider'; import { Button } from '@/components/base/buttons/button'; import { SocialButton } from '@/components/base/buttons/social-button'; import { Checkbox } from '@/components/base/checkbox/checkbox'; import { Input } from '@/components/base/input/input'; import { MaintOtpModal } from '@/components/modals/maint-otp-modal'; import { useMaintShortcuts } from '@/hooks/use-maint-shortcuts'; import { useThemeTokens } from '@/providers/theme-token-provider'; import { getSetupState } from '@/lib/setup-state'; export const LoginPage = () => { const { loginWithUser } = useAuth(); const { refresh } = useData(); const navigate = useNavigate(); const { isOpen, activeAction, close } = useMaintShortcuts(); const { tokens } = useThemeTokens(); // Load website widget on login page. // // Config comes from the admin-editable sidecar endpoint (not env vars) // so it can be toggled / rotated / moved at runtime. The widget renders // only when all of enabled + embed.loginPage + key are set. // // `url` may be empty (default for fresh configs) — in that case we fall // back to the same origin the app is talking to for its API, since in // practice the sidecar serves /widget.js alongside /api/*. useEffect(() => { let cancelled = false; const apiUrl = import.meta.env.VITE_API_URL ?? ''; if (!apiUrl) return; fetch(`${apiUrl}/api/config/widget`) .then(r => (r.ok ? r.json() : null)) .then((cfg: { enabled?: boolean; key?: string; url?: string; embed?: { loginPage?: boolean } } | null) => { if (cancelled || !cfg) return; if (!cfg.enabled || !cfg.embed?.loginPage || !cfg.key) return; if (document.getElementById('helix-widget-script')) return; const host = cfg.url && cfg.url.length > 0 ? cfg.url : apiUrl; const script = document.createElement('script'); script.id = 'helix-widget-script'; script.src = `${host}/widget.js`; script.setAttribute('data-key', cfg.key); document.body.appendChild(script); }) .catch(err => { // Never block login on a widget config failure. console.warn('[widget] config fetch failed', err); }); return () => { cancelled = true; document.getElementById('helix-widget-script')?.remove(); document.getElementById('helix-widget-host')?.remove(); }; }, []); const saved = localStorage.getItem('helix_remember'); const savedCreds = saved ? JSON.parse(saved) : null; const [email, setEmail] = useState(savedCreds?.email ?? ''); const [password, setPassword] = useState(savedCreds?.password ?? ''); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(!!savedCreds); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setError(null); if (!email || !password) { setError('Email and password are required'); return; } setIsLoading(true); try { const { apiClient } = await import('@/lib/api-client'); const response = await apiClient.login(email, password); // Build user from sidecar response const u = response.user; const firstName = u?.firstName ?? ''; const lastName = u?.lastName ?? ''; const name = `${firstName} ${lastName}`.trim() || email; const initials = `${firstName[0] ?? ''}${lastName[0] ?? ''}`.toUpperCase() || email[0].toUpperCase(); if (rememberMe) { localStorage.setItem('helix_remember', JSON.stringify({ email, password })); } else { localStorage.removeItem('helix_remember'); } // Store agent config for SIP provider (CC agents only) if ((response as any).agentConfig) { localStorage.setItem('helix_agent_config', JSON.stringify((response as any).agentConfig)); } else { localStorage.removeItem('helix_agent_config'); } loginWithUser({ id: u?.id, name, initials, role: (u?.role ?? 'executive') as 'executive' | 'admin' | 'cc-agent', email: u?.email ?? email, avatarUrl: u?.avatarUrl, platformRoles: u?.platformRoles, }); refresh(); // First-run detection: if the workspace's setup is incomplete and // the wizard hasn't been dismissed, route the admin to /setup so // they finish onboarding before reaching the dashboard. Failures // are non-blocking — we always have a fallback to /. try { const state = await getSetupState(); if (state.wizardRequired) { navigate('/setup'); return; } } catch { // Setup state endpoint may be unreachable on older sidecars — // proceed to the normal landing page. } navigate('/'); } catch (err: any) { setError(err.message); setIsLoading(false); } }; const handleGoogleSignIn = () => { setError('Google sign-in not yet configured'); }; return (
{/* Login Card */}
{/* Logo */}
{tokens.brand.name}

{tokens.login.title}

{tokens.login.subtitle}

{/* Google sign-in */} {tokens.login.showGoogleSignIn && Sign in with Google } {/* Divider */} {tokens.login.showGoogleSignIn &&
or continue with
} {/* Form */}
{error && (
{error}
)} setEmail(value)} size="md" />
setPassword(value)} size="md" />
{tokens.login.showForgotPassword && }
{/* Footer */} {tokens.login.poweredBy.label} !open && close()} action={activeAction} />
); };