diff --git a/src/App.jsx b/src/App.jsx index 4d5dfaa5..5f49529a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; import Index from './pages/Index.jsx'; import Items from './pages/Items.jsx'; +import ItemProduct from './pages/ItemProduct.jsx'; import AddItem from './pages/AddItem.jsx'; import Auth from './pages/Auth.jsx'; import Login from './pages/Login.jsx'; @@ -16,7 +17,10 @@ function App() { } /> - } /> + + } /> + } /> + } /> diff --git a/src/api/api.js b/src/api/api.js index 59753381..271a69d0 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,7 +1,20 @@ const BASE_URL = "https://panda-market-api.vercel.app"; -export async function getProducts( { page=1, pageSize=10, orderBy="recent", keyword } ) { +export async function getProducts({ page=1, pageSize=10, orderBy="recent", keyword }) { const query = new URLSearchParams({ page, pageSize, orderBy }).toString(); - const products = await fetch(`${BASE_URL}/products?${query}${keyword ? "&keyword="+keyword : ''}`); - return (await products.json()); + const response = await fetch(`${BASE_URL}/products?${query}${keyword ? "&keyword="+keyword : ''}`); + if (!response.ok) { throw Error("Request Error"); } + return (await response.json()); +} + +export async function getProduct({ productId }) { + const response = await fetch(`${BASE_URL}/products/${productId}`); + if (!response.ok) { throw Error("Request Error"); } + return (await response.json()); +} + +export async function getProductComments({ productId, limit }) { + const response = await fetch(`${BASE_URL}/products/${productId}/comments?limit=${limit}`); + if (!response.ok) { throw Error("Request Error"); } + return (await response.json()); } \ No newline at end of file diff --git a/src/assets/arrow/ic_arrow_left_active.svg b/src/assets/arrow/ic_arrow_left_active.svg new file mode 100644 index 00000000..4b110c20 --- /dev/null +++ b/src/assets/arrow/ic_arrow_left_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/arrow/ic_arrow_left_inactive.svg b/src/assets/arrow/ic_arrow_left_inactive.svg new file mode 100644 index 00000000..1daeca5c --- /dev/null +++ b/src/assets/arrow/ic_arrow_left_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_left.svg b/src/assets/arrow/ic_arrow_right_active.svg similarity index 72% rename from src/assets/ic_arrow_left.svg rename to src/assets/arrow/ic_arrow_right_active.svg index 368742c9..0ad718ef 100644 --- a/src/assets/ic_arrow_left.svg +++ b/src/assets/arrow/ic_arrow_right_active.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/arrow/ic_arrow_right_inactive.svg b/src/assets/arrow/ic_arrow_right_inactive.svg new file mode 100644 index 00000000..764302b6 --- /dev/null +++ b/src/assets/arrow/ic_arrow_right_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/default_image.png b/src/assets/default_image.png new file mode 100644 index 00000000..8a16161a Binary files /dev/null and b/src/assets/default_image.png differ diff --git a/src/assets/heart/ic_heart_large_active.svg b/src/assets/heart/ic_heart_large_active.svg new file mode 100644 index 00000000..aff4192d --- /dev/null +++ b/src/assets/heart/ic_heart_large_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/heart/ic_heart_large_inactive.svg b/src/assets/heart/ic_heart_large_inactive.svg new file mode 100644 index 00000000..1139d16b --- /dev/null +++ b/src/assets/heart/ic_heart_large_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/heart/ic_heart_medium_active.svg b/src/assets/heart/ic_heart_medium_active.svg new file mode 100644 index 00000000..a7c3840d --- /dev/null +++ b/src/assets/heart/ic_heart_medium_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/heart/ic_heart_medium_inactive.svg b/src/assets/heart/ic_heart_medium_inactive.svg new file mode 100644 index 00000000..b1cdd392 --- /dev/null +++ b/src/assets/heart/ic_heart_medium_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/heart/ic_heart_small_active.svg b/src/assets/heart/ic_heart_small_active.svg new file mode 100644 index 00000000..557bade0 --- /dev/null +++ b/src/assets/heart/ic_heart_small_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/heart/ic_heart_small_inactive.svg b/src/assets/heart/ic_heart_small_inactive.svg new file mode 100644 index 00000000..4e73997c --- /dev/null +++ b/src/assets/heart/ic_heart_small_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_back.svg b/src/assets/ic_back.svg new file mode 100644 index 00000000..8db5377e --- /dev/null +++ b/src/assets/ic_back.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/ic_kebab.svg b/src/assets/ic_kebab.svg new file mode 100644 index 00000000..dd7ed7f5 --- /dev/null +++ b/src/assets/ic_kebab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/inquiry_empty.png b/src/assets/inquiry_empty.png new file mode 100644 index 00000000..d5ff7e99 Binary files /dev/null and b/src/assets/inquiry_empty.png differ diff --git a/src/assets/home_01.png b/src/assets/landing/home_01.png similarity index 100% rename from src/assets/home_01.png rename to src/assets/landing/home_01.png diff --git a/src/assets/home_02.png b/src/assets/landing/home_02.png similarity index 100% rename from src/assets/home_02.png rename to src/assets/landing/home_02.png diff --git a/src/assets/home_03.png b/src/assets/landing/home_03.png similarity index 100% rename from src/assets/home_03.png rename to src/assets/landing/home_03.png diff --git a/src/assets/home_bottom.png b/src/assets/landing/home_bottom.png similarity index 100% rename from src/assets/home_bottom.png rename to src/assets/landing/home_bottom.png diff --git a/src/assets/home_top.png b/src/assets/landing/home_top.png similarity index 100% rename from src/assets/home_top.png rename to src/assets/landing/home_top.png diff --git a/src/assets/ic_facebook.svg b/src/assets/sns/ic_facebook.svg similarity index 100% rename from src/assets/ic_facebook.svg rename to src/assets/sns/ic_facebook.svg diff --git a/src/assets/ic_google_color.svg b/src/assets/sns/ic_google_color.svg similarity index 100% rename from src/assets/ic_google_color.svg rename to src/assets/sns/ic_google_color.svg diff --git a/src/assets/ic_instagram.svg b/src/assets/sns/ic_instagram.svg similarity index 100% rename from src/assets/ic_instagram.svg rename to src/assets/sns/ic_instagram.svg diff --git a/src/assets/ic_kakaotalk_color.svg b/src/assets/sns/ic_kakaotalk_color.svg similarity index 100% rename from src/assets/ic_kakaotalk_color.svg rename to src/assets/sns/ic_kakaotalk_color.svg diff --git a/src/assets/ic_twitter.svg b/src/assets/sns/ic_twitter.svg similarity index 100% rename from src/assets/ic_twitter.svg rename to src/assets/sns/ic_twitter.svg diff --git a/src/assets/ic_youtube.svg b/src/assets/sns/ic_youtube.svg similarity index 100% rename from src/assets/ic_youtube.svg rename to src/assets/sns/ic_youtube.svg diff --git a/src/components/Button.jsx b/src/components/Button.jsx index 917d8585..c3308bb1 100644 --- a/src/components/Button.jsx +++ b/src/components/Button.jsx @@ -1,16 +1,9 @@ -import { useNavigate } from "react-router-dom"; import styles from "./Button.module.css"; //styleType은 small, medium, large로 나뉜다 -function Button ({ styleType="small", className, onClick, to, children, ...props }) { - const navigate = useNavigate(); - const handleClick = (e) => { - if (onClick) onClick(e); - if (to) setTimeout(() => navigate(to), 0); - } - +function Button ({ styleType="small", className='', children, ...props }) { return ( - ); diff --git a/src/components/Button.module.css b/src/components/Button.module.css index 85d59458..fcae8d16 100644 --- a/src/components/Button.module.css +++ b/src/components/Button.module.css @@ -21,18 +21,22 @@ } .btn.small { + padding: 8px 23px; border-radius: 8px; font-size: 16px; line-height: 26px; } .btn.medium { + width: 240px; + padding: 11px 0; border-radius: 9999px; font-size: 18px; line-height: 26px; } .btn.large { + padding: 12px 0; border-radius: 9999px; font-size: 20px; line-height: 32px; diff --git a/src/components/ButtonHeart.jsx b/src/components/ButtonHeart.jsx new file mode 100644 index 00000000..e09efa8e --- /dev/null +++ b/src/components/ButtonHeart.jsx @@ -0,0 +1,17 @@ +import icHeartLargeActive from "../assets/heart/ic_heart_large_active.svg"; +import icHeartLargeInactive from "../assets/heart/ic_heart_large_inactive.svg"; +import icHeartMediumActive from "../assets/heart/ic_heart_medium_active.svg"; +import icHeartMediumInactive from "../assets/heart/ic_heart_medium_inactive.svg"; +import styles from "./ButtonHeart.module.css"; + +function ButtonHeart({ isActive=false, className='', children, ...props }) { + return (); +} + +export default ButtonHeart; \ No newline at end of file diff --git a/src/components/ButtonHeart.module.css b/src/components/ButtonHeart.module.css new file mode 100644 index 00000000..2b5d840b --- /dev/null +++ b/src/components/ButtonHeart.module.css @@ -0,0 +1,29 @@ +.btn { + display: flex; + align-items: center; + gap: 4px; + padding: 3px 11px; + border: 1px solid var(--gray200); + border-radius: 9999px; + background-color: white; + color: var(--gray500); + font-weight: 500; + font-size: 16px; + line-height: 26px; +} + +.btn img.medium, +.btn img.false { + display: none; +} + +@media (max-width: 1199px) { + .btn img.medium { + display: block; + } + + .btn img.large, + .btn img.false { + display: none; + } +} \ No newline at end of file diff --git a/src/components/Dropdown.jsx b/src/components/Dropdown.jsx index 74297635..09fd9736 100644 --- a/src/components/Dropdown.jsx +++ b/src/components/Dropdown.jsx @@ -1,14 +1,11 @@ -import { useEffect, useRef, useState } from 'react'; -import icArrowDown from '../assets/ic_arrow_down.svg'; -import icSort from '../assets/ic_sort.svg'; -import styles from './Dropdown.module.css'; +import { useEffect, useRef, useState } from "react"; +import icKebab from "../assets/ic_kebab.svg"; +import styles from "./Dropdown.module.css"; -const option = { recent: "최신순", favorite: "좋아요순", } - -function Dropdown({ state, setState, mode }) { +function Dropdown({ className='', onClickEdit, onClickDelete, ...props }) { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); - + useEffect(() => { const handleClickOutside = (e) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target)) setIsOpen(false); @@ -17,23 +14,13 @@ function Dropdown({ state, setState, mode }) { return () => document.removeEventListener("click", handleClickOutside);; }, []); - return ( - - ); + return (); } export default Dropdown; \ No newline at end of file diff --git a/src/components/Dropdown.module.css b/src/components/Dropdown.module.css index 98117e5b..1566421b 100644 --- a/src/components/Dropdown.module.css +++ b/src/components/Dropdown.module.css @@ -1,51 +1,55 @@ .dropdown { position: relative; - display: flex; - justify-content: space-between; - align-items: center; - gap: 0; - width: 130px; - padding: 0 19px; - border: 1px solid var(--gray200); - border-radius: 12px; - color: var(--gray800); - font-weight: 400; - font-size: 16px; - line-height: 26px; -} - -.dropdown.small { - justify-content: center; - width: 42px; + padding: 0; + border: none; } -.dropdownList { +.dropdown .list { + z-index: 1; position: absolute; - top: calc(100% + 8px); + top: calc(100% + 10px); right: 0; - width: 130px; - margin: 0; - padding: 0; - border: 1px solid var(--gray200); - border-radius: 12px; - background-color: white; + width: 139px; } -.dropdownList li input { - display: block; +.dropdown .list input { cursor: pointer; width: 100%; - height: 41px; - padding: 8px 0 6px; - border: none; - border-bottom: 1px solid var(--gray200); - background-color: transparent; - color: var(--gray800); + border: 1px solid #D1D5DB; + background-color: white; + color: var(--gray500); font-weight: 400; font-size: 16px; line-height: 26px; } -.dropdownList li:last-child input { - border: none; +.dropdown .list input:first-child { + padding: 11px 0 8px; + border-bottom: none; + border-radius: 8px 8px 0 0; +} + +.dropdown .list input:last-child { + padding: 8px 0 11px; + border-top: none; + border-radius: 0 0 8px 8px; +} + +@media (max-width: 767px) { + .dropdown .list { + width: 102px; + } + + .dropdown .list input { + font-size: 14px; + line-height: 17px; + } + + .dropdown .list input:first-child { + padding: 15px 0 12px; + } + + .dropdown .list input:last-child { + padding: 12px 0 15px; + } } \ No newline at end of file diff --git a/src/components/DropdownSort.jsx b/src/components/DropdownSort.jsx new file mode 100644 index 00000000..cf3c205d --- /dev/null +++ b/src/components/DropdownSort.jsx @@ -0,0 +1,40 @@ +import { useEffect, useRef, useState } from 'react'; +import icArrowDown from '../assets/ic_arrow_down.svg'; +import icSort from '../assets/ic_sort.svg'; +import styles from './DropdownSort.module.css'; + +const option = { recent: "최신순", favorite: "좋아요순", } + +function Dropdown({ state, setState, mode }) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) setIsOpen(false); + }; + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside);; + }, []); + + return ( + + ); +} + +export default Dropdown; \ No newline at end of file diff --git a/src/components/DropdownSort.module.css b/src/components/DropdownSort.module.css new file mode 100644 index 00000000..98117e5b --- /dev/null +++ b/src/components/DropdownSort.module.css @@ -0,0 +1,51 @@ +.dropdown { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + gap: 0; + width: 130px; + padding: 0 19px; + border: 1px solid var(--gray200); + border-radius: 12px; + color: var(--gray800); + font-weight: 400; + font-size: 16px; + line-height: 26px; +} + +.dropdown.small { + justify-content: center; + width: 42px; +} + +.dropdownList { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 130px; + margin: 0; + padding: 0; + border: 1px solid var(--gray200); + border-radius: 12px; + background-color: white; +} + +.dropdownList li input { + display: block; + cursor: pointer; + width: 100%; + height: 41px; + padding: 8px 0 6px; + border: none; + border-bottom: 1px solid var(--gray200); + background-color: transparent; + color: var(--gray800); + font-weight: 400; + font-size: 16px; + line-height: 26px; +} + +.dropdownList li:last-child input { + border: none; +} \ No newline at end of file diff --git a/src/components/Input.jsx b/src/components/Input.jsx index e0e836b2..4ba17117 100644 --- a/src/components/Input.jsx +++ b/src/components/Input.jsx @@ -1,11 +1,11 @@ import styles from "./Input.module.css"; -function Input ({ label, name, className, inputClassName, type, children, ...props }) { +function Input ({ label, name, className, inputClassName, type, inputRef, children, ...props }) { return (
{type === "textarea" - ?