Skip to content

Commit 36059e7

Browse files
authored
Merge pull request #135 from prgrms-web-devcourse-final-project/134-feature/mypage-setting
[Feature] #134 마이페이지 데이터바인딩, 알람설정 부분 구현
2 parents 99b24ca + 7bcd98f commit 36059e7

File tree

17 files changed

+220
-117
lines changed

17 files changed

+220
-117
lines changed

src/apis/myPage/updateGangbuntta.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// import { AxiosError } from 'axios'
2+
// import { APIResponse, ErrorResponse } from '~types/api'
3+
// import { axiosInstance } from '~apis/axiosInstance'
4+
5+
// export type UpdateGangbunttaRequest = {}
6+
7+
// export type UpdateGangbunttaResponse = Pick<APIResponse>
8+
9+
// export const updateGangbuntta = async (
10+
// req: UpdateGangbunttaRequest
11+
// ): Promise<APIResponse<UpdateGangbunttaResponse>> => {
12+
// try {
13+
// const { data } = await axiosInstance.patch<APIResponse<UpdateGangbunttaResponse>>(`/member`, req)
14+
// return data
15+
// } catch (error) {
16+
// if (error instanceof AxiosError) {
17+
// const { response } = error as AxiosError<ErrorResponse>
18+
19+
// if (response) {
20+
// const { code, message } = response.data
21+
// switch (code) {
22+
// case 400:
23+
// throw new Error(message || '잘못된 요청입니다.')
24+
// case 401:
25+
// throw new Error(message || '인증에 실패했습니다.')
26+
// case 500:
27+
// throw new Error(message || '서버 오류가 발생했습니다.')
28+
// default:
29+
// throw new Error(message || '알 수 없는 오류가 발생했습니다.')
30+
// }
31+
// }
32+
// // 요청 자체가 실패한 경우
33+
// throw new Error('네트워크 연결을 확인해주세요')
34+
// }
35+
36+
// console.error('예상치 못한 에러:', error)
37+
// throw new Error('다시 시도해주세요')
38+
// }
39+
// }

src/apis/myPage/updateSetting.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { AxiosError } from 'axios'
2+
import { APIResponse, ErrorResponse, Setting } from '~types/api'
3+
import { axiosInstance } from '~apis/axiosInstance'
4+
5+
export type UpdateSettingRequest = Pick<Setting, 'type' | 'isAgreed'>
6+
7+
export type UpdateSettingResponse = Pick<Setting, 'notificationSettingsId' | 'memberId' | 'type' | 'isAgreed'>
8+
9+
export const updateSetting = async (req: UpdateSettingRequest): Promise<APIResponse<UpdateSettingResponse>> => {
10+
try {
11+
const { data } = await axiosInstance.patch<APIResponse<UpdateSettingResponse>>(`/notification-settings/update`, req)
12+
console.log('알림 상태변경 : ', data.data)
13+
return data
14+
} catch (error) {
15+
if (error instanceof AxiosError) {
16+
const { response } = error as AxiosError<ErrorResponse>
17+
18+
if (response) {
19+
const { code, message } = response.data
20+
switch (code) {
21+
case 400:
22+
throw new Error(message || '잘못된 요청입니다.')
23+
case 401:
24+
throw new Error(message || '인증에 실패했습니다.')
25+
case 500:
26+
throw new Error(message || '서버 오류가 발생했습니다.')
27+
default:
28+
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
29+
}
30+
}
31+
// 요청 자체가 실패한 경우
32+
throw new Error('네트워크 연결을 확인해주세요')
33+
}
34+
35+
console.error('예상치 못한 에러:', error)
36+
throw new Error('다시 시도해주세요')
37+
}
38+
}

src/apis/register/createRegister.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ export type CreateRegisterResponse = Pick<
1515
export const createRegister = async (req: CreateRegisterRequest): Promise<APIResponse<CreateRegisterResponse>> => {
1616
try {
1717
const response = await axiosInstance.post<APIResponse<CreateRegisterResponse>>(`/member/join`, req)
18-
18+
console.log(response)
1919
// 토큰 추출 및 저장
20-
const accessToken = response.headers['authorization']
20+
const accessToken = (response.headers['authorization'] as string).split('Bearer ')[1]
21+
2122
if (accessToken) {
2223
localStorage.setItem('token', accessToken)
23-
axiosInstance.defaults.headers.common['Authorization'] = accessToken
2424
}
2525

2626
return response.data

src/components/Toggle/index.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SettingsStoreKey, useSettingsStore } from '~stores/settingsStore'
2+
import { updateSetting } from '~apis/myPage/updateSetting'
23
import * as S from './styles'
34

45
type ToggleProps = {
@@ -7,11 +8,26 @@ type ToggleProps = {
78
}
89

910
export default function Toggle({ id, setting }: ToggleProps) {
10-
const value = useSettingsStore(state => state[setting])
11+
const value = useSettingsStore(state => state.settings[setting])
1112
const setSetting = useSettingsStore(state => state.setSetting)
1213

13-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
14-
setSetting(setting, e.target.checked)
14+
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
15+
try {
16+
const newValue = e.target.checked
17+
// setting이 'messages' 또는 'myWalkNotifications'인 경우에만 API 호출
18+
if (setting === 'messages' || setting === 'myWalkNotifications') {
19+
await updateSetting({
20+
type: setting === 'messages' ? 'CHAT' : 'WALK',
21+
isAgreed: newValue ? 'TRUE' : 'FALSE',
22+
})
23+
}
24+
// 상태 업데이트
25+
setSetting(setting, newValue)
26+
} catch (error) {
27+
console.error('토글 변경 중 오류 발생:', error)
28+
// 에러 발생 시 사용자에게 알림
29+
alert('설정 변경에 실패했습니다. 다시 시도해주세요.')
30+
}
1531
}
1632

1733
return (

src/constants/settingsInfo.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
import { SettingsStoreKey } from '~stores/settingsStore'
22

33
export const SETTINGS_INFO: Record<SettingsStoreKey, { title: string; desc: string }> = {
4-
allNotifications: {
5-
title: '모든 알림',
6-
desc: '',
7-
},
8-
friendRequests: {
9-
title: '친구 신청',
10-
desc: '부연 설명이 들어가는 공간',
11-
},
12-
familyWalkNotifications: {
13-
title: '가족 산책 알림',
14-
desc: '부연 설명이 들어가는 공간',
15-
},
164
myWalkNotifications: {
175
title: '내 산책 알림',
186
desc: '부연 설명이 들어가는 공간',
@@ -25,4 +13,16 @@ export const SETTINGS_INFO: Record<SettingsStoreKey, { title: string; desc: stri
2513
title: '강번따 허용 여부',
2614
desc: '부연 설명이 들어가는 공간',
2715
},
16+
allNotifications: {
17+
title: '모든 알림',
18+
desc: '',
19+
},
20+
friendRequests: {
21+
title: '친구 신청',
22+
desc: '부연 설명이 들어가는 공간',
23+
},
24+
familyWalkNotifications: {
25+
title: '가족 산책 알림',
26+
desc: '부연 설명이 들어가는 공간',
27+
},
2828
}

src/modals/PositionChoiceModal/index.tsx

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,37 @@ import { useState } from 'react'
33
import { RadioGroup } from '@mui/material'
44
import { useModalStore } from '~stores/modalStore'
55
import { ActionButton } from '~components/Button/ActionButton'
6+
import { FAMILY_ROLE } from '~constants/familyRole'
67
import { FamilyRole } from '~types/common'
78

8-
interface PositionChoiceModalProps {
9-
onSelect: (position: string) => void
10-
initialValue?: string | null
11-
}
12-
interface Position {
13-
label: string
14-
value: FamilyRole
15-
}
16-
17-
const positions: Position[] = [
18-
{ label: '엄마', value: 'MOTHER' },
19-
{ label: '아빠', value: 'FATHER' },
20-
{ label: '형', value: 'OLDER_BROTHER' },
21-
{ label: '오빠', value: 'ELDER_BROTHER' },
22-
{ label: '언니', value: 'ELDER_SISTER' },
23-
{ label: '누나', value: 'OLDER_SISTER' },
24-
{ label: '할머니', value: 'GRANDMOTHER' },
25-
{ label: '할아버지', value: 'GRANDFATHER' },
26-
]
9+
const familyRoles = Object.values(FAMILY_ROLE)
10+
type FamilyRoleChoiceModalProps = { onSelectRole: (role: FamilyRole) => void; initialRole: FamilyRole }
2711

28-
export default function PositionChoiceModal({ onSelect, initialValue = 'null' }: PositionChoiceModalProps) {
29-
const [value, setValue] = useState(initialValue)
12+
export default function FamilyRoleChoiceModal({ onSelectRole, initialRole }: FamilyRoleChoiceModalProps) {
13+
const [selectedFamilyRole, setSelectedFamilyRole] = useState<FamilyRole>(initialRole)
3014
const { popModal } = useModalStore()
3115

3216
const handleConfirm = () => {
33-
if (value !== null) {
34-
onSelect(value)
17+
if (selectedFamilyRole !== null) {
18+
onSelectRole(selectedFamilyRole)
3519
popModal()
3620
}
3721
}
3822

3923
return (
4024
<S.DialogContainer open={true} onClose={popModal}>
4125
<S.DialogTitle>가족 포지션 선택</S.DialogTitle>
42-
<RadioGroup value={value} onChange={e => setValue(e.target.value)}>
26+
<RadioGroup value={selectedFamilyRole} onChange={e => setSelectedFamilyRole(e.target.value as FamilyRole)}>
4327
<S.RadioGroupContainer>
44-
{positions.map(position => (
45-
<S.StyledFormControlLabel
46-
key={position.value}
47-
value={position.value}
48-
control={<S.StyledRadio />}
49-
label={position.label}
50-
/>
28+
{familyRoles.map(role => (
29+
<S.StyledFormControlLabel key={role} value={role} control={<S.StyledRadio />} label={role} />
5130
))}
5231
</S.RadioGroupContainer>
5332
</RadioGroup>
5433

5534
<S.ButtonContainer>
5635
<ActionButton onClick={popModal}>취소</ActionButton>
57-
<ActionButton onClick={handleConfirm} disabled={!value} $fontWeight='700'>
36+
<ActionButton onClick={handleConfirm} disabled={!selectedFamilyRole} $fontWeight='700'>
5837
확인
5938
</ActionButton>
6039
</S.ButtonContainer>

src/pages/HomePage/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useEffect } from 'react'
2222

2323
function HomeContent() {
2424
const { data } = useHomePageData()
25+
console.log(data)
2526
const { pushModal } = useModalStore()
2627
return (
2728
<>
@@ -32,7 +33,7 @@ function HomeContent() {
3233

3334
<S.Visual>
3435
<Typo24 $weight='700' $textAlign='center'>
35-
오늘은 {data && FAMILY_ROLE[data.familyRole]}
36+
오늘은 {data?.familyRole ? FAMILY_ROLE[data.familyRole] : ''}
3637
</Typo24>
3738
<Typo24 $weight='700' $textAlign='center'>
3839
산책가는 날!

src/pages/ProfilePage/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function ProfileContent({ id }: { id: number }) {
3434
<S.TypoWrapper $gap={8}>
3535
<Typo13 $weight='700'>{data?.gender === 'MALE' ? '남자' : '여자'}</Typo13>
3636
<Separator $height={8} />
37-
<Typo13 $weight='700'>{data ? FAMILY_ROLE[data.familyRole] : ''}</Typo13>
37+
<Typo13 $weight='700'>{data ? FAMILY_ROLE[data.familyRole as keyof typeof FAMILY_ROLE] : ''}</Typo13>
3838
</S.TypoWrapper>
3939
</S.ProfileArea>
4040

src/pages/RegisterPage/Register/index.tsx

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Input } from '~components/Input'
66
import RegisterAvatarModal from '~modals/RegisterAvatarModal'
77
import { useModalStore } from '~stores/modalStore'
88
import { ActionButton } from '~components/Button/ActionButton'
9-
import PositionChoiceModal from '~modals/PositionChoiceModal'
9+
import FamilyRoleChoiceModal from '~modals/PositionChoiceModal'
1010
import { useGeolocation } from '~hooks/useGeolocation'
1111
import { useOwnerProfileStore } from '~stores/ownerProfileStore'
1212
import { validateOwnerProfile } from '~utils/validateOwnerProfile'
@@ -17,17 +17,7 @@ import { useSearchParams } from 'react-router-dom'
1717
import { createRegister } from '~apis/register/createRegister'
1818
import { FamilyRole } from '~types/common'
1919
import { useEffect } from 'react'
20-
21-
const positionLabelMap: Record<FamilyRole, string> = {
22-
MOTHER: '엄마',
23-
FATHER: '아빠',
24-
OLDER_BROTHER: '형',
25-
ELDER_BROTHER: '오빠',
26-
ELDER_SISTER: '언니',
27-
OLDER_SISTER: '누나',
28-
GRANDFATHER: '할아버지',
29-
GRANDMOTHER: '할머니',
30-
}
20+
import { FAMILY_ROLE } from '~constants/familyRole'
3121

3222
export default function Register() {
3323
const { ownerProfile, setOwnerProfile } = useOwnerProfileStore()
@@ -44,21 +34,23 @@ export default function Register() {
4434
showToast(alertMessage)
4535
return
4636
}
47-
4837
try {
38+
const familyRoleKey = Object.keys(FAMILY_ROLE).find(
39+
key => FAMILY_ROLE[key as keyof typeof FAMILY_ROLE] === ownerProfile.familyRole
40+
)
4941
const registerData = {
5042
email,
5143
provider,
52-
name: ownerProfile.nickName,
44+
name: ownerProfile.name,
5345
gender: ownerProfile.gender as 'MALE' | 'FEMALE',
54-
address: ownerProfile.location,
55-
familyRole: ownerProfile.position as FamilyRole,
56-
profileImg: ownerProfile.avatar || '',
46+
address: ownerProfile.address,
47+
familyRole: familyRoleKey as FamilyRole,
48+
profileImg: ownerProfile.profileImg || '',
5749
}
5850

5951
const response = await createRegister({
6052
...registerData,
61-
provider: registerData.provider as 'KAKAO' | 'NAVER' | 'GOOGLE',
53+
provider: registerData.provider as 'KAKAO' | 'GOOGLE',
6254
})
6355
if (response.code === 201) {
6456
pushModal(<RegisterDogPage />)
@@ -69,7 +61,7 @@ export default function Register() {
6961
}
7062
useEffect(() => {
7163
if (location.address) {
72-
setOwnerProfile({ location: location.address })
64+
setOwnerProfile({ address: location.address })
7365
}
7466
}, [location.address, setOwnerProfile])
7567
const handleLocationClick = () => {
@@ -78,15 +70,18 @@ export default function Register() {
7870

7971
const handleRoleClick = () => {
8072
pushModal(
81-
<PositionChoiceModal onSelect={position => setOwnerProfile({ position })} initialValue={ownerProfile.position} />
73+
<FamilyRoleChoiceModal
74+
onSelectRole={role => setOwnerProfile({ familyRole: role })}
75+
initialRole={ownerProfile.familyRole}
76+
/>
8277
)
8378
}
8479

8580
const handleAvatarClick = () => {
8681
pushModal(
8782
<RegisterAvatarModal
88-
onSelectAvatar={avatarSrc => setOwnerProfile({ avatar: avatarSrc })}
89-
initialSelectedAvatar={ownerProfile.avatar}
83+
onSelectAvatar={avatarSrc => setOwnerProfile({ profileImg: avatarSrc })}
84+
initialSelectedAvatar={ownerProfile.profileImg}
9085
/>
9186
)
9287
}
@@ -105,9 +100,9 @@ export default function Register() {
105100
<S.TextSection weight='700'>견주님에 대해{'\n'}알려주세요</S.TextSection>
106101

107102
<S.AddOwnerAvatarBtnWrapper>
108-
{ownerProfile.avatar ? (
103+
{ownerProfile.profileImg ? (
109104
<S.Avatar onClick={handleAvatarClick}>
110-
<img src={ownerProfile.avatar} alt='선택된 아바타' />
105+
<img src={ownerProfile.profileImg} alt='선택된 아바타' />
111106
</S.Avatar>
112107
) : (
113108
<S.AddOwnerAvatarBtn onClick={handleAvatarClick}>
@@ -121,15 +116,15 @@ export default function Register() {
121116
<S.NickNameWrapper>
122117
<Input
123118
placeholder='닉네임 입력'
124-
value={ownerProfile.nickName}
125-
onChange={e => setOwnerProfile({ nickName: e.target.value })}
119+
value={ownerProfile.name}
120+
onChange={e => setOwnerProfile({ name: e.target.value })}
126121
/>
127122
</S.NickNameWrapper>
128-
<S.PositionChoiceBtn onClick={handleRoleClick} $hasSelected={!!ownerProfile.position}>
129-
{ownerProfile.position ? positionLabelMap[ownerProfile.position as FamilyRole] : '가족 포지션 선택'}
123+
<S.PositionChoiceBtn onClick={handleRoleClick} $hasSelected={!!ownerProfile.familyRole}>
124+
{ownerProfile.familyRole || '가족 포지션 선택'}
130125
</S.PositionChoiceBtn>
131-
<S.LocationBtn onClick={handleLocationClick} $hasSelected={!!ownerProfile.location}>
132-
{ownerProfile.location || '내 동네 불러오기'}
126+
<S.LocationBtn onClick={handleLocationClick} $hasSelected={!!ownerProfile.address}>
127+
{ownerProfile.address || '내 동네 불러오기'}
133128
</S.LocationBtn>
134129
<S.GenderSelectBtnWrapper>
135130
<GenderSelectButton

src/pages/SocialPage/components/ChatItem/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function ChatItem({ lastMessage, members, name, unreadMessageCoun
2121
<Typo17 $weight='700'>{name}</Typo17>
2222
<S.DetailWrapper>
2323
<Typo13 $color='font_2' $weight='500'>
24-
{FAMILY_ROLE[members[0].familyRole]}
24+
{FAMILY_ROLE[members[0].familyRole as keyof typeof FAMILY_ROLE]}
2525
</Typo13>
2626
<Separator $height={8} />
2727
<Typo13 $color='font_2' $weight='500'>

0 commit comments

Comments
 (0)