import { A, useNavigate } from '@solidjs/router'; import { createMemo, createSignal, For, Show } from 'solid-js'; import { useAuth } from '~/lib/auth'; import PublicBackground from '~/components/PublicBackground'; import PublicHeader from '~/components/PublicHeader'; import CaptchaCanvas from '~/components/CaptchaCanvas'; import { isValidEmail } from '~/lib/form-validation'; type RoleKey = 'company' | 'job_seeker' | 'professional' | 'customer'; function normalizeRoleValue(value: unknown): string { return String(value || '').trim().toUpperCase().replace(/\s+/g, '_'); } function getStoredPreferredRole(emailHint?: string): string | null { if (typeof window === 'undefined') return null; const keys = ['nxtgauge_signup_profile_v1', 'nxtgauge_auth_user', 'nxtgauge_user']; for (const key of keys) { const raw = window.localStorage.getItem(key); if (!raw) continue; try { const parsed = JSON.parse(raw) as Record; const storedEmail = String(parsed?.email || '').trim().toLowerCase(); if (emailHint && storedEmail && storedEmail !== emailHint.trim().toLowerCase()) continue; const selectedProfessionalRole = normalizeRoleValue(parsed?.selectedProfessionalRole); if (selectedProfessionalRole) return selectedProfessionalRole; const activeRole = normalizeRoleValue(parsed?.active_role || parsed?.role); if (activeRole) return activeRole; } catch { // Ignore malformed local storage payloads. } } return null; } function resolveActiveRole(rawBackendRole: unknown, emailHint?: string): string { const backendRole = normalizeRoleValue(rawBackendRole); if (backendRole) return backendRole; const preferredRole = getStoredPreferredRole(emailHint); if (preferredRole) return preferredRole; return preferredRole || 'JOB_SEEKER'; } function makeCaptcha() { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); } function PasswordVisibilityIcon(props: { visible: boolean }) { if (props.visible) { return ( ); } return ( ); } export default function LoginRoute() { const navigate = useNavigate(); const auth = useAuth(); const [email, setEmail] = createSignal(''); const [password, setPassword] = createSignal(''); const [otp, setOtp] = createSignal(['', '', '', '', '', '']); const [showVerify, setShowVerify] = createSignal(false); const [showPassword, setShowPassword] = createSignal(false); const [captcha, setCaptcha] = createSignal(makeCaptcha()); const [captchaInput, setCaptchaInput] = createSignal(''); const [error, setError] = createSignal(''); const [submitting, setSubmitting] = createSignal(false); const [roleGuess, setRoleGuess] = createSignal('job_seeker'); const [roleHint, setRoleHint] = createSignal(''); const [checkingRole, setCheckingRole] = createSignal(false); const otpCode = createMemo(() => otp().join('')); const formatRoleLabel = (value: string): string => String(value || '') .trim() .replace(/[_\s]+/g, ' ') .toLowerCase() .replace(/\b\w/g, (ch) => ch.toUpperCase()); const lookupRoleByEmail = async (emailValue: string) => { const normalized = emailValue.trim().toLowerCase(); if (!normalized || !isValidEmail(normalized)) { setRoleHint(''); return; } setCheckingRole(true); try { const response = await fetch('/api/gateway/api/auth/check-email', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ email: normalized }), }); const payload = await response.json().catch(() => ({})); if (!response.ok || !payload?.exists) { setRoleHint(''); return; } const detectedRole = normalizeRoleValue( payload?.active_role || payload?.role || payload?.roles?.[0] ); if (!detectedRole) { const fallbackRole = normalizeRoleValue(getStoredPreferredRole(normalized)); setRoleHint( fallbackRole ? `Role: ${formatRoleLabel(fallbackRole)}` : 'Role: Not assigned' ); return; } setRoleHint(`Role: ${formatRoleLabel(detectedRole)}`); const roleLower = detectedRole.toLowerCase(); if (roleLower === 'company' || roleLower === 'customer' || roleLower === 'job_seeker' || roleLower === 'professional') { setRoleGuess(roleLower as RoleKey); } } catch { setRoleHint(''); } finally { setCheckingRole(false); } }; const setOtpDigit = (index: number, value: string) => { const clean = value.replace(/\D/g, '').slice(0, 1); setOtp((prev) => { const next = prev.slice(); next[index] = clean; return next; }); if (clean) { const nextEl = document.querySelector(`#login-otp-${index + 1}`); if (nextEl) nextEl.focus(); } }; const saveUser = (user: any) => { const fullName = String(user?.full_name || user?.fullName || '').trim(); const [firstName, ...rest] = fullName.split(' '); const lastName = rest.join(' '); const normalizedRole = resolveActiveRole( user?.active_role || user?.role || roleGuess(), String(user?.email || email()) ); const storedRole = normalizedRole ? normalizedRole.toLowerCase() : roleGuess(); const selectedProfessionalRole = getStoredPreferredRole(String(user?.email || email())); const payload = { firstName: firstName || '', lastName: lastName || '', fullName: fullName || '', name: fullName || '', displayName: fullName || '', email: String(user?.email || email()).trim().toLowerCase(), roleKey: storedRole, role: storedRole, active_role: normalizedRole || 'JOB_SEEKER', selectedProfessionalRole: selectedProfessionalRole || null, user, }; if (typeof window !== 'undefined') { window.localStorage.setItem('nxtgauge_auth_user', JSON.stringify(payload)); window.localStorage.setItem('nxtgauge_user', JSON.stringify(payload)); window.localStorage.setItem('nxtgauge_signup_profile_v1', JSON.stringify(payload)); } }; const login = async () => { if (submitting()) return; setError(''); if (!isValidEmail(email())) { setError('Enter a valid email address.'); return; } if (!password().trim()) { setError('Password is required.'); return; } if (!captchaInput().trim() || captchaInput().trim().toUpperCase() !== captcha().toUpperCase()) { setError('Captcha does not match. Please try again.'); setCaptcha(makeCaptcha()); setCaptchaInput(''); return; } setSubmitting(true); try { const res = await fetch('/api/gateway/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ email: email().trim().toLowerCase(), password: password(), }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { const code = String(data?.code || '').toUpperCase(); if (code === 'EMAIL_NOT_VERIFIED') { setShowVerify(true); setError('Email not verified. Enter OTP sent to your inbox.'); return; } setError(String(data?.error || data?.message || 'Invalid login credentials.')); return; } const accessToken = String(data?.access_token || '').trim(); if (typeof window !== 'undefined' && accessToken) { window.sessionStorage.setItem('nxtgauge_access_token', accessToken); window.sessionStorage.setItem('nxtgauge_frontend_access_token', accessToken); } const resolvedActiveRole = resolveActiveRole( data?.user?.active_role || data?.user?.role, data?.user?.email || email().trim().toLowerCase() ); const normalizedEmail = email().trim().toLowerCase(); const userPayload = { id: String(data?.user?.id || ''), email: String(data?.user?.email || normalizedEmail), full_name: String(data?.user?.full_name || ''), active_role: resolvedActiveRole, email_verified: Boolean(data?.user?.email_verified ?? true), }; saveUser({ ...(data?.user || {}), ...userPayload }); if (auth.setUser) { auth.setUser(userPayload); } navigate('/dashboard', { replace: true }); } catch { setError('Network error during login. Please try again.'); } finally { setSubmitting(false); } }; const resendOtp = async () => { if (submitting()) return; setError(''); setSubmitting(true); try { const res = await fetch('/api/gateway/api/auth/resend-otp', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ email: email().trim().toLowerCase() }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { setError(String(data?.error || data?.message || 'Unable to resend OTP.')); } } finally { setSubmitting(false); } }; const verifyThenLogin = async () => { if (submitting()) return; setError(''); if (otpCode().length !== 6) { setError('Enter a valid 6-digit OTP.'); return; } setSubmitting(true); try { const verifyRes = await fetch('/api/gateway/api/auth/verify-email', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ otp: otpCode() }), }); const verifyData = await verifyRes.json().catch(() => ({})); if (!verifyRes.ok) { setError(String(verifyData?.error || verifyData?.message || 'OTP verification failed.')); return; } await login(); } finally { setSubmitting(false); } }; return (
Public Workspace

Public Workspace

Welcome Back To Nxtgauge

Sign in to manage your profile, portfolio, and verification in one place.

Sign In

{ const value = e.currentTarget.value; setEmail(value); void lookupRoleByEmail(value); }} onBlur={(e) => { void lookupRoleByEmail(e.currentTarget.value); }} placeholder="Enter your email" />

{email().trim() && isValidEmail(email()) ? '✓ Valid email format' : '• Enter a valid email format'}

{checkingRole() ? 'Checking account role...' : `• ${roleHint()}`}

setPassword(e.currentTarget.value)} placeholder="Enter your password" />
setCaptchaInput(e.currentTarget.value)} placeholder="Enter captcha" />
index)}> {(index) => ( setOtpDigit(index, e.currentTarget.value)} /> )}

{error()}

); }