diff --git a/public/index.html b/public/index.html
index 21b8800f6..d8b2efff5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,10 +1,30 @@
-
-
-
- 판다 마켓
+
+
+ 판다마켓
+
+
+
+
+
diff --git a/src/Api.js b/src/Api.js
deleted file mode 100644
index e6979ea58..000000000
--- a/src/Api.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export async function productList({ orderBy = "recent", pageSize = 10 }) {
- const query = `page=1&pageSize=${pageSize}&orderBy=${orderBy}`;
- const response = await fetch(
- `https://panda-market-api.vercel.app/products?${query}`
- );
- if (!response.ok) {
- throw new Error("제품목록을 가지고 오는데 실패했습니다.");
- }
- const date = await response.json();
- return date;
-}
-
-export default productList;
diff --git a/src/App.js b/src/App.js
new file mode 100644
index 000000000..74736b325
--- /dev/null
+++ b/src/App.js
@@ -0,0 +1,29 @@
+import { BrowserRouter, Route, Routes } from "react-router-dom";
+import HomePage from "./pages/HomePage/HomePage";
+import LoginPage from "./pages/LoginPage/LoginPage";
+import MarketPage from "./pages/MarketPage/MarketPage";
+import AddItemPage from "./pages/AddItemPage/AddItemPage";
+import CommunityFeedPage from "./pages/CommunityFeedPage/CommunityFeedPage";
+import Header from "./components/Layout/Header";
+
+function App() {
+ return (
+
+ {/* Global Navigation Bar */}
+
+
+
+
+ {/* React Router v6부터는 path="/" 대신 간단하게 `index`라고 표기하면 돼요 */}
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/App.jsx b/src/App.jsx
deleted file mode 100644
index 51f561d13..000000000
--- a/src/App.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Route, Routes, BrowserRouter, Navigate } from "react-router-dom";
-import Header from "./components/Header/Header.jsx";
-import Home from "./pages/Home/Home.jsx";
-import AddItem from "./pages/AddItem/AddItem.jsx";
-import Items from "./pages/Items/Items.jsx";
-import "./css/Reset.css";
-import "./css/Global.css";
-
-function App() {
- return (
-
-
-
-
- }
- >
- }>
- }>
-
-
-
- );
-}
-
-export default App;
diff --git a/src/api/itemApi.js b/src/api/itemApi.js
new file mode 100644
index 000000000..fd6ab6a90
--- /dev/null
+++ b/src/api/itemApi.js
@@ -0,0 +1,18 @@
+export async function getProducts(params = {}) {
+ // URLSearchParams을 이용하면 파라미터 값을 자동으로 쉽게 인코딩할 수 있어요.
+ const query = new URLSearchParams(params).toString();
+
+ try {
+ const response = await fetch(
+ `https://panda-market-api.vercel.app/products?${query}`
+ );
+ if (!response.ok) {
+ throw new Error(`HTTP error: ${response.status}`);
+ }
+ const body = await response.json();
+ return body;
+ } catch (error) {
+ console.error("Failed to fetch products:", error);
+ throw error;
+ }
+}
diff --git a/src/assets/images/icons/arrow_left.svg b/src/assets/images/icons/arrow_left.svg
new file mode 100644
index 000000000..2a9de23a6
--- /dev/null
+++ b/src/assets/images/icons/arrow_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/arrow_right.svg b/src/assets/images/icons/arrow_right.svg
new file mode 100644
index 000000000..daa483c3e
--- /dev/null
+++ b/src/assets/images/icons/arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_X.png b/src/assets/images/icons/ic_X.png
new file mode 100644
index 000000000..4acc054fe
Binary files /dev/null and b/src/assets/images/icons/ic_X.png differ
diff --git a/src/assets/images/icons/ic_heart.svg b/src/assets/images/icons/ic_heart.svg
new file mode 100644
index 000000000..cad016c13
--- /dev/null
+++ b/src/assets/images/icons/ic_heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_search.svg b/src/assets/images/icons/ic_search.svg
new file mode 100644
index 000000000..52241e6d8
--- /dev/null
+++ b/src/assets/images/icons/ic_search.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_sort.svg b/src/assets/images/icons/ic_sort.svg
new file mode 100644
index 000000000..657b44f93
--- /dev/null
+++ b/src/assets/images/icons/ic_sort.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/images/logo/logo.svg b/src/assets/images/logo/logo.svg
new file mode 100644
index 000000000..d497acbfe
--- /dev/null
+++ b/src/assets/images/logo/logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css
deleted file mode 100644
index 3b4f7c965..000000000
--- a/src/components/Header/Header.css
+++ /dev/null
@@ -1,43 +0,0 @@
-.header {
- height: 70px;
- border-bottom: 1px solid #dfdfdf;
-}
-
-.header-nav__container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px 200px;
-}
-
-.header-nav {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 32px;
-}
-
-.logo__container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 153px;
-}
-
-.logo__text {
- font-size: 26px;
- font-weight: 700;
- line-height: 34px;
- color: #3692ff;
-}
-
-.header-menu__container {
- display: flex;
-}
-
-.header-menu__text {
- padding: 15px 21px;
- font-size: 18px;
- line-height: 26px;
- font-weight: 700;
-}
diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx
deleted file mode 100644
index 80535e8c8..000000000
--- a/src/components/Header/Header.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from "react";
-import PandaImg from "../../img/pandaLogo__header.png";
-import UserImg from "../../img/userImg.png";
-import "./Header.css";
-
-export default function Header() {
- return (
-
-
-
-
-
-
판다마켓
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ItemList/ItemList.css b/src/components/ItemList/ItemList.css
deleted file mode 100644
index e68f5dd86..000000000
--- a/src/components/ItemList/ItemList.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.ItemList-ul.flex {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 24px;
-}
-
-.ItemList-ul.grid {
- display: grid;
- grid-template-columns: repeat(5, 1fr);
- justify-content: center;
- align-items: center;
- gap: 24px;
-}
diff --git a/src/components/ItemList/ItemList.jsx b/src/components/ItemList/ItemList.jsx
deleted file mode 100644
index f02e6b8fa..000000000
--- a/src/components/ItemList/ItemList.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from "react";
-import Products from "../Products/Products.jsx";
-import "./ItemList.css";
-
-export default function ItemList({ items, layoutType }) {
- return (
-
- {items.map((item) => {
- return (
- -
-
-
- );
- })}
-
- );
-}
diff --git a/src/components/Layout/Header.css b/src/components/Layout/Header.css
new file mode 100644
index 000000000..1bc2b02c5
--- /dev/null
+++ b/src/components/Layout/Header.css
@@ -0,0 +1,45 @@
+.headerLeft {
+ display: flex;
+ align-items: center;
+}
+
+.headerLogo {
+ margin-right: 16px;
+}
+
+.globalHeader nav ul {
+ display: flex;
+ list-style: none;
+ gap: 8px;
+ font-weight: bold;
+ font-size: 16px;
+ color: #4b5563;
+}
+
+.globalHeader nav ul li a:hover {
+ color: var(--blue);
+}
+
+.loginLink {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 11.5px 23px;
+}
+
+@media (min-width: 768px) {
+ .globalHeader nav ul {
+ gap: 36px;
+ font-size: 18px;
+ }
+
+ .headerLogo {
+ margin-right: 35px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .headerLogo {
+ margin-right: 47px;
+ }
+}
diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx
new file mode 100644
index 000000000..9bab51e9b
--- /dev/null
+++ b/src/components/Layout/Header.jsx
@@ -0,0 +1,42 @@
+import React from "react";
+import Logo from "../../assets/images/logo/logo.svg";
+import { Link, NavLink } from "react-router-dom";
+import "./Header.css";
+
+// react-router-dom의 NavLink를 이용하면 활성화된 네비게이션 항목을 하이라이트해줄 수 있어요!
+function getLinkStyle({ isActive }) {
+ return { color: isActive ? "var(--blue)" : undefined };
+}
+
+function Header() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ 로그인
+
+
+ );
+}
+
+export default Header;
diff --git a/src/components/Products/Product.css b/src/components/Products/Product.css
deleted file mode 100644
index 90ba25a8b..000000000
--- a/src/components/Products/Product.css
+++ /dev/null
@@ -1,44 +0,0 @@
-.product {
- display: flex;
- flex-direction: column;
- gap: 16px;
-}
-
-.product-img {
- border-radius: 16px;
- object-fit: cover;
-}
-
-.product-img.flex {
- width: 282px;
- height: 282px;
-}
-
-.product-img.grid {
- width: 221px;
- height: 221px;
-}
-
-.product-name {
- font-size: 14px;
- font-weight: 500;
- line-height: 24px;
-}
-
-.favorite-count__container {
- display: flex;
- gap: 4px;
-}
-
-.product-price {
- font-size: 16px;
- font-weight: 700;
- line-height: 26px;
-}
-
-.product-favoriteCount {
- color: #4b5563;
- font-size: 12px;
- font-weight: 500;
- line-height: 18px;
-}
diff --git a/src/components/Products/Products.jsx b/src/components/Products/Products.jsx
deleted file mode 100644
index 99667b855..000000000
--- a/src/components/Products/Products.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from "react";
-import "./Product.css";
-import heartImg from "../../img/heartIcon.png";
-
-export default function Products({ item, layoutType }) {
- return (
-
-
-
{item.name}
-
{item.price}
-
-
-
{item.favoriteCount}
-
-
- );
-}
diff --git a/src/components/UI/DropdownList.css b/src/components/UI/DropdownList.css
new file mode 100644
index 000000000..74f59823a
--- /dev/null
+++ b/src/components/UI/DropdownList.css
@@ -0,0 +1,22 @@
+.dropdownList {
+ position: absolute;
+ top: 110%;
+ right: 0;
+ background: #fff;
+ border-radius: 8px;
+ border: 1px solid #e5e7eb;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ z-index: 99;
+}
+
+.dropdownItem {
+ padding: 12px 44px;
+ border-bottom: 1px solid #e5e7eb;
+ font-size: 16px;
+ color: #1f2937;
+ cursor: pointer;
+}
+
+.dropdownItem:last-child {
+ border-bottom: none;
+}
diff --git a/src/components/UI/DropdownList.jsx b/src/components/UI/DropdownList.jsx
new file mode 100644
index 000000000..a9adae989
--- /dev/null
+++ b/src/components/UI/DropdownList.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+import "./DropdownList.css";
+
+function DropdownList({ onSortSelection }) {
+ return (
+
+
onSortSelection("recent")}>
+ 최신순
+
+
onSortSelection("favorite")}>
+ 인기순
+
+
+ );
+}
+export default DropdownList;
diff --git a/src/components/UI/PaginationBar.css b/src/components/UI/PaginationBar.css
new file mode 100644
index 000000000..1841297a9
--- /dev/null
+++ b/src/components/UI/PaginationBar.css
@@ -0,0 +1,29 @@
+.paginationBar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+}
+
+.paginationButton {
+ border: 1px solid #e5e7eb;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ color: #6b7280;
+ font-weight: 600;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.paginationButton:disabled {
+ cursor: default;
+ opacity: 0.5;
+}
+
+.paginationButton.active {
+ background-color: var(--blue);
+ color: #fff;
+}
diff --git a/src/components/UI/PaginationBar.jsx b/src/components/UI/PaginationBar.jsx
new file mode 100644
index 000000000..4116ebb40
--- /dev/null
+++ b/src/components/UI/PaginationBar.jsx
@@ -0,0 +1,53 @@
+import React from "react";
+import "./PaginationBar.css";
+import { ReactComponent as LeftArrow } from "../../assets/images/icons/arrow_left.svg";
+import { ReactComponent as RightArrow } from "../../assets/images/icons/arrow_right.svg";
+
+const PaginationBar = ({ totalPageNum, activePageNum, onPageChange }) => {
+ const maxVisiblePages = 5;
+ let startPage;
+
+ if (totalPageNum <= maxVisiblePages) {
+ startPage = 1;
+ } else {
+ startPage = Math.max(activePageNum - Math.floor(maxVisiblePages / 2), 1);
+ startPage = Math.min(startPage, totalPageNum - maxVisiblePages + 1);
+ }
+
+ const pages = Array.from(
+ { length: Math.min(maxVisiblePages, totalPageNum - startPage + 1) },
+ (_, i) => startPage + i
+ );
+
+ return (
+
+
+ {pages.map((page) => (
+
+ ))}
+
+
+ );
+};
+
+export default PaginationBar;
diff --git a/src/css/Global.css b/src/css/Global.css
deleted file mode 100644
index e2b2e1679..000000000
--- a/src/css/Global.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.App {
- background-color: #fcfcfc;
- font-family: "Pretendard-Regular";
-}
-
-* {
- box-sizing: border-box;
-}
-
-@font-face {
- font-family: "Pretendard-Regular";
- src: url("https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff")
- format("woff");
- font-weight: 400;
- font-style: normal;
-}
-
-p {
- color: #1f2937;
-}
diff --git a/src/css/Reset.css b/src/css/Reset.css
deleted file mode 100644
index 45a05ecf8..000000000
--- a/src/css/Reset.css
+++ /dev/null
@@ -1,129 +0,0 @@
-/* http://meyerweb.com/eric/tools/css/reset/
- v2.0 | 20110126
- License: none (public domain)
-*/
-
-html,
-body,
-div,
-span,
-applet,
-object,
-iframe,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-blockquote,
-pre,
-a,
-abbr,
-acronym,
-address,
-big,
-cite,
-code,
-del,
-dfn,
-em,
-img,
-ins,
-kbd,
-q,
-s,
-samp,
-small,
-strike,
-strong,
-sub,
-sup,
-tt,
-var,
-b,
-u,
-i,
-center,
-dl,
-dt,
-dd,
-ol,
-ul,
-li,
-fieldset,
-form,
-label,
-legend,
-table,
-caption,
-tbody,
-tfoot,
-thead,
-tr,
-th,
-td,
-article,
-aside,
-canvas,
-details,
-embed,
-figure,
-figcaption,
-footer,
-header,
-hgroup,
-menu,
-nav,
-output,
-ruby,
-section,
-summary,
-time,
-mark,
-audio,
-video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
-}
-/* HTML5 display-role reset for older browsers */
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-menu,
-nav,
-section {
- display: block;
-}
-body {
- line-height: 1;
-}
-ol,
-ul {
- list-style: none;
-}
-blockquote,
-q {
- quotes: none;
-}
-blockquote:before,
-blockquote:after,
-q:before,
-q:after {
- content: "";
- content: none;
-}
-table {
- border-collapse: collapse;
- border-spacing: 0;
-}
diff --git a/src/img/heartIcon.png b/src/img/heartIcon.png
deleted file mode 100644
index bd7e864de..000000000
Binary files a/src/img/heartIcon.png and /dev/null differ
diff --git a/src/img/pandaLogo__header.png b/src/img/pandaLogo__header.png
deleted file mode 100644
index 0a1a4f202..000000000
Binary files a/src/img/pandaLogo__header.png and /dev/null differ
diff --git a/src/img/userImg.png b/src/img/userImg.png
deleted file mode 100644
index 0844dd1db..000000000
Binary files a/src/img/userImg.png and /dev/null differ
diff --git a/src/index.js b/src/index.js
index db7b51ca3..77ca5404e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,11 @@
+import React from "react";
import ReactDOM from "react-dom/client";
-
import App from "./App";
+import "./styles/global.css"; // index.js에서 global stylesheet을 import하면 전역적으로 스타일이 적용돼요
const root = ReactDOM.createRoot(document.getElementById("root"));
-root.render();
+root.render(
+
+
+
+);
diff --git a/src/pages/AddItem/AddItem.jsx b/src/pages/AddItem/AddItem.jsx
deleted file mode 100644
index 2d23dfadf..000000000
--- a/src/pages/AddItem/AddItem.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from "react";
-
-export default function AddItem() {
- return ;
-}
diff --git a/src/pages/AddItemPage/AddItemPage.css b/src/pages/AddItemPage/AddItemPage.css
new file mode 100644
index 000000000..2f671d5a5
--- /dev/null
+++ b/src/pages/AddItemPage/AddItemPage.css
@@ -0,0 +1,80 @@
+.addItems__container {
+ width: 1200px;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding-top: 24px;
+ padding-bottom: 69px;
+}
+
+.addItems-title__container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 24px;
+}
+
+.addItems-title {
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 32px;
+}
+
+.addItems-button {
+ background-color: #9ca3af;
+ height: 42px;
+ border-radius: 8px;
+ padding: 0 23px;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 26px;
+}
+
+.input-form__container {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+}
+
+.addItems-img__container,
+.addItems-name__container,
+.addItems-explain__container,
+.addItems-price__container,
+.addItems-tag__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.addItems-img__title,
+.addItems-name__title,
+.addItems-explain__title,
+.addItems-price__title,
+.addItems-tag__title {
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 26px;
+}
+
+.addItems-name__input,
+.addItems-price__input,
+.addItems-tag__input {
+ height: 56px;
+ border: none;
+ background-color: #f3f4f6;
+ border-radius: 12px;
+ padding: 24px 15px;
+}
+
+.addItems-explain__input {
+ height: 282px;
+ border: none;
+ background-color: #f3f4f6;
+ border-radius: 12px;
+ padding: 24px 15px;
+}
+
+.addItems-tag__flex-container {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
diff --git a/src/pages/AddItemPage/AddItemPage.jsx b/src/pages/AddItemPage/AddItemPage.jsx
new file mode 100644
index 000000000..90f4a591b
--- /dev/null
+++ b/src/pages/AddItemPage/AddItemPage.jsx
@@ -0,0 +1,86 @@
+import React, { useState } from "react";
+import "./AddItemPage.css";
+import Tag from "./components/Tag";
+function AddItemPage() {
+ const [tags, setTags] = useState([]);
+ const [inputValue, setInputValue] = useState("");
+
+ const handleKeyDown = (event) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ if (inputValue.trim()) {
+ setTags((prevTags) => [...prevTags, inputValue.trim()]);
+ setInputValue("");
+ }
+ }
+ };
+ const handleInputChange = (event) => {
+ setInputValue(event.target.value);
+ };
+
+ const handleDeleteClick = (tags) => {
+ setTags((prevTags) => prevTags.filter((tag) => tag != tags));
+ };
+ return (
+
+ );
+}
+
+export default AddItemPage;
diff --git a/src/pages/AddItemPage/components/Tag.css b/src/pages/AddItemPage/components/Tag.css
new file mode 100644
index 000000000..2aa8f66bc
--- /dev/null
+++ b/src/pages/AddItemPage/components/Tag.css
@@ -0,0 +1,14 @@
+.tags__container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ border-radius: 26px;
+ background-color: #f3f4f6;
+ flex-shrink: 0;
+}
diff --git a/src/pages/AddItemPage/components/Tag.jsx b/src/pages/AddItemPage/components/Tag.jsx
new file mode 100644
index 000000000..2690215b7
--- /dev/null
+++ b/src/pages/AddItemPage/components/Tag.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+import "./Tag.css";
+import iconX from "../../../assets/images/icons/ic_X.png";
+
+export default function Tag({ tags, onDelete }) {
+ return (
+
+ {tags.map((tag) => {
+ return (
+
+
{tag}
+
onDelete(tag)}
+ />
+
+ );
+ })}
+
+ );
+}
diff --git a/src/pages/AddItem/AddItem.css b/src/pages/CommunityFeedPage/CommunityFeedPage.css
similarity index 100%
rename from src/pages/AddItem/AddItem.css
rename to src/pages/CommunityFeedPage/CommunityFeedPage.css
diff --git a/src/pages/CommunityFeedPage/CommunityFeedPage.jsx b/src/pages/CommunityFeedPage/CommunityFeedPage.jsx
new file mode 100644
index 000000000..60f3d3be2
--- /dev/null
+++ b/src/pages/CommunityFeedPage/CommunityFeedPage.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+function CommunityFeedPage() {
+ return CommunityFeedPage
;
+}
+
+export default CommunityFeedPage;
diff --git a/src/pages/Home/Home.css b/src/pages/Home/Home.css
deleted file mode 100644
index 5a084e16a..000000000
--- a/src/pages/Home/Home.css
+++ /dev/null
@@ -1,76 +0,0 @@
-.products-section {
- width: 1200px;
- max-width: 1200px;
- height: auto;
- margin: 0 auto;
- padding-top: 24px;
-}
-
-.favorite-products__container,
-.all-products__container {
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 16px;
-}
-
-.favorite-products__container {
- padding-bottom: 40px;
-}
-
-.all-products__container {
- padding-bottom: 43px;
-}
-
-.products-section__title--best,
-.products-section__title--all {
- font-size: 20px;
- font-weight: 700;
- line-height: 32px;
- color: #111827;
-}
-
-.all-products__header {
- display: flex;
- justify-content: space-between;
-}
-
-.all-products__search-container {
- display: flex;
- gap: 12px;
- align-items: center;
-}
-
-.all-products__add-button {
- text-decoration: none;
- padding: 8px 23px;
- height: 42px;
- border: none;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 500;
- line-height: 26px;
- background-color: #3692ff;
- color: #f3f4f6;
- text-align: center;
-}
-
-.all-products__input {
- width: 325px;
- height: 42px;
- border-radius: 12px;
- border: none;
- background-color: #f3f4f6;
- padding-left: 16px;
-}
-
-.dropDown-products {
- width: 130px;
- height: 42px;
- border: 1px solid #e5e7eb;
- border-radius: 12px;
-}
-
-.dropDown-option__recent {
- border-top-left-radius: 8px;
-}
diff --git a/src/pages/Home/Home.jsx b/src/pages/Home/Home.jsx
deleted file mode 100644
index 44e8a199d..000000000
--- a/src/pages/Home/Home.jsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useEffect, useState } from "react";
-import productList from "../../Api.js";
-import ItemList from "../../components/ItemList/ItemList.jsx";
-import "./Home.css";
-import { Link } from "react-router-dom";
-
-function Home() {
- const [allItems, setAllItems] = useState([]);
- const [bestItems, setBestItems] = useState([]);
- const [selectedOption, setSelectedOption] = useState("최신순");
- const loadAllItems = async () => {
- try {
- const { list } = await productList({ orderBy: "recent" });
- setAllItems(list);
- } catch (error) {
- console.error("전체 상품 데이터를 불러오는 중 오류 발생:", error.message);
- }
- };
-
- const loadBestItems = async () => {
- try {
- const { list } = await productList({ orderBy: "favorite", pageSize: 4 });
- setBestItems(list);
- } catch (error) {
- console.error(
- "베스트 상품 데이터를 불러오는 중 오류 발생:",
- error.message
- );
- }
- };
-
- useEffect(() => {
- loadAllItems();
- loadBestItems();
- }, []);
-
- const handleOptionClick = (e) => {
- setSelectedOption(e.target.value);
- };
-
- return (
-
- );
-}
-
-export default Home;
diff --git a/src/pages/Items/Items.css b/src/pages/HomePage/HomePage.css
similarity index 100%
rename from src/pages/Items/Items.css
rename to src/pages/HomePage/HomePage.css
diff --git a/src/pages/HomePage/HomePage.jsx b/src/pages/HomePage/HomePage.jsx
new file mode 100644
index 000000000..26705c224
--- /dev/null
+++ b/src/pages/HomePage/HomePage.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+function HomePage() {
+ return HomePage
;
+}
+
+export default HomePage;
diff --git a/src/pages/Items/Items.jsx b/src/pages/Items/Items.jsx
deleted file mode 100644
index 2bb95a20b..000000000
--- a/src/pages/Items/Items.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from "react";
-
-export default function Items() {
- return ;
-}
diff --git a/src/pages/LoginPage/LoginPage.css b/src/pages/LoginPage/LoginPage.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/pages/LoginPage/LoginPage.jsx b/src/pages/LoginPage/LoginPage.jsx
new file mode 100644
index 000000000..15f723760
--- /dev/null
+++ b/src/pages/LoginPage/LoginPage.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+function LoginPage() {
+ return LoginPage
;
+}
+
+export default LoginPage;
diff --git a/src/pages/MarketPage/MarketPage.css b/src/pages/MarketPage/MarketPage.css
new file mode 100644
index 000000000..f304e4a4f
--- /dev/null
+++ b/src/pages/MarketPage/MarketPage.css
@@ -0,0 +1,145 @@
+.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;
+}
+
+.bestItemsContainer {
+ padding-top: 17px;
+ padding-bottom: 24px;
+}
+
+.allItemsCardSection {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 32px 8px;
+}
+
+.allItemsSectionHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.allItemsSectionHeader:first-child {
+ padding-bottom: 8px;
+}
+
+.allItemsSectionHeader:nth-child(2) {
+ padding-bottom: 16px;
+}
+
+.sortButtonWrapper {
+ position: relative;
+}
+
+.sortDropdownTriggerButton {
+ border: 1px solid #e5e7eb;
+ border-radius: 12px;
+ padding: 9px;
+ margin-left: 8px;
+}
+
+.searchBarWrapper {
+ display: flex;
+ background-color: #f3f4f6;
+ border-radius: 12px;
+ padding: 9px 16px;
+ flex: 1;
+ align-items: center;
+}
+
+.searchBarInput {
+ border: none;
+ flex: 1;
+ background-color: inherit;
+ margin-left: 4px;
+}
+
+.searchBarInput::placeholder {
+ color: #9ca3af;
+ font-size: 16px;
+}
+
+.searchBarInput:focus {
+ outline: none;
+}
+
+.paginationBarWrapper {
+ padding-top: 40px;
+ padding-bottom: 80px;
+}
+
+@media (min-width: 768px) {
+ .bestItemsContainer {
+ margin-bottom: 40px;
+ }
+
+ .bestItemsCardSection {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 24px;
+ }
+
+ .allItemsCardSection {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 40px 16px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .bestItemsCardSection {
+ grid-template-columns: repeat(4, 1fr);
+ }
+
+ .allItemsCardSection {
+ grid-template-columns: repeat(5, 1fr);
+ gap: 40px 24px;
+ }
+}
diff --git a/src/pages/MarketPage/MarketPage.jsx b/src/pages/MarketPage/MarketPage.jsx
new file mode 100644
index 000000000..dd78d9065
--- /dev/null
+++ b/src/pages/MarketPage/MarketPage.jsx
@@ -0,0 +1,15 @@
+import React from "react";
+import BestItemsSection from "./components/BestItemsSection";
+import AllItemsSection from "./components/AllItemsSection";
+import "./MarketPage.css";
+
+function MarketPage() {
+ return (
+
+ );
+}
+
+export default MarketPage;
diff --git a/src/pages/MarketPage/components/AllItemsSection.jsx b/src/pages/MarketPage/components/AllItemsSection.jsx
new file mode 100644
index 000000000..a0876a483
--- /dev/null
+++ b/src/pages/MarketPage/components/AllItemsSection.jsx
@@ -0,0 +1,113 @@
+import React, { useEffect, useState } from "react";
+import { getProducts } from "../../../api/itemApi";
+import ItemCard from "./ItemCard";
+import { ReactComponent as SortIcon } from "../../../assets/images/icons/ic_sort.svg";
+import { ReactComponent as SearchIcon } from "../../../assets/images/icons/ic_search.svg";
+import { Link } from "react-router-dom";
+import DropdownList from "../../../components/UI/DropdownList";
+import PaginationBar from "../../../components/UI/PaginationBar";
+
+const getPageSize = () => {
+ const width = window.innerWidth;
+ if (width < 768) {
+ // Mobile viewport
+ return 4;
+ } else if (width < 1280) {
+ // Tablet viewport
+ return 6;
+ } else {
+ // Desktop viewport
+ return 10;
+ }
+};
+
+function AllItemsSection() {
+ const [orderBy, setOrderBy] = useState("recent");
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(getPageSize());
+ const [itemList, setItemList] = useState([]);
+ const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+ const [totalPageNum, setTotalPageNum] = useState();
+
+ const fetchSortedData = async ({ orderBy, page, pageSize }) => {
+ const products = await getProducts({ orderBy, page, pageSize });
+ setItemList(products.list);
+ setTotalPageNum(Math.ceil(products.totalCount / pageSize));
+ };
+
+ const handleSortSelection = (sortOption) => {
+ setOrderBy(sortOption);
+ setIsDropdownVisible(false);
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ setPageSize(getPageSize());
+ };
+
+ // 화면 크기 변경할 때마다 pageSize를 다시 계산해 넣음
+ window.addEventListener("resize", handleResize);
+ fetchSortedData({ orderBy, page, pageSize });
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [orderBy, page, pageSize]);
+
+ const toggleDropdown = () => {
+ setIsDropdownVisible(!isDropdownVisible);
+ };
+
+ const onPageChange = (pageNumber) => {
+ setPage(pageNumber);
+ };
+
+ return (
+
+
+
판매 중인 상품
+
+ 상품 등록하기
+
+
+
+
+
+
+
+
+
+
+ {isDropdownVisible && (
+
+ )}
+
+
+
+
+ {itemList?.map((item) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+export default AllItemsSection;
diff --git a/src/pages/MarketPage/components/BestItemsSection.jsx b/src/pages/MarketPage/components/BestItemsSection.jsx
new file mode 100644
index 000000000..a2072fbb9
--- /dev/null
+++ b/src/pages/MarketPage/components/BestItemsSection.jsx
@@ -0,0 +1,56 @@
+import React, { useEffect, useState } from "react";
+import ItemCard from "./ItemCard";
+import { getProducts } from "../../../api/itemApi";
+
+const getPageSize = () => {
+ const width = window.innerWidth;
+ if (width < 768) {
+ // Mobile viewport
+ return 1;
+ } else if (width < 1280) {
+ // Tablet viewport
+ return 2;
+ } else {
+ // Desktop viewport
+ return 4;
+ }
+};
+
+function BestItemsSection() {
+ const [itemList, setItemList] = useState([]);
+ const [pageSize, setPageSize] = useState(getPageSize());
+
+ const fetchSortedData = async ({ orderBy, pageSize }) => {
+ const products = await getProducts({ orderBy, pageSize });
+ setItemList(products.list);
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ setPageSize(getPageSize());
+ };
+
+ // 화면 크기 변경할 때마다 pageSize를 다시 계산해 넣음
+ window.addEventListener("resize", handleResize);
+ fetchSortedData({ orderBy: "favorite", pageSize });
+
+ // Cleanup function
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [pageSize]);
+
+ return (
+
+
베스트 상품
+
+
+ {itemList?.map((item) => (
+
+ ))}
+
+
+ );
+}
+
+export default BestItemsSection;
diff --git a/src/pages/MarketPage/components/ItemCard.jsx b/src/pages/MarketPage/components/ItemCard.jsx
new file mode 100644
index 000000000..226c06baa
--- /dev/null
+++ b/src/pages/MarketPage/components/ItemCard.jsx
@@ -0,0 +1,20 @@
+import React from "react";
+import { ReactComponent as HeartIcon } from "../../../assets/images/icons/ic_heart.svg";
+
+function ItemCard({ item }) {
+ return (
+
+
+
+
{item.name}
+
{item.price.toLocaleString()}원
+
+
+ {item.favoriteCount}
+
+
+
+ );
+}
+
+export default ItemCard;
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 000000000..851341a11
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,206 @@
+/* Mobile styles */
+
+:root {
+ /* Gray scale */
+ --gray-900: #1b1d1f;
+ --gray-800: #26282b;
+ --gray-600: #454c53;
+ --gray-500: #72787f;
+ --gray-400: #9ea4a8;
+ --gray-200: #e5e7eb;
+ --gray-100: #e8ebed;
+ --gray-50: #f7f7f8;
+
+ /* Primary color */
+ --blue: #3692ff;
+
+ /* Layout dimensions */
+ --header-height: 70px;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+button,
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+button {
+ background: none;
+ border: none;
+ outline: none;
+ box-shadow: none;
+ cursor: pointer;
+}
+
+img,
+svg {
+ vertical-align: bottom;
+}
+
+body {
+ color: #374151;
+ word-break: keep-all;
+ font-family: "Pretendard", sans-serif;
+}
+
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: var(--header-height);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #dfdfdf;
+ z-index: 999;
+}
+
+.withHeader {
+ margin-top: var(--header-height);
+}
+
+footer {
+ background-color: #111827;
+ color: #9ca3af;
+ font-size: 16px;
+ padding: 32px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 60px;
+}
+
+#copyright {
+ order: 3;
+ flex-basis: 100%;
+}
+
+#footerMenu {
+ display: flex;
+ gap: 30px;
+ color: var(--gray-200);
+}
+
+#socialMedia {
+ display: flex;
+ gap: 12px;
+}
+
+.wrapper {
+ width: 100%;
+ padding: 0 16px;
+}
+
+h1 {
+ font-size: 40px;
+ font-weight: 700;
+ line-height: 56px;
+ letter-spacing: 0.02em;
+}
+
+.button {
+ background-color: var(--blue);
+ color: #ffffff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.button:hover {
+ background-color: #1967d6;
+}
+
+.button:focus {
+ background-color: #1251aa;
+}
+
+.button:disabled {
+ background-color: #9ca3af;
+ cursor: default;
+ pointer-events: none;
+}
+
+.pill-button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 999px;
+ padding: 14.5px 33.5px;
+}
+
+.full-width {
+ width: 100%;
+}
+
+.break-on-desktop {
+ display: none;
+}
+
+/* Tablet styles */
+
+@media (min-width: 768px) {
+ header {
+ padding: 0 24px;
+ }
+
+ .wrapper {
+ padding: 0 24px;
+ }
+
+ .pill-button {
+ font-size: 20px;
+ font-weight: 700;
+ padding: 16px 126px;
+ }
+
+ footer {
+ padding: 32px 104px 108px 104px;
+ }
+
+ #copyright {
+ flex-basis: auto;
+ order: 0;
+ }
+}
+
+/* Desktop styles */
+
+@media (min-width: 1280px) {
+ header {
+ padding: 0 200px;
+ }
+
+ .wrapper {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+
+ .break-on-desktop {
+ display: inline;
+ }
+
+ footer {
+ padding: 32px 200px 108px 200px;
+ }
+}