diff --git a/src/components/common/statisticscard/StatisticsCard.tsx b/src/components/common/statisticscard/StatisticsCard.tsx
index 10278f8..642a949 100644
--- a/src/components/common/statisticscard/StatisticsCard.tsx
+++ b/src/components/common/statisticscard/StatisticsCard.tsx
@@ -1,104 +1,105 @@
-import Image from "next/image";
-
-import calculateDifference from "./calculateDifference";
-
-type StatisticsCardProps = {
- type: "rate" | "like" | "review";
- rateData?: number;
- likeData?: number;
- reviewData?: number;
- rateAvg?: number;
- likeAvg?: number;
- reviewAvg?: number;
-};
-
-export default function StatisticsCard({
- type,
- rateData,
- likeData,
- reviewData,
- rateAvg,
- likeAvg,
- reviewAvg,
-}: StatisticsCardProps) {
- const typeList = {
- rate: {
- label: "별점 평균",
- productData: rateData,
- avgData: rateAvg,
- icon: "/icons/star.svg",
- unit: "점",
- bottomDecription: {
- higher: "더 높아요!",
- lower: "더 낮아요!",
- same: "동일해요!",
- },
- },
- like: {
- label: "찜",
- productData: likeData,
- avgData: likeAvg,
- icon: "/icons/heart_on.svg",
- unit: "개",
- bottomDecription: {
- higher: "더 많아요!",
- lower: "더 적어요!",
- same: "동일해요!",
- },
- },
- review: {
- label: "리뷰",
- productData: reviewData,
- avgData: reviewAvg,
- icon: "/icons/message.svg",
- unit: "개",
- bottomDecription: {
- higher: "더 많아요!",
- lower: "더 적어요!",
- same: "동일해요!",
- },
- },
- };
-
- const { label, productData, avgData, icon, unit, bottomDecription } =
- typeList[type];
-
- const result = calculateDifference(productData, avgData, bottomDecription);
- return (
-
-
-
- {label}
-
-
-
-
-
-
- {type === "rate" ? productData : productData?.toLocaleString()}
-
-
-
-
-
-
-
-
- {type === "rate" ? productData : productData?.toLocaleString()}
-
-
-
- {result.topDescription}
-
- {!result.isSame && (
-
- {type === "rate" ? result.rateDiff : result.likeReviewDiff}
- {unit}
-
- )}
- {result.bottomDescriptionResult}
-
-
-
- );
-}
+import Image from "next/image";
+
+import calculateDifference from "./calculateDifference";
+
+type StatisticsCardProps = {
+ type: "rate" | "like" | "review";
+ rateData?: number;
+ likeData?: number;
+ reviewData?: number;
+ rateAvg?: number;
+ likeAvg?: number;
+ reviewAvg?: number;
+};
+
+export default function StatisticsCard({
+ type,
+ rateData,
+ likeData,
+ reviewData,
+ rateAvg,
+ likeAvg,
+ reviewAvg,
+}: StatisticsCardProps) {
+ const typeList = {
+ rate: {
+ label: "별점 평균",
+ productData: rateData,
+ avgData: rateAvg,
+ icon: "/icons/star_on.svg",
+ unit: "점",
+ bottomDecription: {
+ higher: "더 높아요!",
+ lower: "더 낮아요!",
+ same: "동일해요!",
+ },
+ },
+ like: {
+ label: "찜",
+ productData: likeData,
+ avgData: likeAvg,
+ icon: "/icons/heart_on.svg",
+ unit: "개",
+ bottomDecription: {
+ higher: "더 많아요!",
+ lower: "더 적어요!",
+ same: "동일해요!",
+ },
+ },
+ review: {
+ label: "리뷰",
+ productData: reviewData,
+ avgData: reviewAvg,
+ icon: "/icons/message.svg",
+ unit: "개",
+ bottomDecription: {
+ higher: "더 많아요!",
+ lower: "더 적어요!",
+ same: "동일해요!",
+ },
+ },
+ };
+
+ const { label, productData, avgData, icon, unit, bottomDecription } =
+ typeList[type];
+
+ const result = calculateDifference(productData, avgData, bottomDecription);
+ return (
+
+
+
+ {label}
+
+
+
+
+
+
+ {type === "rate" ? productData : productData?.toLocaleString()}
+
+
+
+
+
+
+
+
+ {type === "rate" ? productData : productData?.toLocaleString()}
+
+
+
+ {result.topDescription}
+
+ {!result.isSame && (
+
+ {type === "rate" ? result.rateDiff : result.likeReviewDiff}
+ {unit}
+
+ )}
+ {result.bottomDescriptionResult}
+
+
+
+ );
+}
+
diff --git a/src/components/productdetail/DetailCard.tsx b/src/components/productdetail/DetailCard.tsx
index a7eabf9..088e87d 100644
--- a/src/components/productdetail/DetailCard.tsx
+++ b/src/components/productdetail/DetailCard.tsx
@@ -1,129 +1,131 @@
-import clsx from "clsx";
-import Image from "next/image";
-
-import { ProductDetail } from "@/types/product";
-import cn from "@/utils/cn";
-
-import BasicButton from "../common/button/BasicButton";
-import CategoryBadge from "../common/categoryBadge/CategoryBadge";
-
-type Props = {
- productData: ProductDetail;
- isMyProduct: boolean;
-};
-
-type ShareProps = {
- className: string;
-};
-
-type FavoriteProps = {
- isFavorite: boolean;
- className: string;
-};
-
-const imageCn = "object-contain";
-
-export default function DetailCard({ productData, isMyProduct }: Props) {
- const { name, description, image, isFavorite, category } = productData;
- const mobileHiddenCn = "hidden md:flex";
- const onlyMobileCn = "flex md:hidden";
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {description}
-
-
-
-
- {isMyProduct && (
-
- )}
-
-
-
- );
-}
-
-export function Share({ className }: ShareProps) {
- const buttonCn =
- "flex size-[2.4rem] items-center justify-center rounded-[0.6rem] bg-black-bg lg:size-[2.8rem]";
- const imageDivCn = "relative size-[1.4rem] lg:size-[1.8rem]";
- const kakaoShareIconSrc = "/icons/kakaotalk.svg";
- const shareIconSrc = "/icons/share.svg";
- return (
-
- );
-}
-
-export function Favorite({ isFavorite, className }: FavoriteProps) {
- const heartOnIconSrc = "/icons/heart_on.svg";
- const heartOffIconSrc = "/icons/heart_off.svg";
- return (
-
-
-
-
-
- );
-}
+import clsx from "clsx";
+import Image from "next/image";
+
+import { ProductDetail } from "@/types/product";
+import cn from "@/utils/cn";
+
+import BasicButton from "../common/button/BasicButton";
+import CategoryBadge from "../common/categoryBadge/CategoryBadge";
+
+type Props = {
+ productData: ProductDetail;
+ isMyProduct: boolean;
+};
+
+type ShareProps = {
+ className: string;
+};
+
+type FavoriteProps = {
+ isFavorite: boolean;
+ className: string;
+};
+
+export default function DetailCard({ productData, isMyProduct }: Props) {
+ const { name, description, image, isFavorite, category } = productData;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {description}
+
+
+
+ {/**TODO: 리뷰 작성 모달, 비로그인 시 로그인 요청 모달*/}
+
+ {/**TODO: 비교상품 없을 경우 alert표시, 하나 있을 경우 확인할지 안할지 모달 표시 확인하면 /compare 이동, 두개 있을 경우 비교 상품 교체 모달 비로그인시 로그인 요청 모달*/}
+ {isMyProduct && (
+
+ )}
+
+ {/**TODO: 상품 편집 모달 추가*/}
+
+
+ );
+}
+
+export function Share({ className }: ShareProps) {
+ const buttonCn =
+ "flex size-[2.4rem] items-center justify-center rounded-[0.6rem] bg-black-bg lg:size-[2.8rem]";
+ const imageDivCn = "relative size-[1.4rem] lg:size-[1.8rem]";
+ const kakaoShareIconSrc = "/icons/kakaotalk.svg";
+ const shareIconSrc = "/icons/share.svg";
+ return (
+
+
+
+
+
+
+ {/**TODO: 카카오공유는 배포이후 추가 가능*/}
+
+
+
+
+
+ {/**TODO: 클립보드 복사 기능 추가*/}
+
+ );
+}
+
+export function Favorite({ isFavorite, className }: FavoriteProps) {
+ const heartOnIconSrc = "/icons/heart_on.svg";
+ const heartOffIconSrc = "/icons/heart_off.svg";
+ return (
+
+
+
+
+
+ );
+}
+
diff --git a/src/components/productdetail/MockData.ts b/src/components/productdetail/MockData.ts
new file mode 100644
index 0000000..3127945
--- /dev/null
+++ b/src/components/productdetail/MockData.ts
@@ -0,0 +1,107 @@
+import { ProductDetail, ReviewResponse } from "@/types/product";
+
+export const productDetailData: ProductDetail = {
+ id: 1,
+ name: "Sony WH-1000XM3",
+ description:
+ "한층 업그레이된 고급 노이즈 캔슬과 상황에 맞게 조정되는 스마트 청취 기능을 갖춘 WH-1000XM3 헤드폰으로 더욱 깊은 고요 속에서 청취할 수 있습니다.",
+ image: "/images/headset.svg",
+ rating: 4.9,
+ reviewCount: 4123,
+ favoriteCount: 566,
+ categoryId: 1,
+ createdAt: "2024-03-18",
+ updatedAt: "2024-03-18",
+ writerId: 1,
+ isFavorite: true,
+ category: {
+ id: 1,
+ name: "전자기기",
+ },
+ categoryMetric: {
+ rating: 4.1,
+ favoriteCount: 543,
+ reviewCount: 3937,
+ },
+};
+
+export const reviewData: ReviewResponse = {
+ nextCursor: 0,
+ list: [
+ {
+ user: {
+ image: "/images/profile1.svg",
+ nickname: "테스트1",
+ id: 1,
+ },
+ reviewImages: [
+ {
+ source: "/images/test1.jpg",
+ id: 1,
+ },
+ {
+ source: "/images/test2.png",
+ id: 2,
+ },
+ ],
+ productId: 1,
+ userId: 1,
+ updatedAt: "2024-01-29",
+ createdAt: "2024-01-27",
+ isLiked: true,
+ likeCount: 14,
+ content:
+ "음질 미칩니다ㅎㅎ 최고예용~ 어플 연동으로 음향 설정 및 설정모드 되고, 설정별로 사운드감이 틀려요 서라운드 느낌까지 들고, 따로는 베이스깐 우퍼 느낌도 있어요",
+ rating: 5,
+ id: 1,
+ },
+ {
+ user: {
+ image: "/images/profile1.svg",
+ nickname: "테스트2",
+ id: 1,
+ },
+ reviewImages: [],
+ productId: 1,
+ userId: 2,
+ updatedAt: "2024-01-23",
+ createdAt: "2024-01-24",
+ isLiked: false,
+ likeCount: 11,
+ content:
+ "전작과 동일하게, 소니 헤드폰 커넥트 애플리케이션을 통한 노이즈 캔슬링 컨트롤이 가능하다. 1000XM2에 있었던 대기압 센서도 그대로 탑재!",
+ rating: 3,
+ id: 2,
+ },
+ {
+ user: {
+ image: "/images/profile1.svg",
+ nickname: "테스트3",
+ id: 1,
+ },
+ reviewImages: [
+ {
+ source: "/images/test1.jpg",
+ id: 1,
+ },
+ {
+ source: "/images/test2.png",
+ id: 2,
+ },
+ {
+ source: "/images/testImage.png",
+ id: 3,
+ },
+ ],
+ productId: 1,
+ userId: 3,
+ updatedAt: "2024-01-25",
+ createdAt: "2024-01-26",
+ isLiked: true,
+ likeCount: 42,
+ content: "구려요",
+ rating: 1,
+ id: 3,
+ },
+ ],
+};
diff --git a/src/components/productdetail/ProductDetail.tsx b/src/components/productdetail/ProductDetail.tsx
new file mode 100644
index 0000000..bfbfdef
--- /dev/null
+++ b/src/components/productdetail/ProductDetail.tsx
@@ -0,0 +1,25 @@
+import { useEffect, useState } from "react";
+
+import DetailCard from "./DetailCard";
+import { productDetailData } from "./MockData";
+
+export default function ProductDetail() {
+ const [cookieid, setCookieId] = useState
(0);
+
+ useEffect(() => {
+ const cookies = Object.fromEntries(
+ document.cookie.split(";").map((cookie) => cookie.trim().split("=")),
+ );
+ setCookieId(Number(cookies["id"]));
+ }, []);
+ //TODO: 쿠키는 아마도 기능구현때 store에서 관리
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/productdetail/ProductDetailPageLayout.tsx b/src/components/productdetail/ProductDetailPageLayout.tsx
new file mode 100644
index 0000000..0d4f593
--- /dev/null
+++ b/src/components/productdetail/ProductDetailPageLayout.tsx
@@ -0,0 +1,15 @@
+import AddProductButton from "../common/button/AddProductButton";
+import ProductDetail from "./ProductDetail";
+import ProductReview from "./ProductReview";
+import ProductStatistics from "./ProductStatistics";
+
+export default function ProductDetailPageLayout() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/productdetail/ProductReview.tsx b/src/components/productdetail/ProductReview.tsx
new file mode 100644
index 0000000..fbbc268
--- /dev/null
+++ b/src/components/productdetail/ProductReview.tsx
@@ -0,0 +1,73 @@
+import Image from "next/image";
+import { useEffect, useState } from "react";
+
+import { filterBy } from "@/constants/filterBy";
+
+import Dropdown from "../common/dropdown/Dropdown";
+import { reviewData } from "./MockData";
+import ReviewCard from "./ReviewCard";
+
+export default function ProductReview() {
+ const [cookieid, setCookieId] = useState(0);
+
+ useEffect(() => {
+ const cookies = Object.fromEntries(
+ document.cookie.split(";").map((cookie) => cookie.trim().split("=")),
+ );
+ setCookieId(Number(cookies["id"]));
+ }, []);
+ //TODO: 쿠키는 아마도 기능구현때 store에서 관리
+
+ return (
+
+ {/**TODO: 리뷰 목록 무한 스크롤 구현 */}
+
+
+ 상품 리뷰
+
+ console.log(`선택된 항목: ${item.name}`)}
+ >
+ {/**TODO: 선택된 항목 별 정렬 */}
+
+
+
+
+
+ {reviewData.list.map((data) => (
+
+ ))}
+
+
+ );
+}
+
+type NoneReviewProps = {
+ type: "none" | "loading";
+};
+// 기능 구현 때 데이터가 없거나 로딩중일때 렌더링하도록 컴포넌트 사용할 예정
+export function NoneReview({ type }: NoneReviewProps) {
+ const text = type === "none" ? "첫 리뷰를 작성해 보세요!" : "Loading...";
+ const noneReviewIconSrc = "/icons/none_review.svg";
+ return (
+
+ );
+}
diff --git a/src/components/productdetail/ProductStatistics.tsx b/src/components/productdetail/ProductStatistics.tsx
new file mode 100644
index 0000000..1e4c795
--- /dev/null
+++ b/src/components/productdetail/ProductStatistics.tsx
@@ -0,0 +1,29 @@
+import StatisticsCard from "../common/statisticscard/StatisticsCard";
+import { productDetailData } from "./MockData";
+
+export default function ProductStatistics() {
+ return (
+
+ );
+}
diff --git a/src/components/productdetail/ReviewCard.tsx b/src/components/productdetail/ReviewCard.tsx
index 4af6fc8..d0b1e75 100644
--- a/src/components/productdetail/ReviewCard.tsx
+++ b/src/components/productdetail/ReviewCard.tsx
@@ -1,92 +1,98 @@
-import Image from "next/image";
-
-import { Review } from "@/types/product";
-
-import ReviewerProfile from "../common/reviewerProfile/ReviewerProfile";
-import Thumbs from "../common/thumbs/Thumbs";
-
-type Props = {
- reviewData: Review;
- isMyReview: boolean;
-};
-
-export default function ReviewCard({ reviewData, isMyReview }: Props) {
- const { user, reviewImages, createdAt, isLiked, likeCount, content, rating } =
- reviewData;
- const MAX_RATE = 5;
- const rateArray = Array.from({ length: MAX_RATE }, (_, i) => i + 1);
- const starOnIconSrc = "/icons/star_on.svg";
- const starOffIconSrc = "/icons/star_off.svg";
-
- const handleButtonClick = () => {
- console.log("TODO:좋아요 개수 증가");
- };
-
- const reviewerData = {
- image: user.image,
- rank: 1,
- nickname: user.nickname,
- followersCount: 162,
- reviewCount: 37,
- };
- //TODO: 유저랭크, 팔로워 카운터, 리뷰카운터를 백엔드에서 제공안해서, api 수정 요청하거나 userId로 유저 정보/랭킹 조회 데이터 가져와야함
-
- return (
-
-
-
-
- {rateArray.map((index) => (
-
-
-
- ))}
-
-
-
-
- {content}
-
-
- {reviewImages.map((data) => (
-
-
-
- ))}
-
-
-
-
{createdAt}
- {isMyReview && (
-
- 수정
- 삭제
-
- )}
-
-
-
-
-
- );
-}
+import Image from "next/image";
+
+import { Review } from "@/types/product";
+
+import ProfileImage from "../common/profileImage/ProfileImage";
+import ReviewerProfile from "../common/reviewerProfile/ReviewerProfile";
+import Thumbs from "../common/thumbs/Thumbs";
+
+type Props = {
+ reviewData: Review;
+ isMyReview: boolean;
+};
+
+export default function ReviewCard({ reviewData, isMyReview }: Props) {
+ const { user, reviewImages, createdAt, isLiked, likeCount, content, rating } =
+ reviewData;
+ const MAX_RATE = 5;
+ const rateArray = Array.from({ length: MAX_RATE }, (_, i) => i + 1);
+ const starOnIconSrc = "/icons/star_on.svg";
+ const starOffIconSrc = "/icons/star_off.svg";
+
+ const handleButtonClick = () => {
+ console.log("TODO:좋아요 개수 증가 비로그인 시 로그인요청 모달");
+ };
+
+ //TODO: 유저랭크, 팔로워 카운터, 리뷰카운터를 백엔드에서 제공X, qna 확인요청한 상태
+
+ return (
+
+
+
+
+
+
+
+ {reviewData.user.nickname}
+
+
+ {/**TODO: 버튼 클릭 시 유저 프로필화면 이동 /user/{userId} */}
+
+
+
+
+ {rateArray.map((index) => (
+
+
+
+ ))}
+
+
+
+ {content}
+
+
+ {reviewImages.map((data) => (
+
+
+
+ ))}
+
+
+
+
{createdAt}
+ {isMyReview && (
+
+ 수정
+ 삭제
+ {/**TODO: 수정모달 추가, 삭제 alert추가 */}
+
+ )}
+
+
+
+
+
+ );
+}
+
diff --git a/src/constants/filterBy.ts b/src/constants/filterBy.ts
new file mode 100644
index 0000000..a034afa
--- /dev/null
+++ b/src/constants/filterBy.ts
@@ -0,0 +1,18 @@
+export const filterBy = [
+ {
+ id: 0,
+ name: "최신순",
+ },
+ {
+ id: 1,
+ name: "별점 높은순",
+ },
+ {
+ id: 2,
+ name: "별점 낮은순",
+ },
+ {
+ id: 3,
+ name: "좋아요순",
+ },
+];
diff --git a/src/pages/productdetail/[id].tsx b/src/pages/productdetail/[id].tsx
new file mode 100644
index 0000000..ddc61fd
--- /dev/null
+++ b/src/pages/productdetail/[id].tsx
@@ -0,0 +1,5 @@
+import ProductDetailPageLayout from "@/components/productdetail/ProductDetailPageLayout";
+
+export default function ProductDetailPage() {
+ return ;
+}
diff --git a/src/stories/DetailCard.stories.ts b/src/stories/DetailCard.stories.ts
index 9b1801b..ac1d56e 100644
--- a/src/stories/DetailCard.stories.ts
+++ b/src/stories/DetailCard.stories.ts
@@ -1,66 +1,76 @@
-import type { Meta, StoryObj } from "@storybook/react";
-
-import DetailCard from "@/components/productdetail/DetailCard";
-
-const meta = {
- title: "Components/ProductDetail/DetailCard",
- component: DetailCard,
- tags: ["autodocs"],
- parameters: {
- layout: "centered",
- },
-} satisfies Meta;
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Product: Story = {
- args: {
- productData: {
- id: 1,
- name: "Sony WH-1000XM3",
- description:
- "한층 업그레이된 고급 노이즈 캔슬과 상황에 맞게 조정되는 스마트 청취 기능을 갖춘 WH-1000XM3 헤드폰으로 더욱 깊은 고요 속에서 청취할 수 있습니다.",
- image: "/images/headset.svg",
- rating: 0,
- reviewCount: 0,
- favoriteCount: 0,
- categoryId: 1,
- createdAt: "",
- updatedAt: "",
- writerId: 1,
- isFavorite: true,
- category: {
- id: 1,
- name: "전자기기",
- },
- },
- isMyProduct: false,
- },
-};
-
-export const MyProduct: Story = {
- args: {
- productData: {
- id: 1,
- name: "Sony WH-1000XM3",
- description:
- "한층 업그레이된 고급 노이즈 캔슬과 상황에 맞게 조정되는 스마트 청취 기능을 갖춘 WH-1000XM3 헤드폰으로 더욱 깊은 고요 속에서 청취할 수 있습니다.",
- image: "/images/headset.svg",
- rating: 0,
- reviewCount: 0,
- favoriteCount: 0,
- categoryId: 1,
- createdAt: "",
- updatedAt: "",
- writerId: 1,
- isFavorite: true,
- category: {
- id: 1,
- name: "전자기기",
- },
- },
- isMyProduct: true,
- },
-};
+import type { Meta, StoryObj } from "@storybook/react";
+
+import DetailCard from "@/components/productdetail/DetailCard";
+
+const meta = {
+ title: "Components/ProductDetail/DetailCard",
+ component: DetailCard,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Product: Story = {
+ args: {
+ productData: {
+ id: 1,
+ name: "Sony WH-1000XM3",
+ description:
+ "한층 업그레이된 고급 노이즈 캔슬과 상황에 맞게 조정되는 스마트 청취 기능을 갖춘 WH-1000XM3 헤드폰으로 더욱 깊은 고요 속에서 청취할 수 있습니다.",
+ image: "/images/headset.svg",
+ rating: 0,
+ reviewCount: 0,
+ favoriteCount: 0,
+ categoryId: 1,
+ createdAt: "",
+ updatedAt: "",
+ writerId: 1,
+ isFavorite: true,
+ category: {
+ id: 1,
+ name: "전자기기",
+ },
+ categoryMetric: {
+ rating: 4.5,
+ favoriteCount: 1000,
+ reviewCount: 100,
+ },
+ },
+ isMyProduct: false,
+ },
+};
+
+export const MyProduct: Story = {
+ args: {
+ productData: {
+ id: 1,
+ name: "Sony WH-1000XM3",
+ description:
+ "한층 업그레이된 고급 노이즈 캔슬과 상황에 맞게 조정되는 스마트 청취 기능을 갖춘 WH-1000XM3 헤드폰으로 더욱 깊은 고요 속에서 청취할 수 있습니다.",
+ image: "/images/headset.svg",
+ rating: 0,
+ reviewCount: 0,
+ favoriteCount: 0,
+ categoryId: 1,
+ createdAt: "",
+ updatedAt: "",
+ writerId: 1,
+ isFavorite: true,
+ category: {
+ id: 1,
+ name: "전자기기",
+ },
+ categoryMetric: {
+ rating: 4.5,
+ favoriteCount: 1000,
+ reviewCount: 100,
+ },
+ },
+ isMyProduct: true,
+ },
+};
diff --git a/src/stories/ProductDetailPageLayout.stories.ts b/src/stories/ProductDetailPageLayout.stories.ts
new file mode 100644
index 0000000..2908890
--- /dev/null
+++ b/src/stories/ProductDetailPageLayout.stories.ts
@@ -0,0 +1,18 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import ProductDetailPageLayout from "@/components/productdetail/ProductDetailPageLayout";
+
+const meta = {
+ title: "Components/ProductDetail/ProductDetailPageLayout",
+ component: ProductDetailPageLayout,
+ tags: ["autodocs"],
+ parameters: {
+ layout: "centered",
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const ProductDetailPage: Story = {};
diff --git a/src/types/product.ts b/src/types/product.ts
index 1f6b060..ee335f2 100644
--- a/src/types/product.ts
+++ b/src/types/product.ts
@@ -1,55 +1,61 @@
-import { Response } from "./common";
-
-export type ProductDetail = {
- id: number;
- name: string;
- description: string;
- image: string;
- rating: number;
- reviewCount: number;
- favoriteCount: number;
- categoryId: number;
- createdAt: string;
- updatedAt: string;
- writerId: number;
- isFavorite: boolean;
- category: {
- id: number;
- name:
- | "음악"
- | "영화/드라마"
- | "강의/책"
- | "호텔"
- | "가구/인테리어"
- | "식당"
- | "전자기기"
- | "화장품"
- | "의류/잡화"
- | "앱";
- };
-};
-
-export type ReviewResponse = Response;
-
-export type Review = {
- user: {
- image: string | null;
- nickname: string;
- id: number;
- };
- reviewImages: ReviewImages[];
- productId: number;
- userId: number;
- updatedAt: string;
- createdAt: string;
- isLiked: boolean;
- likeCount: number;
- content: string;
- rating: number;
- id: number;
-};
-
-export type ReviewImages = {
- source: string;
- id: number;
-};
+import { Response } from "./common";
+
+export type ProductDetail = {
+ id: number;
+ name: string;
+ description: string;
+ image: string;
+ rating: number;
+ reviewCount: number;
+ favoriteCount: number;
+ categoryId: number;
+ createdAt: string;
+ updatedAt: string;
+ writerId: number;
+ isFavorite: boolean;
+ category: {
+ id: number;
+ name:
+ | "음악"
+ | "영화/드라마"
+ | "강의/책"
+ | "호텔"
+ | "가구/인테리어"
+ | "식당"
+ | "전자기기"
+ | "화장품"
+ | "의류/잡화"
+ | "앱";
+ };
+ categoryMetric: {
+ rating: number;
+ favoriteCount: number;
+ reviewCount: number;
+ };
+};
+
+export type ReviewResponse = Response;
+
+export type Review = {
+ user: {
+ image: string | null;
+ nickname: string;
+ id: number;
+ };
+ reviewImages: ReviewImages[];
+ productId: number;
+ userId: number;
+ updatedAt: string;
+ createdAt: string;
+ isLiked: boolean;
+ likeCount: number;
+ content: string;
+ rating: number;
+ id: number;
+};
+
+export type ReviewImages = {
+ source: string;
+ id: number;
+};
+