@@ -23,6 +23,26 @@ import Loading from "@/widgets/Loading";
2323import defaultProfile from "/assets/defaultProfile.png" ;
2424import BottomNav from "@/widgets/BottomNav" ;
2525
26+ // 이름 길이 제한: 한글 1자 = 2, 그 외 1자 = 1 → 상한 20 (한글 10자 / 영어 20자)
27+ const MAX_NAME_WEIGHT = 20 ;
28+
29+ const isKoreanChar = ( char : string ) => / [ ㄱ - ㅎ ㅏ - ㅣ 가 - 힣 ] / . test ( char ) ;
30+
31+ const getNameWeight = ( value : string ) =>
32+ [ ...value ] . reduce ( ( acc , char ) => acc + ( isKoreanChar ( char ) ? 2 : 1 ) , 0 ) ;
33+
34+ // 가중치 상한을 넘지 않도록 문자열을 잘라낸다
35+ const truncateToWeight = ( value : string , max : number ) => {
36+ let weight = 0 ;
37+ let result = "" ;
38+ for ( const char of value ) {
39+ weight += isKoreanChar ( char ) ? 2 : 1 ;
40+ if ( weight > max ) break ;
41+ result += char ;
42+ }
43+ return result ;
44+ } ;
45+
2646const ProfileCard = ( { onClickInterest } : { onClickInterest : ( ) => void } ) => {
2747 const { user, updateUsername, setProfileImg } = useAuth ( ) ;
2848 const { interestCategories } = useUserData ( ) ;
@@ -40,6 +60,9 @@ const ProfileCard = ({ onClickInterest }: { onClickInterest: () => void }) => {
4060 const [ isEditmode , setIsEditmode ] = useState < boolean > ( false ) ;
4161 const navigate = useNavigate ( ) ;
4262
63+ const nameWeight = getNameWeight ( username ) ;
64+ const isNameMax = nameWeight >= MAX_NAME_WEIGHT ;
65+
4366 const handleImageError = ( ) => {
4467 setProfilePreviewUrl ( defaultProfile ) ;
4568 } ;
@@ -105,20 +128,38 @@ const ProfileCard = ({ onClickInterest }: { onClickInterest: () => void }) => {
105128 </ div >
106129 < div className = { styles . nameEmailCol } >
107130 { isEditmode ? (
108- < input
109- className = { styles . nameInput }
110- type = "text"
111- value = { username }
112- placeholder = "이름을 입력하세요"
113- onChange = { ( e ) => setUsername ( e . currentTarget . value ) }
114- onKeyDown = { ( e : React . KeyboardEvent ) => {
115- if ( e . key === "Enter" && ! e . nativeEvent . isComposing ) {
116- e . stopPropagation ( ) ;
117- e . preventDefault ( ) ;
118- handleChangesSave ( ) ;
131+ < div className = { styles . nameInputWrapper } >
132+ < input
133+ className = { styles . nameInput }
134+ type = "text"
135+ value = { username }
136+ placeholder = "이름을 입력하세요"
137+ onChange = { ( e ) =>
138+ setUsername (
139+ truncateToWeight ( e . currentTarget . value , MAX_NAME_WEIGHT ) ,
140+ )
119141 }
120- } }
121- />
142+ onKeyDown = { ( e : React . KeyboardEvent ) => {
143+ if ( e . key === "Enter" && ! e . nativeEvent . isComposing ) {
144+ e . stopPropagation ( ) ;
145+ e . preventDefault ( ) ;
146+ handleChangesSave ( ) ;
147+ }
148+ } }
149+ />
150+ < div className = { styles . nameHintRow } >
151+ < span
152+ className = { `${ styles . nameHint } ${ isNameMax ? styles . nameHintMax : "" } ` }
153+ >
154+ 한글 10자 · 영어 20자 이내로 입력해주세요
155+ </ span >
156+ < span
157+ className = { `${ styles . nameCounter } ${ isNameMax ? styles . nameHintMax : "" } ` }
158+ >
159+ { nameWeight } /{ MAX_NAME_WEIGHT }
160+ </ span >
161+ </ div >
162+ </ div >
122163 ) : (
123164 < span className = { styles . nameText } > { user ?. username } </ span >
124165 ) }
0 commit comments