Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] #134 마이페이지 데이터바인딩, 알람설정 부분 구현 #135

Merged
merged 8 commits into from
Dec 7, 2024
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
Loading