diff --git a/src/apis/family/fetchFamilyDDang.ts b/src/apis/family/fetchFamilyDDang.ts new file mode 100644 index 00000000..8087d8ec --- /dev/null +++ b/src/apis/family/fetchFamilyDDang.ts @@ -0,0 +1,49 @@ +import { AxiosError } from 'axios' +import { APIResponse, CommonAPIResponse, ErrorResponse } from '~types/api' +import { axiosInstance } from '~apis/axiosInstance' +import { DayOfWeek } from '~types/common' + +export type FetchFamilyDDangResponse = Pick< + CommonAPIResponse, + 'familyId' | 'dogs' | 'totalWalkCount' | 'totalDistanceInKilometers' | 'totalCalorie' +> & { + members: (CommonAPIResponse['members'][number] & { + walkScheduleInfoList: { + walkScheduleId: number + dayOfWeek: DayOfWeek + walkTime: string + }[] + totalWalkCount: number // 추가된 속성 + })[] +} + +export const fetchFamilyDDang = async (): Promise> => { + try { + const { data } = await axiosInstance.get>(`/family`) + console.log('패밀리댕 정보 바인딩 : ', data) + return data + } catch (error) { + if (error instanceof AxiosError) { + const { response } = error as AxiosError + + 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('다시 시도해주세요') + } +} diff --git a/src/apis/family/fetchOwnerProfile.ts b/src/apis/family/fetchOwnerProfile.ts new file mode 100644 index 00000000..808c3959 --- /dev/null +++ b/src/apis/family/fetchOwnerProfile.ts @@ -0,0 +1,39 @@ +import { AxiosError } from 'axios' +import { APIResponse, CommonAPIResponse, ErrorResponse } from '~types/api' +import { axiosInstance } from '~apis/axiosInstance' + +export type FetchOwnerProfileResponse = Pick< + CommonAPIResponse, + 'memberId' | 'name' | 'gender' | 'familyRole' | 'profileImg' +> + +export const fetchOwnerProfile = async (): Promise> => { + try { + const { data } = await axiosInstance.get>(`/member/update`) + console.log('잘 오나?', data) + return data + } catch (error) { + if (error instanceof AxiosError) { + const { response } = error as AxiosError + + 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('다시 시도해주세요') + } +} diff --git a/src/apis/family/updateOwnerProfile.ts b/src/apis/family/updateOwnerProfile.ts new file mode 100644 index 00000000..51a51401 --- /dev/null +++ b/src/apis/family/updateOwnerProfile.ts @@ -0,0 +1,42 @@ +import { AxiosError } from 'axios' +import { APIResponse, CommonAPIRequest, ErrorResponse, CommonAPIResponse } from '~types/api' +import { axiosInstance } from '~apis/axiosInstance' + +export type UpdateOwnerProfileRequest = Pick + +export type UpdateOwnerProfileResponse = Pick< + CommonAPIResponse, + 'memberId' | 'name' | 'gender' | 'familyRole' | 'profileImg' +> + +export const updateOwnerProfile = async ( + req: UpdateOwnerProfileRequest +): Promise> => { + try { + const { data } = await axiosInstance.patch>(`/member/update`, req) + return data + } catch (error) { + if (error instanceof AxiosError) { + const { response } = error as AxiosError + + 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('다시 시도해주세요') + } +} diff --git a/src/components/DogProfile/styles.ts b/src/components/DogProfile/styles.ts index 53754d17..6a545b7f 100644 --- a/src/components/DogProfile/styles.ts +++ b/src/components/DogProfile/styles.ts @@ -60,6 +60,17 @@ export const OneLineIntro = styled(Box)` } ` +// export const EditIconWrapper = styled.div` +// width: 2rem; +// height: 2rem; +// background-color: ${({ theme }) => theme.colors.brand.lighten_2}; +// border-radius: 50%; +// display: flex; +// justify-content: center; +// align-items: center; +// margin-left: auto; +// ` + export const InviteBtn = styled.button` width: 57px; height: 36px; diff --git a/src/components/WalkCountArea/index.tsx b/src/components/WalkCountArea/index.tsx index 1625530a..30c2268f 100644 --- a/src/components/WalkCountArea/index.tsx +++ b/src/components/WalkCountArea/index.tsx @@ -1,27 +1,32 @@ import * as S from './styles' +import { useQuery } from '@tanstack/react-query' +import { fetchFamilyDDang, FetchFamilyDDangResponse } from '~apis/family/fetchFamilyDDang' +import { APIResponse } from '~types/api' -interface CountSectionProps { - walkCount: number - totalDistance: number - gangCount: number -} +export default function WalkCountArea() { + const { data } = useQuery>({ + queryKey: ['familyList'], + queryFn: fetchFamilyDDang, + }) + const totalCalorie = data?.data?.totalCalorie + const totalDistanceInKilometers = data?.data?.totalDistanceInKilometers + const totalWalkCount = data?.data?.totalWalkCount -export default function WalkCountArea({ walkCount, totalDistance, gangCount }: CountSectionProps) { return ( - {walkCount}회 + {totalCalorie}회 누적 산책 횟수 - {totalDistance}km + {totalDistanceInKilometers}km 총 산책거리 - {gangCount}회 - 강번따 횟수 + {totalWalkCount}kcal + 소요 칼로리 ) diff --git a/src/constants/familyRole.ts b/src/constants/familyRole.ts index ace24a22..535df9e2 100644 --- a/src/constants/familyRole.ts +++ b/src/constants/familyRole.ts @@ -9,3 +9,8 @@ export const FAMILY_ROLE = { GRANDMOTHER: '할머니', '': '', } +export type FamilyRoleKey = keyof typeof FAMILY_ROLE +export type FamilyRoleValue = (typeof FAMILY_ROLE)[FamilyRoleKey] +export const REVERSE_FAMILY_ROLE = Object.fromEntries( + Object.entries(FAMILY_ROLE).map(([key, value]) => [value, key]) +) as { [key in FamilyRoleValue]: FamilyRoleKey } \ No newline at end of file diff --git a/src/constants/queryKey.ts b/src/constants/queryKey.ts index c84eb160..8f90fe0c 100644 --- a/src/constants/queryKey.ts +++ b/src/constants/queryKey.ts @@ -20,7 +20,10 @@ export const queryKey = { }, myPage: () => ['myPage'], family: { - inviteCode: () => ['family', 'inviteCode'], + familyList: ()=> ['familyList'], + prevOwnerInto: () => ['prevOwnerInfo'], + UpdateOwner: () => ['updateOwnerInfo'], + inviteCode: () => ['family', 'inviteCode'], profile: () => ['family', 'dogProfile'], }, -} +} as const diff --git a/src/modals/FamilyDDangModal/UpdateMemberModal/index.tsx b/src/modals/FamilyDDangModal/UpdateMemberModal/index.tsx deleted file mode 100644 index b2387539..00000000 --- a/src/modals/FamilyDDangModal/UpdateMemberModal/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as S from './styles' -// import GenderSelectButton from '~components/GenderSelectButton' -// import { useEffect } from 'react' -// import { Input } from '~components/Input' -// import { useModalStore } from '~stores/modalStore' -// import { useToastStore } from '~stores/toastStore' -// import Toast from '~components/Toast' -// import { useGeolocation } from '~hooks/useGeolocation' -// import AddOwnerAvatar from '~assets/add-dog-picture.svg' -// import RegisterAvatarModal from '~modals/RegisterAvatarModal' -// import { ActionButton } from '~components/Button/ActionButton' -// import PositionChoiceModal from '~modals/PositionChoiceModal' -// import { useOwnerProfileStore } from '~stores/ownerProfileStore' -// import { validateOwnerProfile } from '~utils/validateOwnerProfile' -import Header from '~components/Header' -// type UpdateMemberModalProps = { -// : -// } - -export default function UpdateMemberModal() { - // const popModal = useModalStore() - return ( - -
-
- UpdateMemberModal - - ) -} diff --git a/src/modals/FamilyDDangModal/UpdateMemberModal/styles.ts b/src/modals/FamilyDDangModal/UpdateMemberModal/styles.ts deleted file mode 100644 index 7a119756..00000000 --- a/src/modals/FamilyDDangModal/UpdateMemberModal/styles.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { FontWeight, styled } from 'styled-components' - -export const UpdateMemberModal = styled.div`` - -export const RegisterPage = styled.div` - height: 100dvh; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 5.5rem 1.25rem 1.8rem 1.25rem; - background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - - @media (max-height: 700px) { - padding: 4.75rem 1.25rem 1rem; - gap: 0.5rem; - } -` - -export const TextSection = styled.text<{ weight: FontWeight }>` - flex-shrink: 0; - color: ${({ theme }) => theme.colors.grayscale.font_1}; - font-size: ${({ theme }) => theme.typography._24}; - font-weight: ${({ weight }) => weight}; - white-space: pre-line; - text-align: center; - margin: 1rem 0; -` - -export const AddOwnerAvatarBtnWrapper = styled.div` - display: flex; - justify-content: center; - flex-shrink: 0; -` - -export const AddOwnerAvatarBtn = styled.div` - width: 180px; - height: 180px; - background-color: ${({ theme }) => theme.colors.brand.lighten_2}; - border-radius: 50%; - margin-top: 0.7rem; - - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - gap: 0.5rem; - - font-weight: 700; - color: ${({ theme }) => theme.colors.brand.darken}; - cursor: pointer; - - @media (max-height: 700px) { - width: 150px; - height: 150px; - } -` -export const Avatar = styled.div` - width: 190px; - height: 190px; - border-radius: 50%; - margin-top: 0.7rem; - - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - gap: 0.5rem; - cursor: pointer; - font-weight: 700; - color: ${({ theme }) => theme.colors.brand.darken}; - - img { - width: 100%; - height: 100%; - object-fit: cover; - } - - @media (max-height: 700px) { - width: 150px; - height: 150px; - } -` - -export const OwnerProfileSection = styled.div` - display: flex; - flex-direction: column; - flex: 1; - justify-content: center; - padding-bottom: 2rem; - @media (max-height: 700px) { - gap: 0.5rem; - } -` - -export const NickNameWrapper = styled.div` - width: 100%; -` - -export const PositionChoiceBtn = styled.div<{ $hasSelected?: boolean }>` - width: 100%; - border: none; - text-align: center; - padding: 1rem 2rem; - border-radius: 0.75rem; - font-size: ${({ theme }) => theme.typography._20}; - color: ${({ theme, $hasSelected }) => ($hasSelected ? theme.colors.grayscale.font_1 : theme.colors.grayscale.font_3)}; - cursor: pointer; - - @media (max-height: 700px) { - padding: 0.75rem 1.5rem; - } -` - -export const LocationBtn = styled.div<{ $hasSelected?: boolean }>` - width: 100%; - border: none; - text-align: center; - padding: 1.063rem 2rem; - border-radius: 0.75rem; - font-size: ${({ theme }) => theme.typography._20}; - color: ${({ theme, $hasSelected }) => ($hasSelected ? theme.colors.grayscale.font_1 : theme.colors.grayscale.font_3)}; - cursor: pointer; - - @media (max-height: 700px) { - padding: 0.75rem 1.5rem; - } -` - -export const GenderSelectBtnWrapper = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; - height: 4rem; - width: 90%; - margin: 1rem auto; - & > * { - height: 100%; - } - - button { - flex-direction: row; - gap: 0.8rem; - height: 100%; - } - - @media (max-height: 700px) { - height: 3.25rem; - } -` - -export const ToastWrapper = styled.div` - position: relative; -` diff --git a/src/modals/OwnerUpdateModal/index.tsx b/src/modals/OwnerUpdateModal/index.tsx new file mode 100644 index 00000000..34fa7348 --- /dev/null +++ b/src/modals/OwnerUpdateModal/index.tsx @@ -0,0 +1,191 @@ +import { useEffect, useState } from 'react' +import * as S from '~pages/RegisterPage/Register/styles' +import { Helmet } from 'react-helmet-async' +import GenderSelectButton from '~components/GenderSelectButton' +import { Input } from '~components/Input' +import RegisterAvatarModal from '~modals/RegisterAvatarModal' +import { useModalStore } from '~stores/modalStore' +import { ActionButton } from '~components/Button/ActionButton' +import FamilyRoleChoiceModal from '~modals/PositionChoiceModal' +import Toast from '~components/Toast' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' // 추가 +import { fetchOwnerProfile } from '~apis/family/fetchOwnerProfile' +import { UpdateOwnerProfileRequest, updateOwnerProfile } from '~apis/family/updateOwnerProfile' // 추가 +import { Typo14 } from '~components/Typo' +import { queryKey } from '~constants/queryKey' +import { FAMILY_ROLE } from '~constants/familyRole' +import { REVERSE_FAMILY_ROLE } from '~constants/familyRole' + +import { FamilyRole, Gender } from '~types/common' + +interface updateProfileType { + familyRole: FamilyRole + gender: Gender + name: string + profileImg: string +} + +export default function OwnerUpdateModal() { + const pushModal = useModalStore(state => state.pushModal) + const popModal = useModalStore(state => state.popModal) + + const [ownerProfile, setOwnerProfile] = useState({ + familyRole: '', + gender: 'MALE', // 기본값 + name: '', + profileImg: '', + }) + const [ProfileImage, setProfileImage] = useState(null) + + const queryClient = useQueryClient() + + const { data, isLoading, isError } = useQuery({ + queryKey: queryKey.family.prevOwnerInto(), + queryFn: fetchOwnerProfile, + }) + + useEffect(() => { + if (data?.data) { + setOwnerProfile(data?.data) + console.log('ownerProfile :', ownerProfile) + } + }, [data]) + + const updateOwnerMutation = useMutation({ + mutationFn: (data: UpdateOwnerProfileRequest) => updateOwnerProfile(data), + onSuccess: () => { + alert('견주정보 수정 완료') + queryClient.invalidateQueries({ queryKey: queryKey.family.UpdateOwner() }) + popModal() + }, + onError: error => { + console.error('정보 수정 실패:', error) + alert('정보 수정에 실패했습니다. 다시 시도해주세요.') + }, + }) + + useEffect(() => { + if (ownerProfile.profileImg) { + const avatarNumber = ownerProfile.profileImg.match(/Avatar(\d+)/)?.[1] // 프로필 이미지에서 번호 추출 + if (avatarNumber) { + import(`../../../src/assets/avatars/Avatar${avatarNumber}.svg?react`) + .then(module => setProfileImage(() => module.default)) // 동적으로 가져온 컴포넌트를 상태에 저장 + .catch(err => console.error('Error loading SVG:', err)) + } + } + }, [ownerProfile.profileImg]) // profileImg가 변경될 때마다 실행 + + if (!ownerProfile) { + return null + } + + const handleRoleClick = () => { + pushModal( + + setOwnerProfile(prev => ({ + ...prev!, + familyRole: role, + })) + } + initialRole={ownerProfile.familyRole} + /> + ) + } + + const handleAvatarClick = () => { + pushModal( + { + setOwnerProfile(prev => ({ + ...prev, + profileImg: avatarSrc, // 선택된 아바타 경로 업데이트 + })) + }} + initialSelectedAvatar={ownerProfile.profileImg} + /> + ) + } + + const handleGenderSelect = (gender: 'MALE' | 'FEMALE') => { + setOwnerProfile(prev => ({ + ...prev!, + gender, + })) + } + + const handleUpdateClick = () => { + if (!ownerProfile.name || !ownerProfile.familyRole || !ownerProfile.gender || !ownerProfile.profileImg) { + alert('모든 필드를 입력해주세요.') + return + } + + // familyRole을 영어 Enum 값으로 변환 + const updatedProfile = { + ...ownerProfile, + familyRole: REVERSE_FAMILY_ROLE[ownerProfile.familyRole || ''], // value를 key로 변환 + } + + console.log('프로필 업데이트 요청 데이터:', updatedProfile) + updateOwnerMutation.mutate(updatedProfile) // 변환된 데이터를 서버로 전송 + } + if (isLoading) return
Loading...
+ if (isError) return
Error loading data
+ + return ( + + + DDang | 내 정보 수정 + + + + 내 정보 수정 + + + {ProfileImage && } + + + 아바타 선택 + + + + + + + + setOwnerProfile(prev => ({ + ...prev!, + name: e.target.value, + })) + } + /> + + + {FAMILY_ROLE[ownerProfile.familyRole] || ownerProfile.familyRole} + + + handleGenderSelect('MALE')} + /> + handleGenderSelect('FEMALE')} + /> + + + + + + 수정 완료 + + + + + ) +} diff --git a/src/modals/RegisterAvatarModal/styles.ts b/src/modals/RegisterAvatarModal/styles.ts index ca3084f8..bfbff724 100644 --- a/src/modals/RegisterAvatarModal/styles.ts +++ b/src/modals/RegisterAvatarModal/styles.ts @@ -8,7 +8,12 @@ export const RegisterAvatarModal = styled.div` position: relative; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; overflow: hidden; - + z-index: 200; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; @media (max-height: 700px) { padding: 3.3rem 1.25rem 1rem; gap: 0.5rem; diff --git a/src/pages/FamilyDDangPage/index.tsx b/src/pages/FamilyDDangPage/index.tsx index 8fd5d8cb..bb1188d9 100644 --- a/src/pages/FamilyDDangPage/index.tsx +++ b/src/pages/FamilyDDangPage/index.tsx @@ -3,28 +3,51 @@ import { MdOutlineEditLocation } from 'react-icons/md' import { Typo14, Typo15, Typo17 } from '~components/Typo' import { MdOutlineModeEdit } from 'react-icons/md' import CountSection from '~components/WalkCountArea' -import { Avatar10, Avatar3 } from '~assets/avatars' import Profile from '~components/Profile' -import DogProfile from '~components/DogProfile' +import { useQuery } from '@tanstack/react-query' import { useModalStore } from '~stores/modalStore' import ShareCodeModal from '~modals/FamilyDDangModal/ShareCodeModal' +import OwnerUpdateModal from '~modals/OwnerUpdateModal' +import { fetchFamilyDDang } from '~apis/family/fetchFamilyDDang' +import { FAMILY_ROLE } from '~constants/familyRole' +import DogProfile from '~components/DogProfile' import { useMyPage } from '~apis/myPage/useMyPage' import { useEffect } from 'react' export default function FamilyDDang() { - const { data, refetch } = useMyPage() - const dogInfo = data?.dog - const { pushModal, modalList } = useModalStore() + const { refetch } = useMyPage() + + const { data, isLoading, isError } = useQuery({ + queryKey: ['familyList'], + queryFn: fetchFamilyDDang, + }) useEffect(() => { refetch() }, [modalList]) + if (isLoading) { + return
Loading...
+ } + + if (isError) { + return
Error loading data. Please try again later.
+ } + console.log(data) + + const familyInfo = data?.data + const members = familyInfo?.members + + console.log('familyIdfo : ', familyInfo) + const onClickCodeShare = () => { pushModal(, 'slideLeft') } + const onClickMemberUpdate = () => { + pushModal() + } return ( @@ -33,63 +56,53 @@ export default function FamilyDDang() { - {dogInfo && } + {/* {firstDog && ( + + )} */} + + {familyInfo?.dogs[0] && } + - - - - - {family1Info.nickName} - - {family1Info.gender} | {family1Info.position} - - - - 산책 시간 - - {family1Info.week} - - - {family1Info.time} - - - - 산책 횟수 - - {family1Info.count} - - - - - - - - - - - - {family2Info.nickName} - - {family2Info.gender} | {family2Info.position} - - - - 산책 시간 - - {family2Info.week} - - - {family2Info.time} - - - - 산책 횟수 - - {family2Info.count} - - - - + {members?.map(member => ( + + + + + {member.name} + + {member.gender === 'MALE' ? '남자' : '여자'} | {FAMILY_ROLE[member.familyRole] || member.familyRole} + + + + 산책 시간 + + {/* {member.walkScheduleInfoList.map(schedule => schedule.dayOfWeek).join(', ') || '없음'} */} + + + {member.walkScheduleInfoList.map(schedule => schedule.walkTime).join(', ') || ''} + + + + 산책 횟수 + + {member.totalWalkCount} + + + + + + + + ))} 밤톨이와 함께할 동반자를 초대하세요! @@ -98,23 +111,7 @@ export default function FamilyDDang() { - + ) } -const family1Info = { - nickName: '원돌이', - gender: '여자', - position: '엄마', - week: '월, 수, 금', - time: '10:00', - count: '8', -} -const family2Info = { - nickName: '투돌이', - gender: '남자', - position: '형', - week: '화, 토', - time: '17:00', - count: '23', -} diff --git a/src/pages/MyPage/index.tsx b/src/pages/MyPage/index.tsx index d214ddea..073a264b 100644 --- a/src/pages/MyPage/index.tsx +++ b/src/pages/MyPage/index.tsx @@ -78,8 +78,9 @@ export default function MyPage() { - - {myPageData?.dog && } + + {myPageData?.dog && } + {/* {myPageData?.walkCount}회 diff --git a/src/pages/MyPage/styles.ts b/src/pages/MyPage/styles.ts index b21c9864..c3f884a8 100644 --- a/src/pages/MyPage/styles.ts +++ b/src/pages/MyPage/styles.ts @@ -111,3 +111,4 @@ export const CountWrapperSmall = styled.div` color: ${({ theme }) => theme.colors.grayscale.font_1}; font-weight: 500; ` + diff --git a/src/pages/RegisterPage/Register/styles.ts b/src/pages/RegisterPage/Register/styles.ts index dd2667a3..abfdcef9 100644 --- a/src/pages/RegisterPage/Register/styles.ts +++ b/src/pages/RegisterPage/Register/styles.ts @@ -5,15 +5,15 @@ export const RegisterPage = styled.div` display: flex; flex-direction: column; justify-content: space-between; - padding: 5.5rem 1.25rem 1.8rem 1.25rem; + padding: 3.5rem 1.25rem 1.8rem 1.25rem; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - + z-index: 100; + /* position: absolute; */ @media (max-height: 700px) { padding: 4.75rem 1.25rem 1rem; gap: 0.5rem; } ` - export const TextSection = styled.text<{ weight: FontWeight }>` flex-shrink: 0; color: ${({ theme }) => theme.colors.grayscale.font_1}; @@ -23,11 +23,14 @@ export const TextSection = styled.text<{ weight: FontWeight }>` text-align: center; margin: 1rem 0; ` - export const AddOwnerAvatarBtnWrapper = styled.div` display: flex; + flex-direction: column; justify-content: center; + align-items: center; + position: relative; flex-shrink: 0; + gap: 1rem; ` export const AddOwnerAvatarBtn = styled.div` @@ -101,7 +104,7 @@ export const PositionChoiceBtn = styled.div<{ $hasSelected?: boolean }>` padding: 1rem 2rem; border-radius: 0.75rem; font-size: ${({ theme }) => theme.typography._20}; - color: ${({ theme, $hasSelected }) => ($hasSelected ? theme.colors.grayscale.font_1 : theme.colors.grayscale.font_3)}; + color: ${({ theme }) => theme.colors.grayscale.font_1}; cursor: pointer; @media (max-height: 700px) { @@ -149,3 +152,27 @@ export const GenderSelectBtnWrapper = styled.div` export const ToastWrapper = styled.div` position: relative; ` + +export const ChoiceAvatarBtn = styled.div` + width: 87px; + height: 32px; + /* display: inline-flex; */ + padding: 6.5px 12px; + justify-content: center; + align-items: center; + gap: 4px; + border-radius: 22px; + color: ${({ theme }) => theme.colors.grayscale.gc_4}; + background-color: ${({ theme }) => theme.colors.grayscale.font_1}; + margin-top: 1rem; +` +export const ProfileArea = styled.div` + flex-direction: column; + width: 13rem; + height: 13rem; + flex-shrink: 0; + margin: 1.5rem auto; + display: flex; + justify-content: center; + align-items: center; +` diff --git a/src/types/api.ts b/src/types/api.ts index f40ade56..b3442351 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -246,3 +246,10 @@ export type GetSettingsResponse = { isMatched: BooleanString settings: NotificationSettings } + +export type UpdateOwnerProfileResponse = { + familyRole: FamilyRole + gender: Gender + name: string + profileImg: string +} diff --git a/src/utils/validateOwnerProfile.ts b/src/utils/validateOwnerProfile.ts index 8762ca56..2d09657f 100644 --- a/src/utils/validateOwnerProfile.ts +++ b/src/utils/validateOwnerProfile.ts @@ -2,12 +2,15 @@ import { OwnerProfileType } from '~types/ownerProfile' const regex = /^[가-힣]{1,10}$/ -export const validateOwnerProfile = (ownerProfile: OwnerProfileType): string | null => { +export const validateOwnerProfile = ( + ownerProfile: OwnerProfileType, + options?: { skipAddress?: boolean } // 옵션 추가 +): string | null => { if (!ownerProfile.familyRole) return '아바타를 선택해 주세요' if (!ownerProfile.name) return '견주님의 닉네임을 입력해주세요' if (!regex.test(ownerProfile.name)) return '닉네임은 한글로 10자 이내로 작성해 주세요' if (!ownerProfile.familyRole) return '견주님의 역할을 선택해주세요' - if (!ownerProfile.address) return '견주님의 위치 인증을 해주세요' + if (!options?.skipAddress && !ownerProfile.address) return '견주님의 위치 인증을 해주세요' // address 검사 조건 추가 if (!ownerProfile.gender) return '견주님의 성별을 선택해주세요' return null }