2026-03-19 03:36:46 +01:00
import { useNavigate } from '@solidjs/router' ;
2026-03-24 02:36:40 +01:00
import { Show , createMemo , createSignal , onMount } from 'solid-js' ;
2026-03-20 22:37:17 +01:00
import { isExternalIdentity , pickManagementLoginError } from '~/lib/admin-auth' ;
2026-03-19 03:36:46 +01:00
import { hasAdminSession , setAdminSession } from '~/lib/admin-session' ;
type AuthMode = 'login' | 'reset' ;
type ResetStep = 'request' | 'verify' ;
function pickChallengeId ( payload : any ) : string {
const direct = String ( payload ? . challengeId || '' ) . trim ( ) ;
if ( direct ) return direct ;
const nested = String ( payload ? . data ? . challengeId || '' ) . trim ( ) ;
if ( nested ) return nested ;
2026-03-24 16:01:07 +01:00
return String ( payload ? . challenge_id || '' ) . trim ( ) ;
2026-03-19 03:36:46 +01:00
}
function pickMaskedEmail ( payload : any , fallback : string ) : string {
const direct = String ( payload ? . maskedEmail || '' ) . trim ( ) ;
if ( direct ) return direct ;
const nested = String ( payload ? . data ? . maskedEmail || '' ) . trim ( ) ;
2026-03-24 16:01:07 +01:00
return nested || fallback ;
2026-03-19 03:36:46 +01:00
}
2026-03-24 16:01:07 +01:00
/* Matches public website input exactly */
2026-03-24 15:57:28 +01:00
const inputCls =
2026-03-24 16:01:07 +01:00
'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]' ;
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
const labelCls =
'mb-2 block text-xs font-semibold uppercase tracking-[0.11em] text-[#4b546f]' ;
2026-03-24 15:57:28 +01:00
2026-03-19 03:36:46 +01:00
export default function LoginPage() {
const navigate = useNavigate ( ) ;
const [ mode , setMode ] = createSignal < AuthMode > ( 'login' ) ;
const [ resetStep , setResetStep ] = createSignal < ResetStep > ( 'request' ) ;
const [ email , setEmail ] = createSignal ( '' ) ;
const [ password , setPassword ] = createSignal ( '' ) ;
2026-03-24 16:01:07 +01:00
const [ showPassword , setShowPassword ] = createSignal ( false ) ;
2026-03-19 03:36:46 +01:00
const [ resetCode , setResetCode ] = createSignal ( '' ) ;
const [ newPassword , setNewPassword ] = createSignal ( '' ) ;
const [ confirmPassword , setConfirmPassword ] = createSignal ( '' ) ;
const [ challengeId , setChallengeId ] = createSignal ( '' ) ;
const [ maskedEmail , setMaskedEmail ] = createSignal ( '' ) ;
const [ isSubmitting , setIsSubmitting ] = createSignal ( false ) ;
const [ error , setError ] = createSignal ( '' ) ;
const [ info , setInfo ] = createSignal ( '' ) ;
2026-03-24 16:01:07 +01:00
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 ) ;
2026-03-19 03:36:46 +01:00
2026-03-24 15:57:28 +01:00
const clearMessages = ( ) = > { setError ( '' ) ; setInfo ( '' ) ; } ;
2026-03-19 03:36:46 +01:00
2026-03-24 16:01:07 +01:00
const switchMode = ( next : AuthMode ) = > {
2026-03-19 03:36:46 +01:00
clearMessages ( ) ;
2026-03-24 16:01:07 +01:00
setMode ( next ) ;
if ( next === 'login' ) {
setResetStep ( 'request' ) ;
setResetCode ( '' ) ; setChallengeId ( '' ) ; setMaskedEmail ( '' ) ;
setNewPassword ( '' ) ; setConfirmPassword ( '' ) ;
}
2026-03-19 03:36:46 +01:00
} ;
2026-03-24 16:01:07 +01:00
onMount ( ( ) = > { if ( hasAdminSession ( ) ) navigate ( '/admin' , { replace : true } ) ; } ) ;
2026-03-19 03:36:46 +01:00
const completeAdminLogin = ( ) = > {
setAdminSession ( ) ;
2026-03-24 16:01:07 +01:00
const from = new URLSearchParams ( window . location . search ) . get ( 'from' ) ;
2026-03-24 15:57:28 +01:00
navigate ( from && from . startsWith ( '/admin' ) ? from : '/admin' , { replace : true } ) ;
2026-03-19 03:36:46 +01:00
} ;
const directSignIn = async ( ) = > {
clearMessages ( ) ;
2026-03-24 16:01:07 +01:00
if ( ! canSubmitLogin ( ) ) { setError ( 'Email and password are required.' ) ; return ; }
2026-03-19 03:36:46 +01:00
setIsSubmitting ( true ) ;
try {
2026-03-24 16:01:07 +01:00
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 ; }
2026-03-20 22:37:17 +01:00
}
if ( ! success ) {
2026-03-24 15:57:28 +01:00
const fallback = status === 502 ? 'Auth service unavailable (502). Please retry in 1– 2 minutes.' : 'Sign in failed.' ;
2026-03-20 22:37:17 +01:00
throw new Error ( pickManagementLoginError ( payload ) || fallback ) ;
}
2026-03-24 15:57:28 +01:00
if ( isExternalIdentity ( payload ) ) throw new Error ( 'External users cannot use this portal.' ) ;
2026-03-24 16:01:07 +01:00
const token = String ( payload ? . access_token || payload ? . accessToken || '' ) . trim ( ) ;
if ( token ) sessionStorage . setItem ( 'nxtgauge_admin_access_token' , token ) ;
2026-03-19 03:36:46 +01:00
completeAdminLogin ( ) ;
2026-03-24 16:01:07 +01:00
} catch ( e : any ) { setError ( String ( e ? . message || 'Sign in failed.' ) ) ; }
finally { setIsSubmitting ( false ) ; }
2026-03-19 03:36:46 +01:00
} ;
const requestResetCode = async ( ) = > {
clearMessages ( ) ;
2026-03-24 16:01:07 +01:00
if ( ! canSubmitResetRequest ( ) ) { setError ( 'All fields are required.' ) ; return ; }
2026-03-24 15:57:28 +01:00
if ( newPassword ( ) !== confirmPassword ( ) ) { setError ( 'Passwords do not match.' ) ; return ; }
2026-03-19 03:36:46 +01:00
const trimmedEmail = email ( ) . trim ( ) . toLowerCase ( ) ;
2026-03-24 16:01:07 +01:00
const pw = newPassword ( ) . trim ( ) ;
const reqBody = { email : trimmedEmail , newPassword : pw , new_password : pw , password : pw } ;
2026-03-19 03:36:46 +01:00
setIsSubmitting ( true ) ;
try {
2026-03-24 16:01:07 +01:00
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 ;
2026-03-19 03:36:46 +01:00
}
2026-03-24 16:01:07 +01:00
const cId = pickChallengeId ( payload ) ;
if ( ! cId ) {
const fallback = status === 502 ? 'Service unavailable (502). Please retry.' : 'Failed to send reset code.' ;
2026-03-24 15:57:28 +01:00
throw new Error ( String ( payload ? . message || payload ? . error || fallback ) . trim ( ) ) ;
2026-03-19 03:36:46 +01:00
}
2026-03-24 16:01:07 +01:00
setChallengeId ( cId ) ;
2026-03-19 03:36:46 +01:00
setMaskedEmail ( pickMaskedEmail ( payload , trimmedEmail ) ) ;
setResetStep ( 'verify' ) ;
2026-03-24 16:01:07 +01:00
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 ) ; }
2026-03-19 03:36:46 +01:00
} ;
const verifyResetCode = async ( ) = > {
clearMessages ( ) ;
2026-03-24 15:57:28 +01:00
if ( ! canSubmitResetVerify ( ) ) { setError ( 'A valid 6-digit code is required.' ) ; return ; }
if ( newPassword ( ) . trim ( ) !== confirmPassword ( ) . trim ( ) ) { setError ( 'Passwords do not match.' ) ; return ; }
2026-03-24 16:01:07 +01:00
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 } ) ;
2026-03-19 03:36:46 +01:00
setIsSubmitting ( true ) ;
try {
2026-03-24 16:01:07 +01:00
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 ; }
2026-03-19 03:36:46 +01:00
}
if ( ! success ) {
2026-03-24 16:01:07 +01:00
const fallback = status === 502 ? 'Service unavailable (502). Please retry.' : 'Password reset failed.' ;
2026-03-24 15:57:28 +01:00
throw new Error ( String ( payload ? . message || payload ? . error || fallback ) . trim ( ) ) ;
2026-03-19 03:36:46 +01:00
}
2026-03-24 16:01:07 +01:00
setPassword ( '' ) ; switchMode ( 'login' ) ;
2026-03-19 03:36:46 +01:00
setInfo ( 'Password reset successful. Please sign in with your new password.' ) ;
2026-03-24 16:01:07 +01:00
} catch ( e : any ) { setError ( String ( e ? . message || 'Password reset failed.' ) ) ; }
finally { setIsSubmitting ( false ) ; }
2026-03-19 03:36:46 +01:00
} ;
return (
2026-03-24 16:01:07 +01:00
< main
class = "relative min-h-screen overflow-x-clip"
style = "background: linear-gradient(135deg, #0a1d37 0%, #0d2347 40%, #0a1d37 70%, #081628 100%)"
>
{ /* Radial glow accents matching public site MarketingBackground feel */ }
< div class = "pointer-events-none absolute inset-0 overflow-hidden" >
< div class = "absolute -top-40 -left-40 h-[500px] w-[500px] rounded-full bg-[#fd6216]/8 blur-[100px]" / >
< div class = "absolute top-1/2 right-0 h-[400px] w-[400px] -translate-y-1/2 rounded-full bg-[#fd6216]/5 blur-[80px]" / >
< div class = "absolute bottom-0 left-1/3 h-[300px] w-[300px] rounded-full bg-white/3 blur-[60px]" / >
< / div >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
< div class = "relative z-10 mx-auto grid min-h-screen w-full max-w-[1260px] items-center gap-6 px-4 py-8 sm:px-6 lg:grid-cols-[1.02fr_0.98fr] lg:py-10" >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
{ /* ── Left brand panel (hidden on mobile) ── */ }
< section class = "relative hidden min-h-[620px] overflow-hidden rounded-[28px] border border-white/15 bg-white/5 p-10 text-white backdrop-blur-sm lg:flex lg:flex-col lg:justify-between" >
{ /* Inner glow */ }
< div class = "pointer-events-none absolute -top-24 -right-24 h-64 w-64 rounded-full bg-[#fd6216]/12 blur-3xl" / >
{ /* Logo */ }
2026-03-24 15:57:28 +01:00
< img src = "/nxtgauge-logo.png" alt = "NXTGAUGE" class = "h-10 w-auto brightness-0 invert" / >
2026-03-24 16:01:07 +01:00
{ /* Main copy */ }
< div class = "space-y-5" >
< p class = "inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-3 py-1 text-[11px] font-bold uppercase tracking-widest text-orange-300" >
< span class = "h-1.5 w-1.5 rounded-full bg-orange-400" / >
Internal Admin Portal
< / p >
< h1 class = "text-[40px] font-extrabold leading-tight text-white xl:text-[46px]" >
Welcome back < br / > to Nxtgauge .
< / h1 >
< p class = "text-[15px] leading-relaxed text-white/60" >
Sign in to manage operations , roles , and approval workflows from one secure control panel .
< / p >
< ul class = "space-y-3 text-sm text-white/70" >
{ ( [ 'Role & permission management' , 'Approval workflow control' , 'User & company oversight' , 'Dashboard configuration' ] as const ) . map ( item = > (
< li class = "flex items-center gap-3" >
< span class = "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-[#fd6216]/20 text-[#fd6216]" >
< svg width = "10" height = "8" viewBox = "0 0 10 8" fill = "none" > < path d = "M1 4l2.5 2.5L9 1" stroke = "currentColor" stroke-width = "1.5" stroke-linecap = "round" stroke-linejoin = "round" / > < / svg >
< / span >
{ item }
< / li >
) ) }
< / ul >
< / div >
{ /* Bottom tag */ }
< p class = "rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-[13px] text-white/50" >
🔒 Secured with internal access policies . Authorised personnel only .
2026-03-24 15:57:28 +01:00
< / p >
2026-03-24 16:01:07 +01:00
< / section >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
{ /* ── Right form card — matches public website card exactly ── */ }
< section class = "rounded-[28px] border border-white/30 bg-white/92 p-5 text-[#101228] shadow-[0_28px_60px_-34px_rgba(2,6,23,0.88)] backdrop-blur-xl sm:p-6" >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
{ /* Logo (always visible) */ }
< img src = "/nxtgauge-logo.png" alt = "NXTGAUGE" class = "h-8 w-auto" / >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
< div class = "mt-4" >
< h2 class = "text-3xl font-extrabold text-[#101228]" >
{ mode ( ) === 'login' ? 'Sign In' : 'Reset Password' }
< / h2 >
< p class = "mt-1.5 text-sm text-[#535e7a]" >
{ mode ( ) === 'login' ? 'Internal team access only.' : 'Use your internal email to reset access.' }
< / p >
2026-03-24 02:36:40 +01:00
< / div >
2026-03-19 03:36:46 +01:00
2026-03-24 16:01:07 +01:00
< div class = "mt-5 space-y-3.5" >
{ /* Email */ }
< div >
< label class = { labelCls } > Email < / label >
< input
type = "email"
value = { email ( ) }
onInput = { ( e ) = > { setEmail ( e . currentTarget . value ) ; clearMessages ( ) ; } }
placeholder = "Enter your email"
class = { inputCls }
autocomplete = "email"
/ >
2026-03-24 15:57:28 +01:00
< / div >
2026-03-24 16:01:07 +01:00
{ /* Login mode */ }
< Show when = { mode ( ) === 'login' } >
2026-03-24 15:57:28 +01:00
< div >
2026-03-24 16:01:07 +01:00
< div class = "mb-2 flex items-center justify-between" >
< label class = { labelCls } style = "margin-bottom:0" > Password < / label >
< button type = "button" class = "text-xs font-semibold text-[#fd6216] underline" onClick = { ( ) = > switchMode ( 'reset' ) } >
Forgot ?
< / button >
< / div >
< div class = "relative" >
2026-03-19 03:36:46 +01:00
< input
2026-03-24 16:01:07 +01:00
type = { showPassword ( ) ? 'text' : 'password' }
2026-03-24 15:57:28 +01:00
value = { password ( ) }
onInput = { ( e ) = > { setPassword ( e . currentTarget . value ) ; clearMessages ( ) ; } }
2026-03-24 16:01:07 +01:00
placeholder = "Enter your password"
class = { ` ${ inputCls } pr-11 ` }
2026-03-24 15:57:28 +01:00
autocomplete = "current-password"
2026-03-19 03:36:46 +01:00
/ >
2026-03-24 16:01:07 +01:00
< button
type = "button"
onClick = { ( ) = > setShowPassword ( v = > ! v ) }
class = "absolute inset-y-0 right-0 flex items-center px-3 text-[#5b6480] transition hover:text-[#1b2440]"
>
< Show when = { showPassword ( ) } fallback = {
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" class = "h-5 w-5" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z" / > < circle cx = "12" cy = "12" r = "3" / >
< / svg >
} >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" class = "h-5 w-5" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M3 3l18 18M10.58 10.58a2 2 0 002.83 2.83" / >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M9.88 5.09A10.96 10.96 0 0112 4.91c5.52 0 10 4.09 10 7.09 0 1.2-.72 2.53-1.95 3.72M6.1 6.1C3.54 7.58 2 9.79 2 12c0 3 4.48 7.09 10 7.09 1.72 0 3.36-.4 4.84-1.12" / >
< / svg >
< / Show >
< / button >
2026-03-19 03:36:46 +01:00
< / div >
2026-03-24 16:01:07 +01:00
< / div >
< / Show >
2026-03-19 03:36:46 +01:00
2026-03-24 16:01:07 +01:00
{ /* Reset mode */ }
< Show when = { mode ( ) === 'reset' } >
< div >
< label class = { labelCls } > New Password < / label >
< input type = "password" value = { newPassword ( ) } onInput = { ( e ) = > { setNewPassword ( e . currentTarget . value ) ; clearMessages ( ) ; } } placeholder = "Minimum 8 characters" class = { inputCls } / >
< / div >
< div >
< label class = { labelCls } > Confirm Password < / label >
< input type = "password" value = { confirmPassword ( ) } onInput = { ( e ) = > { setConfirmPassword ( e . currentTarget . value ) ; clearMessages ( ) ; } } placeholder = "Repeat new password" class = { inputCls } / >
< / div >
< Show when = { resetStep ( ) === 'verify' } >
2026-03-24 15:57:28 +01:00
< div >
2026-03-24 16:01:07 +01:00
< label class = { labelCls } > Verification Code < / label >
2026-03-24 15:57:28 +01:00
< input
2026-03-24 16:01:07 +01:00
type = "text" inputMode = "numeric" maxLength = { 6 }
value = { resetCode ( ) }
onInput = { ( e ) = > { setResetCode ( e . currentTarget . value . replace ( /\D/g , '' ) . slice ( 0 , 6 ) ) ; clearMessages ( ) ; } }
placeholder = "000000"
class = { ` ${ inputCls } text-center text-lg tracking-[0.3em] ` }
2026-03-24 15:57:28 +01:00
/ >
2026-03-24 16:01:07 +01:00
< p class = "mt-1.5 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800" >
Code sent to < span class = "font-semibold" > { maskedEmail ( ) || email ( ) } < / span >
< / p >
2026-03-24 15:57:28 +01:00
< / div >
< / Show >
2026-03-24 16:01:07 +01:00
< / Show >
{ /* Error / Info */ }
< Show when = { error ( ) } >
< p class = "text-sm font-medium text-red-600" > { error ( ) } < / p >
< / Show >
< Show when = { info ( ) } >
< p class = "rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-700" > { info ( ) } < / p >
< / Show >
{ /* Sign In button */ }
< Show when = { mode ( ) === 'login' } >
< button
type = "button"
class = "h-11 w-full rounded-xl bg-[#0a1d37] text-sm font-semibold text-white transition hover:bg-[#0f2a4e] disabled:cursor-not-allowed disabled:opacity-60"
disabled = { isSubmitting ( ) }
onClick = { directSignIn }
>
{ isSubmitting ( ) ? 'Signing in…' : 'Sign In' }
< / button >
< p class = "text-xs text-[#6a7390]" > Secure login with internal access policies . < / p >
< / Show >
{ /* Reset buttons */ }
< Show when = { mode ( ) === 'reset' } >
< Show when = { resetStep ( ) === 'request' } fallback = {
2026-03-24 02:36:40 +01:00
< button
type = "button"
2026-03-24 16:01:07 +01:00
class = "h-11 w-full rounded-xl bg-[#0a1d37] text-sm font-semibold text-white transition hover:bg-[#0f2a4e] disabled:cursor-not-allowed disabled:opacity-60"
disabled = { isSubmitting ( ) || ! canSubmitResetVerify ( ) }
onClick = { verifyResetCode }
2026-03-24 02:36:40 +01:00
>
2026-03-24 16:01:07 +01:00
{ isSubmitting ( ) ? 'Resetting…' : 'Verify & Reset Password' }
2026-03-24 02:36:40 +01:00
< / button >
2026-03-24 16:01:07 +01:00
} >
< button
type = "button"
class = "h-11 w-full rounded-xl bg-[#0a1d37] text-sm font-semibold text-white transition hover:bg-[#0f2a4e] disabled:cursor-not-allowed disabled:opacity-60"
disabled = { isSubmitting ( ) || ! canSubmitResetRequest ( ) }
onClick = { requestResetCode }
>
{ isSubmitting ( ) ? 'Sending Code…' : 'Send Reset Code' }
2026-03-24 15:57:28 +01:00
< / button >
2026-03-24 02:36:40 +01:00
< / Show >
2026-03-24 16:01:07 +01:00
< button type = "button" class = "w-full text-center text-sm font-semibold text-[#fd6216] underline" onClick = { ( ) = > switchMode ( 'login' ) } >
Back to sign in
< / button >
< / Show >
2026-03-24 02:36:40 +01:00
< / div >
2026-03-24 16:01:07 +01:00
< / section >
2026-03-24 15:57:28 +01:00
2026-03-19 03:36:46 +01:00
< / div >
2026-03-24 16:01:07 +01:00
< / main >
2026-03-19 03:36:46 +01:00
) ;
}