diff --git a/public/icons/none_review.svg b/public/icons/none_review.svg new file mode 100644 index 0000000..1b664f0 --- /dev/null +++ b/public/icons/none_review.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/common/activitycard/ActivityCard.tsx b/src/components/common/activitycard/ActivityCard.tsx index 31887bc..a74a50b 100644 --- a/src/components/common/activitycard/ActivityCard.tsx +++ b/src/components/common/activitycard/ActivityCard.tsx @@ -41,7 +41,7 @@ export default function ActivityCard({ const DataList: DataListType = { rate: { label: ["남긴", "별점 평균"], - img: "/icons/star.svg", + img: "/icons/star_on.svg", data: myRateAvg?.toFixed(1), }, review: { diff --git a/src/components/common/productcard/ProductCard.tsx b/src/components/common/productcard/ProductCard.tsx index 4431f6e..dfec661 100644 --- a/src/components/common/productcard/ProductCard.tsx +++ b/src/components/common/productcard/ProductCard.tsx @@ -17,7 +17,7 @@ export default function ProductCard({ likeCount, rate, }: Props) { - const starIconSrc = "/icons/star.svg"; + const starIconSrc = "/icons/star_on.svg"; return (
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} - -
-
- {label} -
- - {type === "rate" ? productData : productData?.toLocaleString()} - -
-
-
-
- {label} -
- - {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} + +
+
+ {label} +
+ + {type === "rate" ? productData : productData?.toLocaleString()} + +
+
+
+
+ {label} +
+ + {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 ( -
-
- {name} -
-
-
- - -
-
-
- - {name} - - -
- - -
-
- {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 ( +
+
+ {name} +
+
+
+ + +
+
+
+ + {name} + + +
+ + +
+
+ {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 ( +
+
+ none +
+ + {text} + +
+ ); +} 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) => ( -
- {data.source} -
- ))} -
-
-
-
{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) => ( +
+ {data.source} +
+ ))} +
+
+
+
{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; +}; +