@@ -8,26 +8,76 @@ import {
8
8
faSignOutAlt ,
9
9
} from '@fortawesome/free-solid-svg-icons' ;
10
10
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
11
- import Image from 'next/image' ;
12
- import { useState } from 'react' ;
13
11
import { LoggedUserData } from '@web/src/modules/auth/types/User' ;
12
+ import Image from 'next/image' ;
13
+ import { useEffect , useState } from 'react' ;
14
14
import { UserMenuButton } from '../client/UserMenuButton' ;
15
- import { EditUsernameModal } from './EditUsernameModal' ;
16
15
import { UserMenuLink , UserMenuSplitLine } from './UserMenuLink' ;
17
16
import {
18
17
Popover ,
19
18
PopoverArrow ,
20
19
PopoverContent ,
21
20
PopoverTrigger ,
22
21
} from './popover' ;
22
+ import { SubmitHandler , useForm } from 'react-hook-form' ;
23
+ import ClientAxios from '@web/src/lib/axios/ClientAxios' ;
24
+ import { AxiosError } from 'axios' ;
25
+ import toast from 'react-hot-toast' ;
23
26
24
- export function UserMenu ( { userData } : { userData : LoggedUserData } ) {
27
+ interface FormValues {
28
+ username : string ;
29
+ }
30
+
31
+ export const UserMenu = ( { userData } : { userData : LoggedUserData } ) => {
25
32
const [ isEditingUsername , setIsEditingUsername ] = useState ( false ) ;
26
- const [ error , setError ] = useState < string > ( '' ) ;
33
+ const [ name , setName ] = useState ( userData . username ) ;
34
+
35
+ const {
36
+ handleSubmit,
37
+ formState : { isSubmitting, errors } ,
38
+ register,
39
+ } = useForm < FormValues > ( ) ;
40
+
41
+ const onSubmit : SubmitHandler < FormValues > = async ( data ) => {
42
+ try {
43
+ await ClientAxios . patch ( '/user/username' , {
44
+ username : data . username ,
45
+ } ) ;
46
+
47
+ toast . success ( 'Username updated successfully' ) ;
48
+ setIsEditingUsername ( false ) ;
49
+ setName ( data . username ) ;
50
+ } catch ( error : unknown ) {
51
+ if ( ( error as any ) . isAxiosError ) {
52
+ const axiosError = error as AxiosError ;
53
+
54
+ // verify for throttling limit error
55
+ if ( axiosError . response ?. status === 429 ) {
56
+ toast . error ( 'Too many requests. Please try again later.' ) ;
57
+ }
58
+
59
+ // verify for validation error
60
+ if ( axiosError . response ?. status === 400 ) {
61
+ toast . error ( 'Invalid username' ) ;
62
+ }
27
63
28
- // ERRORS:
29
- // 'This username is not available! :('
30
- // 'Your username may only contain these characters: A-Z a-z 0-9 - _ .'
64
+ // verify for unauthorized error
65
+ if ( axiosError . response ?. status === 401 ) {
66
+ toast . error ( 'Unauthorized' ) ;
67
+ }
68
+
69
+ return ;
70
+ }
71
+
72
+ toast . error ( 'An error occurred. Please try again later.' ) ;
73
+ }
74
+ } ;
75
+
76
+ useEffect ( ( ) => {
77
+ if ( errors . username ?. message ) {
78
+ toast . error ( errors . username . message ) ;
79
+ }
80
+ } , [ errors . username ?. message ] ) ;
31
81
32
82
return (
33
83
< Popover onOpenChange = { ( ) => setIsEditingUsername ( false ) } >
@@ -60,7 +110,7 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
60
110
{ ! isEditingUsername ? (
61
111
< >
62
112
< h4 className = 'truncate font-semibold w-48 py-px' >
63
- { userData . username }
113
+ { name }
64
114
</ h4 >
65
115
< button onClick = { ( ) => setIsEditingUsername ( true ) } >
66
116
< FontAwesomeIcon
@@ -72,31 +122,40 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
72
122
</ >
73
123
) : (
74
124
< >
75
- < input
76
- className = 'w-[calc(12rem-52px)] font-semibold bg-transparent border border-zinc-400 rounded-md px-1'
77
- defaultValue = { userData . username }
78
- > </ input >
79
- < button onClick = { ( ) => setIsEditingUsername ( false ) } >
80
- < FontAwesomeIcon
81
- icon = { faClose }
82
- size = 'lg'
83
- className = 'text-zinc-400 hover:text-red-500'
125
+ < form onSubmit = { handleSubmit ( onSubmit ) } >
126
+ < input
127
+ className = 'w-[calc(12rem-52px)] font-semibold bg-transparent border border-zinc-400 rounded-md px-1'
128
+ defaultValue = { name }
129
+ { ...register ( 'username' , {
130
+ required : 'Username is required' ,
131
+ pattern : {
132
+ value : / ^ [ a - z A - Z 0 - 9 - _ .] { 1 , 32 } $ / ,
133
+ message :
134
+ 'Your username may only contain these characters: A-Z a-z 0-9 - _ .' ,
135
+ } ,
136
+ } ) }
84
137
/>
85
- </ button >
86
- < button onClick = { ( ) => setIsEditingUsername ( false ) } >
87
- < FontAwesomeIcon
88
- icon = { faCheck }
89
- size = 'lg'
90
- className = 'text-zinc-400 hover:text-green-500'
91
- />
92
- </ button >
138
+ < button onClick = { ( ) => setIsEditingUsername ( true ) } >
139
+ < FontAwesomeIcon
140
+ icon = { faClose }
141
+ size = 'lg'
142
+ className = 'text-zinc-400 hover:text-red-500'
143
+ />
144
+ </ button >
145
+ < button disabled = { isSubmitting } type = 'submit' >
146
+ < FontAwesomeIcon
147
+ icon = { faCheck }
148
+ size = 'lg'
149
+ className = 'text-zinc-400 hover:text-green-500'
150
+ />
151
+ </ button >
152
+ </ form >
93
153
</ >
94
154
) }
95
155
</ div >
96
156
< p className = 'text-zinc-300 text-xs truncate' > { userData . email } </ p >
97
157
</ div >
98
158
</ div >
99
- { error && < p className = 'text-sm text-red-400 px-4 pb-2' > { error } </ p > }
100
159
101
160
< UserMenuSplitLine />
102
161
@@ -110,4 +169,4 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
110
169
</ PopoverContent >
111
170
</ Popover >
112
171
) ;
113
- }
172
+ } ;
0 commit comments