Skip to content

Commit

Permalink
Merge pull request #135 from prgrms-web-devcourse-final-project/134-f…
Browse files Browse the repository at this point in the history
…eature/mypage-setting

[Feature] #134 마이페이지 데이터바인딩, 알람설정 부분 구현
  • Loading branch information
wonill authored Dec 7, 2024
2 parents 99b24ca + 7bcd98f commit 36059e7
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 117 deletions.
39 changes: 39 additions & 0 deletions src/apis/myPage/updateGangbuntta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// import { AxiosError } from 'axios'
// import { APIResponse, ErrorResponse } from '~types/api'
// import { axiosInstance } from '~apis/axiosInstance'

// export type UpdateGangbunttaRequest = {}

// export type UpdateGangbunttaResponse = Pick<APIResponse>

// export const updateGangbuntta = async (
// req: UpdateGangbunttaRequest
// ): Promise<APIResponse<UpdateGangbunttaResponse>> => {
// try {
// const { data } = await axiosInstance.patch<APIResponse<UpdateGangbunttaResponse>>(`/member`, req)
// return data
// } catch (error) {
// if (error instanceof AxiosError) {
// const { response } = error as AxiosError<ErrorResponse>

// if (response) {
// const { code, message } = response.data
// switch (code) {
// case 400:
// throw new Error(message || '잘못된 요청입니다.')
// case 401:
// throw new Error(message || '인증에 실패했습니다.')
// case 500:
// throw new Error(message || '서버 오류가 발생했습니다.')
// default:
// throw new Error(message || '알 수 없는 오류가 발생했습니다.')
// }
// }
// // 요청 자체가 실패한 경우
// throw new Error('네트워크 연결을 확인해주세요')
// }

// console.error('예상치 못한 에러:', error)
// throw new Error('다시 시도해주세요')
// }
// }
38 changes: 38 additions & 0 deletions src/apis/myPage/updateSetting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AxiosError } from 'axios'
import { APIResponse, ErrorResponse, Setting } from '~types/api'
import { axiosInstance } from '~apis/axiosInstance'

export type UpdateSettingRequest = Pick<Setting, 'type' | 'isAgreed'>

export type UpdateSettingResponse = Pick<Setting, 'notificationSettingsId' | 'memberId' | 'type' | 'isAgreed'>

export const updateSetting = async (req: UpdateSettingRequest): Promise<APIResponse<UpdateSettingResponse>> => {
try {
const { data } = await axiosInstance.patch<APIResponse<UpdateSettingResponse>>(`/notification-settings/update`, req)
console.log('알림 상태변경 : ', data.data)
return data
} catch (error) {
if (error instanceof AxiosError) {
const { response } = error as AxiosError<ErrorResponse>

if (response) {
const { code, message } = response.data
switch (code) {
case 400:
throw new Error(message || '잘못된 요청입니다.')
case 401:
throw new Error(message || '인증에 실패했습니다.')
case 500:
throw new Error(message || '서버 오류가 발생했습니다.')
default:
throw new Error(message || '알 수 없는 오류가 발생했습니다.')
}
}
// 요청 자체가 실패한 경우
throw new Error('네트워크 연결을 확인해주세요')
}

console.error('예상치 못한 에러:', error)
throw new Error('다시 시도해주세요')
}
}
6 changes: 3 additions & 3 deletions src/apis/register/createRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ export type CreateRegisterResponse = Pick<
export const createRegister = async (req: CreateRegisterRequest): Promise<APIResponse<CreateRegisterResponse>> => {
try {
const response = await axiosInstance.post<APIResponse<CreateRegisterResponse>>(`/member/join`, req)

console.log(response)
// 토큰 추출 및 저장
const accessToken = response.headers['authorization']
const accessToken = (response.headers['authorization'] as string).split('Bearer ')[1]

if (accessToken) {
localStorage.setItem('token', accessToken)
axiosInstance.defaults.headers.common['Authorization'] = accessToken
}

return response.data
Expand Down
22 changes: 19 additions & 3 deletions src/components/Toggle/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SettingsStoreKey, useSettingsStore } from '~stores/settingsStore'
import { updateSetting } from '~apis/myPage/updateSetting'
import * as S from './styles'

type ToggleProps = {
Expand All @@ -7,11 +8,26 @@ type ToggleProps = {
}

export default function Toggle({ id, setting }: ToggleProps) {
const value = useSettingsStore(state => state[setting])
const value = useSettingsStore(state => state.settings[setting])
const setSetting = useSettingsStore(state => state.setSetting)

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSetting(setting, e.target.checked)
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
try {
const newValue = e.target.checked
// setting이 'messages' 또는 'myWalkNotifications'인 경우에만 API 호출
if (setting === 'messages' || setting === 'myWalkNotifications') {
await updateSetting({
type: setting === 'messages' ? 'CHAT' : 'WALK',
isAgreed: newValue ? 'TRUE' : 'FALSE',
})
}
// 상태 업데이트
setSetting(setting, newValue)
} catch (error) {
console.error('토글 변경 중 오류 발생:', error)
// 에러 발생 시 사용자에게 알림
alert('설정 변경에 실패했습니다. 다시 시도해주세요.')
}
}

return (
Expand Down
24 changes: 12 additions & 12 deletions src/constants/settingsInfo.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import { SettingsStoreKey } from '~stores/settingsStore'

export const SETTINGS_INFO: Record<SettingsStoreKey, { title: string; desc: string }> = {
allNotifications: {
title: '모든 알림',
desc: '',
},
friendRequests: {
title: '친구 신청',
desc: '부연 설명이 들어가는 공간',
},
familyWalkNotifications: {
title: '가족 산책 알림',
desc: '부연 설명이 들어가는 공간',
},
myWalkNotifications: {
title: '내 산책 알림',
desc: '부연 설명이 들어가는 공간',
Expand All @@ -25,4 +13,16 @@ export const SETTINGS_INFO: Record<SettingsStoreKey, { title: string; desc: stri
title: '강번따 허용 여부',
desc: '부연 설명이 들어가는 공간',
},
allNotifications: {
title: '모든 알림',
desc: '',
},
friendRequests: {
title: '친구 신청',
desc: '부연 설명이 들어가는 공간',
},
familyWalkNotifications: {
title: '가족 산책 알림',
desc: '부연 설명이 들어가는 공간',
},
}
43 changes: 11 additions & 32 deletions src/modals/PositionChoiceModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,37 @@ import { useState } from 'react'
import { RadioGroup } from '@mui/material'
import { useModalStore } from '~stores/modalStore'
import { ActionButton } from '~components/Button/ActionButton'
import { FAMILY_ROLE } from '~constants/familyRole'
import { FamilyRole } from '~types/common'

interface PositionChoiceModalProps {
onSelect: (position: string) => void
initialValue?: string | null
}
interface Position {
label: string
value: FamilyRole
}

const positions: Position[] = [
{ label: '엄마', value: 'MOTHER' },
{ label: '아빠', value: 'FATHER' },
{ label: '형', value: 'OLDER_BROTHER' },
{ label: '오빠', value: 'ELDER_BROTHER' },
{ label: '언니', value: 'ELDER_SISTER' },
{ label: '누나', value: 'OLDER_SISTER' },
{ label: '할머니', value: 'GRANDMOTHER' },
{ label: '할아버지', value: 'GRANDFATHER' },
]
const familyRoles = Object.values(FAMILY_ROLE)
type FamilyRoleChoiceModalProps = { onSelectRole: (role: FamilyRole) => void; initialRole: FamilyRole }

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

const handleConfirm = () => {
if (value !== null) {
onSelect(value)
if (selectedFamilyRole !== null) {
onSelectRole(selectedFamilyRole)
popModal()
}
}

return (
<S.DialogContainer open={true} onClose={popModal}>
<S.DialogTitle>가족 포지션 선택</S.DialogTitle>
<RadioGroup value={value} onChange={e => setValue(e.target.value)}>
<RadioGroup value={selectedFamilyRole} onChange={e => setSelectedFamilyRole(e.target.value as FamilyRole)}>
<S.RadioGroupContainer>
{positions.map(position => (
<S.StyledFormControlLabel
key={position.value}
value={position.value}
control={<S.StyledRadio />}
label={position.label}
/>
{familyRoles.map(role => (
<S.StyledFormControlLabel key={role} value={role} control={<S.StyledRadio />} label={role} />
))}
</S.RadioGroupContainer>
</RadioGroup>

<S.ButtonContainer>
<ActionButton onClick={popModal}>취소</ActionButton>
<ActionButton onClick={handleConfirm} disabled={!value} $fontWeight='700'>
<ActionButton onClick={handleConfirm} disabled={!selectedFamilyRole} $fontWeight='700'>
확인
</ActionButton>
</S.ButtonContainer>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/HomePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useEffect } from 'react'

function HomeContent() {
const { data } = useHomePageData()
console.log(data)
const { pushModal } = useModalStore()
return (
<>
Expand All @@ -32,7 +33,7 @@ function HomeContent() {

<S.Visual>
<Typo24 $weight='700' $textAlign='center'>
오늘은 {data && FAMILY_ROLE[data.familyRole]}
오늘은 {data?.familyRole ? FAMILY_ROLE[data.familyRole] : ''}
</Typo24>
<Typo24 $weight='700' $textAlign='center'>
산책가는 날!
Expand Down
2 changes: 1 addition & 1 deletion src/pages/ProfilePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function ProfileContent({ id }: { id: number }) {
<S.TypoWrapper $gap={8}>
<Typo13 $weight='700'>{data?.gender === 'MALE' ? '남자' : '여자'}</Typo13>
<Separator $height={8} />
<Typo13 $weight='700'>{data ? FAMILY_ROLE[data.familyRole] : ''}</Typo13>
<Typo13 $weight='700'>{data ? FAMILY_ROLE[data.familyRole as keyof typeof FAMILY_ROLE] : ''}</Typo13>
</S.TypoWrapper>
</S.ProfileArea>

Expand Down
55 changes: 25 additions & 30 deletions src/pages/RegisterPage/Register/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Input } from '~components/Input'
import RegisterAvatarModal from '~modals/RegisterAvatarModal'
import { useModalStore } from '~stores/modalStore'
import { ActionButton } from '~components/Button/ActionButton'
import PositionChoiceModal from '~modals/PositionChoiceModal'
import FamilyRoleChoiceModal from '~modals/PositionChoiceModal'
import { useGeolocation } from '~hooks/useGeolocation'
import { useOwnerProfileStore } from '~stores/ownerProfileStore'
import { validateOwnerProfile } from '~utils/validateOwnerProfile'
Expand All @@ -17,17 +17,7 @@ import { useSearchParams } from 'react-router-dom'
import { createRegister } from '~apis/register/createRegister'
import { FamilyRole } from '~types/common'
import { useEffect } from 'react'

const positionLabelMap: Record<FamilyRole, string> = {
MOTHER: '엄마',
FATHER: '아빠',
OLDER_BROTHER: '형',
ELDER_BROTHER: '오빠',
ELDER_SISTER: '언니',
OLDER_SISTER: '누나',
GRANDFATHER: '할아버지',
GRANDMOTHER: '할머니',
}
import { FAMILY_ROLE } from '~constants/familyRole'

export default function Register() {
const { ownerProfile, setOwnerProfile } = useOwnerProfileStore()
Expand All @@ -44,21 +34,23 @@ export default function Register() {
showToast(alertMessage)
return
}

try {
const familyRoleKey = Object.keys(FAMILY_ROLE).find(
key => FAMILY_ROLE[key as keyof typeof FAMILY_ROLE] === ownerProfile.familyRole
)
const registerData = {
email,
provider,
name: ownerProfile.nickName,
name: ownerProfile.name,
gender: ownerProfile.gender as 'MALE' | 'FEMALE',
address: ownerProfile.location,
familyRole: ownerProfile.position as FamilyRole,
profileImg: ownerProfile.avatar || '',
address: ownerProfile.address,
familyRole: familyRoleKey as FamilyRole,
profileImg: ownerProfile.profileImg || '',
}

const response = await createRegister({
...registerData,
provider: registerData.provider as 'KAKAO' | 'NAVER' | 'GOOGLE',
provider: registerData.provider as 'KAKAO' | 'GOOGLE',
})
if (response.code === 201) {
pushModal(<RegisterDogPage />)
Expand All @@ -69,7 +61,7 @@ export default function Register() {
}
useEffect(() => {
if (location.address) {
setOwnerProfile({ location: location.address })
setOwnerProfile({ address: location.address })
}
}, [location.address, setOwnerProfile])
const handleLocationClick = () => {
Expand All @@ -78,15 +70,18 @@ export default function Register() {

const handleRoleClick = () => {
pushModal(
<PositionChoiceModal onSelect={position => setOwnerProfile({ position })} initialValue={ownerProfile.position} />
<FamilyRoleChoiceModal
onSelectRole={role => setOwnerProfile({ familyRole: role })}
initialRole={ownerProfile.familyRole}
/>
)
}

const handleAvatarClick = () => {
pushModal(
<RegisterAvatarModal
onSelectAvatar={avatarSrc => setOwnerProfile({ avatar: avatarSrc })}
initialSelectedAvatar={ownerProfile.avatar}
onSelectAvatar={avatarSrc => setOwnerProfile({ profileImg: avatarSrc })}
initialSelectedAvatar={ownerProfile.profileImg}
/>
)
}
Expand All @@ -105,9 +100,9 @@ export default function Register() {
<S.TextSection weight='700'>견주님에 대해{'\n'}알려주세요</S.TextSection>

<S.AddOwnerAvatarBtnWrapper>
{ownerProfile.avatar ? (
{ownerProfile.profileImg ? (
<S.Avatar onClick={handleAvatarClick}>
<img src={ownerProfile.avatar} alt='선택된 아바타' />
<img src={ownerProfile.profileImg} alt='선택된 아바타' />
</S.Avatar>
) : (
<S.AddOwnerAvatarBtn onClick={handleAvatarClick}>
Expand All @@ -121,15 +116,15 @@ export default function Register() {
<S.NickNameWrapper>
<Input
placeholder='닉네임 입력'
value={ownerProfile.nickName}
onChange={e => setOwnerProfile({ nickName: e.target.value })}
value={ownerProfile.name}
onChange={e => setOwnerProfile({ name: e.target.value })}
/>
</S.NickNameWrapper>
<S.PositionChoiceBtn onClick={handleRoleClick} $hasSelected={!!ownerProfile.position}>
{ownerProfile.position ? positionLabelMap[ownerProfile.position as FamilyRole] : '가족 포지션 선택'}
<S.PositionChoiceBtn onClick={handleRoleClick} $hasSelected={!!ownerProfile.familyRole}>
{ownerProfile.familyRole || '가족 포지션 선택'}
</S.PositionChoiceBtn>
<S.LocationBtn onClick={handleLocationClick} $hasSelected={!!ownerProfile.location}>
{ownerProfile.location || '내 동네 불러오기'}
<S.LocationBtn onClick={handleLocationClick} $hasSelected={!!ownerProfile.address}>
{ownerProfile.address || '내 동네 불러오기'}
</S.LocationBtn>
<S.GenderSelectBtnWrapper>
<GenderSelectButton
Expand Down
2 changes: 1 addition & 1 deletion src/pages/SocialPage/components/ChatItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function ChatItem({ lastMessage, members, name, unreadMessageCoun
<Typo17 $weight='700'>{name}</Typo17>
<S.DetailWrapper>
<Typo13 $color='font_2' $weight='500'>
{FAMILY_ROLE[members[0].familyRole]}
{FAMILY_ROLE[members[0].familyRole as keyof typeof FAMILY_ROLE]}
</Typo13>
<Separator $height={8} />
<Typo13 $color='font_2' $weight='500'>
Expand Down
Loading

0 comments on commit 36059e7

Please sign in to comment.