@@ -13,23 +13,51 @@ export default function SignupPage() {
1313 name : '' ,
1414 } ) ;
1515 const [ error , setError ] = useState ( '' ) ;
16+ const [ fieldErrors , setFieldErrors ] = useState < Record < string , string > > ( { } ) ;
1617 const [ loading , setLoading ] = useState ( false ) ;
1718
19+ const validateEmail = ( email : string ) : string | null => {
20+ if ( ! email ) return 'Email is required' ;
21+ if ( ! / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( email ) ) return 'Invalid email format' ;
22+ return null ;
23+ } ;
24+
25+ const validatePassword = ( password : string ) : string | null => {
26+ if ( ! password ) return 'Password is required' ;
27+ if ( password . length < 8 ) return 'Must be at least 8 characters' ;
28+ if ( ! / [ A - Z ] / . test ( password ) ) return 'Must contain an uppercase letter' ;
29+ if ( ! / [ a - z ] / . test ( password ) ) return 'Must contain a lowercase letter' ;
30+ if ( ! / [ 0 - 9 ] / . test ( password ) ) return 'Must contain a number' ;
31+ return null ;
32+ } ;
33+
34+ const isFormValid = ( ) : boolean => {
35+ return (
36+ ! validateEmail ( formData . email ) &&
37+ ! validatePassword ( formData . password ) &&
38+ formData . password === formData . confirmPassword &&
39+ formData . password . length > 0
40+ ) ;
41+ } ;
42+
1843 const handleSubmit = async ( e : React . FormEvent ) => {
1944 e . preventDefault ( ) ;
2045 setError ( '' ) ;
2146
22- // Validate passwords match
47+ // Inline validation
48+ const errors : Record < string , string > = { } ;
49+ const emailErr = validateEmail ( formData . email ) ;
50+ if ( emailErr ) errors . email = emailErr ;
51+ const passErr = validatePassword ( formData . password ) ;
52+ if ( passErr ) errors . password = passErr ;
2353 if ( formData . password !== formData . confirmPassword ) {
24- setError ( 'Passwords do not match' ) ;
25- return ;
54+ errors . confirmPassword = 'Passwords do not match' ;
2655 }
27-
28- // Validate password strength
29- if ( formData . password . length < 8 ) {
30- setError ( 'Password must be at least 8 characters' ) ;
56+ if ( Object . keys ( errors ) . length > 0 ) {
57+ setFieldErrors ( errors ) ;
3158 return ;
3259 }
60+ setFieldErrors ( { } ) ;
3361
3462 setLoading ( true ) ;
3563
@@ -118,12 +146,16 @@ export default function SignupPage() {
118146 type = "email"
119147 required
120148 value = { formData . email }
121- onChange = { ( e ) =>
122- setFormData ( { ...formData , email : e . target . value } )
123- }
124- className = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900"
149+ onChange = { ( e ) => {
150+ setFormData ( { ...formData , email : e . target . value } ) ;
151+ if ( fieldErrors . email ) setFieldErrors ( ( prev ) => ( { ...prev , email : '' } ) ) ;
152+ } }
153+ className = { `w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900 ${ fieldErrors . email ? 'border-red-400' : 'border-gray-300' } ` }
125154 placeholder = "you@example.com"
126155 />
156+ { fieldErrors . email && (
157+ < p className = "mt-1 text-xs text-red-600" > { fieldErrors . email } </ p >
158+ ) }
127159 </ div >
128160
129161 < div >
@@ -138,12 +170,16 @@ export default function SignupPage() {
138170 type = "password"
139171 required
140172 value = { formData . password }
141- onChange = { ( e ) =>
142- setFormData ( { ...formData , password : e . target . value } )
143- }
144- className = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900"
173+ onChange = { ( e ) => {
174+ setFormData ( { ...formData , password : e . target . value } ) ;
175+ if ( fieldErrors . password ) setFieldErrors ( ( prev ) => ( { ...prev , password : '' } ) ) ;
176+ } }
177+ className = { `w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900 ${ fieldErrors . password ? 'border-red-400' : 'border-gray-300' } ` }
145178 placeholder = "••••••••"
146179 />
180+ { fieldErrors . password && (
181+ < p className = "mt-1 text-xs text-red-600" > { fieldErrors . password } </ p >
182+ ) }
147183 < p className = "mt-1 text-xs text-gray-500" >
148184 Must be at least 8 characters with uppercase, lowercase, and numbers
149185 </ p >
@@ -161,17 +197,21 @@ export default function SignupPage() {
161197 type = "password"
162198 required
163199 value = { formData . confirmPassword }
164- onChange = { ( e ) =>
165- setFormData ( { ...formData , confirmPassword : e . target . value } )
166- }
167- className = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900"
200+ onChange = { ( e ) => {
201+ setFormData ( { ...formData , confirmPassword : e . target . value } ) ;
202+ if ( fieldErrors . confirmPassword ) setFieldErrors ( ( prev ) => ( { ...prev , confirmPassword : '' } ) ) ;
203+ } }
204+ className = { `w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent placeholder:text-gray-400 text-gray-900 ${ fieldErrors . confirmPassword ? 'border-red-400' : 'border-gray-300' } ` }
168205 placeholder = "••••••••"
169206 />
207+ { fieldErrors . confirmPassword && (
208+ < p className = "mt-1 text-xs text-red-600" > { fieldErrors . confirmPassword } </ p >
209+ ) }
170210 </ div >
171211
172212 < button
173213 type = "submit"
174- disabled = { loading }
214+ disabled = { loading || ! isFormValid ( ) }
175215 className = "w-full bg-purple-600 text-white py-3 px-4 rounded-lg font-semibold hover:bg-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
176216 >
177217 { loading ? 'Creating account...' : 'Sign up' }
0 commit comments