-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from teamViNO/feature-058
Feature 058
- Loading branch information
Showing
27 changed files
with
666 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,140 @@ | ||
import { useEffect, useMemo, useRef, useState } from 'react'; | ||
import { useRecoilState } from 'recoil'; | ||
|
||
import { getMyInfoAPI, updateMyInfoAPI } from '@/apis/user'; | ||
|
||
import NaverIconImage from '@/assets/naver-icon.png'; | ||
|
||
import { Tooltip } from '@/components/common'; | ||
|
||
import { GENDER_TYPE_LIST } from '@/constants/user'; | ||
|
||
import useFocus from '@/hooks/useFocus'; | ||
|
||
import { userInfoState } from '@/stores/user'; | ||
|
||
import theme from '@/styles/theme'; | ||
import { Box } from '@/styles/ProfilePage'; | ||
|
||
import { validateNickname } from '@/utils/validation'; | ||
|
||
import { ChangePassword } from './ChangePassword'; | ||
|
||
const Account = () => { | ||
const isSocialAccount = true; | ||
const timerRef = useRef<NodeJS.Timeout>(); | ||
const [userInfo, setUserInfo] = useRecoilState(userInfoState); | ||
const [nickname, setNickname] = useState(userInfo?.nick_name || ''); | ||
const [gender, setGender] = useState(userInfo?.gender || ''); | ||
|
||
const [isDuplicateNickname, setIsDuplicateNickname] = useState(false); | ||
const [isErrorNickname, setIsErrorNickname] = useState( | ||
validateNickname(nickname), | ||
); | ||
|
||
const [isShowTooltip, setIsShowTooltip] = useState(false); | ||
const [nicknameInputRef, focusNicknameInput, isNicknameFocus] = | ||
useFocus<HTMLInputElement>(); | ||
|
||
const isSocialAccount = !!userInfo?.platform; | ||
const birthDate = new Date(userInfo?.birth_date || ''); | ||
|
||
const isDisabled = useMemo(() => { | ||
if (isErrorNickname) return true; | ||
|
||
return !(nickname !== userInfo?.nick_name || gender !== userInfo?.gender); | ||
}, [userInfo, nickname, gender, isErrorNickname]); | ||
|
||
const nicknameInputStyle = { | ||
border: `1.5px solid ${ | ||
isErrorNickname | ||
? theme.color.red | ||
: isNicknameFocus | ||
? theme.color.gray500 | ||
: theme.color.gray200 | ||
}`, | ||
}; | ||
|
||
const clearTooltipTimer = () => { | ||
if (timerRef.current) { | ||
clearTimeout(timerRef.current); | ||
} | ||
}; | ||
|
||
const showTooltip = () => { | ||
setIsShowTooltip(true); | ||
|
||
clearTooltipTimer(); | ||
|
||
timerRef.current = setTimeout(() => { | ||
setIsShowTooltip(false); | ||
}, 1000 * 5); | ||
}; | ||
|
||
const refreshMyInfo = async () => { | ||
try { | ||
const { result } = (await getMyInfoAPI()).data; | ||
|
||
setUserInfo(result); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}; | ||
|
||
const handleChangeNickname: React.ChangeEventHandler<HTMLInputElement> = ({ | ||
target: { value }, | ||
}) => { | ||
setIsDuplicateNickname(false); | ||
setNickname(value); | ||
setIsErrorNickname(validateNickname(value)); | ||
}; | ||
|
||
const handleClickSubmitButton = async () => { | ||
try { | ||
const { success } = ( | ||
await updateMyInfoAPI({ nick_name: nickname, gender }) | ||
).data; | ||
|
||
if (success) { | ||
showTooltip(); | ||
refreshMyInfo(); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
return () => { | ||
clearTooltipTimer(); | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}> | ||
<div style={{ display: 'flex', justifyContent: 'space-between' }}> | ||
<h2 className="title">계정</h2> | ||
|
||
<button className="submit disabled">변경하기</button> | ||
<div style={{ position: 'relative' }}> | ||
{isShowTooltip && ( | ||
<div className="submit-tooltip"> | ||
<Tooltip direction="down">수정이 완료되었어요!</Tooltip> | ||
</div> | ||
)} | ||
|
||
<button | ||
className="submit" | ||
disabled={isDisabled} | ||
onClick={handleClickSubmitButton} | ||
> | ||
변경하기 | ||
</button> | ||
</div> | ||
</div> | ||
|
||
<Box> | ||
<div | ||
className="account-group" | ||
style={{ flexDirection: 'row', alignItems: 'center', gap: 20 }} | ||
style={{ flexDirection: 'row', gap: 20 }} | ||
> | ||
<div className="avatar"></div> | ||
|
||
|
@@ -30,50 +148,86 @@ const Account = () => { | |
> | ||
<span className="group-title">닉네임</span> | ||
|
||
<div className="input-box"> | ||
<div | ||
className="input-box" | ||
style={nicknameInputStyle} | ||
onClick={focusNicknameInput} | ||
> | ||
<div style={{ display: 'flex', flex: '1 1 auto' }}> | ||
<input type="text" value="여울" /> | ||
<input | ||
ref={nicknameInputRef} | ||
type="text" | ||
value={nickname} | ||
onChange={handleChangeNickname} | ||
/> | ||
</div> | ||
|
||
<span className="input-guide">3/7 (공백포함)</span> | ||
<span className="input-guide"> | ||
{nickname.length}/7 (공백포함) | ||
</span> | ||
</div> | ||
|
||
{isErrorNickname && ( | ||
<span className="input-error-text"> | ||
닉네임 형식이 올바르지 않아요! | ||
</span> | ||
)} | ||
{isDuplicateNickname && ( | ||
<span className="input-error-text"> | ||
이미 존재하는 닉네임이에요! | ||
</span> | ||
)} | ||
</div> | ||
</div> | ||
|
||
<div className="account-group"> | ||
<span className="group-title">이름</span> | ||
|
||
<div className="input-box disabled"> | ||
<div style={{ display: 'flex', flex: '1 1 auto' }}>서예진</div> | ||
<div style={{ display: 'flex', flex: '1 1 auto' }}> | ||
{userInfo?.name} | ||
</div> | ||
|
||
<span className="input-guide">3/7 (공백포함)</span> | ||
<span className="input-guide"> | ||
{userInfo?.name.length}/7 (공백포함) | ||
</span> | ||
</div> | ||
</div> | ||
|
||
<div className="account-group"> | ||
<span className="group-title">성별</span> | ||
|
||
<div style={{ display: 'flex', gap: 12 }}> | ||
<button className="option">미표기</button> | ||
<button className="option">남자</button> | ||
<button className="option selected">여자</button> | ||
{GENDER_TYPE_LIST.map((genderType) => ( | ||
<button | ||
key={'gender-' + genderType.id} | ||
className={`option ${genderType.id === gender && 'selected'}`} | ||
onClick={() => setGender(genderType.id)} | ||
> | ||
{genderType.name} | ||
</button> | ||
))} | ||
</div> | ||
</div> | ||
|
||
<div className="account-group"> | ||
<span className="group-title">생년월일</span> | ||
|
||
<div style={{ display: 'flex', gap: 12 }}> | ||
<div className="input-box disabled">2001</div> | ||
<div className="input-box disabled">07</div> | ||
<div className="input-box disabled">01</div> | ||
<div className="input-box disabled">{birthDate.getFullYear()}</div> | ||
<div className="input-box disabled"> | ||
{String(birthDate.getMonth() + 1).padStart(2, '0')} | ||
</div> | ||
<div className="input-box disabled"> | ||
{String(birthDate.getDate()).padStart(2, '0')} | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div className="account-group"> | ||
<span className="group-title">전화번호</span> | ||
|
||
<div className="input-box disabled">010901716171</div> | ||
<div className="input-box disabled">{userInfo?.phone_number}</div> | ||
</div> | ||
|
||
{isSocialAccount && ( | ||
|
@@ -86,7 +240,7 @@ const Account = () => { | |
> | ||
<img src={NaverIconImage} width={40} /> | ||
|
||
<span>[email protected]</span> | ||
<span>{userInfo?.email}</span> | ||
</div> | ||
</div> | ||
)} | ||
|
@@ -97,20 +251,10 @@ const Account = () => { | |
<div className="account-group"> | ||
<span className="group-title">계정</span> | ||
|
||
<div className="input-box disabled">[email protected]</div> | ||
<div className="input-box disabled">{userInfo?.email}</div> | ||
</div> | ||
|
||
<div className="account-group"> | ||
<span className="group-title">비밀번호</span> | ||
|
||
<div className="input-box" style={{ padding: '5px 4px 5px 16px' }}> | ||
<div style={{ display: 'flex', flex: '1 1 auto' }}> | ||
<input type="password" value="123456" /> | ||
</div> | ||
|
||
<button className="submit disabled">변경하기</button> | ||
</div> | ||
</div> | ||
<ChangePassword /> | ||
</Box> | ||
)} | ||
</div> | ||
|
26 changes: 26 additions & 0 deletions
26
src/components/ProfilePage/Account/ChangePassword/ChangePassword.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import useBoolean from '@/hooks/useBoolean'; | ||
|
||
import ChangePasswordModal from './ChangePasswordModal'; | ||
|
||
const ChangePassword = () => { | ||
const [isOpen, , open, close] = useBoolean(false); | ||
|
||
return ( | ||
<> | ||
<div | ||
className="account-group" | ||
style={{ flexDirection: 'row', justifyContent: 'space-between' }} | ||
> | ||
<span className="group-title">비밀번호</span> | ||
|
||
<button className="submit" onClick={open}> | ||
변경하기 | ||
</button> | ||
</div> | ||
|
||
{isOpen && <ChangePasswordModal onClose={close} />} | ||
</> | ||
); | ||
}; | ||
|
||
export default ChangePassword; |
11 changes: 11 additions & 0 deletions
11
src/components/ProfilePage/Account/ChangePassword/ChangePasswordModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Modal } from '@/components/common'; | ||
|
||
type Props = { | ||
onClose: () => void; | ||
}; | ||
|
||
const ChangePasswordModal = ({ onClose }: Props) => { | ||
return <Modal onClose={onClose}>asdf</Modal>; | ||
}; | ||
|
||
export default ChangePasswordModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as ChangePassword } from './ChangePassword'; |
Oops, something went wrong.