diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3e212e1d..3dca2b7a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -18,4 +18,4 @@ module.exports = { { allowConstantExport: true }, ], }, -} +}; diff --git a/package-lock.json b/package-lock.json index b1c04feb..ab8993f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.60.0", - "react-router": "^7.6.3" + "react-router": "^7.6.3", + "styled-components": "^6.1.19" }, "devDependencies": { "@types/react": "^18.2.66", @@ -320,6 +321,27 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1256,6 +1278,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1632,6 +1660,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", @@ -1728,11 +1765,30 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -3434,7 +3490,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -3703,7 +3758,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/possible-typed-array-names": { @@ -3745,6 +3799,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4171,6 +4231,12 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4274,7 +4340,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4418,6 +4483,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4451,6 +4578,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index d12f9467..27c32455 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.60.0", - "react-router": "^7.6.3" + "react-router": "^7.6.3", + "styled-components": "^6.1.19" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/src/App.jsx b/src/App.jsx index b4467001..3c14e592 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,12 @@ -import { Route, Routes } from 'react-router'; -import Home from './pages/Home'; -import Login from './pages/auth/Login'; -import MainLayout from './layout/MainLayout'; -import Signup from './pages/auth/Signup'; -import AuthLayout from './layout/AuthLayout'; -import ItemsLayout from './layout/ItemsLayout'; -import Items from './pages/items/Items'; +import { Route, Routes } from "react-router"; +import Home from "./pages/Home"; +import Login from "./pages/auth/Login"; +import MainLayout from "./layout/MainLayout"; +import Signup from "./pages/auth/Signup"; +import AuthLayout from "./layout/AuthLayout"; +import ItemsLayout from "./layout/ItemsLayout"; +import Items from "./pages/items/Items"; +import AddItem from "./pages/items/AddItem"; function App() { return ( @@ -25,6 +26,7 @@ function App() { {/* 중고 마켓 */} }> } /> + } /> diff --git a/src/assets/icons/ic_X.svg b/src/assets/icons/ic_X.svg new file mode 100644 index 00000000..f6674f7f --- /dev/null +++ b/src/assets/icons/ic_X.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/ic_plus.svg b/src/assets/icons/ic_plus.svg new file mode 100644 index 00000000..5bb9abf5 --- /dev/null +++ b/src/assets/icons/ic_plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/DropdownList.jsx b/src/components/DropdownList.jsx index c9eff705..9d0d1b84 100644 --- a/src/components/DropdownList.jsx +++ b/src/components/DropdownList.jsx @@ -1,15 +1,15 @@ import '../styles/components/dropDownList.css'; import { memo, useEffect, useState } from 'react'; -function DropdownList({ changeOrder }) { - /** - * 드롭다운 리스트 메뉴 - */ - const orderList = [ - { name: '최신순', value: 'recent' }, - { name: '좋아요순', value: 'favorite' }, - ]; +/** + * 드롭다운 리스트 메뉴 + */ +const orderList = [ + { name: '최신순', value: 'recent' }, + { name: '좋아요순', value: 'favorite' }, +]; +function DropdownList({ changeOrder }) { const [order, setOrder] = useState(orderList[0].name); /** @@ -21,7 +21,7 @@ function DropdownList({ changeOrder }) { * 드롭다운 리스트 열림/닫힘 이벤트 */ const onClickOpen = () => { - setIsOpen(!isOpen); + setIsOpen((prevState) => !prevState); }; /** diff --git a/src/components/ImgInput.jsx b/src/components/ImgInput.jsx new file mode 100644 index 00000000..a701471c --- /dev/null +++ b/src/components/ImgInput.jsx @@ -0,0 +1,136 @@ +import { memo, useRef, useState } from "react"; +import styled from "styled-components"; +import { palette } from "../styles/commonStyles"; +import icPlus from "../assets/icons/ic_plus.svg"; +import icX from "../assets/icons/ic_X.svg"; + +/** + * input 태그를 감싸는 label + */ +const ImgUpload = styled.label` + width: 282px; + height: 282px; + color: ${palette.gray400}; + background-color: ${palette.gray100}; + background-image: url(${icPlus}); + background-repeat: no-repeat; + background-position: center 90px; + border: none; + border-radius: 12px; + cursor: pointer; + display: inline-block; + padding-top: 160px; + text-align: center; + + @media (max-width: 1200px) { + width: 168px; + height: 168px; + background-position: center 40px; + padding-top: 100px; + } +`; + +/** + * 이미지 미리보기를 표출 + */ +const ProductPreviewImg = styled.img` + width: 282px; + height: 282px; + border-radius: 12px; + margin-left: 30px; + position: relative; + + @media (max-width: 1200px) { + width: 168px; + height: 168px; + margin-left: 10px; + } +`; + +/** + * 이미지를 삭제할 버튼 + */ +export const DeleteButton = styled.button` + width: 22px; + height: 24px; + border-radius: 999px; + border: none; + background-color: ${palette.gray400}; + background-image: url(${icX}); + background-repeat: no-repeat; + background-position: center; + position: absolute; + z-index: 8888; + transform: translate(-150%, 50%); +`; + +/** + * 이미지 등록 버튼을 표출하는 컴포넌트 + */ +const ImgInput = ({ isUpload, setIsUpload }) => { + /** + * 미리보기 이미지 문자열을 담는다. + */ + const [imgPreviews, setImgPreviews] = useState(""); + const inputRef = useRef(); + + /** + * 업로드한 이미지의 정보를 URL 문자열로 변환 + * @param {event} e + */ + const onChangeUploadImg = (e) => { + if (imgPreviews !== "") { + return; + } + + const previewImg = e.target.files[0]; + const imgLink = URL.createObjectURL(previewImg); + setImgPreviews(imgLink); + }; + + /** + * 미리보기 이미지를 지우는 함수 + * @param {Event} e + */ + const onClickDeleteImg = (e) => { + e.preventDefault(); + inputRef.current.value = ""; + setImgPreviews(""); + setIsUpload(false); + }; + + /** + * 이미 등록된 이미지가 있을 때 업로드 상태를 true로 변경하는 함수 + */ + const onClickCheckImg = () => { + if (imgPreviews !== "") { + setIsUpload(true); + } + }; + + return ( + <> +
+ 이미지 등록 + +
+ {imgPreviews && ( +
+ + +
+ )} + + ); +}; + +export default memo(ImgInput); diff --git a/src/components/Pagination.jsx b/src/components/Pagination.jsx index aa4a59e7..50fb094d 100644 --- a/src/components/Pagination.jsx +++ b/src/components/Pagination.jsx @@ -41,8 +41,6 @@ export default function Pagination({ } setPageArr(pages); - - console.log(pages); }, [pageGroup, contentNum]); /** diff --git a/src/hooks/useMediaQuery.jsx b/src/hooks/useMediaQuery.jsx index 09c74579..65806367 100644 --- a/src/hooks/useMediaQuery.jsx +++ b/src/hooks/useMediaQuery.jsx @@ -11,15 +11,16 @@ export default function useMediaQuery(query) { /** * 반환할 데이터를 담아둘 state */ + const [isMatch, setIsMatch] = useState(() => getMatches(query)); + const handleChange = (e) => { + setIsMatch(e.matches); + }; + useEffect(() => { const media = window.matchMedia(query); - const handleChange = (e) => { - setIsMatch(e.matches); - }; - media.addEventListener('change', handleChange); return () => { diff --git a/src/layout/Header.jsx b/src/layout/Header.jsx index 06357c47..1300f215 100644 --- a/src/layout/Header.jsx +++ b/src/layout/Header.jsx @@ -1,6 +1,6 @@ -import '../styles/layout/header.css'; -import ic_profile from '../assets/icons/ic_profile.svg'; -import { Link, NavLink, useLocation } from 'react-router'; +import "../styles/layout/header.css"; +import ic_profile from "../assets/icons/ic_profile.svg"; +import { Link, NavLink, useLocation } from "react-router"; /** * 헤더 @@ -20,15 +20,15 @@ export default function Header() {
- {location.pathname !== '/' ? ( + {location.pathname !== "/" ? ( ) : null} - {location.pathname !== '/' ? ( + {location.pathname !== "/" ? ( 프로필_아이콘 ) : ( - {!isLoading && } + {!isLoading && } { +const requestProductList = async (querys) => { const url = new URL('https://panda-market-api.vercel.app/products'); - url.searchParams.append('page', page); - url.searchParams.append('pageSize', pageSize); - url.searchParams.append('orderBy', orderBy); + url.searchParams.append('page', querys.page); + url.searchParams.append('pageSize', querys.pageSize); + url.searchParams.append('orderBy', querys.orderBy); const response = await fetch(url, { method: 'get', diff --git a/src/styles/commonStyles.js b/src/styles/commonStyles.js new file mode 100644 index 00000000..bb58912d --- /dev/null +++ b/src/styles/commonStyles.js @@ -0,0 +1,32 @@ +import styled from "styled-components"; + +export const palette = { + gray900: "#111827", + gray800: "#1f2937", + gray700: "#374151", + gray600: "#4b5563", + gray500: "#6b7280", + gray400: "#9ca3af", + gray200: "#e5e7eb", + gray100: "#f3f4f6", + gray50: "#f9fafb", + blue: "#3692ff", + menu: "#4b5563", +}; + +/** + * 태그 지우기 버튼 div + */ +export const ItemsTag = styled.div` + gap: 10px; + height: 36px; + border: none; + background-color: ${palette.gray100}; + font-size: 16px; + border-radius: 999px; + display: flex; + justify-content: space-evenly; + align-items: center; + padding: 15px; + margin-right: 10px; +`;