import { A, useNavigate, useSearchParams } from '@solidjs/router'; import { createMemo, createSignal, For, onMount, Show } from 'solid-js'; import PublicBackground from '~/components/PublicBackground'; import PublicHeader from '~/components/PublicHeader'; import CaptchaCanvas from '~/components/CaptchaCanvas'; import { checkPasswordStrength, isPasswordStrong, isValidCaptcha, isValidEmail, isValidName, validateRegisterForm, } from '~/lib/form-validation'; type RoleKey = 'company' | 'job_seeker' | 'professional' | 'customer'; type RegisterErrors = Record; function normalizeIntent(intent: string | null | undefined): RoleKey { const v = String(intent || '').toLowerCase(); if (v.includes('company')) return 'company'; if (v.includes('professional')) return 'professional'; if (v.includes('developer') || v.includes('photographer') || v.includes('makeup') || v.includes('tutor') || v.includes('video') || v.includes('graphic') || v.includes('social') || v.includes('fitness') || v.includes('catering') || v.includes('ugc')) return 'professional'; if (v.includes('customer')) return 'customer'; return 'job_seeker'; } function randomCaptcha(length = 6): string { const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; let out = ''; for (let i = 0; i < length; i += 1) { out += alphabet[Math.floor(Math.random() * alphabet.length)]; } return out; } function PasswordVisibilityIcon(props: { visible: boolean }) { if (props.visible) { return ( ); } return ( ); } export default function SignupRoute() { const navigate = useNavigate(); const [search] = useSearchParams(); onMount(() => { const intent = String(search.intent || '').trim(); const role = String(search.role || '').trim(); if (!intent && !role) navigate('/users/choose-role', { replace: true }); }); const [step, setStep] = createSignal<'register' | 'verify'>('register'); const [firstName, setFirstName] = createSignal(''); const [lastName, setLastName] = createSignal(''); const [email, setEmail] = createSignal(''); const [password, setPassword] = createSignal(''); const [confirmPassword, setConfirmPassword] = createSignal(''); const role = createMemo(() => normalizeIntent(search.intent || search.role)); const selectedProfessionalRole = createMemo(() => String(search.role || '').trim().toUpperCase()); const [termsAccepted, setTermsAccepted] = createSignal(false); const [captcha, setCaptcha] = createSignal(''); const [captchaCode, setCaptchaCode] = createSignal(randomCaptcha()); const [otp, setOtp] = createSignal(['', '', '', '', '', '']); const [errors, setErrors] = createSignal({}); const [serverError, setServerError] = createSignal(''); const [emailExists, setEmailExists] = createSignal(false); const [submitting, setSubmitting] = createSignal(false); const [pendingEmail, setPendingEmail] = createSignal(''); const [pendingPassword, setPendingPassword] = createSignal(''); const [showPassword, setShowPassword] = createSignal(false); const [showConfirmPassword, setShowConfirmPassword] = createSignal(false); const passwordChecks = createMemo(() => checkPasswordStrength(password(), confirmPassword())); const otpCode = createMemo(() => otp().join('')); const firstNameValid = createMemo(() => !firstName().trim() || isValidName(firstName())); const lastNameValid = createMemo(() => !lastName().trim() || isValidName(lastName())); const emailValid = createMemo(() => !email().trim() || isValidEmail(email())); const canSubmit = createMemo(() => firstName().trim().length > 0 && firstNameValid() && lastName().trim().length > 0 && lastNameValid() && emailValid() && isValidEmail(email()) && isPasswordStrong(passwordChecks()) && passwordChecks().match && isValidCaptcha(captcha(), captchaCode()) && termsAccepted() && !emailExists() ); const refreshCaptcha = () => { setCaptcha(''); setCaptchaCode(randomCaptcha()); }; const checkEmailExists = async (emailValue: string) => { const normalized = emailValue.trim().toLowerCase(); if (!normalized || !isValidEmail(normalized)) { setEmailExists(false); return false; } 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(() => ({})); const exists = Boolean(response.ok && payload?.exists); setEmailExists(exists); return exists; } catch { setEmailExists(false); return 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(`#otp-${index + 1}`); if (nextEl) nextEl.focus(); } }; const saveUserForDashboard = (input: { firstName: string; lastName: string; email: string; roleKey: RoleKey; user?: any }) => { const fullName = `${input.firstName} ${input.lastName}`.trim(); const payload = { firstName: input.firstName, lastName: input.lastName, fullName, name: fullName, displayName: fullName, email: input.email.toLowerCase(), roleKey: input.roleKey, role: input.roleKey, selectedProfessionalRole: selectedProfessionalRole() || null, user: input.user || null, }; if (typeof window !== 'undefined') { window.localStorage.setItem('nxtgauge_signup_profile_v1', JSON.stringify(payload)); window.localStorage.setItem('nxtgauge_auth_user', JSON.stringify(payload)); window.localStorage.setItem('nxtgauge_user', JSON.stringify(payload)); } }; const storeAuth = (payload: any) => { const accessToken = String(payload?.access_token || '').trim(); if (typeof window !== 'undefined' && accessToken) { window.sessionStorage.setItem('nxtgauge_access_token', accessToken); window.sessionStorage.setItem('nxtgauge_frontend_access_token', accessToken); window.localStorage.setItem('nxtgauge_access_token', accessToken); } }; const register = async () => { setServerError(''); const validation = validateRegisterForm({ firstName: firstName(), lastName: lastName(), email: email(), password: password(), confirmPassword: confirmPassword(), captcha: captcha(), expectedCaptcha: captchaCode(), termsAccepted: termsAccepted(), }); setErrors(validation.errors); if (!validation.isValid) return; setSubmitting(true); try { const res = await fetch('/api/gateway/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ full_name: `${firstName().trim()} ${lastName().trim()}`.trim(), email: email().trim().toLowerCase(), password: password(), phone: null, }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { setServerError(String(data?.error || data?.message || 'Unable to create account.')); refreshCaptcha(); return; } const cleanEmail = email().trim().toLowerCase(); setPendingEmail(cleanEmail); setPendingPassword(password()); saveUserForDashboard({ firstName: firstName().trim(), lastName: lastName().trim(), email: cleanEmail, roleKey: role(), }); setStep('verify'); setOtp(['', '', '', '', '', '']); } finally { setSubmitting(false); } }; const verifyOtp = async () => { setServerError(''); if (otpCode().length !== 6) { setServerError('Enter the 6-digit code sent to your email.'); 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) { setServerError(String(verifyData?.error || verifyData?.message || 'Verification failed.')); return; } const loginRes = await fetch('/api/gateway/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, credentials: 'include', body: JSON.stringify({ email: pendingEmail() || email().trim().toLowerCase(), password: pendingPassword() || password(), }), }); const loginData = await loginRes.json().catch(() => ({})); if (!loginRes.ok) { setServerError(String(loginData?.error || loginData?.message || 'Email verified. Please login manually.')); navigate('/login', { replace: true }); return; } storeAuth(loginData); saveUserForDashboard({ firstName: firstName().trim(), lastName: lastName().trim(), email: pendingEmail() || email().trim().toLowerCase(), roleKey: role(), user: loginData?.user, }); navigate('/dashboard', { replace: true }); } finally { setSubmitting(false); } }; const resendOtp = async () => { setServerError(''); 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: pendingEmail() || email().trim().toLowerCase() }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { setServerError(String(data?.error || data?.message || 'Unable to resend OTP right now.')); } } finally { setSubmitting(false); } }; return (
Get Started

Get Started

Create Your Nxtgauge Account

Join verified opportunities and continue directly to your dashboard after signup.

Verify Email

Enter the 6-digit code sent to {pendingEmail() || email()}.

index)}> {(index) => ( setOtpDigit(index, e.currentTarget.value)} /> )}
}>

Create Your Account

Sign up first, then go directly to dashboard after email verification.

setFirstName(e.currentTarget.value)} />

{firstName().trim() && firstNameValid() ? '✓ First name looks good' : '• First name is required'}

setLastName(e.currentTarget.value)} />

{lastName().trim() && lastNameValid() ? '✓ Last name looks good' : '• Last name is required'}

{ setEmail(e.currentTarget.value); setEmailExists(false); }} onBlur={() => { void checkEmailExists(email()); }} />

{emailExists() ? '• This email is already registered' : (email().trim() && emailValid() ? '✓ Valid email format' : '• Enter a valid email format')}

setPassword(e.currentTarget.value)} />

{passwordChecks().minLength ? '✓' : '•'} 8+ chars

{passwordChecks().uppercase ? '✓' : '•'} Uppercase

{passwordChecks().special ? '✓' : '•'} Special

{passwordChecks().lowercase ? '✓' : '•'} Lowercase

{passwordChecks().number ? '✓' : '•'} Number

setConfirmPassword(e.currentTarget.value)} />

{confirmPassword() && passwordChecks().match ? '✓ Passwords match' : '• Passwords do not match'}

setCaptcha(e.currentTarget.value.toUpperCase())} placeholder="Enter captcha" />

{captcha() ? (isValidCaptcha(captcha(), captchaCode()) ? '✓ Captcha matched' : '• Captcha does not match') : '• Enter captcha to continue'}

{serverError()}

); }