diff --git a/src/API/CommentsAPI.js b/src/API/CommentsAPI.js
new file mode 100644
index 000000000..a64b46d6d
--- /dev/null
+++ b/src/API/CommentsAPI.js
@@ -0,0 +1,12 @@
+export async function getComments(productId = 9, limit = 5) {
+ const path = `/products/${productId}/comments?`;
+ const query = `limit=${limit}`;
+ const response = await fetch(
+ `https://panda-market-api.vercel.app${path}${query}`
+ );
+ if (!response.ok) {
+ throw new Error("댓글을 불러오는데 실패했습니다");
+ }
+ const result = await response.json();
+ return result;
+}
diff --git a/src/App.js b/src/App.js
index 976aec7f7..ed24fcad5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,22 +1,27 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./Layout/Header.jsx";
-import HomePage from "./pages/HomePage/HomePage";
+import HomePage from "./pages/HomePage/HomePage.jsx";
import AddItemPage from "./pages/AddItemPage/AddItemPage.jsx";
import MarketPage from "./pages/MarketPage/MarketPage.jsx";
+import ProductDetail from "./pages/MarketPage/components/ProductDetail.jsx";
import { React } from "react";
+import { ItemProvider } from "../src/context/ItemContext.jsx";
function App() {
return (
-
-
-
-
- } />
- } />
- } />
-
-
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
);
}
diff --git a/src/context/ItemContext.jsx b/src/context/ItemContext.jsx
new file mode 100644
index 000000000..0741f9c6a
--- /dev/null
+++ b/src/context/ItemContext.jsx
@@ -0,0 +1,24 @@
+import React, { createContext, useState, useEffect } from "react";
+import { getProducts } from "../API/ItemAPI";
+
+export const ItemContext = createContext();
+
+export const ItemProvider = ({ children }) => {
+ const [itemList, setItemList] = useState([]);
+
+ useEffect(() => {
+ const fetchAndProcessData = async () => {
+ try {
+ const data = await getProducts();
+ setItemList(data.list);
+ } catch (error) {
+ console.error("데이터를 가져오지 못했습니다", error);
+ }
+ };
+ fetchAndProcessData();
+ }, []);
+
+ return (
+ {children}
+ );
+};
diff --git a/src/images/icons/ic_seemore.svg b/src/images/icons/ic_seemore.svg
new file mode 100644
index 000000000..51b03fba0
--- /dev/null
+++ b/src/images/icons/ic_seemore.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/pages/MarketPage/MarketPage.css b/src/pages/MarketPage/MarketPage.css
index 9f87315a7..036180e22 100644
--- a/src/pages/MarketPage/MarketPage.css
+++ b/src/pages/MarketPage/MarketPage.css
@@ -1,5 +1,82 @@
.MarketPage {
display: flex;
flex-direction: column;
- gap: 100px;
+ gap: 40px;
+ margin: 0px auto;
+ width: 1200px;
+}
+
+.allItemsSectionHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.bestItemsContainer {
+ padding-top: 17px;
+ padding-bottom: 24px;
+ list-style-type: none;
+}
+
+.bestItemsCardSection {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 24px;
+}
+
+.sectionTitle {
+ color: #111827;
+ font-weight: bold;
+ font-size: 20px;
+ line-height: normal;
+}
+
+.itemCard {
+ color: #1f2937;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.itemCardThumbnail {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ border-radius: 16px;
+ overflow: hidden;
+ aspect-ratio: 1;
+ margin-bottom: 16px;
+}
+
+.itemSummary {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ flex-grow: 1;
+}
+
+.itemName {
+ font-size: 16px;
+ font-weight: 400;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.itemPrice {
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.favoriteCount {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: #4b5563;
+ font-size: 12px;
+}
+
+.allItemsCardSection {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 32px 8px;
}
diff --git a/src/pages/MarketPage/components/AllCard.jsx b/src/pages/MarketPage/components/AllCard.jsx
index cc81989f8..c7a78087f 100644
--- a/src/pages/MarketPage/components/AllCard.jsx
+++ b/src/pages/MarketPage/components/AllCard.jsx
@@ -1,19 +1,28 @@
-import { NavLink } from "react-router-dom";
+import { useContext } from "react";
+import { Link } from "react-router-dom";
import "./style/AllCard.css";
-
-function getLinkStyle({ isActive }) {
- return {
- color: isActive ? "#3692ff" : "",
- textDecoration: isActive ? "none" : "",
- };
-}
+import ItemCard from "./ItemCard";
+import { ItemContext } from "../../../context/ItemContext";
function AllCard() {
+ const itemList = useContext(ItemContext);
+
return (
-
-
- 상품 등록하기
-
+
+
+
판매 중인 상품
+
+ 상품 등록하기
+
+
+
+
+ {itemList.map((item) => (
+
+
+
+ ))}
+
);
}
diff --git a/src/pages/MarketPage/components/BestCard.jsx b/src/pages/MarketPage/components/BestCard.jsx
index 75809e346..64c68b6cf 100644
--- a/src/pages/MarketPage/components/BestCard.jsx
+++ b/src/pages/MarketPage/components/BestCard.jsx
@@ -1,6 +1,7 @@
-import { Fragment, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import "./style/BestCard.css";
import { getProducts } from "../../../API/ItemAPI";
+import ItemCard from "./ItemCard";
export const getTopFavoriteItems = (data, n) => {
const sortedItems = data.list.sort(
@@ -9,17 +10,6 @@ export const getTopFavoriteItems = (data, n) => {
return sortedItems.slice(0, n);
};
-export function CardItem({ item }) {
- return (
-
-
- {item.name}
- {item.price}원
- {item.ownerId}
-
- );
-}
-
function BestCard() {
const [itemList, setItemList] = useState([]);
@@ -37,15 +27,19 @@ function BestCard() {
}, []);
return (
-
- {itemList.map((item) => {
- return (
- -
-
-
- );
- })}
-
+
+
베스트 상품
+
+
+ {itemList.map((item) => {
+ return (
+
+
+
+ );
+ })}
+
+
);
}
diff --git a/src/pages/MarketPage/components/Comments.jsx b/src/pages/MarketPage/components/Comments.jsx
new file mode 100644
index 000000000..578b4130c
--- /dev/null
+++ b/src/pages/MarketPage/components/Comments.jsx
@@ -0,0 +1,66 @@
+import { useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+import { getComments } from "../../../API/CommentsAPI";
+
+const timeSince = (date) => {
+ const now = new Date();
+ const updatedAt = new Date(date);
+ const seconds = Math.floor((now - updatedAt) / 1000);
+
+ let interval = Math.floor(seconds / 31536000);
+ if (interval > 1) return `${interval}년 전`;
+
+ interval = Math.floor(seconds / 2592000);
+ if (interval > 1) return `${interval}개월 전`;
+
+ interval = Math.floor(seconds / 86400);
+ if (interval > 1) return `${interval}일 전`;
+
+ interval = Math.floor(seconds / 3600);
+ if (interval > 1) return `${interval}시간 전`;
+
+ interval = Math.floor(seconds / 60);
+ if (interval > 1) return `${interval}분 전`;
+
+ return `${Math.floor(seconds)}초 전`;
+};
+
+function CommentItem({ item }) {
+ return (
+
+
{item.content}
+
+
{item.nickname}
+
{timeSince(item.updatedAt)}
+
+ );
+}
+
+function Comment() {
+ const { id } = useParams(); // URL에서 id 값을 가져옴
+ const [itemList, setItemList] = useState([]);
+
+ useEffect(() => {
+ const fetchAndProcessData = async () => {
+ try {
+ const data = await getComments(id);
+ setItemList(data.list);
+ } catch (error) {
+ console.error("데이터를 가져오지 못했습니다", error);
+ }
+ };
+ fetchAndProcessData();
+ }, [id]);
+
+ return (
+
+ {itemList.map((item) => (
+ -
+
+
+ ))}
+
+ );
+}
+
+export default Comment;
diff --git a/src/pages/MarketPage/components/DetailCard.jsx b/src/pages/MarketPage/components/DetailCard.jsx
new file mode 100644
index 000000000..30d100e88
--- /dev/null
+++ b/src/pages/MarketPage/components/DetailCard.jsx
@@ -0,0 +1,29 @@
+import React, { useContext } from "react";
+import { useParams } from "react-router-dom";
+import { ItemContext } from "../../../context/ItemContext";
+import { ReactComponent as HeartIcon } from "../../../images/icons/ic_heart.svg";
+import { ReactComponent as SeeMoreIcon } from "../../../images/icons/ic_seemore.svg";
+function DetailCard() {
+ const { id } = useParams();
+ const itemList = useContext(ItemContext);
+ const item = itemList.find((item) => item.id === parseInt(id));
+
+ if (!item) {
+ return
Product not found
;
+ }
+
+ return (
+
+
+
{item.name}
+
+
{item.price}
+
{item.description}
+
{item.tags}
+
+
{item.favoriteCount}
+
+ );
+}
+
+export default DetailCard;
diff --git a/src/pages/MarketPage/components/Inquiry.jsx b/src/pages/MarketPage/components/Inquiry.jsx
new file mode 100644
index 000000000..95a3e6f7e
--- /dev/null
+++ b/src/pages/MarketPage/components/Inquiry.jsx
@@ -0,0 +1,24 @@
+import React, { useState } from "react";
+
+function Inquiry() {
+ const [inputValue, setInputValue] = useState("");
+
+ const handleInputChange = (event) => {
+ setInputValue(event.target.value);
+ };
+
+ return (
+
+ 문의하기
+
+
+
+ );
+}
+
+export default Inquiry;
diff --git a/src/pages/MarketPage/components/ItemCard.jsx b/src/pages/MarketPage/components/ItemCard.jsx
index e69de29bb..aa7c38fe2 100644
--- a/src/pages/MarketPage/components/ItemCard.jsx
+++ b/src/pages/MarketPage/components/ItemCard.jsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { ReactComponent as HeartIcon } from "../../../images/icons/ic_heart.svg";
+
+function ItemCard({ item }) {
+ return (
+
+
+
+
{item.name}
+
{item.price}원
+
+
+
+ {item.favoriteCount}
+
+
+
+ );
+}
+
+export default ItemCard;
diff --git a/src/pages/MarketPage/components/ProductDetail.jsx b/src/pages/MarketPage/components/ProductDetail.jsx
new file mode 100644
index 000000000..1f6aead5c
--- /dev/null
+++ b/src/pages/MarketPage/components/ProductDetail.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+import DetailCard from "./DetailCard";
+import Inquiry from "./Inquiry";
+import Comment from "./Comments";
+
+function ProductDetail() {
+ return (
+
+
+
+
+
+ );
+}
+
+export default ProductDetail;
diff --git a/src/pages/MarketPage/components/style/AllCard.css b/src/pages/MarketPage/components/style/AllCard.css
index 20852c91a..6596345b3 100644
--- a/src/pages/MarketPage/components/style/AllCard.css
+++ b/src/pages/MarketPage/components/style/AllCard.css
@@ -1,4 +1,4 @@
-.register a {
+.loginLinkButton {
text-decoration: none;
padding: 12px 23px 12px 23px;
border-radius: 8px;