@@ -2,115 +2,162 @@ import { useEffect, useRef, useState } from 'react'
22
33import styled from 'styled-components'
44
5- import { Profile , Input } from '@shared/ui'
5+ import { Profile , Input , Loading } from '@shared/ui'
66import type { ProfileUrl } from '@shared/ui/Profile'
77
88import { Camera } from '@/assets/icons'
99import { useAuthStore } from '@/features/auth/store/authStore'
10+ import { useProfile } from '@/features/profile/model/useProfile'
1011import { flexRowCenter } from '@/shared/styles/mixins'
1112
13+ const MAX_FILE_SIZE = 1024 * 1024 * 5 // 5MB
14+
1215const UserProfile = ( ) => {
13- const { userInfo } = useAuthStore ( )
16+ const { userInfo, updateUserInfo } = useAuthStore ( )
17+ const { mutate, isPending } = useProfile ( )
1418
1519 const fileInputRef = useRef < HTMLInputElement > ( null )
1620
1721 const [ isEditMode , setIsEditMode ] = useState ( false )
22+ const [ hasErrorMsg , setHasErrorMsg ] = useState ( '' )
23+
1824 const [ updatedProfile , setUpdatedProfile ] = useState < {
1925 nickname : string
20- profileImg : ProfileUrl
26+ file : File | null
27+ profileImage : string | null
2128 } > ( {
2229 nickname : userInfo . username ,
23- profileImg : userInfo ?. userProfileImageUrl || null ,
30+ profileImage : userInfo ?. userProfileImageUrl || null ,
31+ file : null ,
2432 } )
25- const [ isFileError , setIsFileError ] = useState ( false )
2633
27- // React μ»΄ν¬λνΈ λΌμ΄νμ¬μ΄ν΄μ λ§μΆ° blob: URL ν΄μ λ° λ©λͺ¨λ¦¬ λ¦ λ°©μ§
28- useEffect ( ( ) => {
29- return ( ) => {
30- if (
31- typeof updatedProfile . profileImg === 'string' &&
32- updatedProfile . profileImg . startsWith ( 'blob:' )
33- ) {
34- URL . revokeObjectURL ( updatedProfile . profileImg )
35- }
36- }
37- } , [ updatedProfile . profileImg ] )
34+ // νλ©΄μ 보μ¬μ€ ν리뷰 URL
35+ const [ previewImage , setPreviewImage ] = useState < ProfileUrl > (
36+ userInfo ?. userProfileImageUrl || null
37+ )
3838
39+ // νλ‘ν νΈμ§ λ²νΌ ν΄λ¦
3940 const onProfileEditClick = ( ) => {
4041 if ( ! isEditMode ) {
4142 setIsEditMode ( true )
4243 return
4344 }
44- // TODO: νλ‘ν μμ api μ°λ & api μ±κ³΅ μ updatedProfile μ΄κΈ°ν λ‘μ§ μΆκ°
45+
46+ if ( updatedProfile . nickname . length === 0 ) {
47+ setHasErrorMsg ( '1μ μ΄μ μ
λ ₯ν΄μ£ΌμΈμ' )
48+ return
49+ }
50+
51+ mutate ( updatedProfile , {
52+ onSuccess : ( response ) => {
53+ updateUserInfo ( response )
54+
55+ setIsEditMode ( false )
56+ setHasErrorMsg ( '' )
57+ setUpdatedProfile ( {
58+ nickname : response . nickname ,
59+ profileImage : response . profileImageUrl ,
60+ file : null ,
61+ } )
62+ setPreviewImage ( response . profileImageUrl )
63+ } ,
64+ } )
65+
66+ // μ΄κΈ°ν
4567 setIsEditMode ( false )
46- setIsFileError ( false )
68+ setHasErrorMsg ( '' )
4769 setUpdatedProfile ( {
4870 nickname : userInfo . username ,
49- profileImg : userInfo ?. userProfileImageUrl || null ,
71+ profileImage : userInfo ?. userProfileImageUrl || null ,
72+ file : null ,
5073 } )
74+ setPreviewImage ( userInfo ?. userProfileImageUrl || null )
5175 }
5276
77+ // νλ‘ν μ΄λ―Έμ§ μ ν
5378 const onFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
5479 if ( ! e . target . files || ! e . target . files . length ) return
5580
5681 const file = e . target . files [ 0 ]
57- const MAX_FILE_SIZE = 1024 * 1024 * 5 // 5MB
58-
5982 if ( file . size > MAX_FILE_SIZE ) {
60- setIsFileError ( true )
83+ setHasErrorMsg ( '5MB μ΄νμ νμΌλ§ μ
λ‘λ κ°λ₯ν΄μ' )
6184 if ( fileInputRef . current ) {
6285 fileInputRef . current . value = ''
6386 }
64- setUpdatedProfile ( ( prev ) => ( { ...prev , profileImg : userInfo ?. userProfileImageUrl || null } ) )
87+ setUpdatedProfile ( ( prev ) => ( {
88+ ...prev ,
89+ profileImage : userInfo ?. userProfileImageUrl || null ,
90+ file : null ,
91+ } ) )
92+ setPreviewImage ( userInfo ?. userProfileImageUrl || null )
6593 return
6694 }
6795
68- setIsFileError ( false )
69- const image = window . URL . createObjectURL ( file )
70- setUpdatedProfile ( ( prev ) => ( { ...prev , profileImg : image } ) )
96+ setHasErrorMsg ( '' )
97+ const blobUrl = URL . createObjectURL ( file )
98+
99+ setUpdatedProfile ( ( prev ) => ( { ...prev , file, profileImage : null } ) )
100+ setPreviewImage ( blobUrl )
71101 }
72102
103+ // blob URL λ©λͺ¨λ¦¬ μ 리
104+ useEffect ( ( ) => {
105+ return ( ) => {
106+ if ( previewImage && typeof previewImage === 'string' && previewImage . startsWith ( 'blob:' ) ) {
107+ URL . revokeObjectURL ( previewImage )
108+ }
109+ }
110+ } , [ previewImage ] )
111+
73112 return (
74- < ProfileWrapper >
75- < ProfileImgContainer >
76- < Profile
77- size = "L"
78- profileUrl = {
79- isEditMode ? updatedProfile . profileImg : userInfo ?. userProfileImageUrl || null
80- }
81- />
82- { isEditMode && (
83- < >
84- < ProfileImgEditBtn
85- type = "button"
86- aria-label = "νλ‘ν μ΄λ―Έμ§ μμ "
87- onClick = { ( ) => fileInputRef . current ?. click ( ) }
88- >
89- < Camera width = { 14 } height = { 14 } />
90- </ ProfileImgEditBtn >
91- < input type = "file" ref = { fileInputRef } accept = "image/*" onChange = { onFileChange } hidden />
92- </ >
113+ < >
114+ { isPending && < Loading isLoading = { isPending } /> }
115+ < ProfileWrapper >
116+ < ProfileImgContainer >
117+ < Profile size = "L" profileUrl = { previewImage } />
118+
119+ { isEditMode && (
120+ < >
121+ < ProfileImgEditBtn
122+ type = "button"
123+ aria-label = "νλ‘ν μ΄λ―Έμ§ μμ "
124+ onClick = { ( ) => fileInputRef . current ?. click ( ) }
125+ >
126+ < Camera width = { 14 } height = { 14 } />
127+ </ ProfileImgEditBtn >
128+ < input
129+ type = "file"
130+ ref = { fileInputRef }
131+ accept = "image/*"
132+ onChange = { onFileChange }
133+ hidden
134+ />
135+ </ >
136+ ) }
137+ </ ProfileImgContainer >
138+
139+ { hasErrorMsg && < FileErrMsg > { hasErrorMsg } </ FileErrMsg > }
140+
141+ { ! isEditMode ? (
142+ < ProfileName > { userInfo . username } </ ProfileName >
143+ ) : (
144+ < Input
145+ type = "text"
146+ placeholder = "λλ€μ"
147+ value = { updatedProfile . nickname }
148+ maxLength = { 10 }
149+ width = "155px"
150+ onChange = { ( e ) =>
151+ setUpdatedProfile ( { ...updatedProfile , nickname : e . target . value . trim ( ) } )
152+ }
153+ />
93154 ) }
94- </ ProfileImgContainer >
95- { isFileError && < FileErrMsg > 5MB μ΄νμ νμΌλ§ μ
λ‘λ κ°λ₯ν΄μ</ FileErrMsg > }
96- { ! isEditMode ? (
97- < ProfileName > { userInfo . username } </ ProfileName >
98- ) : (
99- < Input
100- type = "text"
101- placeholder = "λλ€μ"
102- value = { updatedProfile . nickname }
103- maxLength = { 10 }
104- width = "155px"
105- onChange = { ( e ) =>
106- setUpdatedProfile ( { ...updatedProfile , nickname : e . target . value . trim ( ) } )
107- }
108- />
109- ) }
110- < ProfileEditBtn type = "button" onClick = { onProfileEditClick } >
111- { isEditMode ? 'μ μ₯νκΈ°' : 'νλ‘ν νΈμ§' }
112- </ ProfileEditBtn >
113- </ ProfileWrapper >
155+
156+ < ProfileEditBtn type = "button" onClick = { onProfileEditClick } >
157+ { isEditMode ? 'μ μ₯νκΈ°' : 'νλ‘ν νΈμ§' }
158+ </ ProfileEditBtn >
159+ </ ProfileWrapper >
160+ </ >
114161 )
115162}
116163
0 commit comments