1
- import { useCallback , useEffect , useRef , useState } from "react" ;
1
+ import { type FormEventHandler , useCallback , useEffect , useRef , useState } from "react" ;
2
2
import clsx from "clsx" ;
3
- import { InputFile , useToast } from "@humansignal/ui" ;
3
+ import { InputFile , ToastType , useToast } from "@humansignal/ui" ;
4
+ import { API } from "apps/labelstudio/src/providers/ApiProvider" ;
5
+ import styles from "../AccountSettings.module.scss" ;
6
+ import { useCurrentUserAtom } from "@humansignal/core/lib/hooks/useCurrentUser" ;
7
+ import { atomWithMutation } from "jotai-tanstack-query" ;
8
+ import { useAtomValue } from "jotai" ;
9
+
10
+ /**
11
+ * FIXME: This is legacy imports. We're not supposed to use such statements
12
+ * each one of these eventually has to be migrated to core or ui
13
+ */
4
14
import { Input } from "/apps/labelstudio/src/components/Form/Elements" ;
5
15
import { Userpic } from "/apps/labelstudio/src/components/Userpic/Userpic" ;
6
- import { useCurrentUser } from "/apps/labelstudio/src/providers/CurrentUser" ;
7
16
import { Button } from "/apps/labelstudio/src/components/Button/Button" ;
8
- import { useAPI } from "apps/labelstudio/src/providers/ApiProvider" ;
9
- import styles from "../AccountSettings.module.scss" ;
10
17
11
- export const PersonalInfo = ( ) => {
12
- const api = useAPI ( ) ;
13
- const toast = useToast ( ) ;
14
- const { user, fetch, isInProgress : userInProgress } = useCurrentUser ( ) ;
15
- const [ fname , setFName ] = useState ( "" ) ;
16
- const [ lname , setLName ] = useState ( "" ) ;
17
- const [ email , setEmail ] = useState ( "" ) ;
18
- const [ phone , setPhone ] = useState ( "" ) ;
19
- const [ isInProgress , setIsInProgress ] = useState ( false ) ;
20
- const userInfoForm = useRef ( ) ;
21
- const userAvatarForm = useRef ( ) ;
22
- const avatarRef = useRef ( ) ;
23
- const fileChangeHandler = ( ) => userAvatarForm . current . requestSubmit ( ) ;
24
- const avatarFormSubmitHandler = useCallback (
25
- async ( e , isDelete = false ) => {
26
- e . preventDefault ( ) ;
27
- const response = await api . callApi ( isDelete ? "deleteUserAvatar" : "updateUserAvatar" , {
28
- params : {
29
- pk : user ?. id ,
30
- } ,
31
- body : {
32
- avatar : avatarRef . current . files [ 0 ] ,
33
- } ,
18
+ const updateUserAvatarAtom = atomWithMutation ( ( ) => ( {
19
+ mutationKey : [ "update-user" ] ,
20
+ async mutationFn ( {
21
+ userId,
22
+ body,
23
+ isDelete,
24
+ } : { userId : number ; body : FormData ; isDelete ?: never } | { userId : number ; isDelete : true ; body ?: never } ) {
25
+ const method = isDelete ? "deleteUserAvatar" : "updateUserAvatar" ;
26
+ const response = await API . invoke (
27
+ method ,
28
+ {
29
+ pk : userId ,
30
+ } ,
31
+ {
32
+ body,
34
33
headers : {
35
34
"Content-Type" : "multipart/form-data" ,
36
35
} ,
37
36
errorFilter : ( ) => true ,
37
+ } ,
38
+ ) ;
39
+ return response ;
40
+ } ,
41
+ } ) ) ;
42
+
43
+ export const PersonalInfo = ( ) => {
44
+ const toast = useToast ( ) ;
45
+ const { user, fetch : refetchUser , isInProgress : userInProgress , updateAsync : updateUser } = useCurrentUserAtom ( ) ;
46
+ const updateUserAvatar = useAtomValue ( updateUserAvatarAtom ) ;
47
+ const [ isInProgress , setIsInProgress ] = useState ( false ) ;
48
+ const avatarRef = useRef < HTMLInputElement > ( ) ;
49
+ const fileChangeHandler : FormEventHandler < HTMLInputElement > = useCallback (
50
+ async ( e ) => {
51
+ if ( ! user ) return ;
52
+
53
+ const input = e . currentTarget as HTMLInputElement ;
54
+ const body = new FormData ( ) ;
55
+ body . append ( "avatar" , input . files ?. [ 0 ] ?? "" ) ;
56
+ const response = await updateUserAvatar . mutateAsync ( {
57
+ body,
58
+ userId : user . id ,
38
59
} ) ;
39
- if ( ! isDelete && response ?. status ) {
40
- toast . show ( { message : response ?. response ?. detail ?? "Error updating avatar" , type : "error" } ) ;
60
+
61
+ if ( ! response . $meta . ok ) {
62
+ toast . show ( { message : response ?. response ?. detail ?? "Error updating avatar" , type : ToastType . error } ) ;
41
63
} else {
42
- fetch ( ) ;
64
+ refetchUser ( ) ;
43
65
}
44
- userAvatarForm . current . reset ( ) ;
66
+ input . value = "" ;
45
67
} ,
46
- [ user ?. id , fetch ] ,
68
+ [ user ?. id ] ,
47
69
) ;
48
- const userFormSubmitHandler = useCallback (
70
+
71
+ const deleteUserAvatar = async ( ) => {
72
+ if ( ! user ) return ;
73
+ await updateUserAvatar . mutateAsync ( { userId : user . id , isDelete : true } ) ;
74
+ refetchUser ( ) ;
75
+ } ;
76
+
77
+ const userFormSubmitHandler : FormEventHandler = useCallback (
49
78
async ( e ) => {
50
79
e . preventDefault ( ) ;
51
- const response = await api . callApi ( "updateUser" , {
52
- params : {
53
- pk : user ?. id ,
54
- } ,
55
- body : {
56
- first_name : fname ,
57
- last_name : lname ,
58
- phone,
59
- } ,
60
- errorFilter : ( ) => true ,
61
- } ) ;
62
- if ( response ?. status ) {
63
- toast . show ( { message : response ?. response ?. detail ?? "Error updating user" , type : "error" } ) ;
64
- } else {
65
- fetch ( ) ;
80
+ if ( ! user ) return ;
81
+ const body = new FormData ( e . currentTarget as HTMLFormElement ) ;
82
+ const json = Object . fromEntries ( body . entries ( ) ) ;
83
+ const response = await updateUser ( json ) ;
84
+
85
+ refetchUser ( ) ;
86
+ if ( ! response ?. $meta . ok ) {
87
+ toast . show ( { message : response ?. response ?. detail ?? "Error updating user" , type : ToastType . error } ) ;
66
88
}
67
89
} ,
68
- [ fname , lname , phone , user ?. id ] ,
90
+ [ user ?. id ] ,
69
91
) ;
70
92
71
- useEffect ( ( ) => {
72
- if ( userInProgress ) return ;
73
- setFName ( user ?. first_name ) ;
74
- setLName ( user ?. last_name ) ;
75
- setEmail ( user ?. email ) ;
76
- setPhone ( user ?. phone ) ;
77
- setIsInProgress ( userInProgress ) ;
78
- } , [ user , userInProgress ] ) ;
79
-
80
93
useEffect ( ( ) => setIsInProgress ( userInProgress ) , [ userInProgress ] ) ;
81
94
82
95
return (
83
96
< div className = { styles . section } id = "personal-info" >
84
97
< div className = { styles . sectionContent } >
85
98
< div className = { styles . flexRow } >
86
99
< Userpic user = { user } isInProgress = { userInProgress } size = { 92 } style = { { flex : "none" } } />
87
- < form ref = { userAvatarForm } className = { styles . flex1 } onSubmit = { ( e ) => avatarFormSubmitHandler ( e ) } >
100
+ < form className = { styles . flex1 } >
88
101
< InputFile
89
102
name = "avatar"
90
103
onChange = { fileChangeHandler }
@@ -93,34 +106,26 @@ export const PersonalInfo = () => {
93
106
/>
94
107
</ form >
95
108
{ user ?. avatar && (
96
- < form onSubmit = { ( e ) => avatarFormSubmitHandler ( e , true ) } >
97
- < button type = "submit" look = "danger" >
98
- Delete
99
- </ button >
100
- </ form >
109
+ < Button type = "submit" look = "danger" onClick = { deleteUserAvatar } >
110
+ Delete
111
+ </ Button >
101
112
) }
102
113
</ div >
103
- < form ref = { userInfoForm } className = { styles . sectionContent } onSubmit = { userFormSubmitHandler } >
114
+ < form onSubmit = { userFormSubmitHandler } className = { styles . sectionContent } >
104
115
< div className = { styles . flexRow } >
105
116
< div className = { styles . flex1 } >
106
- < Input label = "First Name" value = { fname } onChange = { ( e ) => setFName ( e . target . value ) } />
117
+ < Input label = "First Name" value = { user ?. first_name } name = "first_name" />
107
118
</ div >
108
119
< div className = { styles . flex1 } >
109
- < Input label = "Last Name" value = { lname } onChange = { ( e ) => setLName ( e . target . value ) } />
120
+ < Input label = "Last Name" value = { user ?. last_name } name = "last_name" />
110
121
</ div >
111
122
</ div >
112
123
< div className = { styles . flexRow } >
113
124
< div className = { styles . flex1 } >
114
- < Input
115
- label = "E-mail"
116
- type = "email"
117
- readOnly = { true }
118
- value = { email }
119
- onChange = { ( e ) => setEmail ( e . target . value ) }
120
- />
125
+ < Input label = "E-mail" type = "email" readOnly = { true } value = { user ?. email } />
121
126
</ div >
122
127
< div className = { styles . flex1 } >
123
- < Input label = "Phone" type = "phone" value = { phone } onChange = { ( e ) => setPhone ( e . target . value ) } />
128
+ < Input label = "Phone" type = "phone" value = { user ?. phone } name = "phone" />
124
129
</ div >
125
130
</ div >
126
131
< div className = { clsx ( styles . flexRow , styles . flexEnd ) } >
0 commit comments