From a88ff5ffa9e49fb2d07b7bb1135906a1ae7a016c Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 15:31:10 +0900 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EB=81=BC?= =?UTF-8?q?=EC=9A=B0=EA=B8=B0,=20NavBar=20=EC=97=90=EB=9F=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/map/UserLocation.tsx | 4 +- src/components/navBar/NavBar.tsx | 2 +- .../restaurants/useGetDetailRestaurants.ts | 13 +++++ src/libs/api.ts | 55 +++++++++++++++++-- src/pages/FriendPage.tsx | 6 +- 5 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 src/hooks/api/restaurants/useGetDetailRestaurants.ts diff --git a/src/components/map/UserLocation.tsx b/src/components/map/UserLocation.tsx index 64c99fb..2a05e9c 100644 --- a/src/components/map/UserLocation.tsx +++ b/src/components/map/UserLocation.tsx @@ -20,7 +20,9 @@ const UserLocation: React.FC = ({ navermaps }) => { {error ? ( ) : ( <> diff --git a/src/components/navBar/NavBar.tsx b/src/components/navBar/NavBar.tsx index 7a2399c..51d6e5c 100644 --- a/src/components/navBar/NavBar.tsx +++ b/src/components/navBar/NavBar.tsx @@ -7,7 +7,7 @@ import NavItemWrapper from '~/components/navBar/NavItemWrapper'; import { useLocation } from 'react-router-dom'; interface NavBarProps { - handleSearchVisible: () => void; + handleSearchVisible?: () => void; } const Nav = styled.nav` diff --git a/src/hooks/api/restaurants/useGetDetailRestaurants.ts b/src/hooks/api/restaurants/useGetDetailRestaurants.ts new file mode 100644 index 0000000..9b4da0e --- /dev/null +++ b/src/hooks/api/restaurants/useGetDetailRestaurants.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; +import { get } from '~/libs/api'; +import { RestaurantDetail } from '~/types/restaurants'; + +const useGetDetailRestaurants = (id: number) => { + return useQuery({ + queryKey: ['restaurants', id], + queryFn: () => get(`/restaurants/${id}/detail`), + enabled: !!id, + }); +}; + +export default useGetDetailRestaurants; diff --git a/src/libs/api.ts b/src/libs/api.ts index b6fe9ac..13b1ae0 100644 --- a/src/libs/api.ts +++ b/src/libs/api.ts @@ -1,16 +1,29 @@ -import axios, { AxiosError, type AxiosResponse } from 'axios'; +import axios, { + AxiosError, + InternalAxiosRequestConfig, + type AxiosResponse, +} from 'axios'; const baseURL = import.meta.env.VITE_SERVER_URL; -const token = localStorage.getItem('access_token'); const instance = axios.create({ baseURL, timeout: 15000, - headers: { - Authorization: `Bearer ${token}`, - }, withCredentials: true, }); +instance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('access_token'); + if (token && config.headers) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + }, +); + const interceptorResponseFulfilled = (res: AxiosResponse) => { if (200 <= res.status && res.status < 300) { return res.data; @@ -19,7 +32,37 @@ const interceptorResponseFulfilled = (res: AxiosResponse) => { return Promise.reject(res.data); }; -const interceptorResponseRejected = (error: AxiosError) => { +const interceptorResponseRejected = async (error: AxiosError) => { + const originalRequest = error.config as InternalAxiosRequestConfig & { + _retry?: boolean; + }; + + if ( + originalRequest && + error.response?.status === 401 && + !originalRequest._retry + ) { + originalRequest._retry = true; + try { + const refreshToken = localStorage.getItem('refresh_token'); + if (!refreshToken) { + throw new Error('No refresh token available'); + } + const response = await axios.post(`${baseURL}refresh`, { + token: refreshToken, + }); + + const newAccessToken = response.data.access_token; + localStorage.setItem('access_token', newAccessToken); + + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + return instance(originalRequest); + } catch (refreshError) { + return Promise.reject(refreshError); + } + } + return Promise.reject(error); }; diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index c2e22e4..236c609 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -159,11 +159,7 @@ const FriendPage = () => { ))} - + ); }; From ea706dc2241268503b075402da767059bec08fd3 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 17:35:29 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20api=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20URL=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../restaurantSummary/RestaurantImgBox.tsx | 21 ++++++++++--------- .../restaurantSummary/RestaurantSummary.tsx | 12 ++++++----- src/libs/api.ts | 3 +-- src/types/restaurants.d.ts | 20 ++++++++++++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/components/bottomSheet/restaurantSummary/RestaurantImgBox.tsx b/src/components/bottomSheet/restaurantSummary/RestaurantImgBox.tsx index 90ec454..4b0820b 100644 --- a/src/components/bottomSheet/restaurantSummary/RestaurantImgBox.tsx +++ b/src/components/bottomSheet/restaurantSummary/RestaurantImgBox.tsx @@ -2,17 +2,18 @@ import { styled } from 'styled-components'; -const ImgWrapper = styled.div` - width: 68px; - height: 68px; -`; +interface ImgProps { + imgUrl: string; +} -const RestaurantImgBox = () => { - return ( - - restaurantImg - - ); +const RestaurantImgBox: React.FC = ({ imgUrl }) => { + return restaurantImg; }; export default RestaurantImgBox; + +const Img = styled.img` + width: 68px; + height: 68px; + object-fit: cover; +`; diff --git a/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx b/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx index 24c440f..f277dba 100644 --- a/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx +++ b/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx @@ -5,7 +5,8 @@ import StarRating from '~/components/bottomSheet/restaurantSummary/StarRating'; import { Restaurant } from '~/types/restaurants'; interface RestaurantSummaryProps { - restaurant: Restaurant; // Restaurant 타입 사용 + restaurant: Restaurant; + moreButtonClick: () => void; } const SummaryWrapper = styled.div` @@ -46,17 +47,18 @@ const Category = styled.p` const RestaurantSummary: React.FC = ({ restaurant, + moreButtonClick, }) => { + console.log(restaurant); return ( - + {restaurant.name} - {/* TODO: address가 아니라 카테고리로 변경 */} - {restaurant.address} + {restaurant.food_type} - + ); }; diff --git a/src/libs/api.ts b/src/libs/api.ts index 13b1ae0..1e5bf3a 100644 --- a/src/libs/api.ts +++ b/src/libs/api.ts @@ -48,10 +48,9 @@ const interceptorResponseRejected = async (error: AxiosError) => { if (!refreshToken) { throw new Error('No refresh token available'); } - const response = await axios.post(`${baseURL}refresh`, { + const response = await axios.post(`${baseURL}refresh/`, { token: refreshToken, }); - const newAccessToken = response.data.access_token; localStorage.setItem('access_token', newAccessToken); diff --git a/src/types/restaurants.d.ts b/src/types/restaurants.d.ts index d809a13..e3f40db 100644 --- a/src/types/restaurants.d.ts +++ b/src/types/restaurants.d.ts @@ -6,14 +6,30 @@ export interface GeoLocation { } export interface Restaurant extends GeoLocation { + address: string; + food_type: string | null; id: number; + image_url: string; + name: string; - food_type: string | null; rating_average: number | null; rating_naver: number | null; rating_kakao: number | null; rating_google: number | null; - address: string; } export type Restaurants = Reaurant[]; + +export interface Review { + content: string; + data: string; + decommend_count: number; + id: number; + recommend_count: number; + replies_count: number; + user_name: string; +} + +export interface RestaurantDetail extends Restaurant { + reviews: Review[]; +} From 9c7cbfeddb408c499c49b96f55a03defc4b83bf4 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 17:54:10 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=B0=94=ED=85=80=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20=EB=A0=88=EC=8A=A4=ED=86=A0=EB=9E=91=20=EB=94=94?= =?UTF-8?q?=ED=85=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/bottomSheet/BottomSheet.tsx | 37 +++++- .../reaturantDetail/BackButton.tsx | 5 + .../reaturantDetail/PlatformRate.tsx | 66 +++++++++++ .../bottomSheet/reaturantDetail/RatingBox.tsx | 105 ++++++++++++++++++ .../reaturantDetail/RestDetailView.tsx | 72 ++++++++++++ .../restaurantSummary/MoreButton.tsx | 9 +- 6 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 src/components/bottomSheet/reaturantDetail/BackButton.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/PlatformRate.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/RatingBox.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/RestDetailView.tsx diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 6b5c80b..0f50012 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -1,6 +1,9 @@ import { useAtom } from 'jotai'; +import { useState } from 'react'; import styled from 'styled-components'; +import RestDetailView from '~/components/bottomSheet/reaturantDetail/RestDetailView'; import RestaurantSummary from '~/components/bottomSheet/restaurantSummary/RestaurantSummary'; +import useGetDetailRestaurants from '~/hooks/api/restaurants/useGetDetailRestaurants'; import useDraggable from '~/hooks/useDraggable'; import { restaurantAtom } from '~/store/restaurants'; @@ -43,16 +46,40 @@ const BottomSheetContent = styled.div` const BottomSheet: React.FC = ({ onClose }) => { const { translateY, handleMouseDown } = useDraggable(onClose); const [restaurants] = useAtom(restaurantAtom); + const [selectedId, setSelectedId] = useState(null); + const { + data: restaurantDetail, + isLoading, + isError, + } = useGetDetailRestaurants(selectedId || 0); + const moreButtonClick = (id: number) => { + console.log(id); + setSelectedId(id); + }; return ( -
    - {restaurants?.map((restaurant) => ( - - ))} -
+ {selectedId ? ( + <> + {isLoading &&

Loading...

} + {isError &&

Error fetching data

} + {restaurantDetail && ( + + )} + + ) : ( +
    + {restaurants?.map((restaurant) => ( + moreButtonClick(restaurant.id)} + /> + ))} +
+ )}
); diff --git a/src/components/bottomSheet/reaturantDetail/BackButton.tsx b/src/components/bottomSheet/reaturantDetail/BackButton.tsx new file mode 100644 index 0000000..704cb6f --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/BackButton.tsx @@ -0,0 +1,5 @@ +const BackButton = () => { + return
BackButton
; +}; + +export default BackButton; diff --git a/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx new file mode 100644 index 0000000..e3b977e --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx @@ -0,0 +1,66 @@ +import { styled } from 'styled-components'; + +interface PlatformRateProps { + platform: string; + rating: number | string | null; +} + +export const PlatformRate: React.FC = ({ + platform, + rating, +}) => { + if (typeof rating === 'string') { + rating = parseFloat(rating); + } + const score = rating ? rating : 0; + return ( + + {platform} + + {score.toFixed(1)} + + + + + ); +}; + +const Wrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const PlatformName = styled.div` + font-size: 14px; + font-weight: ${({ theme }) => theme.fontWeights.Regular}; + width: 40px; + text-align: center; + flex-shrink: 0; +`; + +const ColDevider = styled.div` + width: 1px; + height: 12px; + background-color: ${({ theme }) => theme.colors.whitegray}; +`; + +const ScoreText = styled.div` + font-size: 14px; + font-weight: ${({ theme }) => theme.fontWeights.Regular}; + flex-shrink: 0; +`; + +const BackgroundBar = styled.div` + display: flex; + width: 100%; + height: 3px; + background-color: ${({ theme }) => theme.colors.whitegray}; + border-radius: 3px; +`; + +const FilledBar = styled.div<{ filledPercentage: number }>` + width: ${({ filledPercentage }) => filledPercentage}%; + background-color: ${({ theme }) => theme.colors.orange}; + border-radius: 3px; +`; diff --git a/src/components/bottomSheet/reaturantDetail/RatingBox.tsx b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx new file mode 100644 index 0000000..ae00c82 --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { styled } from 'styled-components'; +import { PlatformRate } from '~/components/bottomSheet/reaturantDetail/PlatformRate'; + +interface RatingBoxProps { + rating_average: number | null; + rating_google: number | null; + rating_kakao: number | null; + rating_naver: number | null; +} + +const RatingBox: React.FC = ({ + rating_average, + rating_google, + rating_kakao, + rating_naver, +}) => { + return ( + <> + 평점 + +
+ {rating_average?.toFixed(1)} + +
+ + + + + +
+ + ); +}; + +export default RatingBox; + +const Title = styled.h4` + display: inline-block; + margin-top: 40px; + font-size: 16px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +`; + +const RateWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + margin-top: 8px; +`; + +const RateNumber = styled.div` + font-size: 40px; + font-weight: ${({ theme }) => theme.fontWeights.ExtraBold}; +`; + +const Rate = () => ( + + + + + + + + + + + + +); + +const PlatformRateWrapper = styled.div` + flex: 1; +`; diff --git a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx new file mode 100644 index 0000000..9616077 --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx @@ -0,0 +1,72 @@ +import { styled } from 'styled-components'; +import BackButton from '~/components/bottomSheet/reaturantDetail/BackButton'; +import RatingBox from '~/components/bottomSheet/reaturantDetail/RatingBox'; +import RestaurantImgBox from '~/components/bottomSheet/restaurantSummary/RestaurantImgBox'; +import StarRating from '~/components/bottomSheet/restaurantSummary/StarRating'; +import { RestaurantDetail } from '~/types/restaurants'; + +interface RestaurantDetailProps { + restaurantDetail: RestaurantDetail; +} + +const RestDetailView: React.FC = ({ + restaurantDetail, +}) => { + console.log(restaurantDetail); + return ( +
+ + + + + + {restaurantDetail.name} + {restaurantDetail.food_type} + + + + +
+ +
+
+ ); +}; + +export default RestDetailView; + +const SummaryWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + gap: 8px; +`; + +const SummaryInfo = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1; + max-width: calc(100% - 16px - 16px - 50px - 68px); +`; + +const Title = styled.h3` + display: inline-block; + font-size: 16px; + font-weight: ${({ theme }) => theme.fontWeights.Regular}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +`; + +const Category = styled.p` + display: inline-block; + margin-top: 6px; + font-size: 14px; + font-weight: ${({ theme }) => theme.fontWeights.Light}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +`; diff --git a/src/components/bottomSheet/restaurantSummary/MoreButton.tsx b/src/components/bottomSheet/restaurantSummary/MoreButton.tsx index 3c15dd7..4cbbd29 100644 --- a/src/components/bottomSheet/restaurantSummary/MoreButton.tsx +++ b/src/components/bottomSheet/restaurantSummary/MoreButton.tsx @@ -1,6 +1,10 @@ import { styled } from 'styled-components'; import MoreIcon from '~/assets/icons/MoreIcon'; +interface MoreButtonProps { + moreButtonClick: () => void; +} + const Button = styled.button` flex-shrink: 0; width: 50px; @@ -31,10 +35,9 @@ const Button = styled.button` } `; -const MoreButton = () => { +const MoreButton: React.FC = ({ moreButtonClick }) => { const handleClick = () => { - // TODO: 더보기 버튼 클릭 시 상세 정보 표시 - console.log('MoreButton'); + moreButtonClick(); }; return ( From 8d83e78603d6735981c7a12673b239c1dfcca1e4 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 18:31:19 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=ED=95=9C=EC=A4=84=ED=8F=89=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reaturantDetail/PlatformRate.tsx | 1 + .../bottomSheet/reaturantDetail/RatingBox.tsx | 2 +- .../reaturantDetail/RestDetailView.tsx | 13 +++- .../reaturantDetail/ReviewContent.tsx | 74 +++++++++++++++++++ .../bottomSheet/reaturantDetail/Reviews.tsx | 28 +++++++ src/libs/api.ts | 4 +- src/types/restaurants.d.ts | 2 +- 7 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 src/components/bottomSheet/reaturantDetail/ReviewContent.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/Reviews.tsx diff --git a/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx index e3b977e..d05cbc0 100644 --- a/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx +++ b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx @@ -29,6 +29,7 @@ const Wrapper = styled.div` display: flex; align-items: center; gap: 4px; + margin-bottom: 8px; `; const PlatformName = styled.div` diff --git a/src/components/bottomSheet/reaturantDetail/RatingBox.tsx b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx index ae00c82..41d55dd 100644 --- a/src/components/bottomSheet/reaturantDetail/RatingBox.tsx +++ b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx @@ -37,7 +37,7 @@ export default RatingBox; const Title = styled.h4` display: inline-block; - margin-top: 40px; + margin-top: 24px; font-size: 16px; font-weight: ${({ theme }) => theme.fontWeights.Bold}; white-space: nowrap; diff --git a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx index 9616077..aa223aa 100644 --- a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx +++ b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx @@ -1,6 +1,7 @@ import { styled } from 'styled-components'; import BackButton from '~/components/bottomSheet/reaturantDetail/BackButton'; import RatingBox from '~/components/bottomSheet/reaturantDetail/RatingBox'; +import Reviews from '~/components/bottomSheet/reaturantDetail/Reviews'; import RestaurantImgBox from '~/components/bottomSheet/restaurantSummary/RestaurantImgBox'; import StarRating from '~/components/bottomSheet/restaurantSummary/StarRating'; import { RestaurantDetail } from '~/types/restaurants'; @@ -26,9 +27,9 @@ const RestDetailView: React.FC = ({ -
- -
+ + + ); }; @@ -70,3 +71,9 @@ const Category = styled.p` text-overflow: ellipsis; max-width: 100%; `; + +const Divider = styled.div` + margin: 14px; + height: 1px; + background-color: ${({ theme }) => theme.colors.whitegray}; +`; diff --git a/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx new file mode 100644 index 0000000..84d990e --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx @@ -0,0 +1,74 @@ +import { styled } from 'styled-components'; +import { Review } from '~/types/restaurants'; + +interface ReviewContentProps { + review: Review; +} + +const formatDate = (date: string) => { + const dateObj = new Date(date); + const year = dateObj.getFullYear(); + const month = String(dateObj.getMonth() + 1).padStart(2, '0'); + const day = String(dateObj.getDate()).padStart(2, '0'); + return `${year}.${month}.${day}`; +}; + +const ReviewContent: React.FC = ({ review }) => { + console.log(review); + return ( + +
+ {review.content} + + {review.user_name} / {formatDate(review.date)} + + 답글 보기 ({review.replies_count}) +
+
+
+ GOOD + {review.recommend_count} +
+
+ BAD + {review.decommend_count} +
+
+
+ ); +}; + +export default ReviewContent; + +const ReviewBox = styled.div` + display: flex; + justify-content: space-between; + gap: 8px; + margin-top: 8px; + padding: 16px; + border-radius: 4px; + background-color: ${({ theme }) => theme.colors.whitegray}; +`; + +const ContentText = styled.div` + color: ${({ theme }) => theme.colors.gray}; + font-size: 14px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; +`; + +const SubText = styled.div` + color: ${({ theme }) => theme.colors.gray}; + font-size: 12px; + font-weight: ${({ theme }) => theme.fontWeights.Light}; +`; + +const LookReply = styled.button` + display: inline-block; + margin-top: 8px; + font-size: 12px; + font-weight: ${({ theme }) => theme.fontWeights.Light}; + color: ${({ theme }) => theme.colors.gray}; + background-color: transparent; + border: none; + cursor: pointer; +`; diff --git a/src/components/bottomSheet/reaturantDetail/Reviews.tsx b/src/components/bottomSheet/reaturantDetail/Reviews.tsx new file mode 100644 index 0000000..88acbff --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/Reviews.tsx @@ -0,0 +1,28 @@ +import { styled } from 'styled-components'; +import ReviewContent from '~/components/bottomSheet/reaturantDetail/ReviewContent'; +import { Review } from '~/types/restaurants'; + +interface ReviewsProps { + reviews: Review[]; +} + +const Reviews: React.FC = ({ reviews }) => { + return ( + <> + 한줄평 + {reviews.map((review) => ( + + ))} + + ); +}; + +export default Reviews; + +const Title = styled.h4` + display: inline-block; + margin-top: 24px; + font-size: 16px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; + max-width: 100%; +`; diff --git a/src/libs/api.ts b/src/libs/api.ts index 1e5bf3a..ef6ab38 100644 --- a/src/libs/api.ts +++ b/src/libs/api.ts @@ -49,9 +49,9 @@ const interceptorResponseRejected = async (error: AxiosError) => { throw new Error('No refresh token available'); } const response = await axios.post(`${baseURL}refresh/`, { - token: refreshToken, + refresh: refreshToken, }); - const newAccessToken = response.data.access_token; + const newAccessToken = response.data.access; localStorage.setItem('access_token', newAccessToken); originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; diff --git a/src/types/restaurants.d.ts b/src/types/restaurants.d.ts index e3f40db..f807b51 100644 --- a/src/types/restaurants.d.ts +++ b/src/types/restaurants.d.ts @@ -22,7 +22,7 @@ export type Restaurants = Reaurant[]; export interface Review { content: string; - data: string; + date: string; decommend_count: number; id: number; recommend_count: number; From d52d9099210bde3327df63bd92bc3dbc1376b2bb Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 19:43:29 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=ED=95=9C=EC=A4=84=ED=8F=89=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/WriteIcon.tsx | 18 ++ src/components/bottomSheet/BottomSheet.tsx | 1 - .../reaturantDetail/PlatformRate.tsx | 6 +- .../reaturantDetail/RestDetailView.tsx | 6 +- .../reaturantDetail/ReviewContent.tsx | 53 ++++-- .../reaturantDetail/ReviewWrite.tsx | 156 ++++++++++++++++++ .../bottomSheet/reaturantDetail/Reviews.tsx | 5 +- .../restaurantSummary/RestaurantSummary.tsx | 1 - src/hooks/api/restaurants/usePostReview.ts | 20 +++ 9 files changed, 247 insertions(+), 19 deletions(-) create mode 100644 src/assets/icons/WriteIcon.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/ReviewWrite.tsx create mode 100644 src/hooks/api/restaurants/usePostReview.ts diff --git a/src/assets/icons/WriteIcon.tsx b/src/assets/icons/WriteIcon.tsx new file mode 100644 index 0000000..e087a71 --- /dev/null +++ b/src/assets/icons/WriteIcon.tsx @@ -0,0 +1,18 @@ +const WriteIcon = () => { + return ( + + + + ); +}; + +export default WriteIcon; diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 0f50012..d8c50e9 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -53,7 +53,6 @@ const BottomSheet: React.FC = ({ onClose }) => { isError, } = useGetDetailRestaurants(selectedId || 0); const moreButtonClick = (id: number) => { - console.log(id); setSelectedId(id); }; diff --git a/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx index d05cbc0..44eba6a 100644 --- a/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx +++ b/src/components/bottomSheet/reaturantDetail/PlatformRate.tsx @@ -19,7 +19,7 @@ export const PlatformRate: React.FC = ({ {score.toFixed(1)} - + ); @@ -60,8 +60,8 @@ const BackgroundBar = styled.div` border-radius: 3px; `; -const FilledBar = styled.div<{ filledPercentage: number }>` - width: ${({ filledPercentage }) => filledPercentage}%; +const FilledBar = styled.div<{ $filledPercentage: number }>` + width: ${({ $filledPercentage }) => $filledPercentage}%; background-color: ${({ theme }) => theme.colors.orange}; border-radius: 3px; `; diff --git a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx index aa223aa..e05f119 100644 --- a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx +++ b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx @@ -13,7 +13,6 @@ interface RestaurantDetailProps { const RestDetailView: React.FC = ({ restaurantDetail, }) => { - console.log(restaurantDetail); return (
@@ -29,7 +28,10 @@ const RestDetailView: React.FC = ({ - +
); }; diff --git a/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx index 84d990e..13b3154 100644 --- a/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx +++ b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx @@ -13,8 +13,14 @@ const formatDate = (date: string) => { return `${year}.${month}.${day}`; }; +const countFormat = (count: number) => { + if (count >= 1000) { + return `${(count / 1000).toFixed(1)}k`; + } + return count; +}; + const ReviewContent: React.FC = ({ review }) => { - console.log(review); return (
@@ -24,16 +30,16 @@ const ReviewContent: React.FC = ({ review }) => { 답글 보기 ({review.replies_count})
-
-
- GOOD - {review.recommend_count} -
-
- BAD - {review.decommend_count} -
-
+ + +
GOOD
+
{countFormat(review.recommend_count)}
+
+ +
BAD
+
{countFormat(review.decommend_count)}
+
+
); }; @@ -72,3 +78,28 @@ const LookReply = styled.button` border: none; cursor: pointer; `; + +const RecommendBox = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 4px 0; +`; + +const Recommend = styled.button` + display: flex; + justify-content: space-between; + gap: 20px; + color: ${({ theme }) => theme.colors.orange}; + border: none; + cursor: pointer; +`; + +const Decommend = styled.button` + display: flex; + justify-content: space-between; + gap: 20px; + color: ${({ theme }) => theme.colors.gray}; + border: none; + cursor: pointer; +`; diff --git a/src/components/bottomSheet/reaturantDetail/ReviewWrite.tsx b/src/components/bottomSheet/reaturantDetail/ReviewWrite.tsx new file mode 100644 index 0000000..51c6ec1 --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/ReviewWrite.tsx @@ -0,0 +1,156 @@ +import { useEffect, useRef, useState } from 'react'; +import { styled } from 'styled-components'; +import WriteIcon from '~/assets/icons/WriteIcon'; +import usePostReview from '~/hooks/api/restaurants/usePostReview'; + +interface ReviewWriteProps { + restaurentId: number; +} + +const ReviewWrite: React.FC = ({ restaurentId }) => { + const [isOpen, setIsOpen] = useState(false); + const [content, setContent] = useState(''); + const containerRef = useRef(null); + const inputRef = useRef(null); + const { refetch } = usePostReview(restaurentId, { content }); + + const toggleOpen = () => { + setIsOpen(!isOpen); + if (!isOpen) { + setTimeout(() => { + inputRef.current?.focus(); + }, 300); + } + }; + + const handleClickOutside = (event: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const handleSubmit = () => { + if (!content) { + return; + } + refetch(); + setContent(''); + setIsOpen(false); + }; + + return ( + + {isOpen ? ( + + setContent(e.target.value)} + placeholder="한줄평을 작성해주세요." + onClick={(e) => e.stopPropagation()} // input 클릭 시 이벤트 전파 막기 + /> + + + SUBMIT + + + ) : ( + + + WRITE + + )} + + ); +}; + +export default ReviewWrite; + +const WriteButton = styled.div<{ $isOpen: boolean }>` + z-index: 200; + position: fixed; + bottom: 20px; + right: 20px; + width: ${({ $isOpen }) => ($isOpen ? '90%' : '60px')}; + height: ${({ $isOpen }) => ($isOpen ? '80px' : '60px')}; + border-radius: 6px; + background-color: ${({ theme }) => theme.colors.white}; + + color: ${({ $isOpen, theme }) => + $isOpen ? theme.colors.white : theme.colors.orange}; + font-size: ${({ $isOpen }) => ($isOpen ? '16px' : '18px')}; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease-in-out; + overflow: hidden; + box-shadow: 0 5px 8px rgba(0, 0, 0, 0.1); +`; + +const InputContainer = styled.form<{ $isOpen: boolean }>` + display: ${({ $isOpen }) => ($isOpen ? 'flex' : 'none')}; + align-items: center; + gap: 8px; + padding: 10px; + width: 100%; + height: 100%; + box-sizing: border-box; +`; + +const TextInput = styled.input` + width: 100%; + padding: 10px; + font-size: 14px; + border: none; + border-radius: 8px; + outline: none; +`; + +const SubmitButton = styled.button` + width: 60px; + height: 60px; + padding: 4px; + flex-shrink: 0; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + background-color: ${({ theme }) => theme.colors.orange}; + color: ${({ theme }) => theme.colors.white}; + svg > path { + fill: ${({ theme }) => theme.colors.white}; + } + font-size: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + box-shadow: 0 5px 8px rgba(0, 0, 0, 0.1); +`; + +const WriteStart = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + + svg > path { + fill: ${({ theme }) => theme.colors.orange}; + } + + font-size: 10px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; +`; diff --git a/src/components/bottomSheet/reaturantDetail/Reviews.tsx b/src/components/bottomSheet/reaturantDetail/Reviews.tsx index 88acbff..5c217cb 100644 --- a/src/components/bottomSheet/reaturantDetail/Reviews.tsx +++ b/src/components/bottomSheet/reaturantDetail/Reviews.tsx @@ -1,18 +1,21 @@ import { styled } from 'styled-components'; import ReviewContent from '~/components/bottomSheet/reaturantDetail/ReviewContent'; +import ReviewWrite from '~/components/bottomSheet/reaturantDetail/ReviewWrite'; import { Review } from '~/types/restaurants'; interface ReviewsProps { reviews: Review[]; + restaurentId: number; } -const Reviews: React.FC = ({ reviews }) => { +const Reviews: React.FC = ({ reviews, restaurentId }) => { return ( <> 한줄평 {reviews.map((review) => ( ))} + ); }; diff --git a/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx b/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx index f277dba..2c397fc 100644 --- a/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx +++ b/src/components/bottomSheet/restaurantSummary/RestaurantSummary.tsx @@ -49,7 +49,6 @@ const RestaurantSummary: React.FC = ({ restaurant, moreButtonClick, }) => { - console.log(restaurant); return ( diff --git a/src/hooks/api/restaurants/usePostReview.ts b/src/hooks/api/restaurants/usePostReview.ts new file mode 100644 index 0000000..39655ff --- /dev/null +++ b/src/hooks/api/restaurants/usePostReview.ts @@ -0,0 +1,20 @@ +import { useQuery } from '@tanstack/react-query'; +import { post } from '~/libs/api'; +import { Review } from '~/types/restaurants'; + +type Request = { + content: string; +}; + +export const postReviewQueryKey = (request: Request) => ['review', request]; + +const usePostReview = (restaurantId: number, request: Request) => { + return useQuery({ + queryKey: postReviewQueryKey(request), + queryFn: () => + post(`/restaurants/${restaurantId}/reviews/`, request), + enabled: false, + }); +}; + +export default usePostReview; From 42173e078c36c1bc5d04e7c23b4974aafe83eab8 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 22:51:44 +0900 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20axios=20=EB=A6=AC=ED=94=84?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=9E=91=EC=97=85,?= =?UTF-8?q?=20API=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=96=91=EC=8B=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottomSheet/reaturantDetail/RatingBox.tsx | 10 +-- .../restaurantSummary/StarRating.tsx | 11 ++- src/components/search/SmallStarRating.tsx | 6 +- src/libs/axios.tsx | 72 +++++++++++++++++-- src/types/restaurants.d.ts | 8 +-- 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/components/bottomSheet/reaturantDetail/RatingBox.tsx b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx index 41d55dd..88e6664 100644 --- a/src/components/bottomSheet/reaturantDetail/RatingBox.tsx +++ b/src/components/bottomSheet/reaturantDetail/RatingBox.tsx @@ -3,10 +3,10 @@ import { styled } from 'styled-components'; import { PlatformRate } from '~/components/bottomSheet/reaturantDetail/PlatformRate'; interface RatingBoxProps { - rating_average: number | null; - rating_google: number | null; - rating_kakao: number | null; - rating_naver: number | null; + rating_average: string; + rating_google: string; + rating_kakao: string; + rating_naver: string; } const RatingBox: React.FC = ({ @@ -20,7 +20,7 @@ const RatingBox: React.FC = ({ 평점
- {rating_average?.toFixed(1)} + {Number(rating_average).toFixed(1)}
diff --git a/src/components/bottomSheet/restaurantSummary/StarRating.tsx b/src/components/bottomSheet/restaurantSummary/StarRating.tsx index 88e105e..b36d544 100644 --- a/src/components/bottomSheet/restaurantSummary/StarRating.tsx +++ b/src/components/bottomSheet/restaurantSummary/StarRating.tsx @@ -4,7 +4,7 @@ import HalfStar from '~/assets/ratingStar/HalfStar'; import VoidStar from '~/assets/ratingStar/VoidStar'; type StarRatingProps = { - rating: number | null; // 5.0 만점 기준의 평점 + rating: string; // 5.0 만점 기준의 평점 }; const StarWrapper = styled.div` @@ -13,12 +13,9 @@ const StarWrapper = styled.div` `; const StarRating: React.FC = ({ rating }) => { - if (rating === null) { - rating = 0; - } - - const fullStars = Math.floor(rating); // 정수 부분의 개수 (FullStar 개수) - const halfStar = rating % 1 >= 0.5; // 0.5점 이상일 경우 HalfStar 사용 + const ratingNumber = Number(rating); + const fullStars = Math.floor(ratingNumber); // 정수 부분의 개수 (FullStar 개수) + const halfStar = ratingNumber % 1 >= 0.5; // 0.5점 이상일 경우 HalfStar 사용 const voidStars = 5 - fullStars - (halfStar ? 1 : 0); // 나머지 VoidStar 개수 return ( diff --git a/src/components/search/SmallStarRating.tsx b/src/components/search/SmallStarRating.tsx index 9cf7480..f176ebb 100644 --- a/src/components/search/SmallStarRating.tsx +++ b/src/components/search/SmallStarRating.tsx @@ -2,7 +2,7 @@ import { styled } from 'styled-components'; import SmallStar from '~/assets/icons/SmallStar'; type SmallRatingProps = { - rating: number | null; + rating: string | null; }; const RatingWrapper = styled.div` @@ -16,11 +16,11 @@ const RatingText = styled.div` `; const SmallStarRating: React.FC = ({ rating }) => { - rating = rating === null ? 0 : rating; + const ratingNumber = Number(rating); return ( - {rating.toFixed(1)} / 5.0 + {ratingNumber.toFixed(1)} / 5.0 ); }; diff --git a/src/libs/axios.tsx b/src/libs/axios.tsx index 04964e4..29f52f6 100644 --- a/src/libs/axios.tsx +++ b/src/libs/axios.tsx @@ -1,13 +1,73 @@ -import axios from 'axios'; +import axios, { + AxiosError, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; -const token = localStorage.getItem('access_token'); +const baseURL = import.meta.env.VITE_SERVER_URL; const instance = axios.create({ - baseURL: 'https://43.203.225.31.nip.io', - headers: { - Authorization: `Bearer ${token}`, - }, + baseURL, withCredentials: true, }); +instance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('access_token'); + if (token && config.headers) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + }, +); + +const interceptorResponseFulfilled = (res: AxiosResponse) => { + if (200 <= res.status && res.status < 300) { + return res.data; + } + + return Promise.reject(res.data); +}; + +const interceptorResponseRejected = async (error: AxiosError) => { + const originalRequest = error.config as InternalAxiosRequestConfig & { + _retry?: boolean; + }; + + if ( + originalRequest && + error.response?.status === 401 && + !originalRequest._retry + ) { + originalRequest._retry = true; + try { + const refreshToken = localStorage.getItem('refresh_token'); + if (!refreshToken) { + throw new Error('No refresh token available'); + } + const response = await axios.post(`${baseURL}refresh/`, { + refresh: refreshToken, + }); + const newAccessToken = response.data.access; + localStorage.setItem('access_token', newAccessToken); + + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + return instance(originalRequest); + } catch (refreshError) { + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); +}; + +instance.interceptors.response.use( + interceptorResponseFulfilled, + interceptorResponseRejected, +); + export default instance; diff --git a/src/types/restaurants.d.ts b/src/types/restaurants.d.ts index f807b51..dad1f63 100644 --- a/src/types/restaurants.d.ts +++ b/src/types/restaurants.d.ts @@ -12,10 +12,10 @@ export interface Restaurant extends GeoLocation { image_url: string; name: string; - rating_average: number | null; - rating_naver: number | null; - rating_kakao: number | null; - rating_google: number | null; + rating_average: string; + rating_naver: string; + rating_kakao: string; + rating_google: string; } export type Restaurants = Reaurant[]; From b8477858bdca5ee43cce667f18253983a1cb27f7 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Tue, 13 Aug 2024 23:07:00 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=84=A0=ED=83=9D=ED=95=98=EB=A9=B4=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20=EC=97=85=EC=B2=B4=20=EB=94=94=ED=85=8C=EC=9D=BC?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=08=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/bottomSheet/BottomSheet.tsx | 5 ++--- src/components/search/SearchResultLine.tsx | 9 ++++++++- src/pages/HomePage.tsx | 13 +++++++++++-- src/store/restaurants.ts | 2 ++ src/utils/README.md | 1 - 5 files changed, 23 insertions(+), 7 deletions(-) delete mode 100644 src/utils/README.md diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index d8c50e9..3cf9959 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -1,11 +1,10 @@ import { useAtom } from 'jotai'; -import { useState } from 'react'; import styled from 'styled-components'; import RestDetailView from '~/components/bottomSheet/reaturantDetail/RestDetailView'; import RestaurantSummary from '~/components/bottomSheet/restaurantSummary/RestaurantSummary'; import useGetDetailRestaurants from '~/hooks/api/restaurants/useGetDetailRestaurants'; import useDraggable from '~/hooks/useDraggable'; -import { restaurantAtom } from '~/store/restaurants'; +import { restaurantAtom, selectedRestaurantId } from '~/store/restaurants'; type BottomSheetProps = { onClose: () => void; @@ -46,7 +45,7 @@ const BottomSheetContent = styled.div` const BottomSheet: React.FC = ({ onClose }) => { const { translateY, handleMouseDown } = useDraggable(onClose); const [restaurants] = useAtom(restaurantAtom); - const [selectedId, setSelectedId] = useState(null); + const [selectedId, setSelectedId] = useAtom(selectedRestaurantId); const { data: restaurantDetail, isLoading, diff --git a/src/components/search/SearchResultLine.tsx b/src/components/search/SearchResultLine.tsx index 76fc835..722c5d9 100644 --- a/src/components/search/SearchResultLine.tsx +++ b/src/components/search/SearchResultLine.tsx @@ -1,6 +1,8 @@ +import { useSetAtom } from 'jotai'; import { styled } from 'styled-components'; import { Divider } from '~/components/search/Divider'; import SmallStarRating from '~/components/search/SmallStarRating'; +import { selectedRestaurantId } from '~/store/restaurants'; import { Restaurant } from '~/types/restaurants'; const Title = styled.h3` @@ -50,9 +52,14 @@ const Info = styled.div` `; const SearchResultLine: React.FC = (restaurant) => { + const setSelectedId = useSetAtom(selectedRestaurantId); + + const searchClick = () => { + setSelectedId(restaurant.id); + }; return ( <> - + {restaurant.name}
{restaurant.address}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 4746bdd..9bfca0f 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -6,13 +6,14 @@ import { useLocation } from 'react-router-dom'; import NavBar from '~/components/navBar/NavBar'; import BottomSheet from '~/components/bottomSheet/BottomSheet'; import useGetRestaurants from '~/hooks/api/useGetRestaurants'; -import { useSetAtom } from 'jotai'; -import { restaurantAtom } from '~/store/restaurants'; +import { useAtom, useSetAtom } from 'jotai'; +import { restaurantAtom, selectedRestaurantId } from '~/store/restaurants'; import Head from '~/components/common/Head'; const HomePage = () => { const location = useLocation(); const setRestaurants = useSetAtom(restaurantAtom); + const [selectedId] = useAtom(selectedRestaurantId); const { data } = useGetRestaurants(); const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false); @@ -20,6 +21,14 @@ const HomePage = () => { location.pathname === '/search', ); + useEffect(() => { + if (selectedId !== null) { + setIsBottomSheetVisible(true); + setSearchVisible(false); + window.history.pushState(null, '', '/'); + } + }, [selectedId]); + useEffect(() => { if (data) { setRestaurants(data); diff --git a/src/store/restaurants.ts b/src/store/restaurants.ts index 0d1513e..bffb7ae 100644 --- a/src/store/restaurants.ts +++ b/src/store/restaurants.ts @@ -6,3 +6,5 @@ export const restaurantAtom = atom([]); // 검색 결과를 저장하는 atom export const searchRestaurantAtom = atom([]); + +export const selectedRestaurantId = atom(null); diff --git a/src/utils/README.md b/src/utils/README.md deleted file mode 100644 index 57cbc02..0000000 --- a/src/utils/README.md +++ /dev/null @@ -1 +0,0 @@ -자주 사용하는 유틸리티 함수 저장 \ No newline at end of file From 83de678c3dd3638e17fb805259b9f8cb16102659 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Wed, 14 Aug 2024 00:16:46 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/BackwordIcon.tsx | 21 ++++++ src/assets/icons/ListIcon.tsx | 21 ++++++ .../reaturantDetail/BackButton.tsx | 46 ++++++++++++- .../reaturantDetail/ListButton.tsx | 68 +++++++++++++++++++ .../reaturantDetail/RestDetailView.tsx | 5 +- .../bottomSheet/reaturantDetail/Reviews.tsx | 7 +- .../restaurantSummary/StarRating.tsx | 4 +- .../api/restaurants/usePostRestarurant.ts | 17 +++++ 8 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 src/assets/icons/BackwordIcon.tsx create mode 100644 src/assets/icons/ListIcon.tsx create mode 100644 src/components/bottomSheet/reaturantDetail/ListButton.tsx create mode 100644 src/hooks/api/restaurants/usePostRestarurant.ts diff --git a/src/assets/icons/BackwordIcon.tsx b/src/assets/icons/BackwordIcon.tsx new file mode 100644 index 0000000..6ed5a5c --- /dev/null +++ b/src/assets/icons/BackwordIcon.tsx @@ -0,0 +1,21 @@ +const BackwordIcon = () => { + return ( + + + + ); +}; + +export default BackwordIcon; diff --git a/src/assets/icons/ListIcon.tsx b/src/assets/icons/ListIcon.tsx new file mode 100644 index 0000000..c47dd01 --- /dev/null +++ b/src/assets/icons/ListIcon.tsx @@ -0,0 +1,21 @@ +const ListIcon = () => { + return ( + + + + ); +}; + +export default ListIcon; diff --git a/src/components/bottomSheet/reaturantDetail/BackButton.tsx b/src/components/bottomSheet/reaturantDetail/BackButton.tsx index 704cb6f..1282ffd 100644 --- a/src/components/bottomSheet/reaturantDetail/BackButton.tsx +++ b/src/components/bottomSheet/reaturantDetail/BackButton.tsx @@ -1,5 +1,49 @@ +import { useSetAtom } from 'jotai'; +import styled from 'styled-components'; +import BackwordIcon from '~/assets/icons/BackwordIcon'; +import { selectedRestaurantId } from '~/store/restaurants'; + const BackButton = () => { - return
BackButton
; + const setSelectedId = useSetAtom(selectedRestaurantId); + const clickHandler = () => { + setSelectedId(null); + }; + + return ( + + ); }; export default BackButton; + +const Button = styled.button` + position: absolute; + top: -30px; + width: 24px; + height: 24px; + margin-bottom: 8px; + background-color: ${({ theme }) => theme.colors.white}; + border: none; + font-size: 10px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; + text-align: center; + border-radius: 4px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + cursor: pointer; + & svg > path { + stroke: ${({ theme }) => theme.colors.black}; + } + + &:hover { + background-color: ${({ theme }) => theme.colors.whitegray}; + & svg > path { + stroke: ${({ theme }) => theme.colors.gray}; + } + } +`; diff --git a/src/components/bottomSheet/reaturantDetail/ListButton.tsx b/src/components/bottomSheet/reaturantDetail/ListButton.tsx new file mode 100644 index 0000000..65b7c2a --- /dev/null +++ b/src/components/bottomSheet/reaturantDetail/ListButton.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react'; +import { styled } from 'styled-components'; +import ListIcon from '~/assets/icons/ListIcon'; +import XIcon from '~/assets/icons/XIcon'; +import usePostRestaurant from '~/hooks/api/restaurants/usePostRestarurant'; +interface ListButtonProps { + restaurantId: number; +} + +const ListButton: React.FC = ({ restaurantId }) => { + const [isVisible, setIsVisible] = useState(true); + const { refetch } = usePostRestaurant(restaurantId); + + const addList = (e: React.MouseEvent) => { + e.preventDefault(); + refetch(); + }; + + return ( + + + setIsVisible(false)}> + + + + ); +}; + +export default ListButton; + +const ButtonWrapper = styled.div<{ $isVisible: boolean }>` + display: ${({ $isVisible }) => ($isVisible ? 'flex' : 'none')}; + justify-content: space-between; + align-items: center; + margin-top: 16px; + background-color: ${({ theme }) => theme.colors.orange}; + border-radius: 8px; +`; + +const Button = styled.button` + color: ${({ theme }) => theme.colors.white}; + flex: 1; + height: 60px; + padding: 8px 16px; + font-size: 16px; + border: none; + background: none; + cursor: pointer; + + display: flex; + justify-content: center; + align-items: center; + gap: 20px; +`; + +const CloseButton = styled.button` + border: none; + background: none; + cursor: pointer; + padding: 20px; + + & svg > path { + stroke: ${({ theme }) => theme.colors.white}; + } +`; diff --git a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx index e05f119..cc54948 100644 --- a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx +++ b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx @@ -1,5 +1,6 @@ import { styled } from 'styled-components'; import BackButton from '~/components/bottomSheet/reaturantDetail/BackButton'; +import ListButton from '~/components/bottomSheet/reaturantDetail/ListButton'; import RatingBox from '~/components/bottomSheet/reaturantDetail/RatingBox'; import Reviews from '~/components/bottomSheet/reaturantDetail/Reviews'; import RestaurantImgBox from '~/components/bottomSheet/restaurantSummary/RestaurantImgBox'; @@ -15,8 +16,8 @@ const RestDetailView: React.FC = ({ }) => { return (
- + @@ -26,6 +27,7 @@ const RestDetailView: React.FC = ({ + = ({ export default RestDetailView; const SummaryWrapper = styled.div` + position: relative; display: flex; justify-content: flex-start; align-items: center; diff --git a/src/components/bottomSheet/reaturantDetail/Reviews.tsx b/src/components/bottomSheet/reaturantDetail/Reviews.tsx index 5c217cb..557420e 100644 --- a/src/components/bottomSheet/reaturantDetail/Reviews.tsx +++ b/src/components/bottomSheet/reaturantDetail/Reviews.tsx @@ -12,9 +12,10 @@ const Reviews: React.FC = ({ reviews, restaurentId }) => { return ( <> 한줄평 - {reviews.map((review) => ( - - ))} + {reviews && + reviews.map((review) => ( + + ))} ); diff --git a/src/components/bottomSheet/restaurantSummary/StarRating.tsx b/src/components/bottomSheet/restaurantSummary/StarRating.tsx index b36d544..0e1a55b 100644 --- a/src/components/bottomSheet/restaurantSummary/StarRating.tsx +++ b/src/components/bottomSheet/restaurantSummary/StarRating.tsx @@ -13,7 +13,9 @@ const StarWrapper = styled.div` `; const StarRating: React.FC = ({ rating }) => { - const ratingNumber = Number(rating); + const ratingNumber = + !isNaN(Number(rating)) && Number(rating) >= 0 ? Number(rating) : 0; + const fullStars = Math.floor(ratingNumber); // 정수 부분의 개수 (FullStar 개수) const halfStar = ratingNumber % 1 >= 0.5; // 0.5점 이상일 경우 HalfStar 사용 const voidStars = 5 - fullStars - (halfStar ? 1 : 0); // 나머지 VoidStar 개수 diff --git a/src/hooks/api/restaurants/usePostRestarurant.ts b/src/hooks/api/restaurants/usePostRestarurant.ts new file mode 100644 index 0000000..8b8e035 --- /dev/null +++ b/src/hooks/api/restaurants/usePostRestarurant.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import { post } from '~/libs/api'; + +export const postRestaurantQueryKey = (restaurantId: number) => [ + 'restaurants', + restaurantId, +]; + +const usePostRestaurant = (restaurantId: number) => { + return useQuery({ + queryKey: postRestaurantQueryKey(restaurantId), + queryFn: () => post(`/restaurants/${restaurantId}/`), + enabled: false, + }); +}; + +export default usePostRestaurant; From ca2094779f4af240e20200ebc91953ed6ed152f4 Mon Sep 17 00:00:00 2001 From: soulchicken Date: Wed, 14 Aug 2024 01:23:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EB=A7=9B=EC=A7=91=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/RemoveListIcon.tsx | 21 +++++ src/components/bottomSheet/BottomSheet.tsx | 5 +- .../reaturantDetail/BackButton.tsx | 4 +- .../reaturantDetail/ListButton.tsx | 76 +++++++++++++++---- .../reaturantDetail/RestDetailView.tsx | 3 +- .../restaurantSummary/RestaurantImgBox.tsx | 2 - src/components/search/SearchResultLine.tsx | 5 +- src/hooks/api/restaurants/useDelRestaurant.ts | 17 +++++ src/pages/HomePage.tsx | 14 ++-- src/store/restaurants.ts | 2 +- 10 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/assets/icons/RemoveListIcon.tsx create mode 100644 src/hooks/api/restaurants/useDelRestaurant.ts diff --git a/src/assets/icons/RemoveListIcon.tsx b/src/assets/icons/RemoveListIcon.tsx new file mode 100644 index 0000000..928723f --- /dev/null +++ b/src/assets/icons/RemoveListIcon.tsx @@ -0,0 +1,21 @@ +const RemoveListIcon = () => { + return ( + + + + ); +}; + +export default RemoveListIcon; diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx index 3cf9959..aff2f07 100644 --- a/src/components/bottomSheet/BottomSheet.tsx +++ b/src/components/bottomSheet/BottomSheet.tsx @@ -4,7 +4,7 @@ import RestDetailView from '~/components/bottomSheet/reaturantDetail/RestDetailV import RestaurantSummary from '~/components/bottomSheet/restaurantSummary/RestaurantSummary'; import useGetDetailRestaurants from '~/hooks/api/restaurants/useGetDetailRestaurants'; import useDraggable from '~/hooks/useDraggable'; -import { restaurantAtom, selectedRestaurantId } from '~/store/restaurants'; +import { restaurantAtom, selectedRestaurantIdAtom } from '~/store/restaurants'; type BottomSheetProps = { onClose: () => void; @@ -45,7 +45,7 @@ const BottomSheetContent = styled.div` const BottomSheet: React.FC = ({ onClose }) => { const { translateY, handleMouseDown } = useDraggable(onClose); const [restaurants] = useAtom(restaurantAtom); - const [selectedId, setSelectedId] = useAtom(selectedRestaurantId); + const [selectedId, setSelectedId] = useAtom(selectedRestaurantIdAtom); const { data: restaurantDetail, isLoading, @@ -54,7 +54,6 @@ const BottomSheet: React.FC = ({ onClose }) => { const moreButtonClick = (id: number) => { setSelectedId(id); }; - return ( diff --git a/src/components/bottomSheet/reaturantDetail/BackButton.tsx b/src/components/bottomSheet/reaturantDetail/BackButton.tsx index 1282ffd..3bb46db 100644 --- a/src/components/bottomSheet/reaturantDetail/BackButton.tsx +++ b/src/components/bottomSheet/reaturantDetail/BackButton.tsx @@ -1,10 +1,10 @@ import { useSetAtom } from 'jotai'; import styled from 'styled-components'; import BackwordIcon from '~/assets/icons/BackwordIcon'; -import { selectedRestaurantId } from '~/store/restaurants'; +import { selectedRestaurantIdAtom } from '~/store/restaurants'; const BackButton = () => { - const setSelectedId = useSetAtom(selectedRestaurantId); + const setSelectedId = useSetAtom(selectedRestaurantIdAtom); const clickHandler = () => { setSelectedId(null); }; diff --git a/src/components/bottomSheet/reaturantDetail/ListButton.tsx b/src/components/bottomSheet/reaturantDetail/ListButton.tsx index 65b7c2a..cfb9b91 100644 --- a/src/components/bottomSheet/reaturantDetail/ListButton.tsx +++ b/src/components/bottomSheet/reaturantDetail/ListButton.tsx @@ -1,28 +1,53 @@ +import { useSetAtom } from 'jotai'; import { useState } from 'react'; import { styled } from 'styled-components'; import ListIcon from '~/assets/icons/ListIcon'; +import RemoveListIcon from '~/assets/icons/RemoveListIcon'; import XIcon from '~/assets/icons/XIcon'; +import useDelRestaurant from '~/hooks/api/restaurants/useDelRestaurant'; import usePostRestaurant from '~/hooks/api/restaurants/usePostRestarurant'; +import { selectedRestaurantIdAtom } from '~/store/restaurants'; interface ListButtonProps { restaurantId: number; + isAdd: boolean; } -const ListButton: React.FC = ({ restaurantId }) => { +const ListButton: React.FC = ({ restaurantId, isAdd }) => { const [isVisible, setIsVisible] = useState(true); - const { refetch } = usePostRestaurant(restaurantId); + const setSelectedId = useSetAtom(selectedRestaurantIdAtom); + const { refetch: postRest } = usePostRestaurant(restaurantId); + const { refetch: delRest } = useDelRestaurant(restaurantId); const addList = (e: React.MouseEvent) => { e.preventDefault(); - refetch(); + postRest(); + setSelectedId(null); + }; + + const delList = (e: React.MouseEvent) => { + e.preventDefault(); + delRest(); + setSelectedId(null); }; return ( - - - setIsVisible(false)}> + + {isAdd ? ( + <> + + + 맛집 리스트에 추가! + + + ) : ( + <> + + + 맛집 리스트에서 제거 + + + )} + setIsVisible(false)} $isAdd={isAdd}> @@ -31,20 +56,39 @@ const ListButton: React.FC = ({ restaurantId }) => { export default ListButton; -const ButtonWrapper = styled.div<{ $isVisible: boolean }>` +const ButtonWrapper = styled.div<{ $isVisible: boolean; $isAdd: boolean }>` display: ${({ $isVisible }) => ($isVisible ? 'flex' : 'none')}; justify-content: space-between; align-items: center; margin-top: 16px; - background-color: ${({ theme }) => theme.colors.orange}; + background-color: ${({ $isAdd, theme }) => + $isAdd ? theme.colors.orange : theme.colors.whitegray}; border-radius: 8px; `; -const Button = styled.button` +const AddButton = styled.button` color: ${({ theme }) => theme.colors.white}; flex: 1; height: 60px; padding: 8px 16px; + border: none; + background: none; + cursor: pointer; + + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + + font-size: 16px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; +`; + +const RemoveButton = styled.button` + color: ${({ theme }) => theme.colors.orange}; + flex: 1; + height: 60px; + padding: 8px 16px; font-size: 16px; border: none; background: none; @@ -54,15 +98,19 @@ const Button = styled.button` justify-content: center; align-items: center; gap: 20px; + + font-size: 16px; + font-weight: ${({ theme }) => theme.fontWeights.Bold}; `; -const CloseButton = styled.button` +const CloseButton = styled.button<{ $isAdd: boolean }>` border: none; background: none; cursor: pointer; padding: 20px; & svg > path { - stroke: ${({ theme }) => theme.colors.white}; + stroke: ${({ $isAdd, theme }) => + $isAdd ? theme.colors.white : theme.colors.orange}; } `; diff --git a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx index cc54948..26b3870 100644 --- a/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx +++ b/src/components/bottomSheet/reaturantDetail/RestDetailView.tsx @@ -27,7 +27,8 @@ const RestDetailView: React.FC = ({ - + {/* TODO: isAdd에 대한 처리가 필요합니다. */} + = (restaurant) => { - const setSelectedId = useSetAtom(selectedRestaurantId); + const setSelectedId = useSetAtom(selectedRestaurantIdAtom); const searchClick = () => { setSelectedId(restaurant.id); diff --git a/src/hooks/api/restaurants/useDelRestaurant.ts b/src/hooks/api/restaurants/useDelRestaurant.ts new file mode 100644 index 0000000..1923ca7 --- /dev/null +++ b/src/hooks/api/restaurants/useDelRestaurant.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import { del } from '~/libs/api'; + +export const delRestaurantQueryKey = (restaurantId: number) => [ + 'restaurants', + restaurantId, +]; + +const useDelRestaurant = (restaurantId: number) => { + return useQuery({ + queryKey: delRestaurantQueryKey(restaurantId), + queryFn: () => del(`/restaurants/${restaurantId}/`), + enabled: false, + }); +}; + +export default useDelRestaurant; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 9bfca0f..7cefd17 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -7,14 +7,14 @@ import NavBar from '~/components/navBar/NavBar'; import BottomSheet from '~/components/bottomSheet/BottomSheet'; import useGetRestaurants from '~/hooks/api/useGetRestaurants'; import { useAtom, useSetAtom } from 'jotai'; -import { restaurantAtom, selectedRestaurantId } from '~/store/restaurants'; +import { restaurantAtom, selectedRestaurantIdAtom } from '~/store/restaurants'; import Head from '~/components/common/Head'; const HomePage = () => { const location = useLocation(); const setRestaurants = useSetAtom(restaurantAtom); - const [selectedId] = useAtom(selectedRestaurantId); - const { data } = useGetRestaurants(); + const [selectedId] = useAtom(selectedRestaurantIdAtom); + const { data, refetch } = useGetRestaurants(); const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false); const [isSearchVisible, setSearchVisible] = useState( @@ -27,12 +27,12 @@ const HomePage = () => { setSearchVisible(false); window.history.pushState(null, '', '/'); } - }, [selectedId]); + console.log('selectedId', selectedId); + refetch(); + }, [selectedId, refetch]); useEffect(() => { - if (data) { - setRestaurants(data); - } + setRestaurants(data || []); }, [data, setRestaurants]); const handleMapClick = () => { diff --git a/src/store/restaurants.ts b/src/store/restaurants.ts index bffb7ae..f949b35 100644 --- a/src/store/restaurants.ts +++ b/src/store/restaurants.ts @@ -7,4 +7,4 @@ export const restaurantAtom = atom([]); // 검색 결과를 저장하는 atom export const searchRestaurantAtom = atom([]); -export const selectedRestaurantId = atom(null); +export const selectedRestaurantIdAtom = atom(null); From 3268d152c9eb8fc54a691cf0517df747b44f48cb Mon Sep 17 00:00:00 2001 From: soulchicken Date: Wed, 14 Aug 2024 02:12:11 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20=ED=95=9C=EC=A4=84=ED=8F=89=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=8B=AB=EC=96=B4=EC=9A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그렇지만 아직 한줄평 API가 완전하지 않아보임 - 당장 쓰기 어려워보이는 기능은 제거 --- .../reaturantDetail/ReviewContent.tsx | 64 +++++++++++++++---- .../bottomSheet/reaturantDetail/Reviews.tsx | 6 +- src/components/navBar/NavBar.tsx | 4 +- src/hooks/api/restaurants/usePostRecommend.ts | 31 +++++++++ 4 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 src/hooks/api/restaurants/usePostRecommend.ts diff --git a/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx index 13b3154..25def4b 100644 --- a/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx +++ b/src/components/bottomSheet/reaturantDetail/ReviewContent.tsx @@ -1,8 +1,10 @@ import { styled } from 'styled-components'; +import usePostRecommend from '~/hooks/api/restaurants/usePostRecommend'; import { Review } from '~/types/restaurants'; interface ReviewContentProps { review: Review; + restaurentId: number; } const formatDate = (date: string) => { @@ -20,7 +22,40 @@ const countFormat = (count: number) => { return count; }; -const ReviewContent: React.FC = ({ review }) => { +const ReviewContent: React.FC = ({ + review, + restaurentId, +}) => { + const mutation = usePostRecommend(restaurentId, review.id); + + const recommendClick = () => { + mutation.mutate( + { evaluation: 1 }, + { + onSuccess: () => { + alert('좋아요를 반영했습니다.'); + }, + onError: (error) => { + console.error('좋아요 반영에 실패했습니다.', error); + }, + }, + ); + }; + + const decommendClick = () => { + mutation.mutate( + { evaluation: 0 }, + { + onSuccess: () => { + alert('싫어요를 반영했습니다.'); + }, + onError: (error) => { + console.error('싫어요 반영에 실패했습니다.', error); + }, + }, + ); + }; + return (
@@ -28,14 +63,15 @@ const ReviewContent: React.FC = ({ review }) => { {review.user_name} / {formatDate(review.date)} - 답글 보기 ({review.replies_count}) + {/* 답글보기 기능은 아직 다른 기능을 구현하는데 집중하고 있어서 구현하지 않았습니다. */} + {/* 답글 보기 ({review.replies_count}) */}
- +
GOOD
{countFormat(review.recommend_count)}
- +
BAD
{countFormat(review.decommend_count)}
@@ -68,16 +104,16 @@ const SubText = styled.div` font-weight: ${({ theme }) => theme.fontWeights.Light}; `; -const LookReply = styled.button` - display: inline-block; - margin-top: 8px; - font-size: 12px; - font-weight: ${({ theme }) => theme.fontWeights.Light}; - color: ${({ theme }) => theme.colors.gray}; - background-color: transparent; - border: none; - cursor: pointer; -`; +// const LookReply = styled.button` +// display: inline-block; +// margin-top: 8px; +// font-size: 12px; +// font-weight: ${({ theme }) => theme.fontWeights.Light}; +// color: ${({ theme }) => theme.colors.gray}; +// background-color: transparent; +// border: none; +// cursor: pointer; +// `; const RecommendBox = styled.div` display: flex; diff --git a/src/components/bottomSheet/reaturantDetail/Reviews.tsx b/src/components/bottomSheet/reaturantDetail/Reviews.tsx index 557420e..a00ed48 100644 --- a/src/components/bottomSheet/reaturantDetail/Reviews.tsx +++ b/src/components/bottomSheet/reaturantDetail/Reviews.tsx @@ -14,7 +14,11 @@ const Reviews: React.FC = ({ reviews, restaurentId }) => { 한줄평 {reviews && reviews.map((review) => ( - + ))} diff --git a/src/components/navBar/NavBar.tsx b/src/components/navBar/NavBar.tsx index 51d6e5c..1c4fa50 100644 --- a/src/components/navBar/NavBar.tsx +++ b/src/components/navBar/NavBar.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import FriendsIcon from '~/assets/icons/FriendsIcon'; import HomeIcon from '~/assets/icons/HomeIcon'; -import MyListIcon from '~/assets/icons/MyListIcon'; +// import MyListIcon from '~/assets/icons/MyListIcon'; import ProfileIcon from '~/components/navBar/ProfileIcon'; import NavItemWrapper from '~/components/navBar/NavItemWrapper'; import { useLocation } from 'react-router-dom'; @@ -34,7 +34,7 @@ const NavList = styled.ul` `; const navItems = [ - { path: '/myList', label: 'My List', Icon: MyListIcon }, + // { path: '/myList', label: 'My List', Icon: MyListIcon }, { path: '/friends', label: 'Friends', Icon: FriendsIcon }, { path: '/profile', label: 'Profile', Icon: ProfileIcon }, ]; diff --git a/src/hooks/api/restaurants/usePostRecommend.ts b/src/hooks/api/restaurants/usePostRecommend.ts new file mode 100644 index 0000000..c5d8d3d --- /dev/null +++ b/src/hooks/api/restaurants/usePostRecommend.ts @@ -0,0 +1,31 @@ +import { useMutation } from '@tanstack/react-query'; +import { AxiosRequestConfig } from 'axios'; +import { post } from '~/libs/api'; + +type Request = { + evaluation: number; +}; + +export const postRecommendQueryKey = (reviewId: number) => [ + 'recommend', + reviewId, +]; + +const usePostRecommend = (restaurantId: number, reviewId: number) => { + const mutation = useMutation({ + mutationFn: (request: Request) => { + const config: AxiosRequestConfig = { + data: request, + }; + + return post( + `/restaurants/${restaurantId}/reviews/${reviewId}/`, + config, + ); + }, + }); + + return mutation; +}; + +export default usePostRecommend;