Skip to content

Commit

Permalink
feat/connect resort api (#41)
Browse files Browse the repository at this point in the history
* feat: add resort and weather api

* feat: update resorts data with api

* feat: connect resort api in discovery detail page
  • Loading branch information
Najeong-Kim authored Nov 2, 2024
1 parent 896dfa8 commit 934b400
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 62 deletions.
8 changes: 8 additions & 0 deletions src/entities/discovery/api/get-resort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { apiClient } from '@/shared/api/base';
import type { Resort } from '../model';

export const getResorts = async (): Promise<Resort[]> => {
const result = await apiClient.get<Resort[]>('/api/ski-resorts');

return result;
};
8 changes: 8 additions & 0 deletions src/entities/discovery/api/get-weather.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { apiClient } from '@/shared/api/base';
import type { WeatherResponse } from '../model';

export const getWeather = async (resortId: number): Promise<WeatherResponse> => {
const result = await apiClient.get<WeatherResponse>(`/api/weather/${resortId}`);

return result;
};
4 changes: 3 additions & 1 deletion src/entities/discovery/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { discoveryQueries } from './discovery.queries';
export { discoveryQueries } from './query/discovery.queries'
export { resortQueries } from './query/resort.queries'
export { weatherQueries } from './weather.queries';
export { postVote } from './post-vote';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { queryOptions } from '@tanstack/react-query';
import { getDiscoveries } from './get-discoveries';
import { getVote } from './get-vote';
import { getDiscoveries } from '../get-discoveries';
import { getVote } from '../get-vote';

export const discoveryQueries = {
all: () => ['discovery'],
Expand Down
13 changes: 13 additions & 0 deletions src/entities/discovery/api/query/resort.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { queryOptions } from '@tanstack/react-query';
import { getResorts } from '../get-resort';

export const resortQueries = {
all: () => ['resort'],

listQueryKey: () => [...resortQueries.all(), 'list'],
list: () =>
queryOptions({
queryKey: [...resortQueries.listQueryKey()],
queryFn: () => getResorts(),
}),
};
13 changes: 13 additions & 0 deletions src/entities/discovery/api/weather.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { queryOptions } from '@tanstack/react-query';
import { getWeather } from './get-weather';

export const weatherQueries = {
all: () => ['weather'],

weatherQueryKey: (resortId: number) => [...weatherQueries.all(), resortId],
weather: (resortId: number) =>
queryOptions({
queryKey: weatherQueries.weatherQueryKey(resortId),
queryFn: () => getWeather(resortId),
}),
};
2 changes: 1 addition & 1 deletion src/entities/discovery/model/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { DiscoveryData } from './constants';
export type { Weather, WeeklyWeather, Discovery, Vote } from './model';
export type { Weather, Discovery, Vote, WeeklyWeather, WeatherResponse, Resort, Url } from './model';
58 changes: 51 additions & 7 deletions src/entities/discovery/model/model.d.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,77 @@
export type Weather = 'sun' | 'cloud' | 'rain' | 'snow' | 'fog' | 'snow-rain';

export type WeeklyWeather = {
export type DuplicatedWeeklyWeather = {
weather: Weather;
temperature: {
lowest: string;
average: string;
};
};

export type Url = {
bus: string;
homepage: string;
};

export type Discovery = {
id: number;
name: string;
map: string;
slope: number | null;
url: {
bus: string;
homepage: string;
};
url: Url;
weather: {
weather: Weather;
temperature: number;
description: string;
};
weeklyWeather: WeeklyWeather[];
weeklyWeather: DuplicatedWeeklyWeather[];
};

export type Vote = {
resortId: number;
totalVotes: number;
positiveVotes: number;
status: string;
};
};

export type WeatherResponse = {
resortId: number,
currentWeather: {
temperature: number,
maxTemperature: number,
minTemperature: number,
feelsLike: number,
description: string,
condition: string
},
hourlyWeather: [],
weeklyWeather: WeeklyWeather[]
}

export type WeeklyWeather = {
day: string;
date: string;
precipitationChance: string;
maxTemperature: number;
minTemperature: number;
condition: string;
};

export type SummarizedWeeklyWeather = {
day: string;
maxTemperature: number;
minTemperature: number;
description: string;
};

export type Resort = {
resortId: number,
name: string,
status: string,
openSlopes: number,
currentWeather: {
temperature: number,
description: string
},
weeklyWeather: SummarizedWeeklyWeather[]
}
21 changes: 21 additions & 0 deletions src/shared/lib/getWeatherFromDescription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// eslint-disable-next-line boundaries/element-types
import type { Weather } from "@/entities/discovery";

const weatherDescription = {
sun: ['맑음'],
cloud: ['구름', '구름많음'],
rain: ['비', '구름많고 비', '흐리고 비', '구름많고 소나기', '흐리고 소나기'],
snow: ['눈', '구름많고 눈', '흐리고 눈'],
fog: ['안개', '흐림'],
'snow-rain': ['흐리고 비/눈', '구름많고 비/눈']
}

export const getWeatherFromDescription = (description: string): Weather => {
for (const [weather, descriptions] of Object.entries(weatherDescription)) {
if (descriptions.includes(description)) {
return weather as Weather;
}
}

return 'sun';
}
8 changes: 5 additions & 3 deletions src/views/discovery-detail/ui/discovery-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const DiscoveryDetailPage = ({ params }: { params: { resortId: string } }) => {
const discovery = DiscoveryData.find(
(discovery) => discovery.id === +params?.resortId
) as Discovery;
const { data: resortsData } = useQuery(discoveryApi.resortQueries.list());
const resort = resortsData?.find((resort) => resort.resortId === +params?.resortId);
const { data: voteData } = useQuery(discoveryApi.discoveryQueries.vote(+params?.resortId));
const data = RESORT_DOMAIN[discovery?.map as keyof typeof RESORT_DOMAIN];
const [selectedTab, setSelectedTab] = useState('webcam');
Expand Down Expand Up @@ -72,12 +74,12 @@ const DiscoveryDetailPage = ({ params }: { params: { resortId: string } }) => {
}
}, [isPositive, mutateAsync, params?.resortId]);

if (!discovery) return;
if (!discovery || !resort) return;

return (
<div className={cn('size-full')}>
<Header resortName={discovery.name} hasBackButton hasShareButton />
<DiscoverySummary {...discovery} />
<Header resortName={resort.name} hasBackButton hasShareButton />
<DiscoverySummary {...resort} {...discovery.url} />
<ul className={cn('relative z-10 flex size-full h-[53px] bg-white')}>
{DiscoveryContentTabList.map((tab) => (
<li
Expand Down
6 changes: 3 additions & 3 deletions src/views/discovery/ui/discovery-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { discoveryApi } from '@/entities/discovery';
import { cn } from '@/shared/lib';

const DiscoveryPage = () => {
const { data: discoveryData } = useQuery(discoveryApi.discoveryQueries.list());
const { data: resorts } = useQuery(discoveryApi.resortQueries.list());

if (!discoveryData) return null;
if (!resorts) return null;

return (
<div className={cn('size-full bg-gradient-to-b from-[rgba(141,163,221,0.2)] to-transparent')}>
<Header />
<DiscoveryList discoveryData={discoveryData} />
<DiscoveryList resorts={resorts} />
</div>
);
};
Expand Down
38 changes: 22 additions & 16 deletions src/widgets/discovery-detail/ui/discovery-summary.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import Link from 'next/link';
import WeatherIcon from '@/features/discovery/ui/weather-icon';
import VoteDialog from '@/features/discovery-detail/ui/vote-dialog';
import type { Discovery } from '@/entities/discovery';
import type { Resort, Url } from '@/entities/discovery';
import { BusIcon, LiftIcon, VoteIcon } from '@/shared/icons';
import { cn } from '@/shared/lib';
import { getWeatherFromDescription } from '@/shared/lib/getWeatherFromDescription';
import Card from '@/shared/ui/card';
import { DiscoverySummaryActionList } from '../model/constants';
import DiscoverySummaryAction from './discovery-summary-action';

const DiscoverySummary = ({ id, name, slope, url, weather }: Discovery) => {
const DiscoverySummary = ({
resortId,
name,
openSlopes,
currentWeather,
bus,
homepage,
}: Resort & Url) => {
return (
<div className={cn('flex w-full gap-[26px] px-5 pb-[34px] pt-[10px] md:px-[30px]')}>
<Card
className={cn(
'flex flex-col justify-center gap-[5px] h-[134px] flex-1 items-center pl-[30px] pr-6 md:h-[123px]'
'flex h-[134px] flex-1 flex-col items-center justify-center gap-[5px] pl-[30px] pr-6 md:h-[123px]'
)}
>
<div className={cn('flex w-full justify-between items-center')}>
<div className={cn('flex w-full items-center justify-between')}>
<p className={cn('title1 text-gray-90')}>{name}</p>
<div className={cn('flex gap-2 items-center')}>
<WeatherIcon weather={weather.weather} />
<p className={cn('font-semibold text-[30px] leading-tight')}>{weather.temperature}°</p>
<div className={cn('flex items-center gap-2')}>
<WeatherIcon weather={getWeatherFromDescription(currentWeather.description)} />
<p className={cn('text-[30px] font-semibold leading-tight')}>
{currentWeather.temperature}°
</p>
</div>
</div>
<div className={cn('flex w-full justify-between items-center')}>
<div className={cn('flex w-full items-center justify-between')}>
<p className={cn('body1-medium text-gray-60')}>
{slope ? `운행중인 슬로프 ${slope}개` : '개장일이 곧 공개될 예정이에요'}
{openSlopes ? `운행중인 슬로프 ${openSlopes}개` : '개장일이 곧 공개될 예정이에요'}
</p>
<p className={cn('body1-semibold text-gray-60')}>{weather.description}</p>
<p className={cn('body1-semibold text-gray-60')}>{currentWeather.description}</p>
</div>
</Card>
<Card className={cn('hidden h-[123px] items-center justify-center gap-[30px] px-5 md:flex')}>
Expand All @@ -36,19 +46,15 @@ const DiscoverySummary = ({ id, name, slope, url, weather }: Discovery) => {
return (
<VoteDialog
key={action.name}
id={id}
id={resortId}
trigger={
<DiscoverySummaryAction key={action.name} {...action} icon={<VoteIcon />} />
}
/>
);
} else {
return (
<Link
key={action.name}
href={action.name === 'bus' ? url.bus : url.homepage}
target="_blank"
>
<Link key={action.name} href={action.name === 'bus' ? bus : homepage} target="_blank">
<DiscoverySummaryAction
{...action}
icon={action.name === 'bus' ? <BusIcon /> : <LiftIcon />}
Expand Down
34 changes: 15 additions & 19 deletions src/widgets/discovery/ui/discovery-card.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useRouter } from 'next/navigation';
import WeatherIcon from '@/features/discovery/ui/weather-icon';
import type { Discovery } from '@/entities/discovery';
import type { Resort } from '@/entities/discovery';
import { cn } from '@/shared/lib';
import { getWeatherFromDescription } from '@/shared/lib/getWeatherFromDescription';
import Card from '@/shared/ui/card';
import { getTargetDateWeekday } from '../lib/getTargetDateWeekday';
import WeeklyWeather from './weekly-weather';

const DiscoveryCard = ({ id, name, slope, weather, weeklyWeather }: Discovery) => {
const DiscoveryCard = ({ resortId, name, openSlopes, currentWeather, weeklyWeather }: Resort) => {
const router = useRouter();

return (
Expand All @@ -15,36 +15,32 @@ const DiscoveryCard = ({ id, name, slope, weather, weeklyWeather }: Discovery) =
'box-border flex cursor-pointer flex-col gap-[15px] pb-4 pt-[34px] md:pb-[26px] md:pt-10',
'transition-all hover:scale-[1.02] hover:border-2 hover:border-main-1/30 hover:pb-[15px] hover:pt-[33px] hover:md:pb-[25px] hover:md:pt-[39px]'
)}
onClick={() => router.push(`/${id}`)}
onClick={() => router.push(`/${resortId}`)}
>
<div className={cn('mx-[30px] flex flex-col items-center gap-[5px] md:mx-[42px]')}>
<div className={cn('flex w-full items-center justify-between')}>
<h2 className={cn('title1 md:h2 text-gray-90')}>{name}</h2>
<div className={cn('flex items-center gap-2')}>
<WeatherIcon className={cn('origin-right scale-[1.17]')} weather={weather.weather} />
<p className={cn('text-[30px] font-semibold leading-tight')}>{weather.temperature}°</p>
<WeatherIcon
className={cn('origin-right scale-[1.17]')}
weather={getWeatherFromDescription(currentWeather.description)}
/>
<p className={cn('text-[30px] font-semibold leading-tight')}>
{currentWeather.temperature}°
</p>
</div>
</div>
<div className={cn('flex w-full justify-between')}>
<p className={cn('body1-medium text-gray-60')}>
{slope ? `운행중인 슬로프 ${slope}개` : '개장일이 곧 공개될 예정이에요'}
{openSlopes ? `운행중인 슬로프 ${openSlopes}개` : '개장일이 곧 공개될 예정이에요'}
</p>
<p className={cn('body1-semibold text-gray-60')}>{weather.description}</p>
<p className={cn('body1-semibold text-gray-60')}>{currentWeather.description}</p>
</div>
</div>
<hr className={cn('mx-[30px] border-gray-80 opacity-[0.04]')} />
<ul
className={cn(
'flex w-full justify-between gap-[2px] overflow-x-scroll px-[30px] scrollbar-hide'
)}
>
<ul className={cn('flex w-full gap-[2px] overflow-x-scroll px-[30px] scrollbar-hide')}>
{weeklyWeather.map((weather, index) => (
<WeeklyWeather
key={index}
{...weather}
day={getTargetDateWeekday(index)}
isToday={index === 0}
/>
<WeeklyWeather key={index} {...weather} isToday={index === 0} />
))}
</ul>
</Card>
Expand Down
8 changes: 4 additions & 4 deletions src/widgets/discovery/ui/discovery-list.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Discovery } from '@/entities/discovery';
import type { Resort } from '@/entities/discovery';
import { cn } from '@/shared/lib';
import DiscoveryCard from './discovery-card';

const DiscoveryList = ({ discoveryData }: { discoveryData: Discovery[] }) => (
const DiscoveryList = ({ resorts }: { resorts: Resort[] }) => (
<div className={cn('flex flex-col gap-4 px-5 py-4 md:px-8')}>
{discoveryData.map((discovery) => (
<DiscoveryCard key={discovery.id} {...discovery} />
{resorts.map((resort) => (
<DiscoveryCard key={resort.resortId} {...resort} />
))}
</div>
);
Expand Down
Loading

0 comments on commit 934b400

Please sign in to comment.