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(() => { // Legacy redirect to choose-role removed for dashboard-first flow. // If no intent/role provided, normalizeIntent will default to job_seeker. }); 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 [verifiedSuccess, setVerifiedSuccess] = createSignal(false); 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 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({ first_name: firstName().trim(), last_name: lastName().trim(), email: email().trim().toLowerCase(), password: password(), phone: "", intent: role(), role_key: selectedProfessionalRole() || undefined, }), }); 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); setVerifiedSuccess(false); 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; } setVerifiedSuccess(true); setTimeout(() => navigate("/login?verified=1", { replace: true }), 1400); } 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()}.

Your email has been verified.

Redirecting to login...

} >
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()}

); }