diff --git a/1-4/css/auth.css b/1-4/css/auth.css deleted file mode 100644 index d67fcce6..00000000 --- a/1-4/css/auth.css +++ /dev/null @@ -1,146 +0,0 @@ -.material-symbols-outlined { - font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24; -} -* { - color: var(--gray-800); -} -html, -body { - height: 100%; -} - -.auth_container { - min-height: 100%; - min-width: 16rem; - display: flex; - align-items: center; - justify-content: center; -} - -.width_container { - max-width: 40rem; - width: 100%; -} - -.user_input label, -.user_input input, -.auth.btn { - display: block; - width: 100%; -} - -.user_input label { - font-size: 1.125rem; - font-weight: 700; - margin-bottom: 1rem; -} - -.user_input input, -.auth.btn { - margin-bottom: 1.5rem; -} - -.logo { - justify-content: center; - margin-bottom: 2.5rem; -} -.logo h1 { - font-size: 4.15rem; -} -.logo_img { - max-width: 6.5rem; -} - -.user_input input { - background-color: var(--gray-100); - border-radius: 0.75rem; - padding: 0 1.5rem; -} - -.error_text { - position: absolute; - bottom: -1rem; - left: 0; - font-size: 0.875rem; - color: #ff0000; -} - -.auth.btn { - font-size: 1.25rem; - font-weight: 600; - background-color: var(--gray-400); -} - -.input_wrap { - position: relative; -} -.password_icon { - position: absolute; - right: 1.5rem; - top: 50%; - transform: translateY(-50%); - cursor: pointer; -} - -.icon_cover { - width: 2.625rem; - height: 2.625rem; - border-radius: 50%; - overflow: hidden; -} -.sns_icon { - display: block; -} - -.auth_text a { - color: var(--primary-100); - border-bottom: 0.06rem solid var(--primary-100); -} - -.simple_login { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; - background-color: #e6f2ff; - padding: 1rem 1.5rem; - border-radius: var(--border-8); -} - -.simple_login span { - word-break: keep-all; - font-weight: 500; -} -.sns_login { - display: flex; - justify-content: flex-end; - gap: 1rem; -} - -.auth_helper { - text-align: center; -} -.auth_link { - margin-left: 0.3rem; -} - -@media screen and (max-width: 767px) { - .auth_container { - padding: 1rem; - } - - .logo h1 { - font-size: 2rem; - } - .logo_img { - max-width: 3.25rem; - } - .user_input label { - font-size: 0.875rem; - margin-bottom: 0.5rem; - } - - .width_container { - max-width: 25rem; - } -} diff --git a/1-4/css/common.css b/1-4/css/common.css deleted file mode 100644 index 19cdf7df..00000000 --- a/1-4/css/common.css +++ /dev/null @@ -1,141 +0,0 @@ -* { - color: var(--gray-700); -} -h2, -.main span { - font-weight: 700; -} -h2 { - font-size: 2.5rem; - word-break: keep-all; - line-height: 3rem; -} - -section:not(.main) { - background-color: #cfe5ff; -} - -.intro, -.ending { - padding-top: 4.2rem; -} -.flex_div { - height: 33rem; - display: flex; - justify-content: space-between; - align-items: flex-end; -} -.title_div { - margin-bottom: 6.25rem; - width: 30rem; -} -.ending .title_div { - margin-bottom: 11rem; -} - -.intro h2 { - margin-bottom: 2rem; -} - -.intro .shopping { - display: block; - font-size: 1.25rem; - text-align: center; - width: 21rem; -} - -.main img { - max-width: 36.75rem; -} -.main .card { - display: flex; - align-items: center; - justify-content: center; - gap: 3.5rem; - padding: 7rem 0; -} -.main .card.right { - flex-direction: row-reverse; - text-align: right; -} - -.card_info span { - display: block; - font-size: 1.125rem; - color: var(--primary-100); - margin-bottom: 1.4rem; -} -.card_info h2 { - width: 18rem; - margin-bottom: 2.8rem; -} -.card_info p { - font-size: 1.5rem; -} - -@media screen and (max-width: 1199px) { - .flex_div { - flex-direction: column; - justify-content: flex-end; - align-items: center; - gap: 5rem; - text-align: center; - } - .intro .flex_div { - height: 48rem; - } - .ending .flex_div { - height: 58rem; - } - .title_div { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 100%; - } - - .card_outter { - margin: 1.5rem; - } - - .main .card.right { - flex-direction: column; - } - .main .card { - flex-direction: column; - align-items: unset; - padding: 0 0 3.25rem; - } - .main img { - max-width: 100%; - border-radius: var(--border-16); - } - .card_info h2 { - width: 100%; - font-size: 2rem; - margin-bottom: 1.5rem; - } - .card_info p { - font-size: 1.125rem; - } -} - -@media screen and (max-width: 767px) { - .intro, - .ending { - padding-top: 0; - } - - .flex_div { - height: 33.75rem; - } - - .title_div { - max-width: 21rem; - } - - .main .card { - padding: 1.5rem 0; - } -} diff --git a/1-4/css/global.css b/1-4/css/global.css deleted file mode 100644 index 50f84f97..00000000 --- a/1-4/css/global.css +++ /dev/null @@ -1,139 +0,0 @@ -* { - box-sizing: border-box; - margin: 0; - font-family: "Pretendard", sans-serif; -} -:root { - --primary-100: #3692ff; - --primary-200: #1967d6; - --primary-300: #1251aa; - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-800: #1f2937; - --gray-900: #111827; - --error-red: #f74747; -} - -.width_container { - max-width: 70rem; - margin: 0 auto; -} - -a, -.btn { - color: var(--gray-100); -} -.text_tall { - line-height: 3.5rem; -} -.btn.text_tall { - border-radius: 2.5rem; -} -.btn { - background-color: var(--primary-100); -} -a.btn:hover { - background-color: var(--primary-200); -} -img { - width: 100%; -} - -header { - position: sticky; - top: 0; - background-color: #fff; - box-shadow: 0 0.3rem 1.875rem rgba(0, 0, 0, 0.1); - z-index: 9999; -} -header > div { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.4rem 0; -} - -header .logo_img { - max-width: 2.5rem; - max-height: 2.5rem; - width: 100%; -} -a.logo { - display: flex; - align-items: center; - gap: 1rem; -} -h1 { - color: var(--primary-100); - white-space: nowrap; -} -header h1 { - font-size: 2rem; -} - -header a.login { - display: block; - max-width: 8rem; - font-size: 1rem; - text-align: center; - flex-basis: 8rem; - padding: 0.7rem 2.1rem; - border-radius: var(--border-8); -} - -footer { - background-color: var(--gray-900); -} -.footer_container { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.4rem 0 3.5rem; - font-size: 1rem; -} - -footer .faq, -footer .sns_wrapper { - display: flex; - gap: 0.625rem; -} - -@media screen and (max-width: 1920px) { - header { - padding: 0 12.5rem; - } -} - -@media screen and (max-width: 1199px) { - header { - padding: 0 1.5rem; - } - .footer_container { - margin: 0 6.5rem; - } - footer .company_since { - color: var(--gray-100); - } -} - -@media screen and (max-width: 767px) { - header { - padding: 0 1rem; - } - - .footer_container { - flex-wrap: wrap; - margin: 0 auto; - padding: 2rem; - } - .company_since { - order: 3; - width: 100%; - padding-top: 3.75rem; - } -} diff --git a/1-4/css/import.css b/1-4/css/import.css deleted file mode 100644 index ef4277dc..00000000 --- a/1-4/css/import.css +++ /dev/null @@ -1,4 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"); - -@import url("reset.css"); -@import url("global.css"); diff --git a/1-4/css/reset.css b/1-4/css/reset.css deleted file mode 100644 index 50f66215..00000000 --- a/1-4/css/reset.css +++ /dev/null @@ -1,170 +0,0 @@ -html, -body, -div, -span, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -abbr, -address, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -samp, -small, -strong, -sub, -sup, -var, -b, -i, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - outline: 0; - vertical-align: baseline; - background: transparent; - font-style: normal; -} -ul { - line-height: normal; -} -body { - line-height: 1; -} - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section { - display: block; -} - -ul, -ol { - list-style: none; -} - -blockquote, -q { - quotes: none; -} - -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ""; - content: none; -} - -a { - margin: 0; - padding: 0; - font-size: 100%; - text-decoration: none; - vertical-align: baseline; - background: transparent; -} - -/* change colours to suit your needs */ -ins { - background-color: #ff9; - color: #000; - text-decoration: none; -} - -/* change colours to suit your needs */ -mark { - background-color: transparent; - color: #000; - font-weight: bold; -} - -del { - text-decoration: line-through; -} - -abbr[title], -dfn[title] { - border-bottom: 1px dotted; - cursor: help; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* change border colour to suit your needs */ -hr { - display: block; - height: 1px; - border: 0; - border-top: 1px solid #cccccc; - margin: 1em 0; - padding: 0; -} -input, button{ - border: none; -} -input, -select { - vertical-align: middle; -} diff --git a/1-4/faq.html b/1-4/faq.html deleted file mode 100644 index 2a0ed93c..00000000 --- a/1-4/faq.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - - - diff --git a/1-4/image/logo.png b/1-4/image/logo.png deleted file mode 100644 index b8626892..00000000 Binary files a/1-4/image/logo.png and /dev/null differ diff --git a/1-4/image/meta_padamarket.png b/1-4/image/meta_padamarket.png deleted file mode 100644 index d9def111..00000000 Binary files a/1-4/image/meta_padamarket.png and /dev/null differ diff --git a/1-4/image/sns01.png b/1-4/image/sns01.png deleted file mode 100644 index 39fed4aa..00000000 Binary files a/1-4/image/sns01.png and /dev/null differ diff --git a/1-4/image/sns02.png b/1-4/image/sns02.png deleted file mode 100644 index 45c46baa..00000000 Binary files a/1-4/image/sns02.png and /dev/null differ diff --git a/1-4/image/sns03.png b/1-4/image/sns03.png deleted file mode 100644 index 0b4c07a8..00000000 Binary files a/1-4/image/sns03.png and /dev/null differ diff --git a/1-4/image/sns04.png b/1-4/image/sns04.png deleted file mode 100644 index 272c3841..00000000 Binary files a/1-4/image/sns04.png and /dev/null differ diff --git a/1-4/image/sns_google.png b/1-4/image/sns_google.png deleted file mode 100644 index 97d45425..00000000 Binary files a/1-4/image/sns_google.png and /dev/null differ diff --git a/1-4/image/sns_kakao.png b/1-4/image/sns_kakao.png deleted file mode 100644 index 54c40bbf..00000000 Binary files a/1-4/image/sns_kakao.png and /dev/null differ diff --git a/1-4/index.html b/1-4/index.html deleted file mode 100644 index 756ab4ad..00000000 --- a/1-4/index.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - -
-
- - -
-
- -
-
-
-

일상의 모든 물건을 거래해 보세요

- 구매하러 가기 -
- intro panda -
-
- -
-
- -
-
- -
-
-
-

믿을 수 있는
판다마켓 중고 거래

-
- ending panda -
-
- - - - diff --git a/1-4/items.html b/1-4/items.html deleted file mode 100644 index a125abf2..00000000 --- a/1-4/items.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - - - diff --git a/1-4/js/auth.js b/1-4/js/auth.js deleted file mode 100644 index 22ece1d5..00000000 --- a/1-4/js/auth.js +++ /dev/null @@ -1,165 +0,0 @@ -const inputs = document.querySelectorAll('.input_group'); -const authBtn = document.querySelector('.auth.btn'); - -//input상태 저장소 -const inputValidState = {}; - -//forEach 공통 변수 -function parseGroupInput(group){ - const input = group.querySelector('.input_text'); - const errorText = group.querySelector('.error_text'); - const type = group.dataset.type; - - return { input, errorText, type }; -} - -//error_text 초기화 -function clearError(target, text){ - target.style.border = ''; - text.textContent = ''; -} - -//label변수 -function getLabelText(input) { - const label = input.closest('.input_group')?.querySelector('label'); - const labelText = label.textContent.trim(); - - return labelText; - -} - -//에러메세지 -const ERROR_MESSAGES = { - REQUIRED: (field) => `${field}을(를) 입력해주세요.`, - EMAIL: '잘못된 이메일 형식입니다.', - PASSWORD_LENGTH: '비밀번호를 8자 이상 입력해주세요.', - PASSWORD_MISMATCH: '비밀번호가 일치하지 않습니다.' -}; - -//password 일치 비교 -function validatePasswordMatch(){ - const pw = document.querySelector('#user_password'); - const pwCheck = document.querySelector('#user_password_check'); - const pwCheckError = pwCheck?.closest('.input_wrap')?.querySelector('.error_text'); - - if(pw && pwCheck){ - if(pw?.value !== pwCheck?.value){ - pwCheckError.textContent = ERROR_MESSAGES.PASSWORD_MISMATCH; - inputValidState[pw] = false; - inputValidState[pwCheck] = false; - }else{ - clearError(pwCheck, pwCheckError); - inputValidState[pw] = true; - inputValidState[pwCheck] = true; - } - } -} - -//에러메세지 적용 -function validateInput(type, value){ - const emailPattern = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,}$/; - - switch(type){ - case 'email': - return emailPattern.test(value) ? '' : ERROR_MESSAGES.EMAIL; - case 'password': - return value.length < 8 ? ERROR_MESSAGES.PASSWORD_LENGTH : ''; - default: - return ''; - } -} - -//오류메세지 이벤트 -function attachValidationHandlers(){ - inputs.forEach(group =>{ - const {input, errorText, type} = parseGroupInput(group); - - inputValidState[type] = false; - - //focusout - input.addEventListener('focusout', (e) => { - const target = e.target; - const values = target.value.trim(); - - const labelText = getLabelText(target); - - //빈 값 - if(values === ''){ - target.style.border = '1px solid #ff0000'; - errorText.textContent = ERROR_MESSAGES.REQUIRED(labelText); - inputValidState[type] = false; - return; - } - - //input 오류메세지 - const errorMessage = validateInput(type, values); - - if (errorMessage) { - errorText.textContent = errorMessage; - input.style.border = '1px solid #ff0000'; - inputValidState[type] = false; - } else { - clearError(input, errorText); - inputValidState[type] = true; - } - - //비밀번호 일치 비교 - if (type === 'password' || type === 'password_check') { - validatePasswordMatch(); - } - updateAuthButtonState(); - }); - }); -} - -//모든 input 작성 확인 -//true, false값 반환 -function updateInputState(){ - return Object.values(inputValidState).every(Boolean); -} - -//submit버튼 활성화 -function updateAuthButtonState(){ - - const isvalid = updateInputState(); - - authBtn.disabled = !isvalid; - authBtn.style.backgroundColor = isvalid ? 'var(--primary-100)' : 'var(--gray-400)'; - authBtn.style.cursor = isvalid ? 'pointer' : 'not-allowed'; -} - -//실시간 반영 -inputs.forEach(group => { - const {input} = parseGroupInput(group); - - ['input', 'change', 'focusout'].forEach(eventName => { - input.addEventListener(eventName, () => { - updateAuthButtonState(); - }); -}); -}); - - - -//패스워드 아이콘 활성화/비활성화 -const passwordIcon = document.querySelectorAll('.password_icon'); - -passwordIcon.forEach( icons => { - const prevInput = icons.previousElementSibling; - - icons.addEventListener('click', (e) => { - const target = e.target; - - if(prevInput.type === 'password'){ - target.classList.add('show'); - prevInput.type = 'text' - target.textContent = 'visibility' - }else{ - target.classList.remove('show'); - prevInput.type = 'password' - target.textContent = 'visibility_off' - } - }); -}); - -attachValidationHandlers(); \ No newline at end of file diff --git a/1-4/login.html b/1-4/login.html deleted file mode 100644 index 277e81ed..00000000 --- a/1-4/login.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - -
-
- -
-
- -
- -
- - -
-
- -
- -
- - visibility_off - -
-
- -
-
- -
- 판다마켓이 처음이신가요?회원가입 -
-
-
- - - diff --git a/1-4/privacy.html b/1-4/privacy.html deleted file mode 100644 index 0b22264a..00000000 --- a/1-4/privacy.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - - - diff --git a/1-4/signup.html b/1-4/signup.html deleted file mode 100644 index adce7490..00000000 --- a/1-4/signup.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - 판다마켓 - - - - - -
-
- -
-
- -
- -
- - -
-
- -
- -
- - -
-
- -
- -
- - visibility_off - -
-
- -
- -
- - visibility_off - -
-
- -
-
- -
- 이미 회원이신가요?로그인 -
-
-
- - - diff --git a/global.d.ts b/global.d.ts index 66b24566..c3b758f3 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,4 +1,2 @@ -declare module "*.png" { - const value: string; - export default value; -} +//경로 설정 +declare module "*.png"; diff --git a/jsconfig.json b/jsconfig.json index 20aab394..2872410b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,6 +5,7 @@ "paths": { "@/*": ["src/*"] }, + "types": ["vite-plugin-svgr/client"], "jsx": "react-jsx", "allowSyntheticDefaultImports": true, //default 없이 import 허용 "module": "esnext", @@ -16,6 +17,6 @@ "checkJs": true // 타입검사 }, //컴파일 검사 - "include": ["src", "global.d.ts"], + "include": ["src", "global.d.ts", "vite-env.d.ts"], "exclude": ["node_modules", "dist", "build", "coverage"] } diff --git a/package-lock.json b/package-lock.json index 99530653..3d5f20f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,8 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", "@vitejs/plugin-react-swc": "^3.10.2", "eslint": "^9.29.0", "eslint-plugin-react-hooks": "^5.2.0", @@ -1800,18 +1800,18 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "version": "19.1.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", + "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", - "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "version": "19.1.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", + "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/package.json b/package.json index a3eb036c..09ca5206 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ }, "devDependencies": { "@eslint/js": "^9.29.0", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", "@vitejs/plugin-react-swc": "^3.10.2", "eslint": "^9.29.0", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/src/App.jsx b/src/App.jsx index b6589767..9f717904 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,18 +1,23 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; +import "@/styles/variables.css"; import HomePage from "@/pages/home"; import ItemsPage from "@/pages/items"; import LoginPage from "@/pages/LoginPage"; import SignupPage from "@/pages/SignupPage"; -import AddItem from "@/pages/AddItem"; +import AddItem from "@/pages/addItem"; import HomeLayout from "@/layout/HomeLayout.jsx"; import DefaultLayout from "@/layout/DefaultLayout.jsx"; -import NoLayout from "@/layout/NoLayout.jsx"; import { ResetStyle } from "@/styles/ResetStyle"; import { GlobalStyle } from "@/styles/GlobalStyle"; import { ToastContainer } from "react-toastify"; +localStorage.setItem( + "accessToken", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTksInNjb3BlIjoiYWNjZXNzIiwiaWF0IjoxNzU1Nzc0MjIwLCJleHAiOjE3NTU3NzYwMjAsImlzcyI6InNwLXBhbmRhLW1hcmtldCJ9.4OaXBQYM8vVusQW7bw8-H8JnBpP1rApa3z-tXiQ0VAY" +); + function App() { return ( @@ -29,10 +34,8 @@ function App() { } /> - }> - } /> - } /> - + } /> + } /> }> } /> diff --git a/src/apis/apiRequest.js b/src/apis/apiRequest.js index 12e98c61..5275bbc2 100644 --- a/src/apis/apiRequest.js +++ b/src/apis/apiRequest.js @@ -4,17 +4,31 @@ const BASE_URL = "https://panda-market-api.vercel.app"; async function apiRequest(path, options = {}, isJson = true) { try { + const token = localStorage.getItem("accessToken"); + + console.log(localStorage.getItem("accessToken")); + const headers = { + ...(options.headers || {}), + }; + + if (token) { + headers["Authorization"] = `Bearer ${token}`; + } + + // FormData 이미지는 json이 아니다. + let body = options.body; + if (!(body instanceof FormData)) { + headers["Content-Type"] = "application/json"; + } + const res = await fetch(`${BASE_URL}${path}`, { ...options, - headers: { - "Content-Type": "application/json", - ...(options.headers || {}), - }, + headers, }); if (!res.ok) { - const errorText = await res.text(); - throw new Error(`[${res.status}] ${errorText}`); + const error_text = await res.text(); + throw new Error(`[${res.status}] ${error_text}`); } return isJson ? await res.json() : true; diff --git a/src/apis/products.js b/src/apis/products.js index e03894f5..d5159097 100644 --- a/src/apis/products.js +++ b/src/apis/products.js @@ -14,6 +14,12 @@ export const getProducts = ({ ); }; +export const postProduct = (data) => + apiRequest(`/products`, { + method: "POST", + body: JSON.stringify(data), + }); + //특정 상품 export const getProductById = (id) => apiRequest(`/products/${id}`); @@ -27,3 +33,12 @@ export const patchProduct = (id, data) => //상품 삭제 export const deleteProduct = (id) => apiRequest(`/products/${id}`, { method: "DELETE" }, false); + +//favorite 추가 +export const postFavorite = (id, data) => + apiRequest(`/products/${id}/favorite`, { method: "POST" }); + +//facorite 삭제 + +export const deleteFavorite = (id) => + apiRequest(`/products/${id}/favorite`, { method: "DELETE" }, false); diff --git a/src/assets/Img_home_01.png b/src/assets/Img_home_01.png deleted file mode 100644 index e54eafe5..00000000 Binary files a/src/assets/Img_home_01.png and /dev/null differ diff --git a/src/assets/Img_home_02.png b/src/assets/Img_home_02.png deleted file mode 100644 index 4afa3b92..00000000 Binary files a/src/assets/Img_home_02.png and /dev/null differ diff --git a/src/assets/Img_home_03.png b/src/assets/Img_home_03.png deleted file mode 100644 index d795d28c..00000000 Binary files a/src/assets/Img_home_03.png and /dev/null differ diff --git a/src/assets/Img_home_bottom.png b/src/assets/Img_home_bottom.png deleted file mode 100644 index 78bfd8e5..00000000 Binary files a/src/assets/Img_home_bottom.png and /dev/null differ diff --git a/src/assets/Img_home_top.png b/src/assets/Img_home_top.png deleted file mode 100644 index a7976703..00000000 Binary files a/src/assets/Img_home_top.png and /dev/null differ diff --git a/src/assets/NoImage.png b/src/assets/NoImage.png deleted file mode 100644 index 365248e4..00000000 Binary files a/src/assets/NoImage.png and /dev/null differ diff --git a/src/assets/arrow_icon.png b/src/assets/arrow_icon.png deleted file mode 100644 index 0b0e9d56..00000000 Binary files a/src/assets/arrow_icon.png and /dev/null differ diff --git a/src/assets/favorit_Icon.png b/src/assets/favorit_Icon.png deleted file mode 100644 index 63b6e81c..00000000 Binary files a/src/assets/favorit_Icon.png and /dev/null differ diff --git a/src/assets/favorit_fill_Icon.png b/src/assets/favorit_fill_Icon.png deleted file mode 100644 index 1f23dbdd..00000000 Binary files a/src/assets/favorit_fill_Icon.png and /dev/null differ diff --git a/1-4/image/Img_home_bottom.png b/src/assets/home_bottom.png similarity index 100% rename from 1-4/image/Img_home_bottom.png rename to src/assets/home_bottom.png diff --git a/1-4/image/Img_home_01.png b/src/assets/home_main01.png similarity index 100% rename from 1-4/image/Img_home_01.png rename to src/assets/home_main01.png diff --git a/1-4/image/Img_home_02.png b/src/assets/home_main02.png similarity index 100% rename from 1-4/image/Img_home_02.png rename to src/assets/home_main02.png diff --git a/1-4/image/Img_home_03.png b/src/assets/home_main03.png similarity index 100% rename from 1-4/image/Img_home_03.png rename to src/assets/home_main03.png diff --git a/1-4/image/Img_home_top.png b/src/assets/home_top.png similarity index 100% rename from 1-4/image/Img_home_top.png rename to src/assets/home_top.png diff --git a/src/assets/ic_arrow_down.svg b/src/assets/ic_arrow_down.svg new file mode 100644 index 00000000..358e4b87 --- /dev/null +++ b/src/assets/ic_arrow_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_left_active.svg b/src/assets/ic_arrow_left_active.svg new file mode 100644 index 00000000..4b110c20 --- /dev/null +++ b/src/assets/ic_arrow_left_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_left_back.svg b/src/assets/ic_arrow_left_back.svg new file mode 100644 index 00000000..00d9784e --- /dev/null +++ b/src/assets/ic_arrow_left_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_left_inactive.svg b/src/assets/ic_arrow_left_inactive.svg new file mode 100644 index 00000000..1daeca5c --- /dev/null +++ b/src/assets/ic_arrow_left_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_left_white.svg b/src/assets/ic_arrow_left_white.svg new file mode 100644 index 00000000..3ec2fc77 --- /dev/null +++ b/src/assets/ic_arrow_left_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_right_active.svg b/src/assets/ic_arrow_right_active.svg new file mode 100644 index 00000000..0ad718ef --- /dev/null +++ b/src/assets/ic_arrow_right_active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_right_inactive.svg b/src/assets/ic_arrow_right_inactive.svg new file mode 100644 index 00000000..764302b6 --- /dev/null +++ b/src/assets/ic_arrow_right_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_arrow_right_white.svg b/src/assets/ic_arrow_right_white.svg new file mode 100644 index 00000000..c3d3f7d4 --- /dev/null +++ b/src/assets/ic_arrow_right_white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_best_badge.svg b/src/assets/ic_best_badge.svg new file mode 100644 index 00000000..8470f48f --- /dev/null +++ b/src/assets/ic_best_badge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/ic_check.svg b/src/assets/ic_check.svg new file mode 100644 index 00000000..baa4aa3b --- /dev/null +++ b/src/assets/ic_check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/ic_close.svg b/src/assets/ic_close.svg new file mode 100644 index 00000000..f6674f7f --- /dev/null +++ b/src/assets/ic_close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/ic_favorit_Icon.svg b/src/assets/ic_favorit_Icon.svg new file mode 100644 index 00000000..f620d41f --- /dev/null +++ b/src/assets/ic_favorit_Icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_favorit_fill_Icon.svg b/src/assets/ic_favorit_fill_Icon.svg new file mode 100644 index 00000000..b47cb5ca --- /dev/null +++ b/src/assets/ic_favorit_fill_Icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_plus.svg b/src/assets/ic_plus.svg new file mode 100644 index 00000000..5bb9abf5 --- /dev/null +++ b/src/assets/ic_plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/ic_search.svg b/src/assets/ic_search.svg new file mode 100644 index 00000000..d323ead6 --- /dev/null +++ b/src/assets/ic_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_search_gray.svg b/src/assets/ic_search_gray.svg new file mode 100644 index 00000000..d9cc31ea --- /dev/null +++ b/src/assets/ic_search_gray.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_sns01.svg b/src/assets/ic_sns01.svg new file mode 100644 index 00000000..b9c9d493 --- /dev/null +++ b/src/assets/ic_sns01.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_sns02.svg b/src/assets/ic_sns02.svg new file mode 100644 index 00000000..14a6069a --- /dev/null +++ b/src/assets/ic_sns02.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_sns03.svg b/src/assets/ic_sns03.svg new file mode 100644 index 00000000..699b5380 --- /dev/null +++ b/src/assets/ic_sns03.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/ic_sns04.svg b/src/assets/ic_sns04.svg new file mode 100644 index 00000000..0b9337b0 --- /dev/null +++ b/src/assets/ic_sns04.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/ic_sort.svg b/src/assets/ic_sort.svg new file mode 100644 index 00000000..657b44f9 --- /dev/null +++ b/src/assets/ic_sort.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/ic_user.svg b/src/assets/ic_user.svg new file mode 100644 index 00000000..0480454d --- /dev/null +++ b/src/assets/ic_user.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/ic_visibillity_off.svg b/src/assets/ic_visibillity_off.svg new file mode 100644 index 00000000..57e303f5 Binary files /dev/null and b/src/assets/ic_visibillity_off.svg differ diff --git a/src/assets/ic_visibillity_on.svg b/src/assets/ic_visibillity_on.svg new file mode 100644 index 00000000..43a5af17 --- /dev/null +++ b/src/assets/ic_visibillity_on.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/left_arrow.png b/src/assets/left_arrow.png deleted file mode 100644 index 1252102c..00000000 Binary files a/src/assets/left_arrow.png and /dev/null differ diff --git a/src/assets/noImage.png b/src/assets/noImage.png new file mode 100644 index 00000000..5a106e57 Binary files /dev/null and b/src/assets/noImage.png differ diff --git a/src/assets/right_arrow.png b/src/assets/right_arrow.png deleted file mode 100644 index a03ad8af..00000000 Binary files a/src/assets/right_arrow.png and /dev/null differ diff --git a/src/assets/sns01.png b/src/assets/sns01.png deleted file mode 100644 index 39fed4aa..00000000 Binary files a/src/assets/sns01.png and /dev/null differ diff --git a/src/assets/sns02.png b/src/assets/sns02.png deleted file mode 100644 index 45c46baa..00000000 Binary files a/src/assets/sns02.png and /dev/null differ diff --git a/src/assets/sns03.png b/src/assets/sns03.png deleted file mode 100644 index 0b4c07a8..00000000 Binary files a/src/assets/sns03.png and /dev/null differ diff --git a/src/assets/sns04.png b/src/assets/sns04.png deleted file mode 100644 index 272c3841..00000000 Binary files a/src/assets/sns04.png and /dev/null differ diff --git a/src/assets/user_icon.png b/src/assets/user_icon.png deleted file mode 100644 index 827c5f2f..00000000 Binary files a/src/assets/user_icon.png and /dev/null differ diff --git a/src/components/layout/Footer.style.js b/src/components/layout/Footer.style.js index a9b8ea37..18ada85d 100644 --- a/src/components/layout/Footer.style.js +++ b/src/components/layout/Footer.style.js @@ -1,4 +1,4 @@ -import { media } from "@/styles/commomStyle"; +import { media } from "@/styles/commonStyle"; import styled from "styled-components"; export const FooterStyle = styled.footer` diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx index b4dac2e2..31eec775 100644 --- a/src/components/layout/Header.jsx +++ b/src/components/layout/Header.jsx @@ -1,13 +1,17 @@ -import { Link, NavLink } from "react-router-dom"; +import { Link, NavLink, useLocation } from "react-router-dom"; import pandaLogo from "@/assets/logo.png"; -import userIcon from "@/assets/user_icon.png"; +import UserIcon from "@/assets/ic_user.svg"; import { HeaderStyle } from "@/components/layout/Header.style"; +import { WidthContainer } from "@/styles/commonStyle"; function Header() { + const { pathname } = useLocation(); + + const itemsActive = pathname.startsWith("/items") || pathname === "/additem"; return ( -
+
판다로고 @@ -26,7 +30,9 @@ function Header() {
  • (isActive ? "active" : "")} + className={({ isActive }) => + isActive || itemsActive ? "active" : "" + } > 중고마켓 @@ -34,8 +40,8 @@ function Header() {
  • - 사용자 이미지 -
    + +
    ); } diff --git a/src/components/layout/Header.style.js b/src/components/layout/Header.style.js index a8f930d9..c2205e4b 100644 --- a/src/components/layout/Header.style.js +++ b/src/components/layout/Header.style.js @@ -1,4 +1,4 @@ -import { flexCenter, fullSize, media, textStyles } from "@/styles/commomStyle"; +import { flexCenter, fullSize, media, textStyles } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import styled from "styled-components"; @@ -66,6 +66,7 @@ export const HeaderStyle = styled.header` text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px; + color: var(--primary-100); } .user_img { diff --git a/src/components/layout/HomeFooter.jsx b/src/components/layout/HomeFooter.jsx index d1da3699..0c07092f 100644 --- a/src/components/layout/HomeFooter.jsx +++ b/src/components/layout/HomeFooter.jsx @@ -1,27 +1,21 @@ import { Link } from "react-router-dom"; -import { snsImages } from "@/components/layout/snsData"; import { FooterStyle } from "@/components/layout/Footer.style"; +import { WidthContainer } from "@/styles/commonStyle"; +import SnsList from "@/components/layout/SnsList"; function HomeFooter() { return ( -
    -

    ©codeit - 2024

    -
    - Privacy Policy - FAQ + +
    +

    ©codeit - 2024

    +
    + Privacy Policy + FAQ +
    +
    - -
      - {snsImages.map((sns, index) => ( -
    1. - - {sns.alt} - -
    2. - ))} -
    -
    + ); } diff --git a/src/components/layout/HomeHeader.jsx b/src/components/layout/HomeHeader.jsx index 85b628d2..7675a2e7 100644 --- a/src/components/layout/HomeHeader.jsx +++ b/src/components/layout/HomeHeader.jsx @@ -1,11 +1,12 @@ import { Link } from "react-router-dom"; import pandaLogo from "@/assets/logo.png"; import { HeaderStyle } from "@/components/layout/Header.style"; +import { WidthContainer } from "@/styles/commonStyle"; function HomeHeader() { return ( -
    + 판다로고

    판다마켓

    @@ -13,7 +14,7 @@ function HomeHeader() { 로그인 -
    +
    ); } diff --git a/src/components/layout/SnsList.jsx b/src/components/layout/SnsList.jsx new file mode 100644 index 00000000..06325884 --- /dev/null +++ b/src/components/layout/SnsList.jsx @@ -0,0 +1,20 @@ +import { snsImages } from "@/components/layout/snsData"; + +export default function SnsList() { + return ( + <> +
      + {snsImages.map((sns, index) => { + const SnsIcon = sns.svg; + return ( +
    1. + + + +
    2. + ); + })} +
    + + ); +} diff --git a/src/components/layout/snsData.js b/src/components/layout/snsData.js index eeba1577..8a51ac9e 100644 --- a/src/components/layout/snsData.js +++ b/src/components/layout/snsData.js @@ -1,27 +1,27 @@ -import imgSNS01 from "@/assets/sns01.png"; -import imgSNS02 from "@/assets/sns02.png"; -import imgSNS03 from "@/assets/sns03.png"; -import imgSNS04 from "@/assets/sns04.png"; +import imgSNS01 from "@/assets/ic_sns01.svg"; +import imgSNS02 from "@/assets/ic_sns02.svg"; +import imgSNS03 from "@/assets/ic_sns03.svg"; +import imgSNS04 from "@/assets/ic_sns04.svg"; export const snsImages = [ { href: "https://www.facebook.com/", - img: imgSNS01, + svg: imgSNS01, alt: "facebook", }, { href: "https://x.com/", - img: imgSNS02, + svg: imgSNS02, alt: "X", }, { href: "https://www.youtube.com/", - img: imgSNS03, + svg: imgSNS03, alt: "youtube", }, { href: "https://www.instagram.com/", - img: imgSNS04, + svg: imgSNS04, alt: "instagram", }, ]; diff --git a/src/layout/NoLayout.jsx b/src/layout/NoLayout.jsx deleted file mode 100644 index fd51323b..00000000 --- a/src/layout/NoLayout.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Outlet } from "react-router-dom"; - -function NoLayout() { - return ; -} - -export default NoLayout; diff --git a/src/pages/AddItem.jsx b/src/pages/AddItem.jsx deleted file mode 100644 index 4d635afd..00000000 --- a/src/pages/AddItem.jsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function AddItem() { - return ( - <> -
    - - ); -} diff --git a/src/pages/addItem/AddImage.jsx b/src/pages/addItem/AddImage.jsx new file mode 100644 index 00000000..8062c105 --- /dev/null +++ b/src/pages/addItem/AddImage.jsx @@ -0,0 +1,114 @@ +import AddIcon from "@/assets/ic_plus.svg"; +import XIcon from "@/assets/ic_close.svg"; +import { useRef, useState } from "react"; +import apiRequest from "@/apis/apiRequest"; +import styled from "styled-components"; +import { pxToRem } from "@/utils/pxToRem"; +import { flexCenter, fullSize, textStyles } from "@/styles/commonStyle"; + +export default function AddImage({ imgTitle = "상품이미지", onChange }) { + const [imageUrl, setImageUrl] = useState(null); + const fileInputRef = useRef(null); + + const handleFileChange = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("image", file); + + try { + const data = await apiRequest("/images/upload", { + method: "POST", + body: formData, + headers: {}, + }); + setImageUrl(data.url); + onChange?.(data.url); + } catch (error) { + console.error("이미지 등록 실패:", error); + } + }; + + const handleClick = () => { + fileInputRef.current.click(); + }; + + const handleRemove = () => { + setImageUrl(null); + fileInputRef.current.value = ""; + onChange?.(null); + }; + + return ( +
  • + + +
    + {imageUrl ? ( +
    + {imgTitle} + +
    + ) : ( +
    +
    + + 이미지 등록 +
    +
    + )} +
    + + +
    +
  • + ); +} + +export const AddImageStyle = styled.div` + #item_img { + position: relative; + width: ${pxToRem(282)}; + height: ${pxToRem(282)}; + cursor: pointer; + margin: 1rem 0 2rem; + } + + .image_add { + ${fullSize} + } + .image_add.btn { + position: relative; + } + + .icon_position { + height: 100%; + ${flexCenter} + flex-direction: column; + } + + .close.bnt { + position: absolute; + background-color: transparent; + width: ${pxToRem(20)}; + height: ${pxToRem(20)}; + right: ${pxToRem(13)}; + top: ${pxToRem(14)}; + } +`; diff --git a/src/pages/addItem/InputField.jsx b/src/pages/addItem/InputField.jsx new file mode 100644 index 00000000..50f5aa82 --- /dev/null +++ b/src/pages/addItem/InputField.jsx @@ -0,0 +1,37 @@ +import { useState } from "react"; + +export default function InputField({ + id, + label, + type = "text", + placeholder, + errorMessage, + ...inputProps +}) { + const [touched, setTouched] = useState(false); + + const handleBlur = (e) => { + setTouched(true); + if (inputProps.onBlur) inputProps.onBlur(e); + }; + + const value = inputProps.value || ""; + + let errors = + touched && (!value.trim() || (type === "number" && !/^\d+$/.test(value))); + + return ( +
  • + + + {errors && {errorMessage}} +
  • + ); +} diff --git a/src/pages/addItem/InputTag.jsx b/src/pages/addItem/InputTag.jsx new file mode 100644 index 00000000..364a5787 --- /dev/null +++ b/src/pages/addItem/InputTag.jsx @@ -0,0 +1,94 @@ +import XIcon from "@/assets/ic_close.svg"; +import { flexCenter } from "@/styles/commonStyle"; +import { pxToRem } from "@/utils/pxToRem"; +import { useState } from "react"; +import styled from "styled-components"; + +export default function InputTag({ onChange }) { + const [tags, setTags] = useState([]); + const [inputValue, setInputValue] = useState(""); + const [includeValue, setIncludeValue] = useState(false); + + const handleKeyDown = (e) => { + if (e.key === "Enter" && inputValue.trim()) { + e.preventDefault(); + + if (tags.some((tag) => tag.label === inputValue.trim())) { + setIncludeValue(true); + return; + } + + const new_tag = { + id: crypto.randomUUID(), + label: inputValue.trim(), + }; + + const newTags = [...tags, new_tag]; + setTags(newTags); + onChange?.(newTags.map((tag) => tag.label)); + setInputValue(""); + } + }; + + const removeTag = (id) => { + const newTags = tags.filter((tag) => tag.id !== id); + setTags(newTags); + onChange?.(newTags.map((tag) => tag.label)); + }; + + return ( +
  • + + + setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + /> + {includeValue && ( + 이미 입력된 태그입니다 + )} + +
      + {tags.map((tag) => ( +
    1. + #{tag.label} + +
    2. + ))} +
    +
    +
  • + ); +} + +const TagStyle = styled.div` + .input_style { + margin-bottom: ${pxToRem(14)}; + } + + .tags { + display: flex; + align-items: center; + flex-wrap: nowrap; + gap: ${pxToRem(12)}; + } + + .tag { + ${flexCenter} + line-height: ${pxToRem(36)}; + background-color: var(--gray-100); + padding: 0 1rem; + border-radius: ${pxToRem(26)}; + } + + .tag_icon { + background-color: transparent; + } +`; diff --git a/src/pages/addItem/index.jsx b/src/pages/addItem/index.jsx new file mode 100644 index 00000000..cc9cf5f1 --- /dev/null +++ b/src/pages/addItem/index.jsx @@ -0,0 +1,109 @@ +import { postProduct } from "@/apis/products"; +import AddImage from "@/pages/addItem/AddImage"; +import { AddItemStyle } from "@/pages/addItem/index.style"; +import InputField from "@/pages/addItem/InputField"; +import InputTag from "@/pages/addItem/InputTag"; +import { WidthContainer } from "@/styles/commonStyle"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export default function AddItem() { + const navigate = useNavigate(); + + const [form, setForm] = useState({ + itemName: "", + itemIntroduction: "", + itemPrice: "", + images: [], + tags: [], + }); + + const errorMessage = { + itemName: "상품명을 입력해주세요", + itemIntroduction: "상품 소개를 입력해주세요", + itemPrice: "숫자로 된 판매 가격을 입력해주세요", + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setForm((prev) => ({ ...prev, [name]: value })); + }; + + const isFormValid = + form.itemName.trim() && + form.itemIntroduction.trim() && + form.itemPrice.trim(); + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const data = { + name: form.itemName, + description: form.itemIntroduction, + price: Number(form.itemPrice), + images: form.images, + tags: form.tags, + }; + await postProduct(data); + + navigate("/items"); + } catch (error) { + console.error("상품 등록 실패:", error); + } + }; + + return ( + + +
    +
    +

    상품 등록하기

    + +
    +
      + setForm({ ...form, images: url ? [url] : [] })} + /> + + + + + + setForm({ ...form, tags })} /> +
    +
    +
    +
    + ); +} diff --git a/src/pages/addItem/index.style.js b/src/pages/addItem/index.style.js new file mode 100644 index 00000000..05de0ab8 --- /dev/null +++ b/src/pages/addItem/index.style.js @@ -0,0 +1,69 @@ +import { pxToRem } from "@/utils/pxToRem"; +import styled from "styled-components"; +import "@/styles/variables.css"; +import { textStyles } from "@/styles/commonStyle"; + +export const AddItemStyle = styled.div` + .input_list { + margin-bottom: ${pxToRem(60)}; + } + + .item_add { + display: flex; + justify-content: space-between; + margin: ${pxToRem(50)} 0 ${pxToRem(24)}; + } + .item_submit { + width: ${pxToRem(74)}; + height: ${pxToRem(42)}; + border-radius: var(--border-8); + } + + .item_submit:disabled { + background-color: var(--gray-400); + cursor: unset; + } + + label { + ${textStyles["text-2lg-bold"]} + } + + .image_add, + .input_style { + background-color: var(--gray-100); + border-radius: ${pxToRem(12)}; + ${textStyles["text-lg-regular"]} + } + + .image_add, + .input_style::placeholder { + color: var(--gray-400); + } + + .input_style { + display: block; + width: 100%; + outline: none; + line-height: ${pxToRem(56)}; + padding: 0 ${pxToRem(24)}; + margin: 1rem 0 2rem; + color: var(--gray-800); + } + + .has_error { + position: relative; + } + + .has_error .input_style { + outline: 2px solid var(--error-red); + box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.08); + } + + .errorText { + position: absolute; + display: block; + bottom: ${pxToRem(-30)}; + color: var(--error-red); + ${textStyles["text-lg-regular"]} + } +`; diff --git a/src/pages/home/HomeEnding.jsx b/src/pages/home/HomeEnding.jsx index e5955a40..ffb8eb15 100644 --- a/src/pages/home/HomeEnding.jsx +++ b/src/pages/home/HomeEnding.jsx @@ -1,18 +1,21 @@ -import EndingImage from "@/assets/Img_home_bottom.png"; +import EndingImage from "@/assets/home_bottom.png"; +import { WidthContainer } from "@/styles/commonStyle"; function HomeEnding() { return (
    -
    -
    -

    - 믿을 수 있는 -
    - 판다마켓 중고 거래 -

    + +
    +
    +

    + 믿을 수 있는 +
    + 판다마켓 중고 거래 +

    +
    + ending panda
    - ending panda -
    +
    ); } diff --git a/src/pages/home/HomeIntro.jsx b/src/pages/home/HomeIntro.jsx index 5193f649..62def1ee 100644 --- a/src/pages/home/HomeIntro.jsx +++ b/src/pages/home/HomeIntro.jsx @@ -1,19 +1,22 @@ import { Link } from "react-router-dom"; -import IntroImage from "@/assets/img_home_top.png"; +import IntroImage from "@/assets/home_top.png"; +import { WidthContainer } from "@/styles/commonStyle"; function HomeIntro() { return (
    -
    -
    -

    일상의 모든 물건을 거래해 보세요

    - - 구매하러 가기 - + +
    +
    +

    일상의 모든 물건을 거래해 보세요

    + + 구매하러 가기 + +
    + intro panda
    - intro panda -
    +
    ); } diff --git a/src/pages/home/HomeMain.jsx b/src/pages/home/HomeMain.jsx index fa72ece1..ae3bfb78 100644 --- a/src/pages/home/HomeMain.jsx +++ b/src/pages/home/HomeMain.jsx @@ -1,17 +1,18 @@ import { homeCards } from "@/pages/home/homeCardData"; import HomeCard from "@/pages/home/HomeCard"; +import { WidthContainer } from "@/styles/commonStyle"; function HomeMain() { return (
    -
    +
      {homeCards.map((card) => ( ))}
    -
    +
    ); } diff --git a/src/pages/home/homeCardData.js b/src/pages/home/homeCardData.js index e6aad9a4..07b34bab 100644 --- a/src/pages/home/homeCardData.js +++ b/src/pages/home/homeCardData.js @@ -1,6 +1,6 @@ -import Homecard1 from "@/assets/Img_home_01.png"; -import Homecard2 from "@/assets/Img_home_02.png"; -import Homecard3 from "@/assets/Img_home_03.png"; +import Homecard1 from "@/assets/home_main01.png"; +import Homecard2 from "@/assets/home_main02.png"; +import Homecard3 from "@/assets/home_main03.png"; export const homeCards = [ { diff --git a/src/pages/items/ItemList.jsx b/src/pages/items/ItemList.jsx index e1ee581a..ce3ee924 100644 --- a/src/pages/items/ItemList.jsx +++ b/src/pages/items/ItemList.jsx @@ -1,30 +1,43 @@ -import { ItemListStyle } from "@/pages/items/ItemList.style"; -import noImage from "@/assets/NoImage.png"; -import favoriteIcon from "@/assets/favorit_Icon.png"; -import favoriteFillIcon from "@/assets/favorit_fill_Icon.png"; -import { useState } from "react"; -import { patchProduct } from "@/apis/products"; +import { useEffect, useState } from "react"; +import { deleteFavorite, getProductById, postFavorite } from "@/apis/products"; import { Link } from "react-router-dom"; +import { ItemListStyle } from "@/pages/items/ItemList.style"; +import noImage from "@/assets/noImage.png"; +import FavoriteIcon from "@/assets/ic_favorit_Icon.svg"; +import FavoriteFillIcon from "@/assets/ic_favorit_fill_Icon.svg"; + export default function ItemList({ id, images, name, price, favoriteCount }) { const [isClick, setIsClick] = useState(false); const [count, setCount] = useState(favoriteCount); const [loading, setLoading] = useState(false); + useEffect(() => { + async function fetchDetail() { + const product = await getProductById(id); + if (product) { + setIsClick(product.isFavorite); + setCount(product.favoriteCount); + } + } + fetchDetail(); + }, [id]); + const handleFavoriteClick = async () => { if (loading) return; setLoading(true); - setIsClick((prev) => !prev); - const newCount = count + (!isClick ? +1 : -1); + const nextState = !isClick; + setIsClick(nextState); + const newCount = count + (nextState ? +1 : -1); setCount(newCount); try { - await patchProduct(id, { favoriteCount: newCount }); + nextState ? await postFavorite(id) : await deleteFavorite(id); } catch (error) { console.error("좋아요 변경 실패:", error); - setIsClick(!isClick); + setIsClick(isClick); setCount(count); } finally { setLoading(false); @@ -45,16 +58,16 @@ export default function ItemList({ id, images, name, price, favoriteCount }) { />
    -

    + {name} -

    + {Number(price).toLocaleString()} 원 - favorite + {isClick ? ( + + ) : ( + + )} {count}
    diff --git a/src/pages/items/ItemList.style.js b/src/pages/items/ItemList.style.js index 7077076d..dadba7e6 100644 --- a/src/pages/items/ItemList.style.js +++ b/src/pages/items/ItemList.style.js @@ -1,4 +1,4 @@ -import { textStyles } from "@/styles/commomStyle"; +import { textStyles } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import styled from "styled-components"; diff --git a/src/pages/items/ItemsOrder.jsx b/src/pages/items/ItemsOrder.jsx index 8b321a0c..ade0db69 100644 --- a/src/pages/items/ItemsOrder.jsx +++ b/src/pages/items/ItemsOrder.jsx @@ -1,11 +1,11 @@ import styled from "styled-components"; - +import ArrowIcon from "@/assets/ic_arrow_down.svg"; +import SortIcon from "@/assets/ic_sort.svg"; import { pxToRem } from "@/utils/pxToRem"; -import arrowIcon from "@/assets/arrow_icon.png"; -import { flexCenter, textStyles } from "@/styles/commomStyle"; +import { flexCenter, media, textStyles } from "@/styles/commonStyle"; import { useState } from "react"; -export default function ItemsOrder({ orderBy, setOrderBy }) { +export default function ItemsOrder({ orderBy, setOrderBy, device }) { const [isOpen, setIsOpen] = useState(false); const handleSelect = (value) => { @@ -17,8 +17,14 @@ export default function ItemsOrder({ orderBy, setOrderBy }) {
    setIsOpen((prev) => !prev)}> - {orderBy === "recent" ? "최신순" : "인기순"} - arrowIcon + {device === "mobile" ? ( + + ) : ( + <> + {orderBy === "recent" ? "최신순" : "인기순"} + + + )}
    {isOpen && (
    @@ -57,6 +63,10 @@ const Div = styled.div` .listLabel { line-height: ${pxToRem(42)}; gap: ${pxToRem(28)}; + ${media.mobile} { + width: ${pxToRem(42)}; + height: ${pxToRem(42)}; + } } .arrow_icon { @@ -73,6 +83,9 @@ const Div = styled.div` display: block; text-align: center; background-color: #fff; + ${media.mobile} { + right: 0; + } } .listOption:hover { diff --git a/src/pages/items/ItemsSearch.jsx b/src/pages/items/ItemsSearch.jsx index 92555b12..1c04a724 100644 --- a/src/pages/items/ItemsSearch.jsx +++ b/src/pages/items/ItemsSearch.jsx @@ -1,9 +1,11 @@ -import { flexCenter, textStyles } from "@/styles/commomStyle"; +import { flexCenter, fullSize, textStyles } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import { useState } from "react"; // import { toast } from "react-toastify"; import styled from "styled-components"; +import SearchIcon from "@/assets/ic_search_gray.svg"; + export default function ItemsSearch({ setSearchInput }) { const [inputValue, setInputValue] = useState(""); @@ -20,6 +22,7 @@ export default function ItemsSearch({ setSearchInput }) { }; const handleClick = () => { + // 빈칸 입력시 경고 토스트 // if (inputValue.trim() === "") { // toast.warning("검색어를 입력해주세요", { // toastId: "empty-search", @@ -43,13 +46,27 @@ export default function ItemsSearch({ setSearchInput }) { return (
    - - +
    + + +
    + +
    ); } @@ -60,17 +77,37 @@ const Div = styled.div` height: ${pxToRem(42)}; padding: 0 ${pxToRem(13)}; } - - .search_input{ + + .search_wrapper { + position: relative; + display: flex; + align-items: center; + padding: 0; + } + + .search_input { + ${fullSize} + padding-left: ${pxToRem(40)}; background-color: var(--gray-100); border-radius: var(--border-10) 0 0 var(--border-10); } - .search_btn{ - ${textStyles["text-lg-medium"]} + .search_input:focus { + outline: none; + } + + .search_icon { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + } + + .search_btn { + ${textStyles["text-lg-medium"]} background-color: var(--primary-100); color: #fff; border-radius: 0 var(--border-10) var(--border-10) 0; } - `; diff --git a/src/pages/items/PageList.jsx b/src/pages/items/PageList.jsx index 8b6ac793..66e3cc7d 100644 --- a/src/pages/items/PageList.jsx +++ b/src/pages/items/PageList.jsx @@ -1,6 +1,6 @@ -import LArrow from "@/assets/left_arrow.png"; -import RArrow from "@/assets/right_arrow.png"; -import { flexCenter } from "@/styles/commomStyle"; +import LArrow from "@/assets/ic_arrow_left_active.svg"; +import RArrow from "@/assets/ic_arrow_right_active.svg"; +import { flexCenter } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import styled from "styled-components"; @@ -34,7 +34,7 @@ export default function PageList({ totalCount, page, setPage, getAllList }) { onClick={goPrevGroup} disabled={!prevGroup} > - pagelist left + {Array.from({ length: groupEnd - groupStart + 1 }, (_, i) => { @@ -56,7 +56,7 @@ export default function PageList({ totalCount, page, setPage, getAllList }) { onClick={goNextGroup} disabled={!nextGroup} > - pagelist right + @@ -71,9 +71,14 @@ const PageListStyle = styled.div` } .page_btn { + ${flexCenter} border: 1px solid var(--gray-200); } + .page_arrow_img > path { + transform: translate(${pxToRem(0.6)}, ${pxToRem(0.6)}); + } + .page_list, .page_btn { outline: none; @@ -91,9 +96,4 @@ const PageListStyle = styled.div` .page_list:hover .page_btn.arrow { background-color: transparent; } - - .page_arrow_img { - width: ${pxToRem(7)}; - height: ${pxToRem(12)}; - } `; diff --git a/src/pages/items/index.jsx b/src/pages/items/index.jsx index 272a1313..ea032ffc 100644 --- a/src/pages/items/index.jsx +++ b/src/pages/items/index.jsx @@ -7,14 +7,15 @@ import { handleFetch } from "@/utils/handleFetch"; import ItemList from "@/pages/items/ItemList"; import ItemsSearch from "@/pages/items/ItemsSearch"; import ItemsOrder from "@/pages/items/ItemsOrder"; -import { ItemsStyle } from "@/pages/items/Items.style"; +import { ItemsStyle } from "@/pages/items/index.style"; import PageList from "@/pages/items/PageList"; +import { breakpoints, media } from "@/styles/commonStyle"; //반응형 const getDeviceType = () => { const width = window.innerWidth; - if (width >= 1200) return "pc"; - if (width >= 768) return "tablet"; + if (width >= breakpoints.pc) return "pc"; + if (width >= breakpoints.tablet) return "tablet"; return "mobile"; }; @@ -82,12 +83,6 @@ function ItemsPage() { "best" ); - //맨 처음 렌더링했을 때 - useEffect(() => { - fetchProducts(); - fetchBestProducts(); - }, []); - // 페이지이동, 검색, 정렬변경, 화면 크기 변경했을 때 렌더링 useEffect(() => { fetchProducts(); @@ -105,7 +100,7 @@ function ItemsPage() { ) : ( <> -

    베스트 상품

    +

    베스트 상품

      {bestProducts.map((item) => (
    • @@ -116,13 +111,17 @@ function ItemsPage() {
      -

      전체 상품

      +

      전체 상품

      - - - - + +
        diff --git a/src/pages/items/Items.style.js b/src/pages/items/index.style.js similarity index 73% rename from src/pages/items/Items.style.js rename to src/pages/items/index.style.js index bebbeb5c..b1003f7b 100644 --- a/src/pages/items/Items.style.js +++ b/src/pages/items/index.style.js @@ -1,4 +1,4 @@ -import { flexCenter, fullSize, media, textStyles } from "@/styles/commomStyle"; +import { flexCenter, media, textStyles } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import styled from "styled-components"; @@ -7,8 +7,10 @@ export const ItemsStyle = styled.div` margin: 0 auto; padding: ${pxToRem(24)} 0 ${pxToRem(58)}; - h3 { + .itmes_title { ${textStyles["text-xl-bold"]} + line-height: ${pxToRem(42)}; + margin-bottom: 0.5rem; } /* 상품 nav */ @@ -18,11 +20,21 @@ export const ItemsStyle = styled.div` align-items: center; margin-bottom: ${pxToRem(24)}; line-height: ${pxToRem(42)}; + + ${media.mobile} { + position: relative; + flex-wrap: wrap; + } } .item_order { ${flexCenter}; gap: ${pxToRem(12)}; + + ${media.mobile} { + width: 100%; + justify-content: space-between; + } } .item_all_list { @@ -36,6 +48,12 @@ export const ItemsStyle = styled.div` border-radius: var(--border-10); background-color: var(--primary-100); padding: 0 ${pxToRem(23)}; + + ${media.mobile} { + position: absolute; + right: 0; + top: 0; + } } /* 상품 이미지 */ @@ -44,6 +62,16 @@ export const ItemsStyle = styled.div` gap: ${pxToRem(24)}; } + .product_img { + aspect-ratio: 1 / 1; + object-fit: cover; + width: 100%; + } + + .best_items > li { + flex: 1; + } + .all_items { display: grid; grid-template-columns: repeat(5, 1fr); @@ -58,25 +86,6 @@ export const ItemsStyle = styled.div` } } - .best_item .product_img { - width: ${pxToRem(282)}; - height: ${pxToRem(282)}; - - ${media.tablet} { - width: ${pxToRem(343)}; - height: ${pxToRem(343)}; - } - } - - .all_items .product_img { - width: ${pxToRem(221)}; - height: ${pxToRem(221)}; - ${media.mobile} { - width: ${pxToRem(168)}; - height: ${pxToRem(168)}; - } - } - ${media.tablet} { max-width: ${pxToRem(696)}; } diff --git a/src/styles/GlobalStyle.js b/src/styles/GlobalStyle.js index 2d70cf8c..5e956316 100644 --- a/src/styles/GlobalStyle.js +++ b/src/styles/GlobalStyle.js @@ -1,3 +1,4 @@ +import { media } from "@/styles/commonStyle"; import { pxToRem } from "@/utils/pxToRem"; import { createGlobalStyle } from "styled-components"; @@ -8,35 +9,6 @@ export const GlobalStyle = createGlobalStyle` font-family: "Pretendard", sans-serif; } - :root { - /* 색상 */ - --primary-100: #3692ff; - --primary-200: #1967d6; - --primary-300: #1251aa; - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-800: #1f2937; - --gray-900: #111827; - --error-red: #f74747; - - /* border-radius */ - --border-8: ${pxToRem(8)}; - --border-10: ${pxToRem(10)}; - --border-16: ${pxToRem(16)}; - - } - - /* pc기준 */ - .width_container { - max-width: 70rem; - margin: 0 auto; - } - a, .btn { color: var(--gray-100); diff --git a/src/styles/commomStyle.js b/src/styles/commonStyle.js similarity index 89% rename from src/styles/commomStyle.js rename to src/styles/commonStyle.js index a6ff56a6..5ab5c111 100644 --- a/src/styles/commomStyle.js +++ b/src/styles/commonStyle.js @@ -1,10 +1,16 @@ -import { css } from "styled-components"; +import { pxToRem } from "@/utils/pxToRem"; +import styled, { css } from "styled-components"; // pc, tablet, mobie +export const breakpoints = { + pc: 1200, + tablet: 768, + mobile: 375, +}; + export const media = { - pc: "@media (min-width: 1200px)", - tablet: "@media (min-width: 768px) and (max-width: 1199px)", - mobile: "@media (min-width: 375px) and (max-width: 767px)", + tablet: `@media (max-width: ${breakpoints.pc - 1}px)`, + mobile: `@media (max-width: ${breakpoints.tablet - 1}px)`, }; //공통 스타일 @@ -18,6 +24,17 @@ export const fullSize = css` height: 100%; `; +export const WidthContainer = styled.div` + max-width: 70rem; + margin: 0 auto; + ${media.tablet} { + max-width: ${pxToRem(696)}; + } + ${media.mobile} { + max-width: ${pxToRem(344)}; + } +`; + //폰트 스타일 export const textStyles = { "text-3xl-bold": css` diff --git a/src/styles/variables.css b/src/styles/variables.css new file mode 100644 index 00000000..478c6c93 --- /dev/null +++ b/src/styles/variables.css @@ -0,0 +1,21 @@ +:root { + /* 색상 */ + --primary-100: #3692ff; + --primary-200: #1967d6; + --primary-300: #1251aa; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --error-red: #f74747; + + /* border-radius */ + --border-8: 0.5rem; + --border-10: 0.625rem; + --border-16: 1rem; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..e7baf44c --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +declare module "*.svg" { + import * as React from "react"; + const Component: React.FunctionComponent< + React.SVGProps & { title?: string } + >; + export default Component; +} diff --git a/vite.config.js b/vite.config.js index 77c4c1c2..c64e5843 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,16 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import path from "path"; +import svgr from "vite-plugin-svgr"; export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + svgr({ + exportAsDefault: true, // 기본 export를 React 컴포넌트로 + include: "**/*.svg", // 모든 .svg 파일 적용 + }), + ], resolve: { alias: { "@": path.resolve(__dirname, "src"),