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.name}

-

{item.price}원

-

{item.ownerId}

-
- ); -} - function BestCard() { const [itemList, setItemList] = useState([]); @@ -37,15 +27,19 @@ function BestCard() { }, []); 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.name} +

    {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 ( + + ); +} + +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.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.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;