From 152f918a7b8a5d3843280da8d59a27139c578701 Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Thu, 16 Apr 2026 17:29:46 +0200 Subject: [PATCH] fix(auth): correct resend-otp API endpoint path - Change from /api/gateway/api/auth/resend-otp to /api/auth/resend-otp - Fix in signup.tsx and login.tsx - Gateway already proxies /api/auth/* to users service --- src/routes/login.tsx | 273 +++++++++++++++++++++++++----------------- src/routes/signup.tsx | 2 +- 2 files changed, 166 insertions(+), 109 deletions(-) diff --git a/src/routes/login.tsx b/src/routes/login.tsx index 28458b1..3a604e1 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -1,27 +1,32 @@ -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'; +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'; +type RoleKey = "company" | "job_seeker" | "professional" | "customer"; function normalizeRoleValue(value: unknown): string { - return String(value || '').trim().toUpperCase().replace(/\s+/g, '_'); + 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']; + 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(); + const storedEmail = String(parsed?.email || "") + .trim() + .toLowerCase(); if (emailHint && storedEmail && storedEmail !== emailHint.trim().toLowerCase()) continue; const selectedProfessionalRole = normalizeRoleValue(parsed?.selectedProfessionalRole); @@ -42,12 +47,12 @@ function resolveActiveRole(rawBackendRole: unknown, emailHint?: string): string if (backendRole) return backendRole; const preferredRole = getStoredPreferredRole(emailHint); if (preferredRole) return preferredRole; - return preferredRole || 'JOB_SEEKER'; + return preferredRole || "JOB_SEEKER"; } function makeCaptcha() { - const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; - return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); + const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join(""); } function PasswordVisibilityIcon(props: { visible: boolean }) { @@ -72,45 +77,45 @@ function PasswordVisibilityIcon(props: { visible: boolean }) { export default function LoginRoute() { const navigate = useNavigate(); const auth = useAuth(); - const [email, setEmail] = createSignal(''); - const [password, setPassword] = createSignal(''); - const [otp, setOtp] = createSignal(['', '', '', '', '', '']); + 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 [captchaInput, setCaptchaInput] = createSignal(""); + const [error, setError] = createSignal(""); const [submitting, setSubmitting] = createSignal(false); - const [roleGuess, setRoleGuess] = createSignal('job_seeker'); - const [roleHint, setRoleHint] = createSignal(''); + const [roleGuess, setRoleGuess] = createSignal("job_seeker"); + const [roleHint, setRoleHint] = createSignal(""); const [checkingRole, setCheckingRole] = createSignal(false); - const otpCode = createMemo(() => otp().join('')); + const otpCode = createMemo(() => otp().join("")); const formatRoleLabel = (value: string): string => - String(value || '') + String(value || "") .trim() - .replace(/[_\s]+/g, ' ') + .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(''); + 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', + 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(''); + setRoleHint(""); return; } const detectedRole = normalizeRoleValue( @@ -118,27 +123,28 @@ export default function LoginRoute() { ); if (!detectedRole) { const fallbackRole = normalizeRoleValue(getStoredPreferredRole(normalized)); - setRoleHint( - fallbackRole - ? `Role: ${formatRoleLabel(fallbackRole)}` - : 'Role: Not assigned' - ); + 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') { + if ( + roleLower === "company" || + roleLower === "customer" || + roleLower === "job_seeker" || + roleLower === "professional" + ) { setRoleGuess(roleLower as RoleKey); } } catch { - setRoleHint(''); + setRoleHint(""); } finally { setCheckingRole(false); } }; const setOtpDigit = (index: number, value: string) => { - const clean = value.replace(/\D/g, '').slice(0, 1); + const clean = value.replace(/\D/g, "").slice(0, 1); setOtp((prev) => { const next = prev.slice(); next[index] = clean; @@ -151,60 +157,60 @@ export default function LoginRoute() { }; const saveUser = (user: any) => { - const fullName = String(user?.full_name || user?.fullName || '').trim(); - const [firstName, ...rest] = fullName.split(' '); - const lastName = rest.join(' '); + 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 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(), + 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', + 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)); + 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(''); + setError(""); if (!isValidEmail(email())) { - setError('Enter a valid email address.'); + setError("Enter a valid email address."); return; } if (!password().trim()) { - setError('Password is required.'); + setError("Password is required."); return; } if (!captchaInput().trim() || captchaInput().trim().toUpperCase() !== captcha().toUpperCase()) { - setError('Captcha does not match. Please try again.'); + setError("Captcha does not match. Please try again."); setCaptcha(makeCaptcha()); - setCaptchaInput(''); + 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', + 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(), @@ -212,20 +218,20 @@ export default function LoginRoute() { }); const data = await res.json().catch(() => ({})); if (!res.ok) { - const code = String(data?.code || '').toUpperCase(); - if (code === 'EMAIL_NOT_VERIFIED') { + const code = String(data?.code || "").toUpperCase(); + if (code === "EMAIL_NOT_VERIFIED") { setShowVerify(true); - setError('Email not verified. Enter OTP sent to your inbox.'); + setError("Email not verified. Enter OTP sent to your inbox."); return; } - setError(String(data?.error || data?.message || 'Invalid login credentials.')); + 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 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, @@ -233,9 +239,9 @@ export default function LoginRoute() { ); const normalizedEmail = email().trim().toLowerCase(); const userPayload = { - id: String(data?.user?.id || ''), + id: String(data?.user?.id || ""), email: String(data?.user?.email || normalizedEmail), - full_name: String(data?.user?.full_name || ''), + full_name: String(data?.user?.full_name || ""), active_role: resolvedActiveRole, email_verified: Boolean(data?.user?.email_verified ?? true), }; @@ -243,9 +249,9 @@ export default function LoginRoute() { if (auth.setUser) { auth.setUser(userPayload); } - navigate('/dashboard', { replace: true }); + navigate("/dashboard", { replace: true }); } catch { - setError('Network error during login. Please try again.'); + setError("Network error during login. Please try again."); } finally { setSubmitting(false); } @@ -253,18 +259,18 @@ export default function LoginRoute() { const resendOtp = async () => { if (submitting()) return; - setError(''); + 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', + const res = await fetch("/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.')); + setError(String(data?.error || data?.message || "Unable to resend OTP.")); } } finally { setSubmitting(false); @@ -273,22 +279,22 @@ export default function LoginRoute() { const verifyThenLogin = async () => { if (submitting()) return; - setError(''); + setError(""); if (otpCode().length !== 6) { - setError('Enter a valid 6-digit OTP.'); + 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', + 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.')); + setError(String(verifyData?.error || verifyData?.message || "OTP verification failed.")); return; } await login(); @@ -309,7 +315,9 @@ export default function LoginRoute() {

Public Workspace

Welcome Back To Nxtgauge

-

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

+

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

@@ -317,7 +325,9 @@ export default function LoginRoute() {

Sign In

- + -

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

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

-

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

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

- +
- setPassword(e.currentTarget.value)} placeholder="Enter your password" /> + setPassword(e.currentTarget.value)} + placeholder="Enter your password" + /> @@ -361,9 +385,23 @@ export default function LoginRoute() {
- + - setCaptchaInput(e.currentTarget.value)} placeholder="Enter captcha" /> + setCaptchaInput(e.currentTarget.value)} + placeholder="Enter captcha" + />
@@ -385,26 +423,45 @@ export default function LoginRoute() {
- - diff --git a/src/routes/signup.tsx b/src/routes/signup.tsx index 3d82b6a..6c653a8 100644 --- a/src/routes/signup.tsx +++ b/src/routes/signup.tsx @@ -272,7 +272,7 @@ export default function SignupRoute() { setServerError(""); setSubmitting(true); try { - const res = await fetch("/api/gateway/api/auth/resend-otp", { + const res = await fetch("/api/auth/resend-otp", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, credentials: "include",