diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777cc4..000000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a37..000000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a654..000000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/src/Api.js b/src/Api.js deleted file mode 100644 index 3523179e0..000000000 --- a/src/Api.js +++ /dev/null @@ -1,13 +0,0 @@ -//product item list api -export async function getItems({ - pageSize = 12, - orderBy = "recent", - page = "1", -}) { - const query = `pageSize=${pageSize}&orderBy=${orderBy}&page=${page}`; - const response = await fetch( - `https://panda-market-api.vercel.app/products?${query}` - ); - const body = await response.json(); - return body; -} diff --git a/src/api/Api.js b/src/api/Api.js new file mode 100644 index 000000000..f5e503c8b --- /dev/null +++ b/src/api/Api.js @@ -0,0 +1,26 @@ +//product item list api +const BASEURL = "https://panda-market-api.vercel.app"; +export async function getItems({ + pageSize = 12, + orderBy = "recent", + page = "1", +}) { + const query = `pageSize=${pageSize}&orderBy=${orderBy}&page=${page}`; + const response = await fetch(`${BASEURL}/products?${query}`); + const body = await response.json(); + return body; +} + +//product item detail api +export async function getItemDetail({ id }) { + const response = await fetch(`${BASEURL}/products/${id}`); + const body = await response.json(); + return body; +} + +//product item comment api +export async function getItemDetailComment({ id }) { + const response = await fetch(`${BASEURL}/products/${id}/comments?limit=30`); + const body = await response.json(); + return body; +} diff --git a/src/component/Header.js b/src/component/Header.js index a710795ea..57de4c36a 100644 --- a/src/component/Header.js +++ b/src/component/Header.js @@ -2,7 +2,7 @@ import { Link, NavLink } from "react-router-dom"; import LogoImg from "../images/logo.png"; import "../css/common.css"; -function getLink({ isActive }) { +function getLinkStyle({ isActive }) { return { color: isActive ? "#3182f6" : undefined, }; @@ -17,10 +17,10 @@ function Header() { 로고 이미지 diff --git a/src/component/Product/BasicProducts.js b/src/component/Product/BasicProducts.js index b2247f37b..6ece26d33 100644 --- a/src/component/Product/BasicProducts.js +++ b/src/component/Product/BasicProducts.js @@ -1,4 +1,4 @@ -import { getItems } from "../../Api.js"; +import { getItems } from "../../api/Api.js"; import Select from "react-select"; import { useState, useEffect, React } from "react"; import ProductList from "./ProductList.js"; @@ -16,25 +16,21 @@ function BasicProducts() { { value: "favorite", label: "좋아요순", className: "order-value-favorite" }, ]; const customStyles = { - control: (styles) => ({ - ...styles, + control: (provided) => ({ borderRadius: "10px", border: "1px solid #ddd", boxShadow: "none", padding: "5px 6px", - }), - option: (styles, { isFocused, isSelected }) => ({ - ...styles, "&:hover": { color: "#7db4f4", }, - color: isSelected ? "#3182f6" : undefined, - zIndex: 1, - background: isFocused ? "#fff" : isSelected ? "#fff" : undefined, }), - input: (styles) => ({ + option: (styles, { isFocused, isSelected }) => ({ ...styles, - color: "transparent", + color: isSelected ? "#3182f6" : "inherit", + boderBottom: "1px solid #ddd", + zIndex: 1, + background: isFocused ? "#fff" : isSelected ? "#fff" : undefined, }), }; const handlePage = (pageNumber) => { @@ -47,8 +43,7 @@ function BasicProducts() { }; const productsLoad = async (orderBy, page) => { - const { list } = await getItems({ orderBy, page }); - const { totalCount } = await getItems({ pageSize: 12 }); + const { list, totalCount } = await getItems({ orderBy, page }); setTotalCount(totalCount); setItems(list); }; @@ -61,7 +56,7 @@ function BasicProducts() {

전체 상품

- +
diff --git a/src/component/Product/BestProduct.js b/src/component/Product/BestProduct.js index 38f504d26..c669d6361 100644 --- a/src/component/Product/BestProduct.js +++ b/src/component/Product/BestProduct.js @@ -1,6 +1,6 @@ import ProductList from "./ProductList.js"; import { useState, useEffect, React } from "react"; -import { getItems } from "../../Api.js"; +import { getItems } from "../../api/Api.js"; function BestProducts() { const [items, setItems] = useState([]); diff --git a/src/component/Product/FileInput.js b/src/component/Product/FileInput.js index e2ec31088..114277d37 100644 --- a/src/component/Product/FileInput.js +++ b/src/component/Product/FileInput.js @@ -52,12 +52,13 @@ function FormInput({ name, value, onChange }) { return (
- 이미지 등록 이미지 inputRef.current?.click()} //useRef 사용해서 이미지 클릭시 input 태그가 클릭 - className="img_placeholder" - /> + {preview && (
diff --git a/src/component/Product/ProductDetailComment.js b/src/component/Product/ProductDetailComment.js new file mode 100644 index 000000000..4a9ab3fbd --- /dev/null +++ b/src/component/Product/ProductDetailComment.js @@ -0,0 +1,54 @@ +import { useState } from "react"; + +function CommentItem({ item }) { + return ( +
+

{item.content}

+
+ 유저 썸네일 +
+

{item.writer.nickname}

+ {item.updatedAt} +
+
+
+ ); +} + +function ProductDetailComment({ comments }) { + const [inquiry, setinquiry] = useState(""); + const handleInputChange = (e) => { + setinquiry(e.target.value); + }; + const abledButton = () => { + //등록 버튼 활성화 + if (inquiry) { + return true; + } else { + return false; + } + }; + return ( +
+
+

문의하기

+ + +
+
+ {comments.length === 0 ? ( +
+

등록된 댓글이 없습니다.

+
+ ) : ( + comments?.map((item) => ) + )} +
+
+ ); +} + +export default ProductDetailComment; diff --git a/src/component/Product/ProductList.js b/src/component/Product/ProductList.js index 0a01a2efb..475b0baa2 100644 --- a/src/component/Product/ProductList.js +++ b/src/component/Product/ProductList.js @@ -1,20 +1,22 @@ +import { Link } from "react-router-dom"; import heartIcon from "../../images/Icon.png"; function ProdictListItem({ item }) { const price = item.price.toLocaleString("ko-KR"); return ( -
-
- {item.name} + +
+
+ {item.name} +
+

{item.name}

+

{price}원

+

+ 좋아요 이미지 {item.favoriteCount} +

-

{item.name}

-

{price}원

-

- {" "} - 좋아요 이미지 {item.favoriteCount} -

-
+ ); } diff --git a/src/component/Product/Tags.js b/src/component/Product/Tags.js index a047731f3..747e53ac5 100644 --- a/src/component/Product/Tags.js +++ b/src/component/Product/Tags.js @@ -37,7 +37,7 @@ function Tags({ name, value, onChange }) { />
    {addTags && - addTags.map((tag, index) => ( + addTags?.map((tag, index) => (
  • {tag} { + const [productList, setProductList] = useState([]); + const [totalCount, setTotalCount] = useState(0); + + useEffect(() => { + const fetchProductList = async () => { + try { + const { list, totalCount } = await getItems(loadOptions); + setProductList(list); + setTotalCount(totalCount); + } catch (error) { + console.error("상품 목록을 불러오는 도중 오류가 발생했습니다:", error); + } + }; + + fetchProductList(); + }, [loadOptions]); + + return { productList, totalCount }; +}; + +export default useProductList; diff --git a/src/css/Product.css b/src/css/Product.css index 5292cad05..a127a7e2a 100644 --- a/src/css/Product.css +++ b/src/css/Product.css @@ -122,7 +122,8 @@ margin-bottom: 20px; } -#addItem_btn { +#addItem_btn, +.detail-inquiry button { float: right; font-size: 16px; padding: 9px 20px; @@ -132,7 +133,8 @@ background: var(--blue); cursor: pointer; } -#addItem_btn[disabled] { +#addItem_btn[disabled], +.detail-inquiry button[disabled] { background: var(--gray); } .addItem_header h2 { @@ -148,6 +150,9 @@ .FileInput { margin-bottom: 20px; } +.FileInput label { + display: inline-block; +} .preview_area { width: 282px; height: 282px; @@ -175,7 +180,8 @@ cursor: pointer; } .AddItemForm input, -.AddItemForm textarea { +.AddItemForm textarea, +.detail-inquiry textarea { width: 100%; border: none; background: var(--coolGray); @@ -183,9 +189,11 @@ padding: 16px 24px; border-radius: 12px; margin-bottom: 24px; + outline: none; } .AddItemForm input::placeholder, -.AddItemForm textarea::placeholder { +.AddItemForm textarea::placeholder, +.detail-inquiry textarea::placeholder { color: var(--gray); } .AddItemForm textarea { @@ -209,3 +217,120 @@ .InputTags ul li img { cursor: pointer; } + +/*Detail*/ +.product-detail { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 24px; + padding-bottom: 32px; + border-bottom: 1px solid #e5e7eb; + margin-bottom: 24px; +} +.detail-img { + border-radius: 16px; + width: 100%; +} +.detail-desc { + position: relative; +} +.detail-desc h2 { + font-size: 24px; + margin-bottom: 16px; +} +.detail-desc h3 { + font-size: 40px; + padding-bottom: 16px; + border-bottom: 1px solid #e5e7eb; + margin-bottom: 16px; + line-height: 1; +} +.detail-desc p { + margin-bottom: 24px; +} +.detail-desc span { + display: block; + font-size: 14px; + color: #4b5563; + margin-bottom: 8px; +} +.detail-desc ul { + display: flex; + gap: 8px; +} +.detail-desc ul li { + background: #f3f4f6; + border-radius: 30px; + font-size: 16px; + padding: 6px 16px; +} +.detail-favorite { + border: 1px solid #e5e7eb; + font-weight: 500; + border-radius: 30px; + padding: 4px 12px; + display: flex; + gap: 5px; + font-size: 16px; + color: #6b7280; + align-items: center; + width: fit-content; + position: absolute; + bottom: 0; +} + +/*detail comment*/ +.detail-inquiry { + margin-bottom: 80px; +} +.detail-inquiry p { + font-weight: bold; + margin-bottom: 16px; +} +.detail-inquiry textarea { + resize: none; + height: 105px; +} +.comment-item { + margin-bottom: 24px; + border-bottom: 1px solid #e5e7eb; + padding-bottom: 24px; +} + +.user-info { + display: flex; + gap: 8px; + margin-top: 24px; + align-items: center; +} +.user-info img { + width: 100%; + max-width: 40px; +} +.user-name p { + font-size: 14px; + color: #4b5563; +} +.user-name span { + font-size: 12px; + color: #9ca3af; +} +.detail-button a { + background: var(--blue); + color: #fff; + padding: 12px 38.5px; + display: flex; + border-radius: 30px; + gap: 5px; + width: fit-content; + margin: 0 auto; +} + +.empty-comment { + text-align: center; + border-top: 1px solid #e5e7eb; + border-bottom: 1px solid #e5e7eb; + padding: 40px 0; + color: #9ca3af; + margin-bottom: 40px; +} diff --git a/src/css/common.css b/src/css/common.css index f12009c1b..e63adbeec 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -35,7 +35,7 @@ body { .container { width: 100%; max-width: 1200px; - margin: 0 auto; + margin: 0 auto 60px; } .img_responsive { width: auto; diff --git a/src/images/.DS_Store b/src/images/.DS_Store index 832a18c0d..8f2c8460c 100644 Binary files a/src/images/.DS_Store and b/src/images/.DS_Store differ diff --git a/src/images/backPage.png b/src/images/backPage.png new file mode 100644 index 000000000..6c5cbec22 Binary files /dev/null and b/src/images/backPage.png differ diff --git a/src/images/favoriteCount.png b/src/images/favoriteCount.png new file mode 100644 index 000000000..abf0a75bc Binary files /dev/null and b/src/images/favoriteCount.png differ diff --git a/src/images/thumbnail.png b/src/images/thumbnail.png new file mode 100644 index 000000000..ae27943e2 Binary files /dev/null and b/src/images/thumbnail.png differ diff --git a/src/page/App.js b/src/page/App.js index adfa3b2f7..ff1dd2456 100644 --- a/src/page/App.js +++ b/src/page/App.js @@ -7,6 +7,7 @@ import NotFound from "./NotFound"; import Join from "../component/member/Join"; import Footer from "../component/Footer"; import HomePage from "./HomePage"; +import ProductDetail from "./ProductDetail"; function App() { return ( @@ -18,6 +19,7 @@ function App() { } /> } /> + } /> } /> } /> diff --git a/src/page/ProductDetail.js b/src/page/ProductDetail.js new file mode 100644 index 000000000..3c74c17aa --- /dev/null +++ b/src/page/ProductDetail.js @@ -0,0 +1,65 @@ +import { Link, useParams } from "react-router-dom"; +import { getItemDetail, getItemDetailComment } from "../api/Api"; +import { useEffect, useState } from "react"; +import favoriteCount from "./../images/favoriteCount.png"; +import backPage from "./../images/backPage.png"; +import ProductDetailComment from "./../component/Product/ProductDetailComment"; + +function ProductDetail() { + const [view, setView] = useState(null); + const [comment, setComment] = useState(null); + const { id } = useParams(); + + const DetailLoad = async (id) => { + const item = await getItemDetail({ id }); + const { list } = await getItemDetailComment({ id }); + setComment(list); + setView(item); + }; + useEffect(() => { + DetailLoad(id); + }, [id]); + + if (!view) { + return null; + } + + if (!comment) { + return null; + } + + return ( +
    +
    + 상품 이미지 +
    +

    {view.name}

    +

    {view.price.toLocaleString("ko-KR")}원

    +

    + 상품소개 + {view.description} +

    + 상품 태그 +
      + {view.tags.map((tag, index) => ( +
    • #{tag}
    • + ))} +
    +
    + 좋아요 하트 + {view.favoriteCount} +
    +
    +
    + +
    + + 목록으로 돌아가기 + 목록으로 돌아가기 + +
    +
    + ); +} + +export default ProductDetail;