diff --git a/src/app/(iTracker)/products/[category]/[productId]/page.tsx b/src/app/(iTracker)/products/[category]/[productId]/page.tsx index 3cd54af..54c1139 100644 --- a/src/app/(iTracker)/products/[category]/[productId]/page.tsx +++ b/src/app/(iTracker)/products/[category]/[productId]/page.tsx @@ -1,10 +1,18 @@ import { CategoryType } from '@/features/category/constants'; import { ProductDetail } from '@/features/productDetail/components/ProductDetail'; +import { Loading } from '@/shared/components/Loading'; +import { Text } from '@/shared/components/shadcn/Text'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; export default function ProductDetailPage({ params }: { params: { productId: number; category: CategoryType } }) { return (
- + 상세 정보를 불러오는 중 오류가 생겼습니다.}> + }> + + +
); } diff --git a/src/features/productDetail/api/getProductDetail.ts b/src/features/productDetail/api/getProductDetail.ts index 489f700..e796dba 100644 --- a/src/features/productDetail/api/getProductDetail.ts +++ b/src/features/productDetail/api/getProductDetail.ts @@ -1,4 +1,7 @@ +import { CategoryType } from '@/features/category/constants'; import { Airpods, Macbook } from '@/features/product/api/getProductList'; +import instance from '@/shared/api/axios/instance'; +import { API_BASE_URL } from '@/shared/api/constants'; export type GetProductDetailResponse = Macbook & Airpods & ProductDetailInfo; @@ -14,15 +17,13 @@ export type ProductDetailInfo = { }[]; }; -// 클라이언트 상태 관리를 위한 api 호출함수 -// export const getProductDetailUrl = (productId: number) => `/api/products/${productId}`; +export const getProductDetail = async ( + productId: number, + category: CategoryType, +): Promise => { + const response = await instance.get(`${API_BASE_URL}/api/v1/products/${category}/${productId}`); -// export const getProductDetail = async (productId: number): Promise => { -// const url = `${getProductDetailUrl(productId)}`; + const data = (await response.data) as GetProductDetailResponse; -// const response = await fetch(url); - -// const data = (await response.json()) as GetProductDetailResponse; - -// return data; -// }; + return data; +}; diff --git a/src/features/productDetail/components/Notification/index.tsx b/src/features/productDetail/components/Notification/index.tsx index cd04419..7d15606 100644 --- a/src/features/productDetail/components/Notification/index.tsx +++ b/src/features/productDetail/components/Notification/index.tsx @@ -20,7 +20,7 @@ const Notification = ({ productId, category, isFavorite }: NotificationProps) => return (
{isFavorite ? ( - ) : ( diff --git a/src/features/productDetail/components/ProductDetail/index.tsx b/src/features/productDetail/components/ProductDetail/index.tsx index e6586dc..c551457 100644 --- a/src/features/productDetail/components/ProductDetail/index.tsx +++ b/src/features/productDetail/components/ProductDetail/index.tsx @@ -1,6 +1,6 @@ +'use client'; + import { Text } from '@/shared/components/shadcn/Text'; -import { API_BASE_URL } from '@/shared/api/constants'; -import { GetProductDetailResponse } from '../../api/getProductDetail'; import Image from 'next/image'; import { FixedBottomButton } from '@/shared/components/FixedBottomButton'; import { convertToLocalFormat } from '@/shared/utils'; @@ -11,152 +11,134 @@ import { Suspense } from 'react'; import PriceChart from '../LineChart'; import { CategoryType, categoryMap } from '@/features/category/constants'; import Notification from '../Notification'; +import { useGetProductDetail } from '../../hooks/useGetProductDetail'; -// server component - -export const ProductDetail = async ({ productId, category }: { productId: number; category: CategoryType }) => { +export const ProductDetail = ({ productId, category }: { productId: number; category: CategoryType }) => { const categoryName = categoryMap[category]; + const { data } = useGetProductDetail(productId, category); - try { - const response = await fetch(`${API_BASE_URL}/api/v1/products/${category}/${productId}`, { - cache: 'no-store', - }); - if (!response.ok) { - throw new Error(`서버에서 데이터를 가져오는 데 실패했습니다. 상태 코드: ${response.status}`); - } + const isMacbook = category === 'macbook_air' || category === 'macbook_pro'; - const data = (await response.json()) as GetProductDetailResponse; + return ( +
+
+
+
+
+ {data.title} +
- const isMacbook = category === 'macbook_air' || category === 'macbook_pro'; +
+
+ {categoryName} + {data.title} +
- console.log(data.isFavorite); + {isMacbook ? ( + <> +
+ + {data.chip} + +
+
+ {data.cpu} + {data.gpu} + {data.storage} + {data.memory} + {data.color} +
+ + ) : ( + {data.color} + )} +
+
- return ( -
-
-
-
-
- {data.title} +
+
+
+ + 전체 평균가 대비 + +
-
-
- {categoryName} - {data.title} -
- - {isMacbook ? ( - <> -
- - {data.chip} - -
-
- {data.cpu} - {data.gpu} - {data.storage} - {data.memory} - {data.color} -
- +
+ {data.label === true ? ( + ) : ( - {data.color} +
)} + + 현재가 + + + {convertToLocalFormat(Math.floor(data.currentPrice))}원 +
-
-
-
- - 전체 평균가 대비 - - -
- -
- {data.label === true ? ( - - ) : ( -
- )} - - 현재가 - - - {convertToLocalFormat(Math.floor(data.currentPrice))}원 - -
+
+
+ + 최저가 + + + {convertToLocalFormat(Math.floor(data.allTimeLowPrice))}원 +
- -
-
- - 최저가 - - - {convertToLocalFormat(Math.floor(data.allTimeLowPrice))}원 - -
- -
- - 평균가 - - - {convertToLocalFormat(Math.floor(data.averagePrice))}원 - -
- -
- - 최고가 - - - {convertToLocalFormat(Math.floor(data.allTimeHighPrice))}원 - -
+ +
+ + 평균가 + + {convertToLocalFormat(Math.floor(data.averagePrice))}원 +
+ +
+ + 최고가 + + + {convertToLocalFormat(Math.floor(data.allTimeHighPrice))}원 +
- - -
- -
-
+ + +
+ +
+
+
- - - + + + -
- - 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다. - - - * 쿠팡 정보와 동일하지 않을 수 있으니 쿠팡에서 가격을 직접 확인 후 이용바랍니다. - -
+
+ + 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다. + + + * 쿠팡 정보와 동일하지 않을 수 있으니 쿠팡에서 가격을 직접 확인 후 이용바랍니다. +
-
- ); - } catch (error) { - console.error(error); - return 제품 정보를 불러오는 중 오류가 발생했습니다.; - } + +
+ ); }; diff --git a/src/features/productDetail/hooks/useGetProductDetail.ts b/src/features/productDetail/hooks/useGetProductDetail.ts new file mode 100644 index 0000000..9b6d396 --- /dev/null +++ b/src/features/productDetail/hooks/useGetProductDetail.ts @@ -0,0 +1,13 @@ +import { CategoryType } from '@/features/category/constants'; +import { UseSuspenseQueryResult, useSuspenseQuery } from '@tanstack/react-query'; +import { GetProductDetailResponse, getProductDetail } from '../api/getProductDetail'; + +export const useGetProductDetail = ( + productId: number, + category: CategoryType, +): UseSuspenseQueryResult => { + return useSuspenseQuery({ + queryKey: ['productDetail', productId, category], + queryFn: () => getProductDetail(productId, category), + }); +}; diff --git a/src/features/productDetail/hooks/usePatchFavorites.tsx b/src/features/productDetail/hooks/usePatchFavorites.tsx index bf8c2a5..edac693 100644 --- a/src/features/productDetail/hooks/usePatchFavorites.tsx +++ b/src/features/productDetail/hooks/usePatchFavorites.tsx @@ -14,6 +14,7 @@ export const usePatchFavorites = (productId: number, category: CategoryType, isF mutationFn: () => patchFavorites(productId, category), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ['favorites'] }); + await queryClient.refetchQueries({ queryKey: ['productDetail'] }); if (!isFavorite) { toast({