diff --git a/src/App.jsx b/src/App.jsx index 5b686173..3480b905 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,7 @@ import Items from "./pages/Items"; import "./App.css"; import Header from "./components/Header"; import AddItem from "./pages/AddItem"; +import ItemDetail from "./pages/ItemDetail"; /** * @@ -18,6 +19,7 @@ function App() { 자유게시판} /> } /> } /> + } /> ); diff --git a/src/api/productApi.js b/src/api/productApi.js index 173da04d..a7741d1f 100644 --- a/src/api/productApi.js +++ b/src/api/productApi.js @@ -1,6 +1,31 @@ +// 전체를 가져오며, params를 사용하여 특정 조건에 맞는 제품을 가져올 수 있ㄷ ㅏ. export const getProducts = async (params = {}) => { const query = new URLSearchParams(params).toString(); const response = await fetch(`https://panda-market-api.vercel.app/products?${query}`); const data = await response.json(); return data; }; + +// 특정 상품에 대한 정보를 가져올 수 있다. +export const getProductById = async (productId) => { + const response = await fetch(`https://panda-market-api.vercel.app/products/${productId}`); + + if (!response.ok) { + throw new Error("Failed to fetch product"); + } + + const data = await response.json(); + return data; +}; + +// 댓글 리스트 가져오는 함수 :: 필수값: id/limit +export const getComments = async (productId, limit) => { + const response = await fetch(`https://panda-market-api.vercel.app/products/${productId}/comments?limit=${limit}`); + + if (!response.ok) { + throw new Error("Failed to fetch comments"); + } + + const data = await response.json(); + return data; +}; diff --git a/src/assets/Img_inquiry_empty.png b/src/assets/Img_inquiry_empty.png new file mode 100644 index 00000000..76fe524d Binary files /dev/null and b/src/assets/Img_inquiry_empty.png differ diff --git a/src/assets/ic_back.png b/src/assets/ic_back.png new file mode 100644 index 00000000..706c218a Binary files /dev/null and b/src/assets/ic_back.png differ diff --git a/src/assets/ic_kebab.png b/src/assets/ic_kebab.png new file mode 100644 index 00000000..d4676503 Binary files /dev/null and b/src/assets/ic_kebab.png differ diff --git a/src/assets/like.png b/src/assets/like.png new file mode 100644 index 00000000..c5df7e17 Binary files /dev/null and b/src/assets/like.png differ diff --git a/src/components/BestProducts.css b/src/components/BestProducts.css index 5d410462..e37d5407 100644 --- a/src/components/BestProducts.css +++ b/src/components/BestProducts.css @@ -14,57 +14,6 @@ grid-template-columns: repeat(4, 1fr); } -.best-products__card { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - max-width: none; - gap: 16px; -} - -.best-products__image-wrapper { - width: 100%; - height: 282px; - border-radius: 16px; - overflow: hidden; -} - -.best-products__image { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 16px; -} - -.best-products__text-group { - display: flex; - /* margin-top: 8px; */ - flex-direction: column; - align-items: flex-start; - width: 100%; - text-align: left; - gap: 10px; -} - -.best-products__title-text { - font-size: 14px; - font-weight: 500; - color: #1f2937; -} - -.best-products__price { - font-size: 16px; - font-weight: 700; - color: #1f2937; -} - -.best-products__favorite { - font-size: 12px; - font-weight: 500; - color: #4b5563; -} - /* 태블릿 사이즈*/ @media (max-width: 1199px) { .best-products__grid { @@ -73,14 +22,6 @@ justify-content: space-between; gap: 16px; } - - .best-products__card { - width: 343px; - } - .best-products__image-wrapper { - width: 343px; - height: 343px; - } } /* 모바일 사이즈 */ @@ -91,11 +32,4 @@ justify-content: center; gap: 16px; } - - .best-products__card { - width: 343px; - } - .best-products__image-wrapper { - width: 343px; - } } diff --git a/src/components/BestProducts.jsx b/src/components/BestProducts.jsx index 27a29ab9..9f1ae716 100644 --- a/src/components/BestProducts.jsx +++ b/src/components/BestProducts.jsx @@ -1,6 +1,7 @@ import { getProducts } from "../api/productApi"; import { useState, useEffect, useCallback } from "react"; import "./BestProducts.css"; +import ProductCard from "./ProductCard"; /** * @@ -40,16 +41,7 @@ const BestProduct = () => {

베스트 상품

{bestProducts.slice(0, visibleCount).map((item) => ( -
-
- 0 ? item.images[0] : ""} alt={item.name} className="best-products__image" /> -
-
-

{item.name}

-

{item.price.toLocaleString()}원

-

❤️ {item.favoriteCount}

-
-
+ ))}
diff --git a/src/components/CommentList.css b/src/components/CommentList.css new file mode 100644 index 00000000..5290f38b --- /dev/null +++ b/src/components/CommentList.css @@ -0,0 +1,200 @@ +.comment-list { + margin-top: 32px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: column; + gap: 24px; +} + +.comment-list__item { + border-bottom: 1px solid #e5e7eb; + padding-bottom: 16px; + position: relative; +} + +.comment-list__content { + font-size: 14px; + font-weight: 400; + line-height: 24px; + color: #1f2937; + margin-bottom: 12px; +} + +.comment-list__info { + display: flex; + justify-content: space-between; + align-items: center; +} + +.comment-list__user { + display: flex; + align-items: center; + gap: 12px; +} + +.comment-list__user-image { + width: 32px; + height: 32px; + border-radius: 50%; +} + +.comment-list__nickname { + font-weight: 400; + font-size: 12px; + line-height: 18px; + color: #4b5563; +} + +.comment-list__created-at { + font-size: 12px; + font-weight: 400; + line-height: 18px; + color: #9ca3af; +} + +.comment-list__actions { + position: absolute; + top: 0; + right: 0; +} + +.comment-list__kebab { + width: 24px; + height: 24px; + cursor: pointer; +} + +.comment-list__dropdown-toggle { + position: relative; + display: inline-block; +} + +.comment-list__dropdown { + width: 139px; + height: 46px; + justify-content: center; + flex-direction: column; + position: absolute; + top: 24px; + right: 0; + background-color: white; + border: 1px solid #e5e7eb; + border-radius: 8px; + z-index: 10; + display: flex; +} + +.comment-list__dropdown button { + background: none; + border: none; + padding: 10px 16px; + text-align: left; + font-size: 14px; + color: #374151; + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +/* 수정하기 */ +.comment-list__edit-box { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: -35px; +} + +.comment-list__edit-textarea { + width: 100%; + height: 80px; + resize: none; + border: none; + border-radius: 12px; + padding: 16px 24px; + font-size: 14px; + font-weight: 400; + background-color: #f3f4f6; + color: #1f2937; + outline: none; +} + +.comment-list__edit-buttons { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.comment-list__edit-buttons button { + padding: 6px 16px; + border: none; + cursor: pointer; +} + +.comment-list__edit-buttons button:first-child { + background-color: transparent; + color: #737373; + font-size: 16px; + font-weight: 600; + line-height: 26px; +} + +.comment-list__edit-buttons button:last-child { + background-color: #3692ff; + padding: 12px 23px; + border-radius: 16px; + height: 42px; + color: #f3f4f6; + font-size: 16px; + font-weight: 600; + line-height: 26px; + display: flex; + align-items: center; + justify-content: center; +} + +/* 댓글, 문의가 없는 경우 */ +.comment-list__empty { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 40px; + gap: 8px; +} + +.comment-list__empty img { + width: 196px; + height: 196px; +} + +.comment-list__empty p { + font-size: 16px; + font-weight: 400; + line-height: 26px; + color: #9ca3af; +} + +.comment-list__return-button { + background-color: #4f91ff; + color: white; + border: none; + border-radius: 999px; + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; +} + +/* tablet */ +@media (max-width: 1199px) { + .comment-list { + margin: 24px; + } +} + +/* mobile */ +@media (max-width: 767px) { +} diff --git a/src/components/CommentList.jsx b/src/components/CommentList.jsx new file mode 100644 index 00000000..0801c701 --- /dev/null +++ b/src/components/CommentList.jsx @@ -0,0 +1,104 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { getComments } from "../api/productApi"; +import { formatTime } from "../utils/formatTime"; +import ic_kebab from "../assets/ic_kebab.png"; +import userImage from "../assets/userImage.png"; +import "./CommentList.css"; +import Img_inquiry_empty from "../assets/Img_inquiry_empty.png"; + +const CommentList = () => { + const { productId } = useParams(); + const [comments, setComments] = useState([]); + const [editCommentId, setEditCommentId] = useState(null); + const [openDropdownId, setOpenDropdownId] = useState(null); + + useEffect(() => { + const fetchComments = async () => { + try { + const data = await getComments(productId, 3); + setComments(data.list); + } catch (err) { + console.error("댓글을 불러오지 못했습니다.", err); + } + }; + + fetchComments(); + }, [productId]); + + // 드롭다운 토글 함수 + const toggleDropdown = (commentId) => { + setOpenDropdownId(openDropdownId === commentId ? null : commentId); + }; + + // 외부 클릭 시 드롭다운 닫기 + useEffect(() => { + const handleClickOutside = (event) => { + if (openDropdownId && !event.target.closest(".comment-list__dropdown-toggle")) { + setOpenDropdownId(null); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [openDropdownId]); + + return ( +
+ {comments.length === 0 ? ( +
+ empty +

아직 문의가 없어요

+
+ ) : ( + comments.map((comment) => ( +
+ {editCommentId === comment.id ? ( +
+