From 7e58133f65ae20ceb6149c1a0b99950d1c2b962a Mon Sep 17 00:00:00 2001 From: Hyun Date: Mon, 5 May 2025 11:03:20 +0900 Subject: [PATCH 01/32] =?UTF-8?q?docs:=20=EC=A7=84=ED=96=89=EC=83=81?= =?UTF-8?q?=ED=99=A9=20=EC=B2=B4=ED=81=AC=EC=9A=A9=EB=8F=84=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a32f37fb..17151b88 100644 --- a/README.md +++ b/README.md @@ -32,4 +32,4 @@ ## [심화 요구사항] - [x] 페이지 네이션 기능을 구현합니다. -- [ ] 반응형으로 보여지는 물품들의 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다. +- [x] 반응형으로 보여지는 물품들의 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다. From 382db983402850f4623fd203ed6a04fc78b6442f Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:41:09 +0900 Subject: [PATCH 02/32] =?UTF-8?q?styles:=20reset=20css=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 72 ++++++++++++++++------------------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/src/App.css b/src/App.css index e6d9159a..27d2f768 100644 --- a/src/App.css +++ b/src/App.css @@ -1,61 +1,31 @@ -.header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 16px; - width: 100%; - height: 70px; -} - -.header__logo { - display: flex; - align-items: center; - margin-left: 200px; +/* Reset CSS */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; } -.header__logo-img { - width: 153px; - height: 51px; -} - -.header__nav-container { - margin-left: 24px; +html, +body { + width: 100%; + height: 100%; + font-family: Arial, sans-serif; } -.header__nav { - display: flex; - gap: 16px; +img { + display: block; + max-width: 100%; + height: auto; } -.header__nav a { - font-size: 18px; - font-weight: 700; - line-height: 26px; +a { + color: inherit; text-decoration: none; - color: #4b5563; -} - -.active { - font-weight: bold; - color: #3692ff; -} - -.header__right .header__user-img { - width: 40px; - height: 40px; - margin-right: 40px; -} - -@media (max-width: 1199px) { - .header__logo { - display: flex; - margin-left: 0; - } } -@media (max-width: 767px) { - .header__logo { - display: flex; - margin-left: 0; - } +button { + background: none; + border: none; + cursor: pointer; + font: inherit; } From 3725fb03aa3b9de4f9d964afb250115544482e06 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:41:39 +0900 Subject: [PATCH 03/32] =?UTF-8?q?refactor:=20Header=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index d4e1ed87..5b686173 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,8 +1,7 @@ -import { BrowserRouter, Routes, Route, NavLink, Navigate } from "react-router-dom"; +import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import Items from "./pages/Items"; import "./App.css"; -import userImage from "./assets/userImage.png"; -import logo from "./assets/pandaLogo.png"; +import Header from "./components/Header"; import AddItem from "./pages/AddItem"; /** @@ -13,27 +12,7 @@ import AddItem from "./pages/AddItem"; function App() { return ( -
-
-
- - logo - -
- -
- -
-
- -
- user -
-
-
+
} /> 자유게시판} /> From 18630be0c325dbd5b4732692a170bc960f6ba1ca Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:42:02 +0900 Subject: [PATCH 04/32] =?UTF-8?q?chore:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/dropdown.png | Bin 0 -> 473 bytes src/assets/mobileFilter.png | Bin 0 -> 540 bytes src/assets/mobileLogo.png | Bin 0 -> 1767 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/assets/dropdown.png create mode 100644 src/assets/mobileFilter.png create mode 100644 src/assets/mobileLogo.png diff --git a/src/assets/dropdown.png b/src/assets/dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..8d0254ea0adc492cbed41ab5f0633126074d2c8e GIT binary patch literal 473 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 Date: Tue, 6 May 2025 21:42:34 +0900 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EC=97=90=EC=84=9C=20params=20=EB=A5=BC=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=20=ED=98=B8=EC=B6=9C=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/productApi.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/productApi.js b/src/api/productApi.js index 1b964ba5..173da04d 100644 --- a/src/api/productApi.js +++ b/src/api/productApi.js @@ -1,5 +1,6 @@ -export const getProducts = async (query = "") => { - const response = await fetch(`https://panda-market-api.vercel.app/products${query}`); +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; }; From 1a1659ae349c8f30c6bc786c224098ac85756d7e Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:43:39 +0900 Subject: [PATCH 06/32] =?UTF-8?q?styles:=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BestProducts.css | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/components/BestProducts.css b/src/components/BestProducts.css index dd8e7999..5d410462 100644 --- a/src/components/BestProducts.css +++ b/src/components/BestProducts.css @@ -20,6 +20,7 @@ align-items: center; width: 100%; max-width: none; + gap: 16px; } .best-products__image-wrapper { @@ -38,17 +39,17 @@ .best-products__text-group { display: flex; - margin-top: 8px; + /* margin-top: 8px; */ flex-direction: column; + align-items: flex-start; width: 100%; text-align: left; - align-items: flex-start; + gap: 10px; } .best-products__title-text { font-size: 14px; font-weight: 500; - margin-bottom: -5px; color: #1f2937; } @@ -56,7 +57,6 @@ font-size: 16px; font-weight: 700; color: #1f2937; - margin-bottom: -3px; } .best-products__favorite { @@ -68,13 +68,34 @@ /* 태블릿 사이즈*/ @media (max-width: 1199px) { .best-products__grid { - grid-template-columns: repeat(2, 1fr); + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 16px; + } + + .best-products__card { + width: 343px; + } + .best-products__image-wrapper { + width: 343px; + height: 343px; } } /* 모바일 사이즈 */ @media (max-width: 767px) { .best-products__grid { - grid-template-columns: repeat(1, 1fr); + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 16px; + } + + .best-products__card { + width: 343px; + } + .best-products__image-wrapper { + width: 343px; } } From b8748643f33b36e02f0486d3ea0bc3abc16433a1 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:44:29 +0900 Subject: [PATCH 07/32] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=EB=B0=B1?= =?UTF-8?q?=20=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20-=20updateVisibleC?= =?UTF-8?q?ount=20useCallback=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BestProducts.jsx | 34 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/BestProducts.jsx b/src/components/BestProducts.jsx index 99d0e9e2..27a29ab9 100644 --- a/src/components/BestProducts.jsx +++ b/src/components/BestProducts.jsx @@ -1,5 +1,5 @@ import { getProducts } from "../api/productApi"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import "./BestProducts.css"; /** @@ -12,32 +12,28 @@ const BestProduct = () => { const [bestProducts, setBestProducts] = useState([]); const [visibleCount, setVisibleCount] = useState(4); // default pc + const updateVisibleCount = useCallback(() => { + const width = window.innerWidth; + if (width <= 767) { + setVisibleCount(1); + } else if (width <= 1199) { + setVisibleCount(2); + } else { + setVisibleCount(4); + } + }, []); + useEffect(() => { - getProducts("?page=1&pageSize=4&orderBy=favorite").then((data) => { + getProducts({ page: 1, pageSize: 4, orderBy: "favorite" }).then((data) => { setBestProducts(data.list); }); }, []); useEffect(() => { - const updateVisibleCount = () => { - const width = window.innerWidth; - - if (width <= 767) { - // mobile - setVisibleCount(1); - } else if (width <= 1199) { - // tablet - setVisibleCount(2); - } else { - setVisibleCount(4); - } - }; - updateVisibleCount(); - window.addEventListener("resize", updateVisibleCount); // resize 시 eventListener - + window.addEventListener("resize", updateVisibleCount); return () => window.removeEventListener("resize", updateVisibleCount); - }, []); + }, [updateVisibleCount]); return (
From 587f061445b7045b75b129cb9357723333f378d8 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:45:28 +0900 Subject: [PATCH 08/32] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=EB=B0=B1?= =?UTF-8?q?=20=EC=9A=94=EC=86=8C=20=EC=88=98=EC=A0=95=20-=20Array.from=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=9D=BC=EA=B4=80?= =?UTF-8?q?=EB=90=9C=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Pagination.jsx | 56 ++++++++++++++--------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/src/components/Pagination.jsx b/src/components/Pagination.jsx index 9b25a4eb..ef0a5d14 100644 --- a/src/components/Pagination.jsx +++ b/src/components/Pagination.jsx @@ -3,48 +3,36 @@ import "./Pagination.css"; /** * 페이징 처리를 하는 컴포넌트입니다. * 현재 페이지를 기준으로 그룹화 시켜 한 그룹당 5개의 페이지를 보여줍니다. - * 선택된 페이지를 클릭 시 페이지 번호를 상위 컴포넌트로 전달합니다. - * 파라미터 (currentPage: 현재 페이지 / totalPage: 전체 페이지 수 / onPageChange: 페이지 클릭 시 실행하는 함수) + * 선택된 페이지를 클릭 시 페이지 번호를 상위 컴포넌트로 전달합니다. + * 파라미터 (currentPage: 현재 페이지 / totalPage: 전체 페이지 수 / onPageChange: 페이지 클릭 시 실행하는 함수) */ const Pagination = ({ currentPage, totalPage, onPageChange }) => { - // 5개씩 자르기위한 그룹의 크기(한 그룹당 5개). const pageGroupSize = 5; - const currentGroup = Math.floor((currentPage - 1) / pageGroupSize); - - // 현재 페이지 그룹의 시작 번호 const startPage = currentGroup * pageGroupSize + 1; - const endPage = Math.min(startPage + pageGroupSize - 1, totalPage); - const pageButtons = []; - - // 이전 페이지 그룹으로 이동하는 버튼 - pageButtons.push( - - ); - - // 그룹 안의 페이지 번호들 - for (let i = startPage; i <= endPage; i++) { - pageButtons.push( - - ); - } - - // 다음 그룹 페이지로 이동하는 버튼 - if (endPage < totalPage) { - pageButtons.push( - - ); - } - - return
{pageButtons}
; + {Array.from({ length: endPage - startPage + 1 }, (_, index) => ( + + ))} + {endPage < totalPage && ( + + )} + + ); }; export default Pagination; From 20fe83d89559b7cb05ee7f28514e06b15ebdfc4c Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:45:53 +0900 Subject: [PATCH 09/32] =?UTF-8?q?styles:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProductCard.css | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/components/ProductCard.css b/src/components/ProductCard.css index 6b508cbf..b34e44ce 100644 --- a/src/components/ProductCard.css +++ b/src/components/ProductCard.css @@ -1,17 +1,20 @@ .product-card { - width: 221px; + display: flex; + flex-direction: column; + gap: 16px; + width: 218px; } .product-card__image-wrapper { width: 100%; - height: 221px; + height: 218px; border-radius: 16px; overflow: hidden; } .product-card__text-group { display: flex; - margin-top: 8px; + gap: 10px; flex-direction: column; } @@ -24,15 +27,26 @@ .product-card__title { font-size: 14px; font-weight: 500; - margin-bottom: -5px; } .product-card__price { font-size: 16px; - margin-bottom: -3px; } .product-card__favorite { font-size: 12px; color: #888; } + +@media (max-width: 1199px) { +} + +@media (max-width: 767px) { + .product-card { + width: 168px; + } + + .product-card__image-wrapper { + width: 100%; + } +} From 1c139101b6aaebaa1c475d6a91e104146356acb8 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:46:29 +0900 Subject: [PATCH 10/32] =?UTF-8?q?styles:=20=EB=A9=94=EC=9D=B8=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=B0=98=EC=9D=91=ED=98=95=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95=20(=EB=AA=A8=EB=B0=94=EC=9D=BC,?= =?UTF-8?q?=20=ED=83=9C=EB=B8=94=EB=A6=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProductList.css | 91 ++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/src/components/ProductList.css b/src/components/ProductList.css index b87d94f9..a2990e02 100644 --- a/src/components/ProductList.css +++ b/src/components/ProductList.css @@ -2,21 +2,33 @@ display: flex; justify-content: space-between; align-items: center; + max-width: 1200px; + width: 100%; + height: 42px; gap: 12px; - margin-bottom: 24px; + margin: 0 auto 24px auto; +} + +.product-list__top { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; } .product-list__actions { display: flex; align-items: center; gap: 12px; + width: 100%; + justify-content: space-between; } .product-list__grid { - display: grid; - grid-template-columns: repeat(5, 1fr); - grid-template-rows: repeat(2, auto); - gap: 24px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: 1200px; } .product-list__add-button { @@ -25,30 +37,79 @@ width: 133px; height: 42px; border-radius: 8px; - padding: 12px 24px; + padding: 12px 23px; border: none; font-size: 15px; font-weight: 600; text-align: center; + cursor: pointer; + white-space: nowrap; } -.product-list__select { - border: 1px solid #e5e7eb; - width: 130px; +.dropdown { + width: 42px; height: 42px; + border-radius: 50%; + background-color: #ffffff; + border: 1px solid #e5e7eb; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} - border-radius: 12px; - padding: 12px 20px; +.dropdown__toggle { + display: flex; + justify-content: center; + align-items: center; +} + +.dropdown__arrow { + width: 24px; + height: 24px; } @media (max-width: 1199px) { - .product-list__grid { - grid-template-columns: repeat(3, 1fr); + .product-list__add-button { + width: 133px; } } +/* 모바일 사이즈 */ @media (max-width: 767px) { - .product-list__grid { - grid-template-columns: repeat(2, 1fr); + .product-list__header { + display: flex; + flex-direction: column; + height: auto; + gap: 12px; + padding: 0 16px; + } + + .product-list__top { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: nowrap; + gap: 12px; + margin-bottom: 0; + } + + .product-list__top h3 { + margin: 0; + font-size: 20px; + font-weight: bold; + } + + .product-list__actions { + display: flex; + align-items: center; + width: 100%; + gap: 12px; + } + + .dropdown { + width: 42px; + height: 42px; + flex-shrink: 0; } } From f131308489211fb8e0f484ae540ecbec6370cb3b Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:47:16 +0900 Subject: [PATCH 11/32] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=EB=B0=B1?= =?UTF-8?q?=20=EC=9A=94=EC=86=8C=20=EC=88=98=EC=A0=95=20-=20=EC=83=81?= =?UTF-8?q?=EC=9C=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20api=ED=98=B8=EC=B6=9C=20=EB=A7=90=EA=B3=A0=20?= =?UTF-8?q?=ED=95=B4=EB=8B=B9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20api=EC=9A=94=EC=B2=AD=20=ED=95=98=EC=97=AC?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=97=90=20=EB=BF=8C=EB=A0=A4=EC=A4=84=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProductList.jsx | 55 +++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/components/ProductList.jsx b/src/components/ProductList.jsx index 780c9d0a..ae4c0bb3 100644 --- a/src/components/ProductList.jsx +++ b/src/components/ProductList.jsx @@ -4,29 +4,37 @@ import Pagination from "./Pagination"; import "./ProductList.css"; import SearchInput from "./SearchInput"; import { useNavigate } from "react-router-dom"; +import { getProducts } from "../api/productApi"; +import Dropdown from "./Dropdown"; /** * 전체 상품 목록을 카드 형태로 표시하는 컴포넌트 입니다. * 검색어 기능, 정렬, 페이징 기능이 포함되어있습니다. */ -const ProductList = ({ products }) => { +const ProductList = () => { + const [products, setProducts] = useState([]); const [orderBy, setOrderBy] = useState("recent"); const [page, setPage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(""); const nav = useNavigate(); // 페이지 이동을 위한 hook 사용 - const pageSize = 10; // 한 페이지에 보여줄 상품 수 - const [responsivePageSize, setResponsivePageSize] = useState(10); // 기본값 10 - // option 값에 따라 정렬된 상품 리스트를 보여줌 (desc) - const sortedProducts = [...products].sort((a, b) => { - if (orderBy === "favorite") return b.favoriteCount - a.favoriteCount; - return new Date(b.createdAt) - new Date(a.createdAt); - }); + useEffect(() => { + const params = { + page: 1, + pageSize: 300, + orderBy, + }; + if (searchKeyword) { + params.keyword = searchKeyword; + } - const filteredProducts = sortedProducts.filter((product) => product.name.includes(searchKeyword)); + getProducts(params).then((data) => { + setProducts(data.list); + }); + }, [orderBy, searchKeyword]); useEffect(() => { const updatePageSize = () => { @@ -51,8 +59,8 @@ const ProductList = ({ products }) => { }, []); // 페이징 처리 - const paginatedProducts = filteredProducts.slice((page - 1) * responsivePageSize, page * responsivePageSize); - const totalPage = Math.ceil(filteredProducts.length / responsivePageSize); + const paginatedProducts = products.slice((page - 1) * responsivePageSize, page * responsivePageSize); + const totalPage = Math.ceil(products.length / responsivePageSize); const handleOrderChange = (e) => { setOrderBy(e.target.value); @@ -68,16 +76,27 @@ const ProductList = ({ products }) => { return (
-

전체 상품

-
- +
+

전체 상품

- +
+ +
+ + { + setOrderBy(value); + setPage(1); + }} + options={[ + { value: "recent", label: "최신순" }, + { value: "favorite", label: "좋아요순" }, + ]} + className="product-list__dropdown" + />
From 6733f3e6df657f0d8e0fd79b6f3a96bfb09eda84 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:47:39 +0900 Subject: [PATCH 12/32] =?UTF-8?q?styles:=20=EA=B2=80=EC=83=89=EC=B0=BD=20?= =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchInput.css | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/SearchInput.css b/src/components/SearchInput.css index edde4359..4736f441 100644 --- a/src/components/SearchInput.css +++ b/src/components/SearchInput.css @@ -1,6 +1,6 @@ .search__input { width: 325px; - height: 30px; + height: 42px; border-radius: 12px; padding: 9px 20px 9px 16px; background-color: #f3f4f6; @@ -12,3 +12,17 @@ font-size: 16px; font-weight: 400; } + +/* 태블릿 사이즈*/ +@media (max-width: 1199px) { + .search__input { + width: 242px; + } +} + +/* 모바일 사이즈 */ +@media (max-width: 767px) { + .search__input { + width: 288px; + } +} From 7b2c147d4dc167827209a4eab34fbb22ff41a388 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:48:15 +0900 Subject: [PATCH 13/32] =?UTF-8?q?refactor:=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=ED=9B=84=20=EC=97=94=ED=84=B0=20=EC=8B=9C=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchInput.jsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index e31f4c7c..50fa4f70 100644 --- a/src/components/SearchInput.jsx +++ b/src/components/SearchInput.jsx @@ -8,6 +8,12 @@ import "./SearchInput.css"; const SearchInput = ({ onSearch }) => { const [keyword, setKeyword] = useState(""); + const handleKeyDown = (e) => { + if (e.key === "Enter") { + onSearch(keyword.trim()); + } + }; + const handleChange = (e) => { const value = e.target.value; setKeyword(value); @@ -15,7 +21,16 @@ const SearchInput = ({ onSearch }) => { onSearch(value.trim()); // 공백 제거 후 상위 컴포넌트로 전달 }; - return ; + return ( + + ); }; export default SearchInput; From 73021f6b340abf47782af6b33f04ce21761c6aa7 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:48:42 +0900 Subject: [PATCH 14/32] =?UTF-8?q?styles:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Items.css | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/pages/Items.css b/src/pages/Items.css index 915451cc..cf7dd132 100644 --- a/src/pages/Items.css +++ b/src/pages/Items.css @@ -1,5 +1,20 @@ .items { max-width: 1200px; margin: 0 auto; - padding: 0 16px; + /* padding: 0 16px; */ + overflow-x: hidden; +} + +@media (max-width: 1199px) { + .items { + width: 100%; + padding: 0 24px; + } +} + +@media (max-width: 767px) { + .items { + width: 100%; + padding: 0 16px; + } } From 8a8fb18e927869ad1a099ec7ef435972458ac671 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:49:18 +0900 Subject: [PATCH 15/32] =?UTF-8?q?refactor:=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20api=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20(=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A7=81=EC=A0=91=20=EB=B6=80=EB=A5=B4=EA=B2=8C=20=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Items.jsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/pages/Items.jsx b/src/pages/Items.jsx index d018de31..0521d792 100644 --- a/src/pages/Items.jsx +++ b/src/pages/Items.jsx @@ -1,5 +1,3 @@ -import { useEffect, useState } from "react"; -import { getProducts } from "../api/productApi"; import BestProduct from "../components/BestProducts"; import ProductList from "../components/ProductList"; import "./Items.css"; @@ -10,18 +8,10 @@ import "./Items.css"; * 베스트 상품 컴포넌트(BestProduct)와 전체 상품 목록 컴포넌트 (ProductList)로 구성되어있습니다. */ const Items = () => { - const [products, setProducts] = useState([]); - - useEffect(() => { - getProducts("?page=1&pageSize=300&orderBy=recent").then((data) => { - setProducts(data.list); - }); - }, []); - return (
- +
); }; From 09a997cf388472980c1451cfcd8e9741d7953166 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:49:42 +0900 Subject: [PATCH 16/32] =?UTF-8?q?styles:=20react=20dropdown=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Dropdown.css | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/components/Dropdown.css diff --git a/src/components/Dropdown.css b/src/components/Dropdown.css new file mode 100644 index 00000000..2d815972 --- /dev/null +++ b/src/components/Dropdown.css @@ -0,0 +1,90 @@ +.dropdown { + position: relative; + display: inline-block; + font-family: inherit; +} + +.dropdown__toggle { + width: 130px; + height: 42px; + padding: 0 16px; + background: white; + border: 2px solid #e5e7eb; + border-radius: 16px; + font-size: 16px; + font-weight: 400; + line-height: 26px; + color: #1f2937; + text-align: left; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; +} + +.dropdown__arrow { + width: 24px; + height: 24px; + top: -3px; + left: 66px; +} + +.dropdown__menu { + position: absolute; + top: 50px; + width: 100%; + background: white; + border: 1px solid #e5e7eb; + border-radius: 16px; + overflow: hidden; +} + +.dropdown__menu li { + padding: 12px; + font-size: 16px; + font-weight: 400; + line-height: 26px; + color: #1f2937; + text-align: center; + cursor: pointer; + border-bottom: 1px solid #e5e7eb; +} + +.dropdown__menu li:last-child { + border-bottom: none; +} + +.dropdown__menu li:hover { + background-color: #f3f4f6; +} + +@media (max-width: 1199px) { + .dropdown { + width: 130px; + height: 42px; + } +} + +@media (max-width: 767px) { + .dropdown { + width: auto; + } + + .dropdown__toggle { + width: 42px; + height: 42px; + padding: 0; + justify-content: center; + } + + .dropdown__arrow { + width: 20px; + height: 20px; + } + + .dropdown__menu { + top: 48px; + min-width: 120px; + left: -75px; + } +} From 34b4f25cad5f98203159d00ce8876ee2dbca1e42 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:50:12 +0900 Subject: [PATCH 17/32] =?UTF-8?q?feat:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=20=EC=A0=81=EC=9A=A9=20-=20react=20dropdown?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Dropdown.jsx | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/Dropdown.jsx diff --git a/src/components/Dropdown.jsx b/src/components/Dropdown.jsx new file mode 100644 index 00000000..568092bf --- /dev/null +++ b/src/components/Dropdown.jsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import "./Dropdown.css"; +import dropdown from "../assets/dropdown.png"; +import mobileFilter from "../assets/mobileFilter.png"; + +const Dropdown = ({ value, options, onSelect }) => { + const [isOpen, setIsOpen] = useState(false); + + const selectedLabel = options.find((opt) => opt.value === value)?.label || ""; + + const isMobile = window.innerWidth <= 767; + + return ( +
+ + {isOpen && ( +
    + {options.map((option) => ( +
  • { + onSelect(option.value); + setIsOpen(false); + }} + > + {option.label} +
  • + ))} +
+ )} +
+ ); +}; + +export default Dropdown; From 28b2211656f34de5d3a13ffb724934a5a96d2eb7 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:50:34 +0900 Subject: [PATCH 18/32] =?UTF-8?q?styles:=20=ED=97=A4=EB=8D=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header.css | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/components/Header.css diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 00000000..ebe6929f --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,72 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + width: 100%; + height: 70px; + border-bottom: 1px solid #dfdfdf; +} + +.header__logo { + display: flex; + align-items: center; + margin-left: 200px; +} + +.header__logo-img { + width: 153px; + height: 51px; +} + +.header__nav-container { + margin-left: 24px; +} + +.header__nav { + display: flex; + gap: 16px; +} + +.header__nav a { + font-size: 18px; + font-weight: 700; + line-height: 26px; + text-decoration: none; + color: #4b5563; +} + +.active { + font-weight: bold; + color: #3692ff; +} + +.header__right .header__user-img { + width: 40px; + height: 40px; + margin-right: 40px; +} + +@media (max-width: 1199px) { + .header__logo { + display: flex; + margin-left: 0; + } +} + +@media (max-width: 767px) { + .header__nav a { + font-size: 16px; + } + + .header__logo { + display: flex; + margin-left: 0; + width: 376px; + padding: 0 16px; + } + + .header__logo-img { + width: 81px; + } +} From a3d4cbf9c1e6a03c12e99673d203bb538ebaf7d7 Mon Sep 17 00:00:00 2001 From: Hyun Date: Tue, 6 May 2025 21:50:45 +0900 Subject: [PATCH 19/32] =?UTF-8?q?feat:=20=ED=97=A4=EB=8D=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header.jsx | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/components/Header.jsx diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..e635c133 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,45 @@ +import { NavLink } from "react-router-dom"; +import userImage from "../assets/userImage.png"; +import logo from "../assets/pandaLogo.png"; +import "../components/Header.css"; +import mobileLogo from "../assets/mobileLogo.png"; +import { useState, useEffect } from "react"; + +const Header = () => { + const [isMobile, setIsMobile] = useState(window.innerWidth <= 767); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth <= 767); + }; + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return ( +
+
+
+ + logo + +
+ +
+ +
+
+ +
+ user +
+
+ ); +}; + +export default Header; From 102026dd8d868b83949bb21a2466f623bb19d205 Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:54:22 +0900 Subject: [PATCH 20/32] =?UTF-8?q?docs:=20sprint6=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=EC=83=81=ED=99=A9=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 17151b88..9ad850b3 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,16 @@ -# 스프린트 미션 5 +# 스프린트 미션 6 ## [기본 요구사항] -- [x] 중고마켓 페이지 주소는 "/items"입니다. -- [x] 페이지 주소가 “/items” 일때 상단네비게이션바의 “중고마켓" 버튼의 색상은 “3692FF”입니다. -- [x] 상단 네비게이션 바는 이전 미션에서 구현한 랜딩 페이지와 동일한 스타일로 만들어 주세요. -- [x] 전체 상품에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다. -- [x] '상품 등록하기’ 버튼을 누르면 “/additem” 로 이동합니다 ( 빈 페이지 ) -- [x] 카드 데이터는 제공된 백엔드 API 페이지의 GET 메소드인 “/products”를 사용해주세요 -- [x] 미디어 쿼리를 사용하여 반응형 view 마다 물품 개수를 다르게 보여줍니다 (서버로 요청하는 값은 동일) - -### 베스트 상품 기준 - -- [x] 정렬: favorite -- [x] favorite가 가장 높은 상품 4가지 - -### 베스트 상품 - -- [x] Desktop : 4개 보이기 -- [x] Tablet : 2개 보이기 -- [x] Mobile : 1개 보이기 - -### 전체 상품 - -- [x] Desktop : 10개 보이기 -- [x] Tablet : 6개 보이기 -- [x] Mobile : 4개 보이기 +- [x] 상품 등록 페이지 주소는 “/additem” 입니다. +- [x] 페이지 주소가 “/additem” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다. +- [x] 상품 이미지는 최대 한개 업로드가 가능합니다 +- [x] 각 input의 placeholder 값을 정확히 입력해주세요. +- [x] 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다. --- ## [심화 요구사항] -- [x] 페이지 네이션 기능을 구현합니다. -- [x] 반응형으로 보여지는 물품들의 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다. +- [x] 이미지 안의 X 버튼을 누르면 이미지가 삭제됩니다. +- [x] 추가된 태그 안의 X 버튼을 누르면 해당 태그는 삭제됩니다. From fe05cefdd397df2e7e93f6057bd2a95efb91d82e Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:54:45 +0900 Subject: [PATCH 21/32] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AddItem.jsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/components/AddItem.jsx diff --git a/src/components/AddItem.jsx b/src/components/AddItem.jsx deleted file mode 100644 index e69de29b..00000000 From f9fe89b330864425048fbe49602f4744284f4b81 Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:55:34 +0900 Subject: [PATCH 22/32] =?UTF-8?q?feat:=20/items=20=EC=9D=B4=EA=B1=B0?= =?UTF-8?q?=EB=82=98=20/additem=20=EC=9D=BC=EB=95=8C=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Header.css | 6 +++++- src/components/Header.jsx | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/Header.css b/src/components/Header.css index ebe6929f..29a80b45 100644 --- a/src/components/Header.css +++ b/src/components/Header.css @@ -33,7 +33,6 @@ font-weight: 700; line-height: 26px; text-decoration: none; - color: #4b5563; } .active { @@ -41,6 +40,11 @@ color: #3692ff; } +.market-active { + color: #3692ff; + font-weight: 700; +} + .header__right .header__user-img { width: 40px; height: 40px; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index e635c133..0e05884b 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,4 +1,4 @@ -import { NavLink } from "react-router-dom"; +import { NavLink, useLocation } from "react-router-dom"; import userImage from "../assets/userImage.png"; import logo from "../assets/pandaLogo.png"; import "../components/Header.css"; @@ -7,6 +7,8 @@ import { useState, useEffect } from "react"; const Header = () => { const [isMobile, setIsMobile] = useState(window.innerWidth <= 767); + const location = useLocation(); + const isMarketPage = location.pathname === "/items" || location.pathname === "/additem"; useEffect(() => { const handleResize = () => { @@ -30,7 +32,9 @@ const Header = () => {
From a59965e82e77231d7c67cc52a8ecb7b6b32ceb57 Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:56:10 +0900 Subject: [PATCH 23/32] =?UTF-8?q?styles:=20=EC=A0=84=EC=B2=B4=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Dropdown.css | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Dropdown.css b/src/components/Dropdown.css index 2d815972..7555f448 100644 --- a/src/components/Dropdown.css +++ b/src/components/Dropdown.css @@ -32,15 +32,18 @@ .dropdown__menu { position: absolute; top: 50px; - width: 100%; + right: 20; + min-width: 130px; + width: auto; background: white; border: 1px solid #e5e7eb; border-radius: 16px; overflow: hidden; + z-index: 10; } .dropdown__menu li { - padding: 12px; + padding: 12px 20px; font-size: 16px; font-weight: 400; line-height: 26px; @@ -48,6 +51,7 @@ text-align: center; cursor: pointer; border-bottom: 1px solid #e5e7eb; + white-space: nowrap; } .dropdown__menu li:last-child { @@ -68,6 +72,7 @@ @media (max-width: 767px) { .dropdown { width: auto; + position: relative; } .dropdown__toggle { @@ -85,6 +90,6 @@ .dropdown__menu { top: 48px; min-width: 120px; - left: -75px; + right: 0; } } From a7b483cacc74a45e077187e4c837a756e61a339e Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:56:56 +0900 Subject: [PATCH 24/32] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=EC=9D=98=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProductList.css | 18 +++++++++++++++++- src/components/ProductList.jsx | 24 +++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/components/ProductList.css b/src/components/ProductList.css index a2990e02..f0747695 100644 --- a/src/components/ProductList.css +++ b/src/components/ProductList.css @@ -21,7 +21,8 @@ align-items: center; gap: 12px; width: 100%; - justify-content: space-between; + margin-right: 40px; + justify-content: flex-start; } .product-list__grid { @@ -46,6 +47,11 @@ white-space: nowrap; } +.product-list__search-wrapper { + flex-grow: 1; + max-width: 325px; +} + .dropdown { width: 42px; height: 42px; @@ -105,6 +111,16 @@ align-items: center; width: 100%; gap: 12px; + justify-content: space-between; /* 모바일에서는 space-between으로 유지 */ + } + + .product-list__search-wrapper { + flex-grow: 1; + max-width: none; /* 모바일에서는 최대 너비 제한 없음 */ + } + + .product-list__add-button { + margin-left: 0; /* 모바일에서는 마진 제거 */ } .dropdown { diff --git a/src/components/ProductList.jsx b/src/components/ProductList.jsx index ae4c0bb3..b97acb5b 100644 --- a/src/components/ProductList.jsx +++ b/src/components/ProductList.jsx @@ -16,6 +16,7 @@ const ProductList = () => { const [orderBy, setOrderBy] = useState("recent"); const [page, setPage] = useState(1); const [searchKeyword, setSearchKeyword] = useState(""); + const [isMobile, setIsMobile] = useState(false); const nav = useNavigate(); // 페이지 이동을 위한 hook 사용 @@ -39,6 +40,7 @@ const ProductList = () => { useEffect(() => { const updatePageSize = () => { const width = window.innerWidth; + setIsMobile(width <= 767); if (width <= 767) { setResponsivePageSize(4); @@ -62,11 +64,6 @@ const ProductList = () => { const paginatedProducts = products.slice((page - 1) * responsivePageSize, page * responsivePageSize); const totalPage = Math.ceil(products.length / responsivePageSize); - const handleOrderChange = (e) => { - setOrderBy(e.target.value); - setPage(1); // 정렬 바꿀 때 첫 페이지로 이동 - }; - // 검색 const handleSearch = (keyword) => { setSearchKeyword(keyword); @@ -78,13 +75,22 @@ const ProductList = () => {

전체 상품

- + {isMobile && ( + + )}
- +
+ +
+ {!isMobile && ( + + )} { From 276956a132a86caa9cc0a99f32db6230836cc2d7 Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:57:18 +0900 Subject: [PATCH 25/32] =?UTF-8?q?styles:=20=EC=A0=84=EC=B2=B4=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchInput.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/SearchInput.css b/src/components/SearchInput.css index 4736f441..cdc610f3 100644 --- a/src/components/SearchInput.css +++ b/src/components/SearchInput.css @@ -5,6 +5,8 @@ padding: 9px 20px 9px 16px; background-color: #f3f4f6; border: none; + flex: none; + margin-right: 10px; } .search__input::placeholder { From e89d7783819d6bae9962cc1dce57e49c9bd999a2 Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:57:46 +0900 Subject: [PATCH 26/32] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=ED=95=98=EA=B8=B0=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/AddItem.css | 54 +++++++++++++++++++++++++++++++++++++++++++ src/pages/AddItem.jsx | 27 ++++++++++++++++++++-- 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/pages/AddItem.css diff --git a/src/pages/AddItem.css b/src/pages/AddItem.css new file mode 100644 index 00000000..727d0428 --- /dev/null +++ b/src/pages/AddItem.css @@ -0,0 +1,54 @@ +.additem { + max-width: 1200px; + margin: 24px auto 0 360px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.additem-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.additem-header > h2 { + font-size: 20px; + font-weight: 700; + color: #1f2937; +} + +.submit-button { + background-color: #b0b8c1; + color: #f3f4f6; + padding: 10px 20px; + border-radius: 8px; + border: none; + cursor: not-allowed; + font-size: 16px; + font-weight: 600; +} + +.submit-button:not(:disabled) { + background-color: #3692ff; + cursor: pointer; +} + +.additem-image-section { + margin-bottom: 20px; +} + +@media (max-width: 1199px) { + .additem { + width: 100%; + padding: 0 24px; + margin: 16px auto 0 auto; + } +} + +@media (max-width: 767px) { + .additem { + width: 100%; + padding: 0 16px; + } +} diff --git a/src/pages/AddItem.jsx b/src/pages/AddItem.jsx index fb6ddabc..ee71a8e4 100644 --- a/src/pages/AddItem.jsx +++ b/src/pages/AddItem.jsx @@ -1,6 +1,29 @@ -// 상품 등록하는 화면입니다. +import { useEffect, useState } from "react"; +import ImageUpload from "../components/ImageUpload"; +import FormInput from "../components/FormInput"; +import "./AddItem.css"; + const AddItem = () => { - return
AddItem
; + const [isFormValid, setIsFormValid] = useState(false); + + return ( +
+
+

상품 등록하기

+ +
+ +
+ +
+ +
+ +
+
+ ); }; export default AddItem; From 83bdaf2e300e8d2042655c6e16c920e5c624c0eb Mon Sep 17 00:00:00 2001 From: Hyun Date: Wed, 7 May 2025 22:58:23 +0900 Subject: [PATCH 27/32] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=ED=95=98=EA=B8=B0=20-=20input=20=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/FormInput.css | 62 ++++++++++++++++++++++++++++ src/components/FormInput.jsx | 79 ++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/components/FormInput.css create mode 100644 src/components/FormInput.jsx diff --git a/src/components/FormInput.css b/src/components/FormInput.css new file mode 100644 index 00000000..2727f028 --- /dev/null +++ b/src/components/FormInput.css @@ -0,0 +1,62 @@ +.input-field { + margin-bottom: 32px; + display: flex; + flex-direction: column; +} + +.input-field__label { + font-size: 18px; + font-weight: 700; + margin-bottom: 12px; + width: 100%; +} + +.input-field__input { + width: 100%; + padding: 16px 20px; + font-size: 16px; + font-weight: 400; + border-radius: 12px; + border: none; + background-color: #f3f4f6; + color: #9ca3af; + resize: none; + line-height: 26px; +} + +.input-field textarea.input-field__input { + min-height: 282px; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 12px; +} + +.tag-item { + background-color: #f3f4f6; + color: #1f2937; + padding: 8px 12px; + border-radius: 9999px; + display: flex; + align-items: center; + font-size: 16px; + font-weight: 400; +} + +.tag-item .tag-remove { + background: #9ca3af; + color: #fff; + border: none; + border-radius: 50%; + margin-left: 8px; + width: 20px; + height: 20px; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/components/FormInput.jsx b/src/components/FormInput.jsx new file mode 100644 index 00000000..bbca092e --- /dev/null +++ b/src/components/FormInput.jsx @@ -0,0 +1,79 @@ +import { useState, useEffect } from "react"; +import "./FormInput.css"; + +const FormInput = ({ onFormValidChange, onFormDataChange }) => { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [price, setPrice] = useState(""); + const [tags, setTags] = useState([]); + const [tagInput, setTagInput] = useState(""); + + const handleTagKeyDown = (e) => { + if (e.nativeEvent.isComposing) return; + if (e.key === "Enter" && tagInput.trim()) { + e.preventDefault(); + if (!tags.includes(tagInput.trim())) { + setTags([...tags, tagInput.trim()]); + } + setTagInput(""); + } + }; + + const handleTagRemove = (removeTag) => { + setTags(tags.filter((tag) => tag !== removeTag)); + }; + + useEffect(() => { + const isValid = title.trim() && description.trim() && price.trim() && tags.length > 0; + onFormValidChange(!!isValid); + onFormDataChange?.({ title, description, price, tags }); + }, [title, description, price, tags, onFormValidChange, onFormDataChange]); + + return ( +
+
+ + setTitle(e.target.value)} /> +
+ +
+ +