@@ -19,6 +19,7 @@ import {
19
19
updateUser ,
20
20
getFileUrl ,
21
21
ToastComponent ,
22
+ resetPasswordFromProfile ,
22
23
} from "@/api/user" ;
23
24
import { useEffect , useRef , useState } from "react" ;
24
25
import { User } from "@/types/user" ;
@@ -29,7 +30,7 @@ import { CgProfile } from "react-icons/cg";
29
30
import MoonLoader from "react-spinners/MoonLoader" ;
30
31
import { IoCloseCircle } from "react-icons/io5" ;
31
32
32
- const formSchema = z . object ( {
33
+ const profileFormSchema = z . object ( {
33
34
username : z
34
35
. string ( )
35
36
. min ( 5 , "Username must be at least 5 characters" )
@@ -42,10 +43,6 @@ const formSchema = z.object({
42
43
message : "Invalid URL" ,
43
44
} ) ,
44
45
email : z . string ( ) . email ( "Invalid email" ) . optional ( ) ,
45
- password : z
46
- . string ( )
47
- . max ( 100 , "Password must be at most 100 characters" )
48
- . optional ( ) ,
49
46
bio : z . string ( ) . optional ( ) ,
50
47
linkedin : z
51
48
. string ( )
@@ -61,50 +58,78 @@ const formSchema = z.object({
61
58
. optional ( ) ,
62
59
} ) ;
63
60
61
+ const resetPasswordFormSchema = z
62
+ . object ( {
63
+ password : z
64
+ . string ( )
65
+ . min ( 8 , "Password must be at least 8 characters" )
66
+ . max ( 100 , "Password must be at most 100 characters" )
67
+ . regex ( / [ a - z ] / , "Password must contain at least one lowercase letter" )
68
+ . regex ( / [ A - Z ] / , "Password must contain at least one uppercase letter" )
69
+ . regex ( / \d / , "Password must contain at least one number" )
70
+ . regex (
71
+ / [ @ $ ! % * ? & ] / ,
72
+ "Password must contain at least one special character (@, $, !, %, *, ?, &)"
73
+ ) ,
74
+ confirmPassword : z
75
+ . string ( )
76
+ . min ( 8 , "Password must be at least 8 characters" ) ,
77
+ } )
78
+ . refine ( ( data ) => data . password === data . confirmPassword , {
79
+ message : "Passwords do not match" ,
80
+ path : [ "confirmPassword" ] , // Error message will show up on confirmPassword
81
+ } ) ;
82
+
64
83
const ProfilePage = ( ) => {
65
84
const [ token , setToken ] = useState ( false ) ;
66
85
const [ user , setUser ] = useState < User > ( { } ) ;
67
86
const fileInputRef = useRef < HTMLInputElement | null > ( null ) ;
68
87
const [ isLoading , setIsLoading ] = useState ( false ) ;
69
88
70
- const form = useForm < z . infer < typeof formSchema > > ( {
71
- resolver : zodResolver ( formSchema ) ,
89
+ const profileForm = useForm < z . infer < typeof profileFormSchema > > ( {
90
+ resolver : zodResolver ( profileFormSchema ) ,
72
91
defaultValues : {
73
92
username : "" ,
74
93
profilePictureUrl : "" ,
75
94
email : "" ,
76
- password : "" ,
77
95
bio : "" ,
78
96
linkedin : "" ,
79
97
github : "" ,
80
98
} ,
81
99
} ) ;
82
100
101
+ const resetPasswordForm = useForm < z . infer < typeof resetPasswordFormSchema > > ( {
102
+ resolver : zodResolver ( resetPasswordFormSchema ) ,
103
+ defaultValues : {
104
+ password : "" ,
105
+ confirmPassword : "" ,
106
+ } ,
107
+ } ) ;
108
+
83
109
useEffect ( ( ) => {
84
110
setToken ( ( _ ) => ! ! getToken ( ) ) ;
85
111
} , [ ] ) ;
86
112
87
113
useEffect ( ( ) => {
88
114
getUser ( ) . then ( ( res ) => {
89
115
setUser ( res . data ) ;
90
- form . reset ( res . data ) ;
116
+ profileForm . reset ( res . data ) ;
91
117
} ) ;
92
- } , [ form ] ) ;
118
+ } , [ profileForm ] ) ;
93
119
94
- const onSubmit = async ( data : z . infer < typeof formSchema > ) => {
95
- // remove unnecessary fields
96
- if ( ! data . password ) delete data . password ;
97
- if ( data . password && data . password . length < 8 ) {
98
- Swal . fire ( {
99
- icon : "error" ,
100
- title : "Password must be at least 8 characters" ,
101
- } ) ;
102
- return ;
103
- }
120
+ const onUpdateProfileSubmit = async (
121
+ data : z . infer < typeof profileFormSchema >
122
+ ) => {
104
123
await updateUser ( data ) ;
105
124
setUser ( data ) ;
106
125
} ;
107
126
127
+ const onResetPasswordSubmit = async (
128
+ data : z . infer < typeof resetPasswordFormSchema >
129
+ ) => {
130
+ await resetPasswordFromProfile ( data . password ) ;
131
+ } ;
132
+
108
133
const triggerFileInput = ( ) => {
109
134
if ( fileInputRef . current && ! isLoading ) {
110
135
console . log ( "Click" ) ;
@@ -127,7 +152,7 @@ const ProfilePage = () => {
127
152
const res = await getFileUrl ( userId , formData ) ;
128
153
129
154
if ( res . fileUrl ) {
130
- form . setValue ( "profilePictureUrl" , res . fileUrl ) ;
155
+ profileForm . setValue ( "profilePictureUrl" , res . fileUrl ) ;
131
156
setUser ( { ...user , profilePictureUrl : res . fileUrl } ) ;
132
157
}
133
158
}
@@ -144,13 +169,13 @@ const ProfilePage = () => {
144
169
< h1 className = "text-white font-extrabold text-h1" >
145
170
Welcome, { user ?. username } !
146
171
</ h1 >
147
- < Form { ...form } >
172
+ < Form { ...profileForm } >
148
173
< form
149
174
className = "my-10 grid gap-4"
150
- onSubmit = { form . handleSubmit ( onSubmit ) }
175
+ onSubmit = { profileForm . handleSubmit ( onUpdateProfileSubmit ) }
151
176
>
152
177
< FormField
153
- control = { form . control }
178
+ control = { profileForm . control }
154
179
name = "profilePictureUrl"
155
180
render = { ( { field } ) => (
156
181
< div >
@@ -179,7 +204,7 @@ const ProfilePage = () => {
179
204
size = { 24 }
180
205
onClick = { ( e ) => {
181
206
e . stopPropagation ( ) ;
182
- form . setValue ( "profilePictureUrl" , "" ) ;
207
+ profileForm . setValue ( "profilePictureUrl" , "" ) ;
183
208
setUser ( { ...user , profilePictureUrl : "" } ) ;
184
209
} }
185
210
/>
@@ -215,7 +240,7 @@ const ProfilePage = () => {
215
240
/>
216
241
217
242
< FormField
218
- control = { form . control }
243
+ control = { profileForm . control }
219
244
name = "username"
220
245
render = { ( { field } ) => (
221
246
< FormItem >
@@ -235,7 +260,7 @@ const ProfilePage = () => {
235
260
) }
236
261
/>
237
262
< FormField
238
- control = { form . control }
263
+ control = { profileForm . control }
239
264
name = "email"
240
265
render = { ( { field } ) => (
241
266
< FormItem >
@@ -254,29 +279,9 @@ const ProfilePage = () => {
254
279
</ FormItem >
255
280
) }
256
281
/>
282
+
257
283
< FormField
258
- control = { form . control }
259
- name = "password"
260
- render = { ( { field } ) => (
261
- < FormItem >
262
- < FormLabel className = "text-yellow-500 text-lg" >
263
- NEW PASSWORD
264
- </ FormLabel >
265
- < FormControl >
266
- < Input
267
- type = "password"
268
- placeholder = "password"
269
- { ...field }
270
- className = "focus:border-yellow-500 text-white"
271
- />
272
- </ FormControl >
273
- { /* <FormDescription>This is your public display name.</FormDescription> */ }
274
- < FormMessage />
275
- </ FormItem >
276
- ) }
277
- />
278
- < FormField
279
- control = { form . control }
284
+ control = { profileForm . control }
280
285
name = "bio"
281
286
render = { ( { field } ) => (
282
287
< FormItem >
@@ -294,7 +299,7 @@ const ProfilePage = () => {
294
299
) }
295
300
/>
296
301
< FormField
297
- control = { form . control }
302
+ control = { profileForm . control }
298
303
name = "linkedin"
299
304
render = { ( { field } ) => (
300
305
< FormItem >
@@ -314,7 +319,7 @@ const ProfilePage = () => {
314
319
) }
315
320
/>
316
321
< FormField
317
- control = { form . control }
322
+ control = { profileForm . control }
318
323
name = "github"
319
324
render = { ( { field } ) => (
320
325
< FormItem >
@@ -336,12 +341,63 @@ const ProfilePage = () => {
336
341
< Button
337
342
type = "submit"
338
343
className = "bg-yellow-500 hover:bg-yellow-300 px-4 py-2 my-2 rounded-md text-black"
339
- disabled = { form . formState . isSubmitting || isLoading }
344
+ disabled = { profileForm . formState . isSubmitting || isLoading }
340
345
>
341
346
Save Changes
342
347
</ Button >
343
348
</ form >
344
349
</ Form >
350
+
351
+ < Form { ...resetPasswordForm } >
352
+ < form
353
+ className = "flex flex-col gap-4"
354
+ onSubmit = { resetPasswordForm . handleSubmit ( onResetPasswordSubmit ) }
355
+ >
356
+ < FormField
357
+ control = { resetPasswordForm . control }
358
+ name = "password"
359
+ render = { ( { field } ) => (
360
+ < FormItem >
361
+ < FormLabel className = "text-yellow-500 text-lg" >
362
+ NEW PASSWORD
363
+ </ FormLabel >
364
+ < FormControl >
365
+ < Input
366
+ type = "password"
367
+ placeholder = "new password"
368
+ { ...field }
369
+ className = "focus:border-yellow-500 text-white"
370
+ />
371
+ </ FormControl >
372
+ < FormMessage />
373
+ </ FormItem >
374
+ ) }
375
+ />
376
+ < FormField
377
+ control = { resetPasswordForm . control }
378
+ name = "confirmPassword"
379
+ render = { ( { field } ) => (
380
+ < FormItem >
381
+ < FormLabel className = "text-yellow-500 text-lg" >
382
+ CONFIRM NEW PASSWORD
383
+ </ FormLabel >
384
+ < FormControl >
385
+ < Input
386
+ type = "password"
387
+ placeholder = "confirm new password"
388
+ { ...field }
389
+ className = "focus:border-yellow-500 text-white"
390
+ />
391
+ </ FormControl >
392
+ < FormMessage />
393
+ </ FormItem >
394
+ ) }
395
+ />
396
+ < Button variant = "destructive" type = "submit" >
397
+ Reset Password
398
+ </ Button >
399
+ </ form >
400
+ </ Form >
345
401
</ div >
346
402
)
347
403
) ;
0 commit comments