@@ -705,11 +705,19 @@ function renderRegisterPage() {
705705 < div class ="auth-error "> </ div >
706706 < div class ="form-group ">
707707 < label for ="reg-email "> Email</ label >
708- < input type ="email " id ="reg-email " placeholder ="your@email.com " required autocomplete ="email " />
708+ < div class ="input-wrap ">
709+ < input type ="email " id ="reg-email " placeholder ="your@email.com " required autocomplete ="email " />
710+ < span class ="input-status " id ="reg-email-status "> </ span >
711+ </ div >
712+ < div class ="field-hint " id ="reg-email-hint "> </ div >
709713 </ div >
710714 < div class ="form-group ">
711715 < label for ="reg-username "> Username</ label >
712- < input type ="text " id ="reg-username " placeholder ="myusername " required autocomplete ="username " />
716+ < div class ="input-wrap ">
717+ < input type ="text " id ="reg-username " placeholder ="myusername " required autocomplete ="username " />
718+ < span class ="input-status " id ="reg-username-status "> </ span >
719+ </ div >
720+ < div class ="field-hint " id ="reg-username-hint "> </ div >
713721 </ div >
714722 < div class ="form-group ">
715723 < label for ="reg-password "> Password</ label >
@@ -742,6 +750,101 @@ function renderRegisterPage() {
742750 e . preventDefault ( ) ;
743751 navigateTo ( 'login' ) ;
744752 } ) ;
753+
754+ setupRegisterAvailabilityChecks ( ) ;
755+ }
756+
757+ const registerAvailability = { email : null , username : null } ;
758+ let registerAvailabilityTimer = null ;
759+
760+ function setFieldStatus ( field , state , message ) {
761+ const statusEl = $ ( `#reg-${ field } -status` ) ;
762+ const hintEl = $ ( `#reg-${ field } -hint` ) ;
763+ if ( ! statusEl ) return ;
764+ statusEl . className = `input-status ${ state || '' } ` ;
765+ if ( state === 'checking' ) {
766+ statusEl . innerHTML = '<span class="spinner" style="width:16px;height:16px;"></span>' ;
767+ } else if ( state === 'ok' ) {
768+ statusEl . innerHTML = '<i data-lucide="check" style="width:18px;height:18px"></i>' ;
769+ } else if ( state === 'taken' ) {
770+ statusEl . innerHTML = '<i data-lucide="x" style="width:18px;height:18px"></i>' ;
771+ } else {
772+ statusEl . innerHTML = '' ;
773+ }
774+ if ( hintEl ) {
775+ hintEl . textContent = message || '' ;
776+ hintEl . className = `field-hint ${ state === 'ok' || state === 'taken' ? state : '' } ` ;
777+ }
778+ if ( state === 'taken' || state === 'ok' ) initIcons ( ) ;
779+ }
780+
781+ function setupRegisterAvailabilityChecks ( ) {
782+ const emailInput = $ ( '#reg-email' ) ;
783+ const usernameInput = $ ( '#reg-username' ) ;
784+ if ( ! emailInput || ! usernameInput ) return ;
785+
786+ const runCheck = async ( ) => {
787+ const email = emailInput . value . trim ( ) ;
788+ const username = usernameInput . value . trim ( ) ;
789+
790+ if ( ! email && ! username ) {
791+ registerAvailability . email = null ;
792+ registerAvailability . username = null ;
793+ setFieldStatus ( 'email' , '' ) ;
794+ setFieldStatus ( 'username' , '' ) ;
795+ return ;
796+ }
797+
798+ if ( email && ! validateRegEmail ( email ) ) {
799+ setFieldStatus ( 'email' , '' ) ;
800+ registerAvailability . email = null ;
801+ }
802+ if ( username && ! validateRegUsername ( username ) ) {
803+ setFieldStatus ( 'username' , '' ) ;
804+ registerAvailability . username = null ;
805+ }
806+
807+ setFieldStatus ( 'email' , email && validateRegEmail ( email ) ? 'checking' : '' ) ;
808+ setFieldStatus ( 'username' , username && validateRegUsername ( username ) ? 'checking' : '' ) ;
809+
810+ try {
811+ const params = new URLSearchParams ( ) ;
812+ if ( email && validateRegEmail ( email ) ) params . set ( 'email' , email ) ;
813+ if ( username && validateRegUsername ( username ) ) params . set ( 'username' , username ) ;
814+ const data = await api ( `/auth/check-availability?${ params . toString ( ) } ` ) ;
815+ if ( email && validateRegEmail ( email ) ) {
816+ const available = data . email ?. available ;
817+ registerAvailability . email = available ;
818+ setFieldStatus ( 'email' , available ? 'ok' : 'taken' , available ? 'Available' : 'This email is already taken' ) ;
819+ }
820+ if ( username && validateRegUsername ( username ) ) {
821+ const available = data . username ?. available ;
822+ registerAvailability . username = available ;
823+ setFieldStatus ( 'username' , available ? 'ok' : 'taken' , available ? 'Available' : 'This username is already taken' ) ;
824+ }
825+ } catch {
826+ setFieldStatus ( 'email' , '' ) ;
827+ setFieldStatus ( 'username' , '' ) ;
828+ }
829+ } ;
830+
831+ const onInput = ( ) => {
832+ clearTimeout ( registerAvailabilityTimer ) ;
833+ registerAvailabilityTimer = setTimeout ( runCheck , 400 ) ;
834+ } ;
835+
836+ emailInput . addEventListener ( 'input' , onInput ) ;
837+ usernameInput . addEventListener ( 'input' , onInput ) ;
838+ emailInput . addEventListener ( 'blur' , runCheck ) ;
839+ usernameInput . addEventListener ( 'blur' , runCheck ) ;
840+ }
841+
842+ function validateRegEmail ( email ) {
843+ return / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( email ) ;
844+ }
845+
846+ function validateRegUsername ( username ) {
847+ return / ^ [ a - z A - Z 0 - 9 _ - ] { 3 , 32 } $ / . test ( username ) ;
745848}
746849
747850async function handleLogin ( e ) {
@@ -886,6 +989,15 @@ async function handleRegister(e) {
886989 return ;
887990 }
888991
992+ if ( registerAvailability . email === false ) {
993+ showError ( e . target , 'This email is already taken. Please choose another one.' ) ;
994+ return ;
995+ }
996+ if ( registerAvailability . username === false ) {
997+ showError ( e . target , 'This username is already taken. Please choose another one.' ) ;
998+ return ;
999+ }
1000+
8891001 btn . disabled = true ;
8901002 btn . innerHTML = '<span class="spinner"></span> Creating...' ;
8911003
0 commit comments