Skip to content

feat: mypage api 연동 (badge history, level) #98

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

Merged
merged 10 commits into from
Sep 30, 2024
2 changes: 1 addition & 1 deletion packages/react-native/src/apis/mutations/useAddTripPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function useAddTripPlan() {
await queryClient.invalidateQueries({
queryKey: [QUERY_KEYS.TRIP_PLANS],
});
navigate.navigate('TripPlanner/Main');
navigate.goBack();
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useQuery } from '@tanstack/react-query';
import useAuthAxios from '@/apis/useAuthAxios';
import { badgePath } from '@/components/common/Badge';
import { BadgeAcquisition } from '@/constants/BADGE_ACQUISITION';
import { City, Region, REVERSE_BADGE_MAPPER } from '@/constants/CITY';
import QUERY_KEYS from '@/constants/QUERY_KEYS';
import { ServerResponse } from '@/types/response';

interface BadgeHistoryResponse {
region: Region;
city: City;
acquisitionType: BadgeAcquisition;
createdAt: string;
}

export default function useBadgeHistoryQuery({
region,
}: {
region: keyof typeof badgePath;
}) {
const badgeRegions = REVERSE_BADGE_MAPPER[region];
const params = Array.isArray(badgeRegions)
? badgeRegions.join(',')
: badgeRegions;
const authAxios = useAuthAxios();

const getBadgeHistory = async () => {
const result = await authAxios.get<ServerResponse<BadgeHistoryResponse[]>>(
`/api/user/badge/acquisition?regions=${params}`,
);
return result.data.result;
};

return useQuery({
queryKey: [QUERY_KEYS.BADGE_HISTORY, region],
queryFn: getBadgeHistory,
});
}
50 changes: 5 additions & 45 deletions packages/react-native/src/apis/queries/mypage/useMyBadgeQuery.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,27 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import useAuthAxios from '@/apis/useAuthAxios';
import { badgePath } from '@/components/common/Badge';
import { Region } from '@/constants/CITY';
import { BADGE_MAPPER, Region } from '@/constants/CITY';
import QUERY_KEYS from '@/constants/QUERY_KEYS';
import { ServerResponse } from '@/types/response';
import unique from '@/utils/unique';

interface BadgeResponse {
region: Region;
count: number;
}

const badgeMapper = (badge: BadgeResponse): keyof typeof badgePath => {
switch (badge.region) {
case Region.SEOUL:
return '서울';
case Region.GYEONGGI:
return '경기';
case Region.INCHEON:
return '인천';
case Region.GANGWON:
return '강원';
case Region.SEJONG:
return '세종';
case Region.DAEJEON:
return '대전';
case Region.GWANGJU:
return '광주';
case Region.DAEGU:
return '대구';
case Region.ULSAN:
return '울산';
case Region.BUSAN:
return '부산';

case Region.CHUNGBUK:
case Region.CHUNGNAM:
return '충청';

case Region.GYEONGBUK:
case Region.GYEONGNAM:
return '경상';

case Region.JEONBUK:
case Region.JEONNAM:
return '전라';

case Region.JEJU:
default:
return '제주';
}
};

export default function useMyBadgeQuery() {
const authAxios = useAuthAxios();
const getBadges = async () => {
const result =
await authAxios.get<ServerResponse<BadgeResponse[]>>('/api/user/badge');

const badges = result.data.result.map((badge) => ({
count: badge.count,
badgeRegion: badgeMapper(badge),
badgeRegion: BADGE_MAPPER[badge.region],
}));

return badges;
return unique(badges, 'badgeRegion');
};

return useSuspenseQuery({
Expand Down
23 changes: 23 additions & 0 deletions packages/react-native/src/apis/queries/mypage/useMyLevelQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import useAuthAxios from '@/apis/useAuthAxios';
import QUERY_KEYS from '@/constants/QUERY_KEYS';
import { ServerResponse } from '@/types/response';

interface LevelResponse {
profileLevel: string;
description: string;
}

export default function useMyLevelQuery() {
const authAxios = useAuthAxios();
const getMyLevel = async () => {
const result =
await authAxios.get<ServerResponse<LevelResponse>>('/api/user/level');
return result.data.result;
};

return useSuspenseQuery({
queryKey: [QUERY_KEYS.MY_LEVEL],
queryFn: getMyLevel,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,58 @@
import { badgePath } from '../common/Badge';
import BadgeListItem from './BadgeListItem';
import BottomSheet from '../common/BottomSheet';

// FIXME: 실제 데이터 받아오기: selectedBadge를 기준으로 데이터를 받아오면 됨
const mockData = [
{ id: 1, title: '안동1', date: '2024. 08. 16.', content: 'SPOT! 퀴즈 정답' },
{ id: 2, title: '안동2', date: '2024. 08. 16.', content: 'SPOT! 퀴즈 정답' },
{ id: 3, title: '안동3', date: '2024. 08. 16.', content: 'SPOT! 퀴즈 정답' },
{ id: 4, title: '안동4', date: '2024. 08. 16.', content: 'SPOT! 퀴즈 정답' },
];
import useBadgeHistoryQuery from '@/apis/queries/mypage/useBadgeHistoryQuery';
import { REVERSE_REGION_MAPPER } from '@/constants/CITY';

Check warning on line 8 in packages/react-native/src/components/mypage/BadgeListBottomSheet.tsx

View workflow job for this annotation

GitHub Actions / build

'REVERSE_REGION_MAPPER' is defined but never used
import { ACQUISITION_MAPPER } from '@/constants/BADGE_ACQUISITION';
import { getDisplayRegion } from '@/utils/getDisplayRegionName';

interface BadgeListBottomSheetProps {
selectedBadge?: keyof typeof badgePath;
selectedBadge: keyof typeof badgePath;
onClose: () => void;
}

export default function BadgeListBottomSheet({
selectedBadge,
onClose,
}: BadgeListBottomSheetProps) {
const { data: badgeHistory, isLoading } = useBadgeHistoryQuery({
region: selectedBadge,
});

const renderContent = () => {
if (isLoading) {
return (
<View>
<Font type="body1" color="black">
잠시만
</Font>
<Font type="body1" color="black">
기다려주세요
</Font>
</View>
);
}

return (
selectedBadge &&
badgeHistory?.map((badgeInfo, index) => (
<BadgeListItem
key={`data-${index}`}
location={selectedBadge}
title={
getDisplayRegion({
locationEnum: badgeInfo.region,
cityEnum: badgeInfo.city,
onlyCity: true,
}) || ''
}
date={badgeInfo.createdAt}
content={ACQUISITION_MAPPER[badgeInfo.acquisitionType]}
/>
))
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 컴포넌트로 만들 수 없나요?? 추가로 Suspense 적용은 어려울까요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영하였습니다! 96352b6


return (
<BottomSheet
isShow={Boolean(selectedBadge)}
Expand All @@ -37,24 +71,7 @@
showsVerticalScrollIndicator={false}
style={{ paddingHorizontal: 16 }}
>
{selectedBadge &&
mockData.map((badgeInfo, index) => (
<>
<BadgeListItem
key={`data-${badgeInfo.id}`}
location={selectedBadge}
title={badgeInfo.title}
date={badgeInfo.date}
content={badgeInfo.content}
/>
{index !== mockData.length - 1 && (
<View
key={`sep-${badgeInfo.id}`}
className="h-[0.5px] bg-[#333333] bg-opacity-30 w-full"
/>
)}
</>
))}
{renderContent()}
</BottomSheetScrollView>
</BottomSheet>
);
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native/src/constants/BADGE_ACQUISITION.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ export enum BadgeAcquisition {
BY_CAMERA_FILTER,
BY_RECORD,
}

export const ACQUISITION_MAPPER: Record<BadgeAcquisition, string> = {
[BadgeAcquisition.BY_QUIZ]: 'Spot! 퀴즈 정답',
[BadgeAcquisition.BY_CAMERA_FILTER]: 'Spot! 카메라 필터',
[BadgeAcquisition.BY_RECORD]: '소중한 여행 기록 작성',
};
20 changes: 20 additions & 0 deletions packages/react-native/src/constants/CITY.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,23 @@ export const BADGE_MAPPER: Record<Region, keyof typeof badgePath> = {
[Region.BUSAN]: '부산',
[Region.JEJU]: '제주',
};

export const REVERSE_BADGE_MAPPER: Record<
keyof typeof badgePath,
Region | Region[]
> = {
서울: Region.SEOUL,
경기: Region.GYEONGGI,
인천: Region.INCHEON,
강원: Region.GANGWON,
충청: [Region.CHUNGNAM, Region.CHUNGBUK],
세종: Region.SEJONG,
대전: Region.DAEJEON,
전라: [Region.JEONNAM, Region.JEONBUK],
광주: Region.GWANGJU,
경상: [Region.GYEONGBUK, Region.GYEONGNAM],
대구: Region.DAEGU,
울산: Region.ULSAN,
부산: Region.BUSAN,
제주: Region.JEJU,
};
2 changes: 2 additions & 0 deletions packages/react-native/src/constants/QUERY_KEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const QUERY_KEYS = {
SEARCH: 'search',
MY_SPOTS: 'mySpots',
MY_BADGES: 'myBadges',
MY_LEVEL: 'myLevel',
BADGE_HISTORY: 'badgeHistory',
SCHEDULES: 'schedules',
};

Expand Down
4 changes: 3 additions & 1 deletion packages/react-native/src/pages/MyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { StackNavigation } from '@/types/navigation';
import CogWheelIcon from '@/assets/CogWheelIcon';
import useProfileImage from '@/hooks/useProfileImage';
import withSuspense from '@/components/HOC/withSuspense';
import useMyLevelQuery from '@/apis/queries/mypage/useMyLevelQuery';

interface MyPageProps {
navigation: StackNavigation<'MyPage/Profile'>;
Expand All @@ -17,6 +18,7 @@ interface MyPageProps {
export default withSuspense(function MyPage({ navigation }: MyPageProps) {
const { profile } = useProfileQuery();
const { ProfileImage } = useProfileImage();
const { data: myLevel } = useMyLevelQuery();

return (
<BackGroundGradient paddingTop={40} withoutScroll>
Expand All @@ -36,7 +38,7 @@ export default withSuspense(function MyPage({ navigation }: MyPageProps) {
</View>
<View className="flex flex-col items-center">
<View>
<Rank content="Beginner" />
<Rank content={myLevel.profileLevel} />
</View>
<View className="mt-2">
<Font type="mainTitle" color="white">
Expand Down
22 changes: 15 additions & 7 deletions packages/react-native/src/pages/MyPage/MyBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useState } from 'react';
import { FlatList, View } from 'react-native';
import { Font } from 'design-system';
import Badge from '@/components/common/Badge';
import Badge, { badgePath } from '@/components/common/Badge';
import Spacing from '@/components/common/Spacing';
import useMyBadgeQuery from '@/apis/queries/mypage/useMyBadgeQuery';
import withSuspense from '@/components/HOC/withSuspense';
import BadgeListBottomSheet from '@/components/mypage/BadgeListBottomSheet';

export default withSuspense(
function MyBadge() {
const { data: badges } = useMyBadgeQuery();
const [containerWidth, setContainerWidth] = useState(0);
// const [selectedBadge, setSelectedBadge] = useState<keyof typeof badgePath>();
const [selectedBadge, setSelectedBadge] =
useState<keyof typeof badgePath>();

const numColumns = 3;
const paddingHorizontal = 8;
Expand All @@ -27,7 +29,11 @@ export default withSuspense(
location={item.badgeRegion}
width={(containerWidth - paddingHorizontal * 2) / numColumns}
label={item.badgeRegion}
// onPress={() => setSelectedBadge(item.badgeRegion)}
onPress={() => {
if (item.count) {
setSelectedBadge(item.badgeRegion);
}
}}
count={item.count}
/>
<Spacing height={20} />
Expand All @@ -36,10 +42,12 @@ export default withSuspense(
keyExtractor={(item) => item.badgeRegion}
numColumns={numColumns}
/>
{/* <BadgeListBottomSheet
selectedBadge={selectedBadge}
onClose={() => setSelectedBadge(undefined)}
/> */}
{selectedBadge && (
<BadgeListBottomSheet
selectedBadge={selectedBadge}
onClose={() => setSelectedBadge(undefined)}
/>
)}
</>
);
},
Expand Down
9 changes: 9 additions & 0 deletions packages/react-native/src/utils/unique.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function unique<T extends object>(array: T[], key: keyof T) {
return array.reduce<T[]>((uniq, item) => {
return uniq.some((element) => element[key] === item[key])
? uniq
: [...uniq, item];
}, []);
}

export default unique;
Loading