diff --git a/vite-project/public/ic_X.svg b/vite-project/public/ic_X.svg
new file mode 100644
index 00000000..a85baced
--- /dev/null
+++ b/vite-project/public/ic_X.svg
@@ -0,0 +1,5 @@
+
diff --git a/vite-project/public/ic_plus.svg b/vite-project/public/ic_plus.svg
new file mode 100644
index 00000000..b104c536
--- /dev/null
+++ b/vite-project/public/ic_plus.svg
@@ -0,0 +1,5 @@
+
diff --git a/vite-project/src/App.jsx b/vite-project/src/App.jsx
index 5e7626d9..931a2cea 100644
--- a/vite-project/src/App.jsx
+++ b/vite-project/src/App.jsx
@@ -3,14 +3,20 @@ import "./App.css";
import ProductList from "./pages/ProductList";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
+import Header from "./components/common/Header";
+import AddItems from "./pages/AddItems";
+import Layout from "./components/style/Layout";
function App() {
return (
- }>
- }>
- }>
+ }>
+ }>
+ }>
+ }>
+ }>
+
);
diff --git a/vite-project/src/components/BestProductSection/BestProduct.jsx b/vite-project/src/components/BestProductSection/BestProduct.jsx
deleted file mode 100644
index 03dd73a3..00000000
--- a/vite-project/src/components/BestProductSection/BestProduct.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useResponsivePage } from "../../hooks/useResponsivePage";
-import ProductGrid from "./../common/ProductGrid";
-
-const BestProduct = ({ products }) => {
- const { bestPicSize, bestPicContainerSize } = useResponsivePage();
-
- return (
-
- );
-};
-
-export default BestProduct;
diff --git a/vite-project/src/components/BestProductSection/BestProducts.jsx b/vite-project/src/components/BestProductSection/BestProducts.jsx
new file mode 100644
index 00000000..82f5c227
--- /dev/null
+++ b/vite-project/src/components/BestProductSection/BestProducts.jsx
@@ -0,0 +1,19 @@
+import { useResponsivePage } from "../../hooks/useResponsivePage";
+import ProductGrid from "../common/ProductGrid";
+
+const BestProducts = ({ products }) => {
+ const responsiveValues = useResponsivePage();
+
+ return (
+
+ );
+};
+
+export default BestProducts;
diff --git a/vite-project/src/components/ProductListSection/Container.jsx b/vite-project/src/components/ProductListSection/Container.jsx
index 61488f3f..0d02c770 100644
--- a/vite-project/src/components/ProductListSection/Container.jsx
+++ b/vite-project/src/components/ProductListSection/Container.jsx
@@ -7,31 +7,35 @@ import searchIcon from "../../assets/searchIcon.svg";
import Dropdown from "./Dropdown";
import { useResponsivePage } from "../../hooks/useResponsivePage";
import { Link } from "react-router-dom";
+import { SORT_OPTIONS } from "../../constant/SORT_OPTIONS";
const Container = ({ pageSize, isMobile }) => {
- const [orderBy, setOrderBy] = useState("recent");
+ const [orderBy, setOrderBy] = useState(SORT_OPTIONS[0].value);
const { setCurrentPage, getPageNumber, currentPage } = usePagination();
const { products, totalProductCount } = useGetProducts({
pageSize,
orderBy,
currentPage,
});
- const { normalPicSize, normalPicContainerSize } = useResponsivePage();
+ const ResponsiveValues = useResponsivePage();
- const handleSelect = (e) => {
- return setOrderBy(e.target.dataset.value);
+ const handleSelect = (val) => {
+ console.log("$$", val);
+ setOrderBy(val);
};
-
const { pages, totalPages } = getPageNumber(totalProductCount);
return (
-
+
{isMobile ? (
전체 상품
-
+
+ 상품등록하기
+
@@ -46,7 +50,11 @@ const Container = ({ pageSize, isMobile }) => {
placeholder="검색할 상품을 입력해주세요"
/>
-
+
@@ -74,8 +82,11 @@ const Container = ({ pageSize, isMobile }) => {
>
상품등록하기
-
-
+
@@ -83,8 +94,8 @@ const Container = ({ pageSize, isMobile }) => {
{
- const [isDropdown, setIsDropdown] = useState(false);
- const [dropdownValue, setDropdownValue] = useState(VALUES[orderBy]);
+const Dropdown = ({ onChange, options, value }) => {
+ const [open, setOpen] = useState(false);
+ const selected =
+ options.find((option) => option.value === value) ?? options[0];
- const handleClick = (e) => {
- handleSelect(e);
- setDropdownValue(VALUES[e.target.dataset.value]);
+ const handleClick = (val) => {
+ onChange(val);
+ setOpen(false);
};
return (
setIsDropdown(!isDropdown)}
+ onClick={() => setOpen(!open)}
className="hidden w-32 gap-4 px-5 py-3 border xs:flex rounded-xl"
>
-
{dropdownValue}
+
{selected.label}
setIsDropdown(!isDropdown)}
+ onClick={() => setOpen(!open)}
className="p-3 border rounded-xl xs:hidden"
>
- {isDropdown && (
-
{
- handleClick(e);
- setIsDropdown(!isDropdown);
- }}
- >
-
-
- 최신순
-
-
-
- 좋아요순
-
-
+ {open && (
+
+ {options.map((option) => {
+ return (
+ - {
+ handleClick(option.value);
+ console.log("$", option.value);
+ }}
+ >
+ {option.label}
+
+ );
+ })}
+
)}
);
diff --git a/vite-project/src/components/addItem/Input.jsx b/vite-project/src/components/addItem/Input.jsx
new file mode 100644
index 00000000..2489c679
--- /dev/null
+++ b/vite-project/src/components/addItem/Input.jsx
@@ -0,0 +1,68 @@
+import { INPUT_TYPE_STYLE } from "./InputClasses";
+
+const Input = ({
+ inputTypeStyle = "basic",
+ as,
+ type,
+ id,
+ imgUrl,
+ onChange,
+ onClick,
+ onKeyDown,
+ ...props
+}) => {
+ const styleClass = INPUT_TYPE_STYLE[inputTypeStyle] || "";
+ const Component = as || "input";
+
+ return (
+
+ {type === "file" ? (
+
+
+
+ {imgUrl && (
+
+ *이미지 등록은 최대 1개까지 가능합니다
+
+ )}
+
+ {imgUrl && (
+
+

+

onClick()}
+ />
+
+ )}
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default Input;
diff --git a/vite-project/src/components/addItem/InputClasses.js b/vite-project/src/components/addItem/InputClasses.js
new file mode 100644
index 00000000..33ef2656
--- /dev/null
+++ b/vite-project/src/components/addItem/InputClasses.js
@@ -0,0 +1,6 @@
+export const INPUT_TYPE_STYLE = {
+ basic: "px-6 py-4 rounded-xl h-[56px] w-full",
+ textarea: "px-6 py-4 rounded-xl h-[282px] w-full self-start ",
+ image:
+ "h-[282px] rounded-xl object-cover aspect-square flex items-center justify-center ",
+};
diff --git a/vite-project/src/components/common/ButtonClasses.js b/vite-project/src/components/common/ButtonClasses.js
new file mode 100644
index 00000000..482b68a8
--- /dev/null
+++ b/vite-project/src/components/common/ButtonClasses.js
@@ -0,0 +1,4 @@
+export const BTN_STYLE_TYPE = {
+ active: "rounded-lg px-[23px] py-3 bg-blue-500 text-white",
+ inactive: "bg-gray-400 rounded-lg px-[23px] py-3 text-white",
+};
diff --git a/vite-project/src/components/common/ButtonLink.jsx b/vite-project/src/components/common/ButtonLink.jsx
new file mode 100644
index 00000000..6851d17c
--- /dev/null
+++ b/vite-project/src/components/common/ButtonLink.jsx
@@ -0,0 +1,14 @@
+import { BTN_STYLE_TYPE } from "./ButtonClasses";
+
+const ButtonLink = ({ btnStyle = "inactive", as, children, ...props }) => {
+ const Component = as || "button";
+ const btnTypeClass = BTN_STYLE_TYPE[btnStyle] || "";
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default ButtonLink;
diff --git a/vite-project/src/components/common/ProductGrid.jsx b/vite-project/src/components/common/ProductGrid.jsx
index d817f146..3b5835de 100644
--- a/vite-project/src/components/common/ProductGrid.jsx
+++ b/vite-project/src/components/common/ProductGrid.jsx
@@ -1,6 +1,6 @@
import ProductCard from "./ProductCard";
-import { GRID_SIZES } from "./ProductGridTokens";
-import { PIC_SIZES } from "./ProductGridTokens";
+import { GRID_SIZES } from "./ProductGridClasses";
+import { PIC_SIZES } from "./ProductGridClasses";
const ProductGrid = ({
gridSize = "grid224",
diff --git a/vite-project/src/components/common/ProductGridTokens.js b/vite-project/src/components/common/ProductGridClasses.js
similarity index 100%
rename from vite-project/src/components/common/ProductGridTokens.js
rename to vite-project/src/components/common/ProductGridClasses.js
diff --git a/vite-project/src/components/style/Layout.jsx b/vite-project/src/components/style/Layout.jsx
new file mode 100644
index 00000000..72f07c32
--- /dev/null
+++ b/vite-project/src/components/style/Layout.jsx
@@ -0,0 +1,15 @@
+import { Outlet } from "react-router-dom";
+import Header from "../common/Header";
+
+const Layout = () => {
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/vite-project/src/constant/INPUT_OPTIONS.js b/vite-project/src/constant/INPUT_OPTIONS.js
new file mode 100644
index 00000000..1220dc64
--- /dev/null
+++ b/vite-project/src/constant/INPUT_OPTIONS.js
@@ -0,0 +1,38 @@
+export const INPUT_OPTIONS = [
+ {
+ id: "product_image",
+ title: "상품 이미지",
+ inputType: "file",
+ inputTypeStyle: "image",
+ src: "/ic_plus.svg",
+ alt: "십자가 모양 아이콘",
+ },
+ {
+ id: "product_name",
+ title: "상품명",
+ inputType: "input",
+ placeholder: "상품명을 입력해주세요",
+ inputTypeStyle: "basic",
+ },
+ {
+ id: "product_description",
+ title: "상품 소개",
+ placeholder: "상품 소개를 입력해주세요",
+ inputTypeStyle: "textarea",
+ tagName: "textarea",
+ },
+ {
+ id: "sales_price",
+ title: "판매가격",
+ inputType: "input",
+ placeholder: "판매 가격을 입력해주세요",
+ inputTypeStyle: "basic",
+ },
+ {
+ id: "hashtag",
+ title: "태그",
+ inputType: "input",
+ placeholder: "태그를 입력해주세요",
+ inputTypeStyle: "basic",
+ },
+];
diff --git a/vite-project/src/constant/RESPONSIVE_CONFIG.js b/vite-project/src/constant/RESPONSIVE_CONFIG.js
new file mode 100644
index 00000000..6c55d344
--- /dev/null
+++ b/vite-project/src/constant/RESPONSIVE_CONFIG.js
@@ -0,0 +1,29 @@
+export const CONFIG = {
+ desktop: {
+ pageSize: 10,
+ bestPageSize: 4,
+ isMobile: false,
+ normalPicSize: "normalPicSize",
+ normalPicContainerSize: "grid224",
+ bestPicContainerSize: "grid228",
+ bestPicSize: "bestPicSize",
+ },
+ tablet: {
+ pageSize: 6,
+ bestPageSize: 2,
+ isMobile: false,
+ normalPicSize: "normalPicSize",
+ normalPicContainerSize: "grid224",
+ bestPicContainerSize: "grid343",
+ bestPicSize: "bestPicTabletMobile",
+ },
+ mobile: {
+ pageSize: 4,
+ bestPageSize: 1,
+ isMobile: true,
+ normalPicSize: "normalPicMobile",
+ normalPicContainerSize: "grid168",
+ bestPicContainerSize: "grid343",
+ bestPicSize: "bestPicTabletMobile",
+ },
+};
diff --git a/vite-project/src/constant/SORT_OPTIONS.js b/vite-project/src/constant/SORT_OPTIONS.js
new file mode 100644
index 00000000..8956a2d5
--- /dev/null
+++ b/vite-project/src/constant/SORT_OPTIONS.js
@@ -0,0 +1,4 @@
+export const SORT_OPTIONS = [
+ { value: "recent", label: "최신순" },
+ { value: "favorite", label: "좋아요순" },
+];
diff --git a/vite-project/src/hooks/useGetProducts.js b/vite-project/src/hooks/useGetProducts.js
index 107d68c0..d35f4607 100644
--- a/vite-project/src/hooks/useGetProducts.js
+++ b/vite-project/src/hooks/useGetProducts.js
@@ -34,7 +34,6 @@ export default function useGetProducts({
loadImg(product.images[0]).catch(() => none_icon)
);
const validUrls = await Promise.all(validImagePromises);
- console.log(validUrls);
const validProducts = recent.list.map((product, index) => ({
...product,
diff --git a/vite-project/src/hooks/useResponsivePage.js b/vite-project/src/hooks/useResponsivePage.js
index c8699bcf..edf831df 100644
--- a/vite-project/src/hooks/useResponsivePage.js
+++ b/vite-project/src/hooks/useResponsivePage.js
@@ -1,56 +1,22 @@
-import { useEffect, useState } from "react";
-import { PIC_SIZES } from "../components/common/ProductGridTokens";
+import { useEffect, useMemo, useState } from "react";
+import { CONFIG } from "../constant/RESPONSIVE_CONFIG";
+
+function getDevice() {
+ if (window.matchMedia("(min-width: 1200px)").matches) return "desktop";
+ if (window.matchMedia("(min-width:680px)").matches) return "tablet";
+ return "mobile";
+}
export const useResponsivePage = () => {
- const [pageSize, setPageSize] = useState(10);
- const [bestPageSize, setBestPageSize] = useState(4);
- const [isMobile, setIsMobile] = useState(false);
- const [normalPicSize, setNormalPicSize] = useState("normalPicSize");
- const [normalPicContainerSize, setNormalPicContainerSize] =
- useState("grid224");
- const [bestPicContainerSize, setBestPicContainerSize] = useState("grid228");
- const [bestPicSize, setBestPicSize] = useState("bestPicSize");
+ const [device, setDevice] = useState(getDevice);
useEffect(() => {
- function updatePageSize() {
- if (window.matchMedia("(min-width: 1200px)").matches) {
- setPageSize(10);
- setBestPageSize(4);
- setIsMobile(false);
- setNormalPicSize("normalPicSize");
- setNormalPicContainerSize("grid224");
- setBestPicContainerSize("grid228");
- setBestPicSize("bestPicSize");
- } else if (window.matchMedia("(min-width:680px)").matches) {
- setPageSize(6);
- setBestPageSize(2);
- setIsMobile(false);
- setNormalPicSize("normalPicSize");
- setNormalPicContainerSize("grid224");
- setBestPicContainerSize("grid343");
- setBestPicSize("bestPicTabletMobile");
- } else {
- setPageSize(4);
- setBestPageSize(1);
- setIsMobile(true);
- setNormalPicSize("normalPicMobile");
- setNormalPicContainerSize("grid168");
- setBestPicContainerSize("grid343");
- setBestPicSize("bestPicTabletMobile");
- }
- }
- updatePageSize();
- window.addEventListener("resize", updatePageSize);
- return () => window.removeEventListener("resize", updatePageSize);
+ const onResize = () => setDevice(getDevice());
+ onResize();
+ window.addEventListener("resize", onResize);
+ return () => window.removeEventListener("resize", onResize);
}, []);
- return {
- pageSize,
- bestPageSize,
- isMobile,
- normalPicSize,
- normalPicContainerSize,
- bestPicSize,
- bestPicContainerSize,
- };
+ const values = useMemo(() => CONFIG[device], [device]);
+ return values;
};
diff --git a/vite-project/src/pages/AddItems.jsx b/vite-project/src/pages/AddItems.jsx
index d60019ab..bd11ac58 100644
--- a/vite-project/src/pages/AddItems.jsx
+++ b/vite-project/src/pages/AddItems.jsx
@@ -1,7 +1,149 @@
-import React from "react";
+import { useEffect, useRef, useState } from "react";
+import ButtonLink from "../components/common/ButtonLink";
+import Input from "../components/addItem/Input";
+import { INPUT_OPTIONS } from "../constant/INPUT_OPTIONS";
+
+const styles = {
+ fontBase: "text-lg font-bold",
+ inputContainer: "flex flex-col gap-4",
+ head: "flex justify-between",
+};
const AddItems = () => {
- return 상품등록하기
;
+ const [previewImage, setPreviewImage] = useState(null);
+ const [isValid, setIsValid] = useState(false);
+ const [hashtags, setHashtags] = useState([]);
+ const inputRefs = useRef({});
+
+ const handleFileChange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ if (previewImage) {
+ URL.revokeObjectURL(previewImage);
+ }
+ const newURL = URL.createObjectURL(file);
+ setPreviewImage(newURL);
+ }
+ };
+
+ const addToRefs = (el) => {
+ if (el) {
+ inputRefs.current[el.name] = el;
+ }
+ };
+
+ const handleRemoveImage = () => {
+ URL.revokeObjectURL(previewImage);
+ setPreviewImage(null);
+ inputRefs.current.product_image.value = "";
+ };
+
+ const handleHashTagEnter = (e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ const newTag = e.target.value.trim();
+ if (newTag && !hashtags.includes(newTag)) {
+ setHashtags((prev) => [...prev, newTag]);
+ e.target.value = "";
+ handleInputChange();
+ }
+ }
+ };
+
+ const handleRemoveHashtag = (tagToRemove) => {
+ setHashtags(hashtags.filter((tag) => tag !== tagToRemove));
+ handleInputChange();
+ };
+
+ // 유효성 검사 함수
+ const isFormValid = () => {
+ const refs = inputRefs.current;
+ const isNameValid = refs.product_name?.value.trim() !== "";
+ const isDescriptionValid = refs.product_description?.value.trim() !== "";
+ const isPriceValid = refs.sales_price?.value.trim() !== "";
+ const areHashtagsValid = hashtags.length > 0;
+
+ return (
+ isNameValid && isDescriptionValid && isPriceValid && areHashtagsValid
+ );
+ };
+
+ useEffect(() => {
+ setIsValid(isFormValid());
+ }, [hashtags, previewImage]);
+
+ // 텍스트 입력 시 유효성 검사
+ const handleInputChange = () => {
+ setIsValid(isFormValid());
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ const formData = {
+ product_image: inputRefs.current.product_image?.files[0],
+ product_name: inputRefs.current.product_name?.value,
+ product_description: inputRefs.current.product_description?.value,
+ sales_price: inputRefs.current.sales_price?.value,
+ hashtag: hashtags,
+ };
+ console.log("$전송할 데이터", formData);
+ };
+
+ return (
+
+ );
};
export default AddItems;
diff --git a/vite-project/src/pages/ProductList.jsx b/vite-project/src/pages/ProductList.jsx
index ecf674cd..f311f2c3 100644
--- a/vite-project/src/pages/ProductList.jsx
+++ b/vite-project/src/pages/ProductList.jsx
@@ -1,22 +1,24 @@
-import BestProduct from "../components/BestProductSection/BestProduct";
+import BestProduct from "../components/BestProductSection/BestProducts";
import useGetProducts from "../hooks/useGetProducts";
import Header from "../components/common/Header";
import Container from "../components/ProductListSection/Container";
import { useResponsivePage } from "../hooks/useResponsivePage";
const ProductList = () => {
- const { pageSize, bestPageSize, isMobile } = useResponsivePage();
+ const responsiveValues = useResponsivePage();
const { products: bestProducts } = useGetProducts({
- pageSize: bestPageSize,
+ pageSize: responsiveValues.bestPageSize,
orderBy: "favorite",
});
return (
<>
-
-
+
-
+
>
);
diff --git a/vite-project/src/services/getProductLists.js b/vite-project/src/services/getProductLists.js
index 9301015c..6976ef8c 100644
--- a/vite-project/src/services/getProductLists.js
+++ b/vite-project/src/services/getProductLists.js
@@ -6,9 +6,9 @@ export default async function getProductLists(
orderBy = "recent"
) {
try {
- const response = await instance.get(
- `products?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}`
- );
+ const response = await instance.get("products", {
+ params: { page, pageSize, orderBy },
+ });
return response.data;
} catch (error) {
throw new Error(`상품을 불러오는데 실패하였습니다. ${error.message}`);