From 36478d65c2ef4a7180ccc3708fdc288a013e1987 Mon Sep 17 00:00:00 2001 From: NAJEONG KIM <73640737+Najeong-Kim@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:37:44 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=A4=EC=A7=88=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EB=B0=B1=EC=97=94=EB=93=9C=20=EC=97=B0=EA=B2=B0=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Connect vote data api * feat: Add post vote api * fix: Fix rewrite api source * fix: Add weski proxy to fix build error --- .../discovery/api/discovery.queries.ts | 7 ++ src/entities/discovery/api/get-vote.ts | 8 ++ src/entities/discovery/api/index.ts | 1 + src/entities/discovery/api/post-vote.ts | 7 ++ src/entities/discovery/api/use-post-vote.ts | 15 +++ src/entities/discovery/model/constants.ts | 96 +++++++++---------- src/entities/discovery/model/index.ts | 2 +- src/entities/discovery/model/model.d.ts | 9 ++ .../discovery-detail/ui/vote-dialog.tsx | 31 ++++-- src/pages/api/weski/[...path].ts | 19 ++++ .../ui/discovery-detail-page.tsx | 3 +- src/shared/api/base.ts | 10 +- .../discovery-detail/ui/discovery-summary.tsx | 4 +- 13 files changed, 144 insertions(+), 68 deletions(-) create mode 100644 src/entities/discovery/api/get-vote.ts create mode 100644 src/entities/discovery/api/post-vote.ts create mode 100644 src/entities/discovery/api/use-post-vote.ts create mode 100644 src/pages/api/weski/[...path].ts diff --git a/src/entities/discovery/api/discovery.queries.ts b/src/entities/discovery/api/discovery.queries.ts index 97ddfda..7fc53a8 100644 --- a/src/entities/discovery/api/discovery.queries.ts +++ b/src/entities/discovery/api/discovery.queries.ts @@ -1,5 +1,6 @@ import { queryOptions } from '@tanstack/react-query'; import { getDiscoveries } from './get-discoveries'; +import { getVote } from './get-vote'; export const discoveryQueries = { all: () => ['discovery'], @@ -10,4 +11,10 @@ export const discoveryQueries = { queryKey: [...discoveryQueries.listQueryKey()], queryFn: () => getDiscoveries(), }), + voteQueryKey: (key: string) => [...discoveryQueries.all(), 'vote', key], + vote: (key: string) => + queryOptions({ + queryKey: discoveryQueries.voteQueryKey(key), + queryFn: () => getVote(key), + }), }; diff --git a/src/entities/discovery/api/get-vote.ts b/src/entities/discovery/api/get-vote.ts new file mode 100644 index 0000000..6cf244b --- /dev/null +++ b/src/entities/discovery/api/get-vote.ts @@ -0,0 +1,8 @@ +import { apiClient } from '@/shared/api/base'; +import type { Vote } from '../model'; + +export const getVote = async (key: string): Promise => { + const result = await apiClient.get(`/ski/${key}/snowmaking`); + + return result; +}; diff --git a/src/entities/discovery/api/index.ts b/src/entities/discovery/api/index.ts index 1767fe4..c5f342f 100644 --- a/src/entities/discovery/api/index.ts +++ b/src/entities/discovery/api/index.ts @@ -1 +1,2 @@ export { discoveryQueries } from './discovery.queries'; +export { postVote } from './post-vote'; diff --git a/src/entities/discovery/api/post-vote.ts b/src/entities/discovery/api/post-vote.ts new file mode 100644 index 0000000..0d13cd4 --- /dev/null +++ b/src/entities/discovery/api/post-vote.ts @@ -0,0 +1,7 @@ +import { apiClient } from '@/shared/api/base'; +import type { PostVoteRequest } from '../model'; + +export const postVote = async (key: string, body: PostVoteRequest) => { + const res = await apiClient.post(`/ski/${key}/snowmaking`, body); + return res; +}; diff --git a/src/entities/discovery/api/use-post-vote.ts b/src/entities/discovery/api/use-post-vote.ts new file mode 100644 index 0000000..4951404 --- /dev/null +++ b/src/entities/discovery/api/use-post-vote.ts @@ -0,0 +1,15 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { discoveryApi } from '..'; + +export const usePostVote = (key: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ isLike }: { isLike: boolean }) => discoveryApi.postVote(key, { isLike }), + async onSettled() { + await queryClient.invalidateQueries({ + queryKey: discoveryApi.discoveryQueries.voteQueryKey(key), + }); + }, + }); +}; diff --git a/src/entities/discovery/model/constants.ts b/src/entities/discovery/model/constants.ts index dae18dc..6a5c2da 100644 --- a/src/entities/discovery/model/constants.ts +++ b/src/entities/discovery/model/constants.ts @@ -70,22 +70,6 @@ export const WeeklyWeatherData: WeeklyWeather[] = [ export const DiscoveryData: Discovery[] = [ { id: 1, - name: '용평스키장 모나', - map: 'yongpyong', - slope: null, - url: { - bus: 'https://skibus.purplebus.co.kr/Yp/', - homepage: 'https://www.yongpyong.co.kr/kor/skiNboard/introduce.do', - }, - weather: { - weather: 'sun', - temperature: -5, - description: '맑음', - }, - weeklyWeather: WeeklyWeatherData, - }, - { - id: 2, name: '지산 리조트', map: 'jisan', slope: 8, @@ -100,14 +84,30 @@ export const DiscoveryData: Discovery[] = [ }, weeklyWeather: WeeklyWeatherData, }, + { + id: 2, + name: '곤지암스키장', + map: 'gonjiam', + slope: 10, + url: { + bus: 'https://m.konjiamresort.co.kr/ski/pickupService.dev', + homepage: 'https://www.konjiamresort.co.kr/main.dev', + }, + weather: { + weather: 'rain', + temperature: -4, + description: '비', + }, + weeklyWeather: WeeklyWeatherData, + }, { id: 3, - name: '에덴밸리 리조트', - map: 'eden', + name: '비발디파크', + map: 'vivaldipark', slope: 10, url: { - bus: 'http://m.newbusantour.co.kr/goods/goods_detail.asp?g_cd=2183', - homepage: 'https://www.edenvalley.co.kr/ski/View.asp?location=01', + bus: 'https://www.busline.co.kr/busline_22K28/reservation_input.html?title=%EB%85%B8%EC%84%A0%EC%84%A0%ED%83%9D%20%EB%B0%8F%20%EC%9D%B4%EC%9A%A9%EC%9E%90%EB%93%B1%EB%A1%9D', + homepage: 'https://www.sonohotelsresorts.com/complex_vp', }, weather: { weather: 'rain', @@ -118,12 +118,12 @@ export const DiscoveryData: Discovery[] = [ }, { id: 4, - name: '웰리힐리파크', - map: 'wellihilli', + name: '엘리시안 강촌', + map: 'elysian-gangchon', slope: 10, url: { - bus: 'https://www.wellihillipark.com/home/guide/map/shuttle', - homepage: 'https://www.wellihillipark.com/snowpark', + bus: 'https://gs.elysian.co.kr/gangchon/enjoyElysian/roadMap_free_230210_pop.asp', + homepage: 'https://www.elysian.co.kr/', }, weather: { weather: 'rain', @@ -134,12 +134,12 @@ export const DiscoveryData: Discovery[] = [ }, { id: 5, - name: '하이원스키장', - map: 'high1', + name: '웰리힐리파크', + map: 'wellihilli', slope: 10, url: { - bus: 'https://skibus.purplebus.co.kr/Hi/', - homepage: 'https://www.high1.com/ski/index.do', + bus: 'https://www.wellihillipark.com/home/guide/map/shuttle', + homepage: 'https://www.wellihillipark.com/snowpark', }, weather: { weather: 'rain', @@ -150,12 +150,12 @@ export const DiscoveryData: Discovery[] = [ }, { id: 6, - name: '곤지암스키장', - map: 'gonjiam', + name: '휘닉스파크', + map: 'phoenix', slope: 10, url: { - bus: 'https://m.konjiamresort.co.kr/ski/pickupService.dev', - homepage: 'https://www.konjiamresort.co.kr/main.dev', + bus: 'https://skibus.purplebus.co.kr/Pp/', + homepage: 'https://phoenixhnr.co.kr/page/main/pyeongchang?q%5BhmpgDivCd%5D=PP&page=1&size=4', }, weather: { weather: 'rain', @@ -166,12 +166,12 @@ export const DiscoveryData: Discovery[] = [ }, { id: 7, - name: '비발디파크', - map: 'vivaldipark', + name: '하이원스키장', + map: 'high1', slope: 10, url: { - bus: 'https://www.busline.co.kr/busline_22K28/reservation_input.html?title=%EB%85%B8%EC%84%A0%EC%84%A0%ED%83%9D%20%EB%B0%8F%20%EC%9D%B4%EC%9A%A9%EC%9E%90%EB%93%B1%EB%A1%9D', - homepage: 'https://www.sonohotelsresorts.com/complex_vp', + bus: 'https://skibus.purplebus.co.kr/Hi/', + homepage: 'https://www.high1.com/ski/index.do', }, weather: { weather: 'rain', @@ -182,17 +182,17 @@ export const DiscoveryData: Discovery[] = [ }, { id: 8, - name: '엘리시안 강촌', - map: 'elysian-gangchon', - slope: 10, + name: '용평스키장 모나', + map: 'yongpyong', + slope: null, url: { - bus: 'https://gs.elysian.co.kr/gangchon/enjoyElysian/roadMap_free_230210_pop.asp', - homepage: 'https://www.elysian.co.kr/', + bus: 'https://skibus.purplebus.co.kr/Yp/', + homepage: 'https://www.yongpyong.co.kr/kor/skiNboard/introduce.do', }, weather: { - weather: 'rain', - temperature: -4, - description: '비', + weather: 'sun', + temperature: -5, + description: '맑음', }, weeklyWeather: WeeklyWeatherData, }, @@ -214,12 +214,12 @@ export const DiscoveryData: Discovery[] = [ }, { id: 10, - name: '휘닉스파크', - map: 'phoenix', + name: '에덴밸리 리조트', + map: 'eden', slope: 10, url: { - bus: 'https://skibus.purplebus.co.kr/Pp/', - homepage: 'https://phoenixhnr.co.kr/page/main/pyeongchang?q%5BhmpgDivCd%5D=PP&page=1&size=4', + bus: 'http://m.newbusantour.co.kr/goods/goods_detail.asp?g_cd=2183', + homepage: 'https://www.edenvalley.co.kr/ski/View.asp?location=01', }, weather: { weather: 'rain', diff --git a/src/entities/discovery/model/index.ts b/src/entities/discovery/model/index.ts index d0b451b..dad5cc9 100644 --- a/src/entities/discovery/model/index.ts +++ b/src/entities/discovery/model/index.ts @@ -1,2 +1,2 @@ export { DiscoveryData } from './constants'; -export type { Weather, WeeklyWeather, Discovery } from './model'; +export type { Weather, WeeklyWeather, Discovery, Vote, PostVoteRequest } from './model'; diff --git a/src/entities/discovery/model/model.d.ts b/src/entities/discovery/model/model.d.ts index f74e89e..16858dd 100644 --- a/src/entities/discovery/model/model.d.ts +++ b/src/entities/discovery/model/model.d.ts @@ -25,3 +25,12 @@ export type Discovery = { }; weeklyWeather: WeeklyWeather[]; }; + +export type Vote = { + totalNum: number; + likeNum: number; +}; + +export type PostVoteRequest = { + isLike: boolean; +}; diff --git a/src/features/discovery-detail/ui/vote-dialog.tsx b/src/features/discovery-detail/ui/vote-dialog.tsx index 1c996d0..172b7f3 100644 --- a/src/features/discovery-detail/ui/vote-dialog.tsx +++ b/src/features/discovery-detail/ui/vote-dialog.tsx @@ -1,4 +1,8 @@ +import { useQuery } from '@tanstack/react-query'; import { useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { discoveryApi } from '@/entities/discovery'; +import { usePostVote } from '@/entities/discovery/api/use-post-vote'; import { CheckIcon } from '@/shared/icons'; import { cn } from '@/shared/lib'; import { @@ -13,19 +17,25 @@ import { } from '@/shared/ui/dialog'; interface VoteDialogProps { + id: number; trigger: React.ReactNode; - count: { - total: number; - voted: number; - }; } -const VoteDialog = ({ trigger, count }: VoteDialogProps) => { +const VoteDialog = ({ id, trigger }: VoteDialogProps) => { const [isGood, setIsGood] = useState(true); + const { data: voteData } = useQuery(discoveryApi.discoveryQueries.vote(id.toString())); - const handleVote = useCallback(() => { - console.log(isGood); - }, [isGood]); + const { mutateAsync } = usePostVote(id.toString()); + + const handleVote = useCallback(async () => { + try { + await mutateAsync({ isLike: isGood }); + } catch (error) { + console.log(error); + } finally { + toast.success('투표가 완료되었습니다.'); + } + }, [isGood, mutateAsync]); return ( @@ -36,8 +46,9 @@ const VoteDialog = ({ trigger, count }: VoteDialogProps) => {
상태가 좋아요

- {count.total}명 중 {count.voted} - 명이 설질에 대해 투표했어요 + {voteData?.totalNum}명 중{' '} + {voteData?.likeNum} + 명이 긍정적으로 투표했어요.

오늘같은 현장은 설질 괜찮을까요? diff --git a/src/pages/api/weski/[...path].ts b/src/pages/api/weski/[...path].ts new file mode 100644 index 0000000..f07d458 --- /dev/null +++ b/src/pages/api/weski/[...path].ts @@ -0,0 +1,19 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { API_URL } from '@/shared/config'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { path } = req.query; + const url = `${API_URL}/${(path as string[]).join('/')}`; + + const response = await fetch(url, { + method: req.method, + headers: { + ...(req.headers as Record), + }, + body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined, + }); + + const data = await response.json(); + + res.status(response.status).json(data); +} diff --git a/src/pages/discovery-detail/ui/discovery-detail-page.tsx b/src/pages/discovery-detail/ui/discovery-detail-page.tsx index 81b9a4c..8972e84 100644 --- a/src/pages/discovery-detail/ui/discovery-detail-page.tsx +++ b/src/pages/discovery-detail/ui/discovery-detail-page.tsx @@ -1,8 +1,7 @@ 'use client'; import Image from 'next/image'; -import { useState } from 'react'; -import { useCallback } from 'react'; +import { useState, useCallback } from 'react'; import blind1 from '@public/blinds/blind_01.png'; import { DiscoveryContentTabList } from '@/widgets/discovery-detail/model/constants'; import DiscoverySummary from '@/widgets/discovery-detail/ui/discovery-summary'; diff --git a/src/shared/api/base.ts b/src/shared/api/base.ts index 5a664d0..834b7a7 100644 --- a/src/shared/api/base.ts +++ b/src/shared/api/base.ts @@ -1,5 +1,3 @@ -import { API_URL } from '@/shared/config'; - export class ApiClient { private baseUrl: string; @@ -24,7 +22,7 @@ export class ApiClient { endpoint: string, queryParams?: Record ): Promise { - const url = new URL(endpoint, this.baseUrl); + const url = new URL('/api/weski' + endpoint, window.location.origin); if (queryParams) { Object.entries(queryParams).forEach(([key, value]) => { @@ -46,7 +44,9 @@ export class ApiClient { endpoint: string, body: TData ): Promise { - const response = await fetch(`${this.baseUrl}${endpoint}`, { + const url = new URL('/api/weski' + endpoint, window.location.origin); + + const response = await fetch(url.toString(), { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -58,4 +58,4 @@ export class ApiClient { } } -export const apiClient = new ApiClient(API_URL); +export const apiClient = new ApiClient(''); diff --git a/src/widgets/discovery-detail/ui/discovery-summary.tsx b/src/widgets/discovery-detail/ui/discovery-summary.tsx index 841468a..e3f802d 100644 --- a/src/widgets/discovery-detail/ui/discovery-summary.tsx +++ b/src/widgets/discovery-detail/ui/discovery-summary.tsx @@ -8,7 +8,7 @@ import Card from '@/shared/ui/card'; import { DiscoverySummaryActionList } from '../model/constants'; import DiscoverySummaryAction from './discovery-summary-action'; -const DiscoverySummary = ({ name, slope, url, weather }: Discovery) => { +const DiscoverySummary = ({ id, name, slope, url, weather }: Discovery) => { return (
@@ -30,10 +30,10 @@ const DiscoverySummary = ({ name, slope, url, weather }: Discovery) => { return ( } /> } - count={{ total: 100, voted: 50 }} /> ); } else {