diff --git a/src/routes/login.tsx b/src/routes/login.tsx index ea4c539..511fefe 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -11,23 +11,22 @@ function pickChallengeId(payload: any): string { if (direct) return direct; const nested = String(payload?.data?.challengeId || '').trim(); if (nested) return nested; - const snake = String(payload?.challenge_id || '').trim(); - if (snake) return snake; - return ''; + 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(); - if (nested) return nested; - return fallback; + return nested || fallback; } +/* Matches public website input exactly */ const inputCls = - 'h-11 w-full rounded-lg border border-gray-200 bg-gray-50 px-3.5 text-sm text-gray-900 outline-none transition placeholder:text-gray-400 focus:border-[#0a1d37] focus:bg-white focus:ring-2 focus:ring-[#0a1d37]/10'; + 'h-11 w-full rounded-xl border border-[#cfd4e3] bg-white px-4 text-sm text-[#101228] outline-none transition placeholder:text-[#9ba3bc] focus:border-[#fd6216] focus:ring-2 focus:ring-[#ffd8c3]'; -const labelCls = 'mb-1.5 block text-xs font-semibold uppercase tracking-wider text-gray-500'; +const labelCls = + 'mb-2 block text-xs font-semibold uppercase tracking-[0.11em] text-[#4b546f]'; export default function LoginPage() { const navigate = useNavigate(); @@ -35,6 +34,7 @@ export default function LoginPage() { 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(''); @@ -44,357 +44,313 @@ export default function LoginPage() { const [error, setError] = createSignal(''); const [info, setInfo] = createSignal(''); - 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 canSubmitLoginCredentials = createMemo( - () => email().trim().length > 0 && password().trim().length > 0, - ); + 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 resetPasswordFlow = () => { - setResetStep('request'); - setResetCode(''); - setChallengeId(''); - setMaskedEmail(''); - setNewPassword(''); - setConfirmPassword(''); - }; - - const switchMode = (nextMode: AuthMode) => { + const switchMode = (next: AuthMode) => { clearMessages(); - setMode(nextMode); - if (nextMode === 'login') resetPasswordFlow(); + setMode(next); + if (next === 'login') { + setResetStep('request'); + setResetCode(''); setChallengeId(''); setMaskedEmail(''); + setNewPassword(''); setConfirmPassword(''); + } }; - onMount(() => { - if (hasAdminSession()) navigate('/admin', { replace: true }); - }); + onMount(() => { if (hasAdminSession()) navigate('/admin', { replace: true }); }); const completeAdminLogin = () => { setAdminSession(); - const params = new URLSearchParams(window.location.search); - const from = params.get('from'); + const from = new URLSearchParams(window.location.search).get('from'); navigate(from && from.startsWith('/admin') ? from : '/admin', { replace: true }); }; const directSignIn = async () => { clearMessages(); - if (!canSubmitLoginCredentials()) { setError('Email and password are required.'); return; } + if (!canSubmitLogin()) { setError('Email and password are required.'); return; } setIsSubmitting(true); try { - const loginPayload = { email: email().trim().toLowerCase(), password: password(), loginTarget: 'admin' }; - const attempts = ['/api/gateway/users/auth/internal/login', '/api/gateway/auth/internal/login']; - let payload: any = {}; - let status = 500; - let success = false; - for (const url of attempts) { - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'x-portal-target': 'admin' }, - credentials: 'include', - body: JSON.stringify(loginPayload), - }); - status = response.status; - payload = await response.json().catch(() => ({})); - if (response.ok) { success = true; break; } + 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; + for (const url of ['/api/gateway/users/auth/internal/login', '/api/gateway/auth/internal/login']) { + const r = await fetch(url, { method: 'POST', headers, credentials: 'include', body }); + status = r.status; payload = await r.json().catch(() => ({})); + if (r.ok) { success = true; break; } } 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 accessToken = String(payload?.access_token || payload?.accessToken || '').trim(); - if (accessToken) sessionStorage.setItem('nxtgauge_admin_access_token', accessToken); + 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); - } + } catch (e: any) { setError(String(e?.message || 'Sign in failed.')); } + finally { setIsSubmitting(false); } }; const requestResetCode = async () => { clearMessages(); - if (!canSubmitResetRequest()) { setError('Email, new password, and confirm password are required.'); return; } + if (!canSubmitResetRequest()) { setError('All fields are required.'); return; } if (newPassword() !== confirmPassword()) { setError('Passwords do not match.'); return; } const trimmedEmail = email().trim().toLowerCase(); - const resolvedPassword = newPassword().trim(); - const requestPayload = { email: trimmedEmail, newPassword: resolvedPassword, new_password: resolvedPassword, password: resolvedPassword }; - const attempts = [ - { url: '/api/gateway/users/auth/internal/forgot-password/request-code', body: JSON.stringify(requestPayload) }, - { url: '/api/gateway/auth/internal/forgot-password/request-code', body: JSON.stringify(requestPayload) }, - { url: '/api/gateway/users/auth/internal/forgot-password/request-code', body: JSON.stringify({ data: requestPayload }) }, - { url: '/api/gateway/auth/internal/forgot-password/request-code', body: JSON.stringify({ data: requestPayload }) }, - ]; + 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 attempt of attempts) { - const response = await fetch(attempt.url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: attempt.body }); - status = response.status; - payload = await response.json().catch(() => ({})); - if (response.ok) break; + let payload: any = {}; let status = 500; + for (const { url, body } of [ + { url: '/api/gateway/users/auth/internal/forgot-password/request-code', body: JSON.stringify(reqBody) }, + { url: '/api/gateway/auth/internal/forgot-password/request-code', body: JSON.stringify(reqBody) }, + { url: '/api/gateway/users/auth/internal/forgot-password/request-code', body: JSON.stringify({ data: reqBody }) }, + { url: '/api/gateway/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 nextChallengeId = pickChallengeId(payload); - if (!nextChallengeId) { - const fallback = status === 502 ? 'Verification service unavailable (502). Please retry in 1–2 minutes.' : 'Failed to send reset code.'; + 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(nextChallengeId); + setChallengeId(cId); setMaskedEmail(pickMaskedEmail(payload, trimmedEmail)); setResetStep('verify'); - const debugCode = String(payload?.debugCode || payload?.data?.debugCode || '').trim(); - setInfo(debugCode ? `Reset code sent. [DEV: ${debugCode}]` : 'Reset code sent to your email.'); - } catch (e: any) { - setError(String(e?.message || 'Failed to send reset code.')); - } finally { - setIsSubmitting(false); - } + 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 resolvedPassword = newPassword().trim(); - const resolvedChallengeId = challengeId().trim(); - const resolvedCode = resetCode().trim(); - const verifyPayload = { - challengeId: resolvedChallengeId, challenge_id: resolvedChallengeId, - code: resolvedCode, otp: resolvedCode, verificationCode: resolvedCode, verification_code: resolvedCode, - newPassword: resolvedPassword, new_password: resolvedPassword, password: resolvedPassword, - }; - const attempts = ['/api/gateway/users/auth/internal/forgot-password/verify-code', '/api/gateway/auth/internal/forgot-password/verify-code']; + 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 attempts) { - const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify(verifyPayload) }); - status = response.status; - payload = await response.json().catch(() => ({})); - if (response.ok) { success = true; break; } + let payload: any = {}; let status = 500; let success = false; + for (const url of ['/api/gateway/users/auth/internal/forgot-password/verify-code', '/api/gateway/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 ? 'Verification service unavailable (502). Please retry in 1–2 minutes.' : 'Password reset failed.'; + 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'); + 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); - } + } catch (e: any) { setError(String(e?.message || 'Password reset failed.')); } + finally { setIsSubmitting(false); } }; return ( -
- - {/* ── Left brand panel ── */} -