import { useNavigate } from '@solidjs/router'; import { Show, createMemo, createSignal, onMount } from 'solid-js'; import { isExternalIdentity, pickManagementLoginError } from '~/lib/admin-auth'; import { hasAdminSession, setAdminSession } from '~/lib/admin-session'; type AuthMode = 'login' | 'reset'; type ResetStep = 'request' | 'verify'; function pickChallengeId(payload: any): string { const direct = String(payload?.challengeId || '').trim(); if (direct) return direct; const nested = String(payload?.data?.challengeId || '').trim(); if (nested) return nested; return String(payload?.challenge_id || '').trim(); } function pickMaskedEmail(payload: any, fallback: string): string { const direct = String(payload?.maskedEmail || '').trim(); if (direct) return direct; const nested = String(payload?.data?.maskedEmail || '').trim(); return nested || fallback; } /* Matches public website input exactly */ const inputCls = 'h-11 w-full rounded-xl border border-[#E5E7EB] bg-white px-4 text-sm text-[#111827] outline-none transition placeholder:text-[#9CA3AF] focus:border-[#FF5E13] focus:ring-2 focus:ring-[#FFE6D9]'; const labelCls = 'mb-2 block text-[12px] font-semibold uppercase tracking-[0.11em] text-[#4B5563]'; export default function LoginPage() { const navigate = useNavigate(); const [mode, setMode] = createSignal('login'); const [resetStep, setResetStep] = createSignal('request'); const [email, setEmail] = createSignal(''); const [password, setPassword] = createSignal(''); const [showPassword, setShowPassword] = createSignal(false); const [resetCode, setResetCode] = createSignal(''); const [newPassword, setNewPassword] = createSignal(''); const [confirmPassword, setConfirmPassword] = createSignal(''); const [challengeId, setChallengeId] = createSignal(''); const [maskedEmail, setMaskedEmail] = createSignal(''); const [isSubmitting, setIsSubmitting] = createSignal(false); const [error, setError] = createSignal(''); const [info, setInfo] = createSignal(''); const canSubmitLogin = createMemo(() => email().trim().length > 0 && password().trim().length > 0); const canSubmitResetRequest = createMemo(() => email().trim().length > 0 && newPassword().trim().length > 0 && confirmPassword().trim().length > 0); const canSubmitResetVerify = createMemo(() => challengeId().trim().length > 0 && resetCode().trim().length === 6); const clearMessages = () => { setError(''); setInfo(''); }; const switchMode = (next: AuthMode) => { clearMessages(); setMode(next); if (next === 'login') { setResetStep('request'); setResetCode(''); setChallengeId(''); setMaskedEmail(''); setNewPassword(''); setConfirmPassword(''); } }; onMount(() => { if (hasAdminSession()) navigate('/admin', { replace: true }); }); const completeAdminLogin = () => { setAdminSession(); const from = new URLSearchParams(window.location.search).get('from'); navigate(from && from.startsWith('/admin') ? from : '/admin', { replace: true }); }; const directSignIn = async () => { clearMessages(); if (!canSubmitLogin()) { setError('Email and password are required.'); return; } setIsSubmitting(true); try { const body = JSON.stringify({ email: email().trim().toLowerCase(), password: password(), loginTarget: 'admin' }); const headers = { 'Content-Type': 'application/json', Accept: 'application/json', 'x-portal-target': 'admin' }; let payload: any = {}; let status = 500; let success = false; const r = await fetch('/api/auth/login', { method: 'POST', headers, credentials: 'include', body }); status = r.status; payload = await r.json().catch(() => ({})); if (r.ok) { success = true; } if (!success) { const fallback = status === 502 ? 'Auth service unavailable (502). Please retry in 1–2 minutes.' : 'Sign in failed.'; throw new Error(pickManagementLoginError(payload) || fallback); } if (isExternalIdentity(payload)) throw new Error('External users cannot use this portal.'); const token = String(payload?.access_token || payload?.accessToken || '').trim(); if (token) sessionStorage.setItem('nxtgauge_admin_access_token', token); completeAdminLogin(); } catch (e: any) { setError(String(e?.message || 'Sign in failed.')); } finally { setIsSubmitting(false); } }; const requestResetCode = async () => { clearMessages(); if (!canSubmitResetRequest()) { setError('All fields are required.'); return; } if (newPassword() !== confirmPassword()) { setError('Passwords do not match.'); return; } const trimmedEmail = email().trim().toLowerCase(); const pw = newPassword().trim(); const reqBody = { email: trimmedEmail, newPassword: pw, new_password: pw, password: pw }; setIsSubmitting(true); try { let payload: any = {}; let status = 500; for (const { url, body } of [ { url: '/api/auth/internal/forgot-password/request-code', body: JSON.stringify(reqBody) }, { url: '/api/auth/internal/forgot-password/request-code', body: JSON.stringify(reqBody) }, { url: '/api/auth/internal/forgot-password/request-code', body: JSON.stringify({ data: reqBody }) }, { url: '/api/auth/internal/forgot-password/request-code', body: JSON.stringify({ data: reqBody }) }, ]) { const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body }); status = r.status; payload = await r.json().catch(() => ({})); if (r.ok) break; } const cId = pickChallengeId(payload); if (!cId) { const fallback = status === 502 ? 'Service unavailable (502). Please retry.' : 'Failed to send reset code.'; throw new Error(String(payload?.message || payload?.error || fallback).trim()); } setChallengeId(cId); setMaskedEmail(pickMaskedEmail(payload, trimmedEmail)); setResetStep('verify'); const dev = String(payload?.debugCode || payload?.data?.debugCode || '').trim(); setInfo(dev ? `Code sent. [DEV: ${dev}]` : 'Reset code sent to your email.'); } catch (e: any) { setError(String(e?.message || 'Failed to send reset code.')); } finally { setIsSubmitting(false); } }; const verifyResetCode = async () => { clearMessages(); if (!canSubmitResetVerify()) { setError('A valid 6-digit code is required.'); return; } if (newPassword().trim() !== confirmPassword().trim()) { setError('Passwords do not match.'); return; } const pw = newPassword().trim(); const cId = challengeId().trim(); const code = resetCode().trim(); const body = JSON.stringify({ challengeId: cId, challenge_id: cId, code, otp: code, verificationCode: code, verification_code: code, newPassword: pw, new_password: pw, password: pw }); setIsSubmitting(true); try { let payload: any = {}; let status = 500; let success = false; for (const url of ['/api/auth/internal/forgot-password/verify-code', '/api/auth/internal/forgot-password/verify-code']) { const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body }); status = r.status; payload = await r.json().catch(() => ({})); if (r.ok) { success = true; break; } } if (!success) { const fallback = status === 502 ? 'Service unavailable (502). Please retry.' : 'Password reset failed.'; throw new Error(String(payload?.message || payload?.error || fallback).trim()); } setPassword(''); switchMode('login'); setInfo('Password reset successful. Please sign in with your new password.'); } catch (e: any) { setError(String(e?.message || 'Password reset failed.')); } finally { setIsSubmitting(false); } }; return (
NXTGAUGE Admin

{mode() === 'login' ? 'Sign In' : 'Reset Password'}

{mode() === 'login' ? 'Internal team access only.' : 'Use your internal email to reset access.'}

{/* Email */}
{ setEmail(e.currentTarget.value); clearMessages(); }} placeholder="Enter your email" class={inputCls} autocomplete="email" />
{/* Login mode */}
{ setPassword(e.currentTarget.value); clearMessages(); }} placeholder="Enter your password" class={`${inputCls} pr-11`} autocomplete="current-password" />
{/* Reset mode */}
{ setNewPassword(e.currentTarget.value); clearMessages(); }} placeholder="Minimum 8 characters" class={inputCls} />
{ setConfirmPassword(e.currentTarget.value); clearMessages(); }} placeholder="Repeat new password" class={inputCls} />
{ setResetCode(e.currentTarget.value.replace(/\D/g, '').slice(0, 6)); clearMessages(); }} placeholder="000000" class={`${inputCls} text-center text-lg tracking-[0.3em]`} />

Code sent to {maskedEmail() || email()}

{/* Error / Info */}

{error()}

{info()}

{/* Sign In button */}

Secure login with internal access policies.

{/* Reset buttons */} {isSubmitting() ? 'Resetting…' : 'Verify & Reset Password'} }>
); }