@@ -24,7 +24,7 @@ import {
24
24
} from '@/graphql/mutations/auth' ;
25
25
import { useRouter } from 'next/navigation' ;
26
26
import { VisuallyHidden } from '@radix-ui/react-visually-hidden' ;
27
- import { AlertCircle , CheckCircle , Mail , Clock } from 'lucide-react' ;
27
+ import { AlertCircle , CheckCircle , Mail , Clock , Github } from 'lucide-react' ;
28
28
import { useEffect } from 'react' ;
29
29
import { logger } from '@/app/log/logger' ;
30
30
@@ -43,6 +43,48 @@ export function SignUpModal({
43
43
const [ registrationSuccess , setRegistrationSuccess ] = useState ( false ) ;
44
44
const [ resendCooldown , setResendCooldown ] = useState ( 0 ) ;
45
45
const [ resendMessage , setResendMessage ] = useState < string | null > ( null ) ;
46
+ const [ passwordConfirm , setPasswordConfirm ] = useState ( '' ) ;
47
+ const [ passwordError , setPasswordError ] = useState < string | null > ( null ) ;
48
+ const [ passwordStrength , setPasswordStrength ] = useState <
49
+ 'weak' | 'medium' | 'strong' | null
50
+ > ( null ) ;
51
+
52
+ const validatePassword = ( value : string ) => {
53
+ // Reset errors
54
+ setPasswordError ( null ) ;
55
+
56
+ // Check minimum length
57
+ if ( value . length < 6 ) {
58
+ setPasswordError ( 'Password must be at least 6 characters long' ) ;
59
+ setPasswordStrength ( 'weak' ) ;
60
+ return false ;
61
+ }
62
+
63
+ // Check for complexity
64
+ const hasUppercase = / [ A - Z ] / . test ( value ) ;
65
+ const hasLowercase = / [ a - z ] / . test ( value ) ;
66
+ const hasNumbers = / \d / . test ( value ) ;
67
+ const hasSpecialChar = / [ ! @ # $ % ^ & * ( ) _ + \- = [ \] { } ; ' : " \\ | , . < > / ? ] / . test ( value ) ;
68
+
69
+ const strengthScore = [
70
+ hasUppercase ,
71
+ hasLowercase ,
72
+ hasNumbers ,
73
+ hasSpecialChar ,
74
+ ] . filter ( Boolean ) . length ;
75
+
76
+ if ( strengthScore < 2 ) {
77
+ setPasswordStrength ( 'weak' ) ;
78
+ setPasswordError ( 'Password is too weak' ) ;
79
+ return false ;
80
+ } else if ( strengthScore < 4 ) {
81
+ setPasswordStrength ( 'medium' ) ;
82
+ return true ;
83
+ } else {
84
+ setPasswordStrength ( 'strong' ) ;
85
+ return true ;
86
+ }
87
+ } ;
46
88
47
89
const [ registerUser , { loading } ] = useMutation ( REGISTER_USER , {
48
90
onError : ( error ) => {
@@ -61,18 +103,28 @@ export function SignUpModal({
61
103
e . preventDefault ( ) ;
62
104
setErrorMessage ( null ) ;
63
105
64
- if ( ! name || ! email || ! password ) {
106
+ if ( ! name || ! email || ! password || ! passwordConfirm ) {
65
107
setErrorMessage ( 'All fields are required.' ) ;
66
108
return ;
67
109
}
68
110
111
+ if ( ! validatePassword ( password ) ) {
112
+ return ;
113
+ }
114
+
115
+ if ( password !== passwordConfirm ) {
116
+ setErrorMessage ( 'Passwords do not match.' ) ;
117
+ return ;
118
+ }
119
+
69
120
try {
70
121
await registerUser ( {
71
122
variables : {
72
123
input : {
73
124
username : name ,
74
125
email,
75
126
password,
127
+ confirmPassword : passwordConfirm ,
76
128
} ,
77
129
} ,
78
130
} ) ;
@@ -133,15 +185,15 @@ export function SignUpModal({
133
185
134
186
return (
135
187
< Dialog open = { isOpen } onOpenChange = { onClose } >
136
- < DialogContent className = "sm:max-w-[425px] fixed top-[50%] left-[50%] transform -translate-x-[50%] -translate-y-[50%] p-0" >
188
+ < DialogContent className = "sm:max-w-[425px] fixed top-[50%] left-[50%] transform -translate-x-[50%] -translate-y-[50%] p-0 max-h-[90vh] overflow-y-auto " >
137
189
< VisuallyHidden >
138
190
< DialogTitle > Sign Up</ DialogTitle >
139
191
< DialogDescription >
140
192
Create an account by entering your information below
141
193
</ DialogDescription >
142
194
</ VisuallyHidden >
143
195
144
- < BackgroundGradient className = "rounded-[22px] p-4 bg-white dark:bg-zinc-900" >
196
+ < BackgroundGradient className = "rounded-[22px] p-4 bg-white dark:bg-zinc-900 overflow-hidden " >
145
197
< div className = "w-full" >
146
198
{ registrationSuccess ? (
147
199
< >
@@ -213,13 +265,50 @@ export function SignUpModal({
213
265
) : (
214
266
< >
215
267
< TextureCardHeader className = "flex flex-col gap-1 items-center justify-center p-4" >
216
- < TextureCardTitle > Create your account</ TextureCardTitle >
268
+ < TextureCardTitle > Create account</ TextureCardTitle >
217
269
< p className = "text-center text-neutral-600 dark:text-neutral-400" >
218
- Welcome! Please fill in the details to get started.
270
+ Enter your information to create your account
219
271
</ p >
220
272
</ TextureCardHeader >
221
273
< TextureSeparator />
222
274
< TextureCardContent >
275
+ < Button
276
+ variant = "outline"
277
+ className = "flex items-center justify-center gap-2 w-full"
278
+ type = "button"
279
+ >
280
+ < img
281
+ src = "/images/google.svg"
282
+ alt = "Google"
283
+ className = "w-5 h-5"
284
+ />
285
+ < span > Continue with Google</ span >
286
+ </ Button >
287
+
288
+ { /* GitHub Sign Up Button - added below Google */ }
289
+ < div className = "mt-4" >
290
+ < Button
291
+ variant = "outline"
292
+ className = "flex items-center justify-center gap-2 w-full"
293
+ type = "button"
294
+ >
295
+ < Github className = "w-5 h-5 text-black dark:text-white" />
296
+ < span > Continue with GitHub</ span >
297
+ </ Button >
298
+ </ div >
299
+
300
+ { /* Divider with "or" text */ }
301
+ < div className = "relative my-6" >
302
+ < div className = "absolute inset-0 flex items-center" >
303
+ < div className = "w-full border-t border-gray-300 dark:border-gray-700" > </ div >
304
+ </ div >
305
+ < div className = "relative flex justify-center text-sm" >
306
+ < span className = "px-2 bg-white dark:bg-zinc-900 text-gray-500" >
307
+ Or continue with
308
+ </ span >
309
+ </ div >
310
+ </ div >
311
+
223
312
< form onSubmit = { handleSubmit } className = "space-y-2" >
224
313
< div className = "space-y-1" >
225
314
< Label htmlFor = "name" > Name</ Label >
@@ -260,6 +349,92 @@ export function SignUpModal({
260
349
value = { password }
261
350
onChange = { ( e ) => {
262
351
setPassword ( e . target . value ) ;
352
+ validatePassword ( e . target . value ) ;
353
+ setErrorMessage ( null ) ;
354
+ } }
355
+ required
356
+ className = { `w-full ${ passwordError ? 'border-red-500' : '' } ` }
357
+ />
358
+ { password && (
359
+ < div className = "mt-2 space-y-2" >
360
+ < div className = "flex items-center gap-2" >
361
+ < div className = "text-sm" > Password strength:</ div >
362
+ < div className = "flex h-2 w-full max-w-[100px] overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700" >
363
+ < div
364
+ className = { `h-full ${
365
+ passwordStrength === 'weak'
366
+ ? 'w-1/3 bg-red-500'
367
+ : passwordStrength === 'medium'
368
+ ? 'w-2/3 bg-yellow-500'
369
+ : 'w-full bg-green-500'
370
+ } `}
371
+ />
372
+ </ div >
373
+ < div className = "text-sm" >
374
+ { passwordStrength === 'weak'
375
+ ? 'Weak'
376
+ : passwordStrength === 'medium'
377
+ ? 'Medium'
378
+ : 'Strong' }
379
+ </ div >
380
+ </ div >
381
+
382
+ < div className = "text-xs text-gray-500 dark:text-gray-400" >
383
+ Password must:
384
+ < ul className = "list-disc pl-5 mt-1 space-y-1" >
385
+ < li
386
+ className = {
387
+ password . length >= 6 ? 'text-green-500' : ''
388
+ }
389
+ >
390
+ Be at least 6 characters long
391
+ </ li >
392
+ < li
393
+ className = {
394
+ / [ A - Z ] / . test ( password ) ? 'text-green-500' : ''
395
+ }
396
+ >
397
+ Include at least one uppercase letter
398
+ </ li >
399
+ < li
400
+ className = {
401
+ / \d / . test ( password ) ? 'text-green-500' : ''
402
+ }
403
+ >
404
+ Include at least one number
405
+ </ li >
406
+ < li
407
+ className = {
408
+ / [ ! @ # $ % ^ & * ( ) _ + \- = [ \] { } ; ' : " \\ | , . < > / ? ] / . test (
409
+ password
410
+ )
411
+ ? 'text-green-500'
412
+ : ''
413
+ }
414
+ >
415
+ Include at least one special character
416
+ </ li >
417
+ </ ul >
418
+ </ div >
419
+ </ div >
420
+ ) }
421
+
422
+ { passwordError && (
423
+ < div className = "text-red-500 text-xs mt-1" >
424
+ { passwordError }
425
+ </ div >
426
+ ) }
427
+ </ div >
428
+
429
+ < div className = "space-y-1" >
430
+ < Label htmlFor = "passwordConfirm" > Confirm Password</ Label >
431
+ < Input
432
+ id = "passwordConfirm"
433
+ placeholder = "Confirm Password"
434
+ type = "password"
435
+ value = { passwordConfirm }
436
+ onChange = { ( e ) => {
437
+ setPasswordConfirm ( e . target . value ) ;
263
438
setErrorMessage ( null ) ;
264
439
} }
265
440
required
@@ -274,6 +449,7 @@ export function SignUpModal({
274
449
</ div >
275
450
) }
276
451
452
+ < div className = "h-2" > </ div >
277
453
< Button type = "submit" className = "w-full" disabled = { loading } >
278
454
{ loading ? 'Signing up...' : 'Sign up' }
279
455
</ Button >
0 commit comments