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-30 04:48:09 +02:00
'h-11 w-full rounded-xl border border-[#E5E7EB] bg-white px-4 text-sm text-[#111827] outline-none transition placeholder:text-[#9CA3AF] focus:border-[#FF5E13] focus:ring-2 focus:ring-[#FFE6D9]' ;
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
const labelCls =
2026-03-30 04:48:09 +02:00
'mb-2 block text-[12px] font-semibold uppercase tracking-[0.11em] text-[#4B5563]' ;
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 ;
2026-04-07 22:12:52 +02:00
const r = await fetch ( '/api/auth/login' , { method : 'POST' , headers , credentials : 'include' , body } ) ;
2026-04-02 13:09:42 +02:00
status = r . status ; payload = await r . json ( ) . catch ( ( ) = > ( { } ) ) ;
if ( r . ok ) { success = true ; }
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 ;
2026-04-07 22:12:52 +02:00
for ( const { url , body } of [
{ url : '/api/auth/internal/forgot-password/request-code' , body : JSON.stringify ( reqBody ) } ,
{ url : '/api/auth/internal/forgot-password/request-code' , body : JSON.stringify ( reqBody ) } ,
{ url : '/api/auth/internal/forgot-password/request-code' , body : JSON.stringify ( { data : reqBody } ) } ,
{ url : '/api/auth/internal/forgot-password/request-code' , body : JSON.stringify ( { data : reqBody } ) } ,
] ) {
2026-03-24 16:01:07 +01:00
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 ;
2026-04-07 22:12:52 +02:00
for ( const url of [ '/api/auth/internal/forgot-password/verify-code' , '/api/auth/internal/forgot-password/verify-code' ] ) {
2026-03-24 16:01:07 +01:00
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-30 04:48:09 +02:00
< main class = "min-h-screen bg-[#F9FAFB] text-[#111827]" >
< div class = "mx-auto flex min-h-screen w-full max-w-[1260px] items-center px-4 py-8 sm:px-6 lg:py-10" >
< div class = "grid w-full gap-6 lg:grid-cols-[1.05fr_0.95fr]" >
< section class = "relative hidden overflow-hidden rounded-3xl border border-[#E5E7EB] bg-white p-10 shadow-sm lg:flex lg:min-h-[640px] lg:flex-col lg:justify-between" >
< div class = "pointer-events-none absolute -right-20 -top-20 h-72 w-72 rounded-full bg-[#FF5E13]/10 blur-3xl" / >
< div class = "pointer-events-none absolute bottom-[-80px] left-[-80px] h-72 w-72 rounded-full bg-[#0D0D2A]/10 blur-3xl" / >
2026-03-24 15:57:28 +01:00
2026-03-30 04:48:09 +02:00
< div >
< img src = "/nxtgauge-logo.png" alt = "NXTGAUGE" class = "h-auto w-[190px] object-contain" / >
< / div >
2026-03-24 16:01:07 +01:00
2026-03-30 04:48:09 +02:00
< div class = "space-y-6" >
< p class = "inline-flex items-center gap-2 rounded-full border border-[#FFE4D5] bg-[#FFF3EC] px-3 py-1 text-[11px] font-bold uppercase tracking-widest text-[#FF5E13]" >
< span class = "h-1.5 w-1.5 rounded-full bg-[#FF5E13]" / >
Internal Admin Portal
< / p >
< h1 class = "text-[40px] font-extrabold leading-tight text-[#0D0D2A]" >
Dashboard Access
< br / >
For Operations Team
< / h1 >
< p class = "max-w-[520px] text-[15px] leading-relaxed text-[#6B7280]" >
Sign in to manage roles , approvals , user operations , and runtime module configuration from one control center .
< / p >
< div class = "grid grid-cols-2 gap-3" >
< div class = "rounded-xl border border-[#E5E7EB] bg-[#FAFAFA] px-4 py-3" >
< p class = "text-[12px] font-semibold text-[#6B7280]" > Access Control < / p >
< p class = "mt-1 text-[14px] font-semibold text-[#0D0D2A]" > Role Management < / p >
< / div >
< div class = "rounded-xl border border-[#E5E7EB] bg-[#FAFAFA] px-4 py-3" >
< p class = "text-[12px] font-semibold text-[#6B7280]" > Workflows < / p >
< p class = "mt-1 text-[14px] font-semibold text-[#0D0D2A]" > Approvals & Verification < / p >
< / div >
< div class = "rounded-xl border border-[#E5E7EB] bg-[#FAFAFA] px-4 py-3" >
< p class = "text-[12px] font-semibold text-[#6B7280]" > Operations < / p >
< p class = "mt-1 text-[14px] font-semibold text-[#0D0D2A]" > Users & Companies < / p >
< / div >
< div class = "rounded-xl border border-[#E5E7EB] bg-[#FAFAFA] px-4 py-3" >
< p class = "text-[12px] font-semibold text-[#6B7280]" > Runtime < / p >
< p class = "mt-1 text-[14px] font-semibold text-[#0D0D2A]" > Module Visibility < / p >
< / div >
< / div >
< / div >
2026-03-24 16:01:07 +01:00
2026-03-30 04:48:09 +02:00
< p class = "rounded-xl border border-[#E5E7EB] bg-[#F9FAFB] px-4 py-3 text-[13px] text-[#6B7280]" >
Secured with internal access policies . Authorized personnel only .
2026-03-24 16:01:07 +01:00
< / p >
2026-03-30 04:48:09 +02:00
< / section >
< section class = "rounded-3xl border border-[#E5E7EB] bg-white p-6 shadow-sm sm:p-7" >
< div class = "mb-5 flex items-center justify-between" >
< img src = "/nxtgauge-logo.png" alt = "NXTGAUGE" class = "h-auto w-[170px] object-contain" / >
< span class = "rounded-full bg-[#FFF1EB] px-3 py-1 text-[11px] font-semibold uppercase tracking-wide text-[#FF5E13]" >
Admin
< / span >
< / div >
2026-03-24 15:57:28 +01:00
2026-03-24 16:01:07 +01:00
< div class = "mt-4" >
2026-03-30 04:48:09 +02:00
< h2 class = "text-[30px] font-extrabold text-[#0D0D2A]" >
2026-03-24 16:01:07 +01:00
{ mode ( ) === 'login' ? 'Sign In' : 'Reset Password' }
< / h2 >
2026-03-30 04:48:09 +02:00
< p class = "mt-1.5 text-sm text-[#6B7280]" >
2026-03-24 16:01:07 +01:00
{ 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 ( ) } >
2026-03-30 04:48:09 +02:00
< p class = "rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm font-medium text-red-600" > { error ( ) } < / p >
2026-03-24 16:01:07 +01:00
< / 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"
2026-03-30 04:48:09 +02:00
class = "h-11 w-full rounded-xl bg-[#0D0D2A] text-sm font-semibold text-white transition hover:bg-[#1A1A3A] disabled:cursor-not-allowed disabled:opacity-60"
2026-03-24 16:01:07 +01:00
disabled = { isSubmitting ( ) }
onClick = { directSignIn }
>
{ isSubmitting ( ) ? 'Signing in…' : 'Sign In' }
< / button >
2026-03-30 04:48:09 +02:00
< p class = "text-xs text-[#6B7280]" > Secure login with internal access policies . < / p >
2026-03-24 16:01:07 +01:00
< / 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-30 04:48:09 +02:00
class = "h-11 w-full rounded-xl bg-[#0D0D2A] text-sm font-semibold text-white transition hover:bg-[#1A1A3A] disabled:cursor-not-allowed disabled:opacity-60"
2026-03-24 16:01:07 +01:00
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"
2026-03-30 04:48:09 +02:00
class = "h-11 w-full rounded-xl bg-[#0D0D2A] text-sm font-semibold text-white transition hover:bg-[#1A1A3A] disabled:cursor-not-allowed disabled:opacity-60"
2026-03-24 16:01:07 +01:00
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-30 04:48:09 +02:00
< / div >
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
) ;
}