diff --git a/public/ic_X.svg b/public/ic_X.svg new file mode 100644 index 00000000..f6674f7f --- /dev/null +++ b/public/ic_X.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/App.jsx b/src/App.jsx index 121e62d4..333937d6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -22,7 +22,7 @@ function App() { } /> } /> } /> - } /> + } /> ); diff --git a/src/components/AddItem/AddItem.jsx b/src/components/AddItem/AddItem.jsx new file mode 100644 index 00000000..749260c6 --- /dev/null +++ b/src/components/AddItem/AddItem.jsx @@ -0,0 +1,107 @@ +import { useState } from "react"; + +import ProductImg from "./components/ProductImg"; +import styles from "./styles/AddItem.module.css"; + +export default function AddItemContent() { + const [productName, setProductName] = useState(""); + const [description, setDescription] = useState(""); + const [price, setPrice] = useState(""); + const [tagInput, setTagInput] = useState(""); + const [tags, setTags] = useState([]); + const [imagePreview, setImagePreview] = useState(null); + + const isFormValid = + productName && description && price && tags.length > 0 && imagePreview; + + const handleKeyDown = (e) => { + if (e.key === "Enter") { + e.preventDefault(); + const trimmed = tagInput.trim(); + if (trimmed && !tags.includes(trimmed)) { + setTags((prev) => [...prev, trimmed]); + setTagInput(""); + } + } + }; + + const handleDelete = (targetTag) => { + setTags((prevTags) => prevTags.filter((tag) => tag !== targetTag)); + }; + + return ( +
+
+

상품 등록하기

+ +
+ +
+

상품 이미지

+ +
+ +
+

상품명

+ setProductName(e.target.value)} + > +
+ +
+

상품 소개

+ +
+ +
+

판매가격

+ setPrice(e.target.value)} + > +
+ +
+

태그

+ setTagInput(e.target.value)} + onKeyDown={handleKeyDown} + > + +
+ {tags.map((tag, idx) => ( + + #{tag} + + + ))} +
+
+
+ ); +} diff --git a/src/components/AddItem/components/ProductImg.jsx b/src/components/AddItem/components/ProductImg.jsx new file mode 100644 index 00000000..b9a1a176 --- /dev/null +++ b/src/components/AddItem/components/ProductImg.jsx @@ -0,0 +1,54 @@ +import { useRef, useState } from "react"; +import styles from "./styles/ProductImg.module.css"; + +export default function ProductImg({ preview, setPreview }) { + const inputRef = useRef(null); + + const handleImageChange = (e) => { + const file = e.target.files?.[0]; + + if (preview) { + alert("이미지는 한 개만 업로드할 수 있습니다."); + return; + } + + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setPreview(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + const handleDelete = () => { + setPreview(null); + }; + + return ( +
+ + + {preview && ( +
+ + 미리보기 +
+ )} +
+ ); +} diff --git a/src/components/AddItem/components/styles/ProductImg.module.css b/src/components/AddItem/components/styles/ProductImg.module.css new file mode 100644 index 00000000..4106c706 --- /dev/null +++ b/src/components/AddItem/components/styles/ProductImg.module.css @@ -0,0 +1,77 @@ +.wrapper { + display: flex; + gap: 2.4rem; +} + +.uploadBox { + width: 28.2rem; + height: 28.2rem; + background-color: var(--gray100); + border-radius: 1.2rem; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + cursor: pointer; + overflow: hidden; + + flex-shrink: 0; +} + +.textContainer { + display: inline-flex; + flex-direction: column; + align-items: center; +} + +.plus { + font-size: 5rem; + font-weight: 100; + color: var(--gray400); +} + +.text { + font-size: 1rem; + color: var(--gray400); + font-weight: 400; +} + +.imagePreviewBox { + width: 28.2rem; + height: 28.2rem; + border-radius: 1.2rem; + overflow: hidden; + position: relative; + + flex-shrink: 0; +} + +.previewImg { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.deleteButton { + position: absolute; + top: 8px; + right: 15px; + + width: 20px; + height: 20px; +} + +@media (max-width: 768px) { + .uploadBox { + width: 16.8rem; + height: 16.8rem; + } + + .imagePreviewBox { + width: 16.8rem; + height: 16.8rem; + } +} diff --git a/src/components/AddItem/styles/AddItem.module.css b/src/components/AddItem/styles/AddItem.module.css new file mode 100644 index 00000000..9faf8c31 --- /dev/null +++ b/src/components/AddItem/styles/AddItem.module.css @@ -0,0 +1,144 @@ +.container { + margin-top: 2.4rem; + margin-bottom: 6.9rem; + + width: 120rem; + height: 100%; + + margin-inline: auto; + + display: flex; + flex-direction: column; + gap: 3.2rem; +} + +.addContainer { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +} + +.addTitle { + font-size: 2rem; + font-weight: 700; + line-height: 3.2rem; +} + +.addBtn { + display: flex; + height: 42px; + padding: 1.2rem 2.3rem; + justify-content: center; + align-items: center; + gap: 1rem; + + border-radius: 0.8rem; + background: var(--blue); + + font-size: 1.6rem; + font-weight: 400; + color: var(--white); +} + +.addBtn:hover { + background: var(--hover-button); +} + +.addBtn:disabled { + background: var(--gray400); + + font-size: 1.6rem; + font-weight: 400; + color: var(--gray100); + + cursor: not-allowed; +} + +.contentContainer { + display: flex; + flex-direction: column; + gap: 1.6rem; +} + +.title { + font-size: 1.8rem; + font-weight: 700; +} + +.input { + width: 100%; + height: 5.6rem; + + border-radius: 1.2rem; +} + +.input::placeholder { + font-size: 1.6rem; + color: var(--gray400); +} + +.inputDescription { + resize: none; + width: 100%; + height: 28.2rem; + border-radius: 1.2rem; + border: none; + background-color: var(--gray100); + + font-size: 1.6rem; + line-height: 2.4rem; + + padding: 1.5rem 2.3rem; +} + +.inputDescription::placeholder { + font-size: 1.6rem; + color: var(--gray400); +} + +.tagList { + display: flex; + gap: 1.2rem; +} + +.tag { + width: 11rem; + height: 3.6rem; + border-radius: 2.6rem; + + position: relative; + + background-color: var(--gray100); + + display: flex; + justify-content: center; + align-items: center; + + gap: 0.8rem; + + font-size: 1.6rem; + font-weight: 400; +} + +.deleteBtn { + width: 2rem; + height: 2rem; +} + +.deleteImg { + width: 100%; + height: 100%; +} + +@media (max-width: 768px) { + .container { + width: 69.6rem; + } +} + +@media (max-width: 426px) { + .container { + width: 34.4rem; + } +} diff --git a/src/components/Item/AllItems.jsx b/src/components/Item/AllItems.jsx index 0f0fe909..61d0a608 100644 --- a/src/components/Item/AllItems.jsx +++ b/src/components/Item/AllItems.jsx @@ -4,8 +4,8 @@ import debounce from "lodash/debounce"; import { getProducts } from "../../api/itemAPI"; import { Link } from "react-router-dom"; -import ItemList from "./ItemList"; -import PageNation from "./PageNation"; +import ItemList from "./component/ItemList"; +import PageNation from "./component/PageNation"; import "./AllItem.css"; import Dropdown from "./component/Dropdown.jsx"; @@ -77,7 +77,7 @@ function AllItem() { className="item-search" placeholder="검색할 상품을 입력해주세요" /> - + 상품 등록하기 diff --git a/src/components/Item/BestItem.jsx b/src/components/Item/BestItem.jsx index 9bedb83a..3fd976ed 100644 --- a/src/components/Item/BestItem.jsx +++ b/src/components/Item/BestItem.jsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from "react"; import debounce from "lodash/debounce"; -import ItemList from "./ItemList"; +import ItemList from "./component/ItemList"; import { getProducts } from "../../api/itemAPI"; const getPageSize = () => { diff --git a/src/components/Item/ItemCard.css b/src/components/Item/component/ItemCard.css similarity index 100% rename from src/components/Item/ItemCard.css rename to src/components/Item/component/ItemCard.css diff --git a/src/components/Item/ItemCard.jsx b/src/components/Item/component/ItemCard.jsx similarity index 100% rename from src/components/Item/ItemCard.jsx rename to src/components/Item/component/ItemCard.jsx diff --git a/src/components/Item/ItemList.css b/src/components/Item/component/ItemList.css similarity index 100% rename from src/components/Item/ItemList.css rename to src/components/Item/component/ItemList.css diff --git a/src/components/Item/ItemList.jsx b/src/components/Item/component/ItemList.jsx similarity index 100% rename from src/components/Item/ItemList.jsx rename to src/components/Item/component/ItemList.jsx diff --git a/src/components/Item/PageNation.css b/src/components/Item/component/PageNation.css similarity index 100% rename from src/components/Item/PageNation.css rename to src/components/Item/component/PageNation.css diff --git a/src/components/Item/PageNation.jsx b/src/components/Item/component/PageNation.jsx similarity index 100% rename from src/components/Item/PageNation.jsx rename to src/components/Item/component/PageNation.jsx diff --git a/src/pages/AddItem.jsx b/src/pages/AddItem.jsx index 95cf562b..72635533 100644 --- a/src/pages/AddItem.jsx +++ b/src/pages/AddItem.jsx @@ -1,5 +1,15 @@ +import AddItemContent from "../components/AddItem/AddItem"; +import Banner from "../components/Item/Banner"; + function AddItem() { - return

상품 등록 페이지

; + return ( + <> +
+ +
+ + + ); } export default AddItem; diff --git a/src/pages/Item.jsx b/src/pages/Item.jsx index 856baa31..800d585e 100644 --- a/src/pages/Item.jsx +++ b/src/pages/Item.jsx @@ -7,7 +7,10 @@ import "../styles/item.css"; function Item() { return ( <> - +
+ +
+
diff --git a/src/styles/common.css b/src/styles/common.css index 6a3e4ae5..f84704a9 100644 --- a/src/styles/common.css +++ b/src/styles/common.css @@ -28,6 +28,8 @@ html { button { background-color: transparent; border: none; + cursor: pointer; + padding: 0; } ul { @@ -39,3 +41,12 @@ ul { a { text-decoration: none; } + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} diff --git a/vite.config.js b/vite.config.js index 8b0f57b9..4990a01f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ + base: "/", plugins: [react()], -}) +});