@@ -8,26 +8,76 @@ import {
88 faSignOutAlt ,
99} from '@fortawesome/free-solid-svg-icons' ;
1010import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
11- import Image from 'next/image' ;
12- import { useState } from 'react' ;
1311import { LoggedUserData } from '@web/src/modules/auth/types/User' ;
12+ import Image from 'next/image' ;
13+ import { useEffect , useState } from 'react' ;
1414import { UserMenuButton } from '../client/UserMenuButton' ;
15- import { EditUsernameModal } from './EditUsernameModal' ;
1615import { UserMenuLink , UserMenuSplitLine } from './UserMenuLink' ;
1716import {
1817 Popover ,
1918 PopoverArrow ,
2019 PopoverContent ,
2120 PopoverTrigger ,
2221} 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' ;
2326
24- export function UserMenu ( { userData } : { userData : LoggedUserData } ) {
27+ interface FormValues {
28+ username : string ;
29+ }
30+
31+ export const UserMenu = ( { userData } : { userData : LoggedUserData } ) => {
2532 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+ }
2763
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 ] ) ;
3181
3282 return (
3383 < Popover onOpenChange = { ( ) => setIsEditingUsername ( false ) } >
@@ -60,7 +110,7 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
60110 { ! isEditingUsername ? (
61111 < >
62112 < h4 className = 'truncate font-semibold w-48 py-px' >
63- { userData . username }
113+ { name }
64114 </ h4 >
65115 < button onClick = { ( ) => setIsEditingUsername ( true ) } >
66116 < FontAwesomeIcon
@@ -72,31 +122,40 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
72122 </ >
73123 ) : (
74124 < >
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+ } ) }
84137 />
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 >
93153 </ >
94154 ) }
95155 </ div >
96156 < p className = 'text-zinc-300 text-xs truncate' > { userData . email } </ p >
97157 </ div >
98158 </ div >
99- { error && < p className = 'text-sm text-red-400 px-4 pb-2' > { error } </ p > }
100159
101160 < UserMenuSplitLine />
102161
@@ -110,4 +169,4 @@ export function UserMenu({ userData }: { userData: LoggedUserData }) {
110169 </ PopoverContent >
111170 </ Popover >
112171 ) ;
113- }
172+ } ;
0 commit comments