diff --git a/14-Sprint-Mission/global/style.css b/14-Sprint-Mission/global/style.css deleted file mode 100644 index b473c2c2..00000000 --- a/14-Sprint-Mission/global/style.css +++ /dev/null @@ -1,460 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -header { - display: flex; - justify-content: center; - align-items: center; - background-color: #ffffff; - width: 100%; - height: 70px; - font-family: "Pretendard", sans-serif; -} - -.header-inner { - width: 100%; - max-width: 1120px; /* 최대 간격 제한 */ - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-logo img { - width: 153px; - height: 51px; -} - -.login-button { - display: flex; /* 플렉스 박스를 사용 */ - justify-content: center; /* 가로 방향 중앙 정렬 */ - align-items: center; /* 세로 방향 중앙 정렬 */ - text-align: center; /* 글자 중앙 정렬 */ - background-color: #3692ff; - width: 128px; - height: 48px; - padding: 12px 23px; - gap: 10px; - border-radius: 8px; - color: #f3f4f6; - font-size: 16px; - font-weight: 600; - line-height: 32px; - letter-spacing: 0%; - text-decoration: none; -} - -body { - margin: 0; - padding: 0; - box-sizing: border-box; - word-break: keep-all; /* 띄어쓰기 기준으로 줄바꿈하도록 설정 */ - font-family: "Pretendard", sans-serif; - overflow-x: hidden; -} - -.banner { - position: relative; - display: flex; - justify-content: center; - align-items: end; - background-color: #cfe5ff; - width: 100vw; /* 화면 꽉 채우기 */ - height: 540px; - box-sizing: border-box; - gap: 7px; - background-position: 80% bottom; -} - -.img-panda { - width: 100%; - height: auto; - min-width: 448px; - max-width: 746px; /* 적절한 최대 너비 */ - object-fit: contain; -} - -#features { - padding: 24px 24px 56px 24px; -} - -.feature { - background-color: #fcfcfc; - padding: 138px 0; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - border-radius: 12px; - padding: 24px 0; - gap: 52px; -} - -.feature-title { - font-size: 28px; - line-height: 40px; - white-space: normal; - word-break: keep-all; - max-width: 205px; -} - -.feature-content { - display: flex; - flex-direction: column; - gap: 8px; - text-align: left; - align-items: flex-start; -} - -.feature:nth-child(2) .feature-content { - text-align: right; - align-items: flex-end; -} - -.feature-content h1 { - font-size: 28px; - line-height: 40px; - white-space: normal; -} - -.feature-description { - font-size: 16px; - line-height: 24px; -} - -/* - .feature-content { - flex: 1; - } -*/ - -/* 요소 최대 넓이보다 큰 경우에 가로 중앙 정렬 해주는 inner container */ -.wrapper { - max-width: 1200px; - margin: 0 auto; - width: 100%; - display: flex; - justify-content: center; -} - -#top-banner-title { - padding: 0; -} - -#bottom-banner-title { - padding: 112.5px 0 0; -} - -h1 { - color: #374151; - font-size: 40px; - font-weight: 700; - line-height: 56px; - letter-spacing: 0%; - padding: 12px 0; -} - -.feature-tag { - font-family: "Pretendard", sans-serif; - color: #3692ff; - font-weight: 700; - font-size: 18px; - line-height: 26px; - letter-spacing: 0%; -} - -.feature-description { - font-family: "Pretendard"; - color: #374151; - font-weight: 500; - font-size: 24px; - line-height: 32px; - letter-spacing: 2%; - padding: 12px 0 0 0; -} - -.hero-button { - font-family: "Pretendard", sans-serif; - background-color: #3692ff; - color: #f9fafb; - font-size: 20px; - font-weight: 600; - text-decoration: none; - border: none; - border-radius: 40px; - padding: 16px 124px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 10px; - margin-top: 32px; -} - -footer { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - background-color: #111827; - font-family: "Pretendard"; - line-height: 100%; - padding: 32px 300px 108px; - gap: 10px; -} - -.copyright { - text-align: left; - font-size: 16px; - font-weight: 400; - color: #e5e7eb; - letter-spacing: 0%; - line-height: 19.09px; -} - -.footerMenu { - display: flex; - gap: 30px; -} - -.footer-link { - color: #e5e7eb; - text-decoration: none; - font-size: 16px; - font-weight: 400; - transition: color 0.3s ease; -} - -.footer-link:hover, -.footer-link:visited, -.footer-link:active { - color: #e5e7eb; - text-decoration: none; -} - -.socialMedia { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 12px; -} - -.socialMedia > img { - width: 20px; - height: 20px; -} - -/* Mobile, Tablet 공통 (최대 1199px) */ -@media screen and (max-width: 1199px) { - .wrapper { - flex-direction: column; - align-items: center; - text-align: center; - } - - .feature { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 0; - gap: 24px; - } - - .feature img { - width: 100%; - height: auto; - object-fit: cover; - } - - .feature-content { - text-align: left; - align-items: flex-start; - width: 100%; - margin-bottom: 52px; - } - - .feature-content h1 { - white-space: nowrap; - max-width: 100%; - } - - .feature:nth-child(2) { - flex-direction: column-reverse; - } - - .feature:nth-child(2) .feature-content { - align-items: flex-end; - text-align: right; - } -} - -/* Tablet: 768px 이상 ~ 1199px 이하 */ -@media (min-width: 768px) and (max-width: 1199px) { - .header-inner { - padding: 0 24px; - } - - .hero-button { - white-space: nowrap; - width: 357px; - min-height: 56px; - } - - .banner { - height: auto; - padding: 84px 0 0 0; - background-size: 120%; - } - - .banner h1 { - font-size: 40px; - line-height: 56px; - padding-top: 84px; - padding-bottom: 24px; - } - - .img-panda { - width: 100%; - height: auto; - max-width: 100%; - margin-top: 211px; - } - - #features { - padding-bottom: 0; - } - - .feature-content h2 { - font-size: 18px; - line-height: 26px; - padding: 0; - } - - .feature-content h1 { - font-size: 32px; - line-height: 42px; - padding: 16px 0 24px; - } - - .feature-description { - font-size: 18px; - line-height: 26px; - padding: 0; - } - - #bottomBanner .wrapper { - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - gap: 6px; - } - - #bottom-banner-title { - padding: 0; - } - - footer { - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 32px 104px 108px; - text-align: center; - } - - .footerMenu { - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - } - - .socialMedia { - justify-content: center; - } -} - -/* Mobile: 375px 이상 ~ 767px 이하 */ -@media (min-width: 375px) and (max-width: 767px) { - .header-inner { - padding: 0 16px; - } - - .hero-button { - white-space: nowrap; - width: 240px; - min-height: 48px; - } - - .banner { - height: auto; - padding: 48px 0 0 0; - background-size: 120%; - } - - .img-panda { - width: 100%; - height: auto; - max-width: 100%; - margin-top: 132px; - } - - #features { - padding: 52px 15px 0; - } - - .feature-content h2 { - font-size: 16px; - line-height: 26px; - padding: 0; - } - - .feature-content h1 { - font-size: 24px; - line-height: 32px; - padding: 8px 0 16px; - } - - .feature-description { - font-size: 16px; - line-height: 26px; - padding: 0; - } - - footer { - padding: 32px; - flex-direction: row; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - } - - .footerMenu, - .socialMedia { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - margin-bottom: 60px; - gap: 12px; - } - - .footerMenu { - display: flex; - flex-direction: row; - gap: 30px; - } - - .socialMedia { - display: flex; - flex-direction: row; - gap: 12px; - } - - .copyright { - order: 2; - width: 100%; - text-align: left; - } -} diff --git a/14-Sprint-Mission/items/items.html b/14-Sprint-Mission/items/items.html deleted file mode 100644 index e69de29b..00000000 diff --git a/14-Sprint-Mission/login/login.html b/14-Sprint-Mission/login/login.html deleted file mode 100644 index 41053339..00000000 --- a/14-Sprint-Mission/login/login.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - 로그인 페이지 - - - - -
-
- -
-
- - -

- 판다마켓이 처음이신가요? 회원가입 -

-
-
- - - diff --git a/14-Sprint-Mission/login/login.js b/14-Sprint-Mission/login/login.js deleted file mode 100644 index 9607c183..00000000 --- a/14-Sprint-Mission/login/login.js +++ /dev/null @@ -1,79 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const emailInput = document.getElementById("email"); - const emailError = document.getElementById("email-error"); - const passwordInput = document.getElementById("password"); - const passwordError = document.getElementById("password-error"); - - emailInput.addEventListener("blur", () => { - const value = emailInput.value.trim(); - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - - if (!value) { - emailError.textContent = "이메일을 입력해주세요."; // textContext는 글자를 넣어주는 역할 - emailInput.classList.add("error"); // classList.add는 클래스 추가 - } else if (!emailRegex.test(value)) { - // test는 정규표현식에 맞는지 검사하는 함수 - emailError.textContent = "잘못된 이메일 형식입니다."; - emailInput.classList.add("error"); - } else { - // 올바른 이메일 입력 시 - emailError.textContent = ""; // 에러메시지 삭제 - emailInput.classList.remove("error"); // error 클래스 제거 - } - }); - - passwordInput.addEventListener("blur", () => { - const value = passwordInput.value.trim(); - - if (!value) { - passwordError.textContent = "비밀번호를 입력해주세요."; - passwordInput.classList.add("error"); - } else if (value.length < 8) { - passwordError.textContent = "비밀번호를 8자 이상 입력해주세요."; - passwordInput.classList.add("error"); - } else { - passwordError.textContent = ""; - passwordInput.classList.remove("error"); - } - }); - - const loginBtn = document.querySelector(".button-wide"); // querySelector는 선택자 - - function updateBtnState() { - const emailValue = emailInput.value.trim(); - const passwordValue = passwordInput.value.trim(); - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - - const isEmailValid = emailValue && emailRegex.test(emailValue); - const isPasswordValid = passwordValue && passwordValue.length >= 8; - - if (isEmailValid && isPasswordValid) { - loginBtn.disabled = false; // 활성화 - loginBtn.style.cursor = "pointer"; - } else { - loginBtn.disabled = true; // 비활성화 - loginBtn.style.cursor = "not-allowed"; - } - } - - emailInput.addEventListener("input", updateBtnState); - passwordInput.addEventListener("input", updateBtnState); - - loginBtn.addEventListener("click", () => { - if (!loginBtn.disabled) { - window.location.href = "/items"; - } - }); - - const togglePassword = document.getElementById("toggle-password"); - - togglePassword.addEventListener("click", () => { - const icon = togglePassword.querySelector("img"); - const isHidden = passwordInput.type === "password"; - - passwordInput.type = isHidden ? "text" : "password"; - icon.src = isHidden - ? "/images/btn_visibility_on_24px.png" - : "/images/btn_visibility_off_24px.png"; - }); -}); diff --git a/package-lock.json b/package-lock.json index a1e590ee..d9442d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.6.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -14671,6 +14672,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", + "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz", + "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==", + "license": "MIT", + "dependencies": { + "react-router": "7.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15480,6 +15528,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", diff --git a/package.json b/package.json index 7ff0d6b5..cc88c710 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.6.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777cc..00000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/14-Sprint-Mission/images/landing/ic_facebook.png b/public/images/facebook_white.png similarity index 100% rename from 14-Sprint-Mission/images/landing/ic_facebook.png rename to public/images/facebook_white.png diff --git a/14-Sprint-Mission/images/logo/google=lg.png b/public/images/google_logo.png similarity index 100% rename from 14-Sprint-Mission/images/logo/google=lg.png rename to public/images/google_logo.png diff --git a/14-Sprint-Mission/images/landing/ic_instagram.png b/public/images/instagram_white.png similarity index 100% rename from 14-Sprint-Mission/images/landing/ic_instagram.png rename to public/images/instagram_white.png diff --git a/14-Sprint-Mission/images/logo/kakao=lg.png b/public/images/kakao_logo.png similarity index 100% rename from 14-Sprint-Mission/images/logo/kakao=lg.png rename to public/images/kakao_logo.png diff --git a/14-Sprint-Mission/images/landing/ic_twitter.png b/public/images/twitter_white.png similarity index 100% rename from 14-Sprint-Mission/images/landing/ic_twitter.png rename to public/images/twitter_white.png diff --git a/14-Sprint-Mission/images/landing/ic_youtube.png b/public/images/youtube_white.png similarity index 100% rename from 14-Sprint-Mission/images/landing/ic_youtube.png rename to public/images/youtube_white.png diff --git a/public/index.html b/public/index.html index aa069f27..ac8e1cee 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,44 @@ - + - - + - + + + + + + + + - - - - - React App + + + + + + + + + + + 판다마켓
- diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a3..00000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a65..00000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c77..f9051fe7 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,23 +1,6 @@ { "short_name": "React App", "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], "start_url": ".", "display": "standalone", "theme_color": "#000000", diff --git a/src/App.css b/src/App.css index 74b5e053..8fa8e28a 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,465 @@ -.App { - text-align: center; +* { + margin: 0; + padding: 0; + box-sizing: border-box; } -.App-logo { - height: 40vmin; - pointer-events: none; +#root { + width: 100%; + height: 100%; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +header { + display: flex; + justify-content: center; + align-items: center; + background-color: #ffffff; + width: 100%; + height: 70px; + font-family: "Pretendard", sans-serif; +} + +.header-inner { + width: 100%; + max-width: 1120px; /* 최대 간격 제한 */ + display: flex; + justify-content: space-between; + align-items: center; +} + +.header-logo img { + width: 153px; + height: 51px; +} + +.login-button { + display: flex; /* 플렉스 박스를 사용 */ + justify-content: center; /* 가로 방향 중앙 정렬 */ + align-items: center; /* 세로 방향 중앙 정렬 */ + text-align: center; /* 글자 중앙 정렬 */ + background-color: #3692ff; + width: 128px; + height: 48px; + padding: 12px 23px; + gap: 10px; + border-radius: 8px; + color: #f3f4f6; + font-size: 16px; + font-weight: 600; + line-height: 32px; + letter-spacing: 0%; + text-decoration: none; +} + +body { + margin: 0; + padding: 0; + box-sizing: border-box; + word-break: keep-all; /* 띄어쓰기 기준으로 줄바꿈하도록 설정 */ + font-family: "Pretendard", sans-serif; + overflow-x: hidden; } -.App-header { - background-color: #282c34; - min-height: 100vh; +.banner { + position: relative; + display: flex; + justify-content: center; + align-items: end; + background-color: #cfe5ff; + width: 100vw; /* 화면 꽉 채우기 */ + height: 540px; + box-sizing: border-box; + gap: 7px; + background-position: 80% bottom; +} + +.img-panda { + width: 100%; + height: auto; + min-width: 448px; + max-width: 746px; /* 적절한 최대 너비 */ + object-fit: contain; +} + +#features { + padding: 24px 24px 56px 24px; +} + +.feature { + background-color: #fcfcfc; + padding: 138px 0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + border-radius: 12px; + padding: 24px 0; + gap: 52px; +} + +.feature-title { + font-size: 28px; + line-height: 40px; + white-space: normal; + word-break: keep-all; + max-width: 205px; +} + +.feature-content { display: flex; flex-direction: column; + gap: 8px; + text-align: left; + align-items: flex-start; +} + +.feature:nth-child(2) .feature-content { + text-align: right; + align-items: flex-end; +} + +.feature-content h1 { + font-size: 28px; + line-height: 40px; + white-space: normal; +} + +.feature-description { + font-size: 16px; + line-height: 24px; +} + +/* + .feature-content { + flex: 1; + } +*/ + +/* 요소 최대 넓이보다 큰 경우에 가로 중앙 정렬 해주는 inner container */ +.wrapper { + max-width: 1200px; + margin: 0 auto; + width: 100%; + display: flex; + justify-content: center; +} + +#top-banner-title { + padding: 0; +} + +#bottom-banner-title { + padding: 112.5px 0 0; +} + +h1 { + color: #374151; + font-size: 40px; + font-weight: 700; + line-height: 56px; + letter-spacing: 0%; + padding: 12px 0; +} + +.feature-tag { + font-family: "Pretendard", sans-serif; + color: #3692ff; + font-weight: 700; + font-size: 18px; + line-height: 26px; + letter-spacing: 0%; +} + +.feature-description { + font-family: "Pretendard"; + color: #374151; + font-weight: 500; + font-size: 24px; + line-height: 32px; + letter-spacing: 2%; + padding: 12px 0 0 0; +} + +.hero-button { + font-family: "Pretendard", sans-serif; + background-color: #3692ff; + color: #f9fafb; + font-size: 20px; + font-weight: 600; + text-decoration: none; + border: none; + border-radius: 40px; + padding: 16px 124px; + display: inline-flex; align-items: center; justify-content: center; - font-size: calc(10px + 2vmin); - color: white; + gap: 10px; + margin-top: 32px; +} + +footer { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + background-color: #111827; + font-family: "Pretendard"; + line-height: 100%; + padding: 32px 300px 108px; + gap: 10px; } -.App-link { - color: #61dafb; +.copyright { + text-align: left; + font-size: 16px; + font-weight: 400; + color: #e5e7eb; + letter-spacing: 0%; + line-height: 19.09px; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); +.footerMenu { + display: flex; + gap: 30px; +} + +.footer-link { + color: #e5e7eb; + text-decoration: none; + font-size: 16px; + font-weight: 400; + transition: color 0.3s ease; +} + +.footer-link:hover, +.footer-link:visited, +.footer-link:active { + color: #e5e7eb; + text-decoration: none; +} + +.socialMedia { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 12px; +} + +.socialMedia > img { + width: 20px; + height: 20px; +} + +/* Mobile, Tablet 공통 (최대 1199px) */ +@media screen and (max-width: 1199px) { + .wrapper { + flex-direction: column; + align-items: center; + text-align: center; + } + + .feature { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 0; + gap: 24px; + } + + .feature img { + width: 100%; + height: auto; + object-fit: cover; + } + + .feature-content { + text-align: left; + align-items: flex-start; + width: 100%; + margin-bottom: 52px; + } + + .feature-content h1 { + white-space: nowrap; + max-width: 100%; + } + + .feature:nth-child(2) { + flex-direction: column-reverse; + } + + .feature:nth-child(2) .feature-content { + align-items: flex-end; + text-align: right; + } +} + +/* Tablet: 768px 이상 ~ 1199px 이하 */ +@media (min-width: 768px) and (max-width: 1199px) { + .header-inner { + padding: 0 24px; + } + + .hero-button { + white-space: nowrap; + width: 357px; + min-height: 56px; + } + + .banner { + height: auto; + padding: 84px 0 0 0; + background-size: 120%; + } + + .banner h1 { + font-size: 40px; + line-height: 56px; + padding-top: 84px; + padding-bottom: 24px; + } + + .img-panda { + width: 100%; + height: auto; + max-width: 100%; + margin-top: 211px; + } + + #features { + padding-bottom: 0; + } + + .feature-content h2 { + font-size: 18px; + line-height: 26px; + padding: 0; + } + + .feature-content h1 { + font-size: 32px; + line-height: 42px; + padding: 16px 0 24px; + } + + .feature-description { + font-size: 18px; + line-height: 26px; + padding: 0; + } + + #bottomBanner .wrapper { + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + gap: 6px; } - to { - transform: rotate(360deg); + + #bottom-banner-title { + padding: 0; + } + + footer { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 32px 104px 108px; + text-align: center; + } + + .footerMenu { + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + } + + .socialMedia { + justify-content: center; + } +} + +/* Mobile: 375px 이상 ~ 767px 이하 */ +@media (min-width: 375px) and (max-width: 767px) { + .header-inner { + padding: 0 16px; + } + + .hero-button { + white-space: nowrap; + width: 240px; + min-height: 48px; + } + + .banner { + height: auto; + padding: 48px 0 0 0; + background-size: 120%; + } + + .img-panda { + width: 100%; + height: auto; + max-width: 100%; + margin-top: 132px; + } + + #features { + padding: 52px 15px 0; + } + + .feature-content h2 { + font-size: 16px; + line-height: 26px; + padding: 0; + } + + .feature-content h1 { + font-size: 24px; + line-height: 32px; + padding: 8px 0 16px; + } + + .feature-description { + font-size: 16px; + line-height: 26px; + padding: 0; + } + + footer { + padding: 32px; + flex-direction: row; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + } + + .footerMenu, + .socialMedia { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + margin-bottom: 60px; + gap: 12px; + } + + .footerMenu { + display: flex; + flex-direction: row; + gap: 30px; + } + + .socialMedia { + display: flex; + flex-direction: row; + gap: 12px; + } + + .copyright { + order: 2; + width: 100%; + text-align: left; } } diff --git a/src/App.js b/src/App.js index 37845757..0c749bfa 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,12 @@ -import logo from './logo.svg'; -import './App.css'; +import { BrowserRouter } from "react-router-dom"; +import AppRouter from "./routes/AppRouter"; +import "./App.css"; function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ + + ); } diff --git a/src/api/productServices.js b/src/api/productServices.js new file mode 100644 index 00000000..8809a999 --- /dev/null +++ b/src/api/productServices.js @@ -0,0 +1,22 @@ +const getProducts = async ( + page = 1, + pageSize = 10, + orderBy = "recent", + keyword +) => { + const response = await fetch( + `https://panda-market-api.vercel.app/products?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}${ + keyword ? `&keyword=${keyword}` : "" + }` + ); + + if (!response.ok) { + throw new Error("상품 목록 조회에 실패하였습니다."); + } + + return await response.json(); +}; + +export const productServices = { + getProducts, +}; diff --git a/src/asset/icon/arrow_down.png b/src/asset/icon/arrow_down.png new file mode 100644 index 00000000..313b4b94 Binary files /dev/null and b/src/asset/icon/arrow_down.png differ diff --git a/src/asset/icon/arrow_left.svg b/src/asset/icon/arrow_left.svg new file mode 100644 index 00000000..040e81c2 --- /dev/null +++ b/src/asset/icon/arrow_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/arrow_right.svg b/src/asset/icon/arrow_right.svg new file mode 100644 index 00000000..368742c9 --- /dev/null +++ b/src/asset/icon/arrow_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/14-Sprint-Mission/images/btn_visibility_off_24px.png b/src/asset/icon/btn_visibility_off.png similarity index 100% rename from 14-Sprint-Mission/images/btn_visibility_off_24px.png rename to src/asset/icon/btn_visibility_off.png diff --git a/14-Sprint-Mission/images/btn_visibility_on_24px.png b/src/asset/icon/btn_visibility_on.png similarity index 100% rename from 14-Sprint-Mission/images/btn_visibility_on_24px.png rename to src/asset/icon/btn_visibility_on.png diff --git a/src/asset/icon/heart.png b/src/asset/icon/heart.png new file mode 100644 index 00000000..f00491e7 Binary files /dev/null and b/src/asset/icon/heart.png differ diff --git a/14-Sprint-Mission/images/logo/Property 1=lg.png b/src/asset/icon/panda_market_logo_1.png similarity index 100% rename from 14-Sprint-Mission/images/logo/Property 1=lg.png rename to src/asset/icon/panda_market_logo_1.png diff --git a/14-Sprint-Mission/images/logo/Property 1=lg@2x.png b/src/asset/icon/panda_market_logo_2.png similarity index 100% rename from 14-Sprint-Mission/images/logo/Property 1=lg@2x.png rename to src/asset/icon/panda_market_logo_2.png diff --git a/14-Sprint-Mission/images/logo/Property 1=lg@3x.png b/src/asset/icon/panda_market_logo_3.png similarity index 100% rename from 14-Sprint-Mission/images/logo/Property 1=lg@3x.png rename to src/asset/icon/panda_market_logo_3.png diff --git a/src/asset/icon/panda_market_logo_no_icon.png b/src/asset/icon/panda_market_logo_no_icon.png new file mode 100644 index 00000000..eb79847a Binary files /dev/null and b/src/asset/icon/panda_market_logo_no_icon.png differ diff --git a/src/asset/icon/profile_icon.svg b/src/asset/icon/profile_icon.svg new file mode 100644 index 00000000..3df63f71 --- /dev/null +++ b/src/asset/icon/profile_icon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/asset/icon/search.svg b/src/asset/icon/search.svg new file mode 100644 index 00000000..52241e6d --- /dev/null +++ b/src/asset/icon/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/asset/icon/sort.png b/src/asset/icon/sort.png new file mode 100644 index 00000000..f690a81a Binary files /dev/null and b/src/asset/icon/sort.png differ diff --git a/14-Sprint-Mission/images/landing/Img_home_01.png b/src/asset/image/Img_home_01.png similarity index 100% rename from 14-Sprint-Mission/images/landing/Img_home_01.png rename to src/asset/image/Img_home_01.png diff --git a/14-Sprint-Mission/images/landing/Img_home_02.png b/src/asset/image/Img_home_02.png similarity index 100% rename from 14-Sprint-Mission/images/landing/Img_home_02.png rename to src/asset/image/Img_home_02.png diff --git a/14-Sprint-Mission/images/landing/Img_home_03.png b/src/asset/image/Img_home_03.png similarity index 100% rename from 14-Sprint-Mission/images/landing/Img_home_03.png rename to src/asset/image/Img_home_03.png diff --git a/14-Sprint-Mission/images/landing/Img_home_bottom.png b/src/asset/image/Img_home_bottom.png similarity index 100% rename from 14-Sprint-Mission/images/landing/Img_home_bottom.png rename to src/asset/image/Img_home_bottom.png diff --git a/14-Sprint-Mission/images/landing/Img_home_top.png b/src/asset/image/Img_home_top.png similarity index 100% rename from 14-Sprint-Mission/images/landing/Img_home_top.png rename to src/asset/image/Img_home_top.png diff --git a/src/asset/image/no_image.png b/src/asset/image/no_image.png new file mode 100644 index 00000000..9d0d64d6 Binary files /dev/null and b/src/asset/image/no_image.png differ diff --git a/14-Sprint-Mission/faq/faq.html b/src/components/common/Button.jsx similarity index 100% rename from 14-Sprint-Mission/faq/faq.html rename to src/components/common/Button.jsx diff --git a/src/components/common/dropdown/Dropdown.jsx b/src/components/common/dropdown/Dropdown.jsx new file mode 100644 index 00000000..e27780d4 --- /dev/null +++ b/src/components/common/dropdown/Dropdown.jsx @@ -0,0 +1,54 @@ +import { useState, useEffect, useRef } from "react"; +import arrowDown from "../../../asset/icon/arrow_down.png"; +import sort from "../../../asset/icon/sort.png"; +import "./dropdown.css"; + +export default function Dropdonw({ sortOption, setSortOption }) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); // 외부 클릭 시 감지용 + + const handleSelect = (option) => { + setSortOption(option); + setIsOpen(false); + }; + + useEffect(() => { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + return ( +
+ + + {isOpen && ( + + )} +
+ ); +} diff --git a/src/components/common/dropdown/dropdown.css b/src/components/common/dropdown/dropdown.css new file mode 100644 index 00000000..8f1b33f5 --- /dev/null +++ b/src/components/common/dropdown/dropdown.css @@ -0,0 +1,88 @@ +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-button { + display: flex; + align-items: center; + justify-content: space-between; + width: 130px; + height: 42px; + padding: 12px 20px; + background-color: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 12px; + font-size: 16px; + font-family: "Pretendard", sans-serif; + font-weight: 400; + color: #1f2937; + line-height: 26px; + cursor: pointer; + gap: 10px; +} + +.dropdown-icon { + width: 24px; + height: 24px; +} + +.sort-icon { + display: none; +} + +.dropdown-list { + position: absolute; + top: 100%; + left: 0; + width: 130px; + margin-top: 4px; + padding: 0; + border: 1px solid #e5e7eb; + border-radius: 12px; + background-color: #ffffff; + list-style: none; + padding: 0; + margin-top: 4px; + z-index: 100; + overflow: hidden; +} + +.dropdown-item { + width: 100%; + padding: 12px 0; + text-align: center; + font-size: 16px; + font-family: "Pretendard", sans-serif; + font-weight: 400; + line-height: 26px; + color: #1f2937; + cursor: pointer; +} + +.dropdown-item:hover { + background-color: #f2f2f2; +} + +@media (max-width: 768px) { + .dropdown-list { + left: -87px; + } + + .dropdown-button { + width: 42px; + padding: 9px; + } + + .dropdown-button-text { + display: none; + } + + .dropdown-icon { + display: none; + } + + .sort-icon { + display: inline; + } +} diff --git a/src/components/common/pagenation/PagenationContainer.jsx b/src/components/common/pagenation/PagenationContainer.jsx new file mode 100644 index 00000000..c848b2b5 --- /dev/null +++ b/src/components/common/pagenation/PagenationContainer.jsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; + +import arrowLeft from "../../../asset/icon/arrow_left.svg"; +import arrowRight from "../../../asset/icon/arrow_right.svg"; +import "./pagenation.css"; + +export default function PagenationContainer({ + totalCount, + itemsPerPage, + setSearchParams, +}) { + const pageCount = Math.ceil(totalCount / itemsPerPage); + const groupSize = 5; + + const [pageList, setPageList] = useState([]); + const [currentGroup, setCurrentGroup] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [visiblePages, setVisiblePages] = useState([]); + + // 그룹 이동 핸들러 + const handlePrevGroup = () => { + if (currentGroup > 0) { + setCurrentGroup(currentGroup - 1); + } + }; + + const handleNextGroup = () => { + if ((currentGroup + 1) * groupSize < pageCount) { + setCurrentGroup(currentGroup + 1); + } + }; + + const handlePageClick = (pageNumber) => { + setSearchParams({ page: pageNumber }); + setCurrentPage(pageNumber); + }; + + useEffect(() => { + if (pageCount > 0) { + const newPageList = Array.from({ length: pageCount }, (_, i) => i + 1); + setPageList(newPageList); + } + }, [pageCount]); + + useEffect(() => { + // 현재 그룹에서 보여줄 페이지들 + const startIndex = currentGroup * groupSize; + setVisiblePages(pageList.slice(startIndex, startIndex + groupSize)); + }, [currentGroup, pageList]); + + return ( +
+ + {visiblePages.map((pageNumber) => ( + + ))} + +
+ ); +} diff --git a/src/components/common/pagenation/pagenation.css b/src/components/common/pagenation/pagenation.css new file mode 100644 index 00000000..494e3fc2 --- /dev/null +++ b/src/components/common/pagenation/pagenation.css @@ -0,0 +1,35 @@ +.pagination-wrapper { + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + padding-top: 9px; + padding-bottom: 58px; +} + +.page, +.arrow-btn { + width: 40px; + height: 40px; + padding: 11px; + font-size: 16px; + font-weight: 600; + color: #6b7280; + border: 1px solid #e5e7eb; + border-radius: 100%; + background-color: #ffffff; + cursor: pointer; + transition: all 0.2s ease; +} + +.page:hover, +.arrow:hover { + background-color: #2f80ed; + color: #f9fafb; +} + +.page.active { + background-color: #2f80ed; + color: #f9fafb; + border: none; +} diff --git a/src/components/items/ItemCard.jsx b/src/components/items/ItemCard.jsx new file mode 100644 index 00000000..f52c3653 --- /dev/null +++ b/src/components/items/ItemCard.jsx @@ -0,0 +1,36 @@ +import { Link } from "react-router-dom"; +import noImage from "../../asset/image/no_image.png"; +import heart from "../../asset/icon/heart.png"; +import "./itemCard.css"; + +export default function ItemCard({ cardInfo, cardType }) { + const { favoriteCount, images, price, name, id } = cardInfo; + + return ( + + 0 ? images[0] : noImage} + alt={name} + onError={(e) => { + e.target.onerror = null; // 무한 루프 방지 + e.target.src = noImage; // 대체 이미지 경로 + }} + /> +
+

{name}

+

{price.toLocaleString()}원

+ +
+ + ); +} diff --git a/src/components/items/itemCard.css b/src/components/items/itemCard.css new file mode 100644 index 00000000..4a264eac --- /dev/null +++ b/src/components/items/itemCard.css @@ -0,0 +1,79 @@ +.best, +.all { + text-decoration-line: none; + font-family: pretendard; +} + +.best-card-img { + width: 282px; + height: 282px; + border-radius: 16px; + object-fit: cover; +} + +.all-card-img { + width: 221px; + height: 221px; + border-radius: 16px; + object-fit: cover; +} + +.item-info { + margin-top: 16px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + gap: 6px; +} + +.item-name { + color: #1f2937; + font-weight: 500; + font-size: 14px; + line-height: 24px; + letter-spacing: 0; +} + +.item-price { + color: #1f2937; + font-weight: 700; + font-size: 16px; + line-height: 26px; + letter-spacing: 0; +} + +.item-favorite { + display: flex; + align-items: center; + gap: 4px; + color: #4b5563; + font-weight: 500; + font-size: 12px; + line-height: 18px; + letter-spacing: 0; + background: none; + border: none; + cursor: pointer; +} + +@media (max-width: 768px) { + .all-card-img { + width: 168px; + height: 168px; + border-radius: 12px; + } + + .best-card-img { + width: 343px; + height: 343px; + border-radius: 19.46px; + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .best-card-img { + width: 343px; + height: 343px; + border-radius: 19.46px; + } +} diff --git a/src/components/layout/Navbar.jsx b/src/components/layout/Navbar.jsx new file mode 100644 index 00000000..1713c83b --- /dev/null +++ b/src/components/layout/Navbar.jsx @@ -0,0 +1,33 @@ +import { Link } from "react-router-dom"; +import logoImg from "../../asset/icon/panda_market_logo_3.png"; +import logoMobileImg from "../../asset/icon/panda_market_logo_no_icon.png"; +import profileImg from "../../asset/icon/profile_icon.svg"; +import "./navbar.css"; + +export default function Navbar() { + return ( +
+
+
+ 판다 로고 + 판다 모바일 로고 +
+
+ + 자유게시판 + + + 중고마켓 + +
+
+
+ 프로필 이미지 +
+
+ ); +} diff --git a/src/components/layout/navbar.css b/src/components/layout/navbar.css new file mode 100644 index 00000000..c1ee8c0a --- /dev/null +++ b/src/components/layout/navbar.css @@ -0,0 +1,73 @@ +.header-container { + width: 100%; + height: 70px; + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 200px; + padding-right: 200px; + border-bottom: 1px solid #dfdfdf; +} + +.header-left-container { + display: flex; + align-items: center; + gap: 32px; +} + +.logo-img { + width: 153px; +} + +.logo-mobile-img { + display: none; +} + +.link-container { + display: flex; +} + +.link { + padding: 25px 15px; + color: black; + text-decoration-line: none; + font-size: 18px; + font-weight: 700; + color: #4b5563; +} + +@media (max-width: 768px) { + .header-container { + padding-left: 16px; + padding-right: 16px; + } + + .header-left-container { + gap: 8px; + } + + .logo-mobile-img { + display: block; + } + .logo-img { + display: none; + } + + .link-container { + gap: 8px; + } + + .link { + font-size: 16px; + font-weight: 700; + padding-left: 0; + padding-right: 0; + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .header-container { + padding-left: 24px; + padding-right: 24px; + } +} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index d563c0fb..cada659b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/14-Sprint-Mission/global/script.js b/src/pages/faq/faq.html similarity index 100% rename from 14-Sprint-Mission/global/script.js rename to src/pages/faq/faq.html diff --git a/src/pages/home/Home.js b/src/pages/home/Home.js new file mode 100644 index 00000000..bdb21857 --- /dev/null +++ b/src/pages/home/Home.js @@ -0,0 +1,152 @@ +export default function Home() { + return ( + <> +
+ +
+
+ +
+
+ Hot item +
+

Hot item

+

인기 상품을 확인해 보세요

+

+ 가장 HOT한 중고거래 물품을 +
+ 판다 마켓에서 확인해 보세요 +

+
+
+
+
+

Search

+

구매를 원하는 상품을 검색하세요

+

+ 구매하고 싶은 물품은 검색해서 +
+ 쉽게 찾아보세요 +

+
+ Search +
+
+ Register +
+

Register

+

판매를 원하는 상품을 등록하세요

+

+ 어떤 물건이든 판매하고 싶은 상품을 +
+ 쉽게 등록하세요 +

+
+
+
+ +
+ + + ); +} diff --git a/src/pages/items/Items.jsx b/src/pages/items/Items.jsx new file mode 100644 index 00000000..f9ec8d52 --- /dev/null +++ b/src/pages/items/Items.jsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react"; +import { Link, useSearchParams } from "react-router-dom"; + +import { productServices } from "../../api/productServices"; +import Navbar from "../../components/layout/Navbar"; +import Dropdown from "../../components/common/dropdown/Dropdown"; +import ItemCard from "../../components/items/ItemCard"; +import PagenationContainer from "../../components/common/pagenation/PagenationContainer"; + +import serchIcon from "../../asset/icon/search.svg"; +import "./items.css"; + +export default function Items() { + const [sort, setSort] = useState("recent"); + const [totalCount, setTotalCount] = useState(0); + const [allProducts, setAllProducts] = useState([]); + const [bestProducts, setBestProducts] = useState([]); + + const [searchParams, setSearchParams] = useSearchParams(); + const pageNumber = searchParams.get("page"); + + const getResponsiveLimit = () => { + const width = window.innerWidth; + + if (width < 768) { + return { best: 1, recent: 4 }; + } else if (width < 1024) { + return { best: 2, recent: 6 }; + } else { + return { best: 4, recent: 10 }; + } + }; + + const getProducts = async (orderBy, pageNumber = 1, limit = 10) => { + if (orderBy === "recent" || orderBy === "favorite") { + const res = await productServices.getProducts(pageNumber, limit, orderBy); + setAllProducts(res.list); + setTotalCount(res.totalCount); + } else if (orderBy === "best") { + // best는 직접 정한 기준, favorite은 서버에서 받는 단어로 하드코딩 + const res = await productServices.getProducts(1, limit, "favorite"); + setBestProducts(res.list); + } + }; + + useEffect(() => { + const { recent } = getResponsiveLimit(); + getProducts(sort, pageNumber || 1, recent); + }, [sort, pageNumber]); + + useEffect(() => { + const { best } = getResponsiveLimit(); + getProducts("best", 1, best); + }, []); + + return ( + <> + +
+
+
베스트 상품
+
+ {bestProducts && + bestProducts.map((bestProduct) => ( + + ))} +
+
+
+
+ 전체 상품 +
+
+ 돋보기 아이콘 + +
+ + 상품 등록하기 + + +
+
+
+
+ 전체 상품 + + 상품 등록하기 + +
+
+
+ 돋보기 아이콘 + +
+ +
+
+
+ {allProducts && + allProducts.map((allProduct) => ( + + ))} +
+ +
+
+ + ); +} diff --git a/src/pages/items/items.css b/src/pages/items/items.css new file mode 100644 index 00000000..3ce63632 --- /dev/null +++ b/src/pages/items/items.css @@ -0,0 +1,157 @@ +.items-container { + width: 1201px; /* 확인 필요 */ + padding-top: 24px; + margin: 0 auto; +} + +.best-section { + margin-bottom: 40px; +} + +.section-title { + color: #111827; + font-weight: 700; + font-size: 20px; + line-height: 32px; + letter-spacing: 0; + vertical-align: middle; + font-family: Pretendard; + margin-bottom: 16px; +} + +.card-container { + display: flex; + gap: 24px; + flex-wrap: wrap; +} + +.all-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} + +.mobile-all-header { + display: none; +} + +.all-header-right { + display: flex; + justify-content: center; + align-items: center; + gap: 12px; +} + +.search-box { + position: relative; + flex: 1; +} + +.search-box input { + width: 325px; + height: 42px; + padding: 9px 16px 9px 44px; + border: none; + border-radius: 12px; + font-weight: 400; + font-size: 16px; + font-family: "Pretendard", sans-serif; + background-color: #f3f4f6; +} + +.search-box input::placeholder { + color: #9ca3af; +} + +.search-box input:focus { + outline: none; +} + +.search-icon { + position: absolute; + top: 50%; + left: 16px; + transform: translateY(-50%); +} + +/* 등록 버튼 */ +.add-button { + padding: 12px 23px; + background-color: #3692ff; + color: #f3f4f6; + font-weight: 600; + font-size: 16px; + vertical-align: middle; + border: none; + border-radius: 8px; + cursor: pointer; + text-decoration-line: none; +} + +.add-button:hover { + background-color: #2176d2; +} + +.pagination-wrapper { + margin-top: 32px; + text-align: center; +} + +@media (max-width: 768px) { + .items-container { + width: 344px; + } + + .mobile-all-header { + display: block; + margin-bottom: 16px; + } + + .all-header { + display: none; + } + + .mobile-all-header-top { + margin-bottom: 8px; + } + + .mobile-all-header-top, + .mobile-all-header-bottom { + display: flex; + align-items: center; + justify-content: space-between; + } + + .mobile-section-title { + color: #111827; + font-weight: 700; + font-size: 20px; + line-height: 32px; + letter-spacing: 0; + vertical-align: middle; + font-family: Pretendard; + } + + .search-box input { + width: 288px; + } + + .card-container { + gap: 8px; + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .items-container { + width: 696px; + } + + .card-container { + gap: 10px; + } + + .search-box input { + width: 242px; + } +} diff --git a/src/pages/login/Login.jsx b/src/pages/login/Login.jsx new file mode 100644 index 00000000..ae43bdb0 --- /dev/null +++ b/src/pages/login/Login.jsx @@ -0,0 +1,138 @@ +import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; + +import mainLogo from "../../asset/icon/panda_market_logo_3.png"; +import eyeOffIcon from "../../asset/icon/btn_visibility_off.png"; +import eyeOnIcon from "../../asset/icon/btn_visibility_on.png"; + +import "./login.css"; + +export default function Login() { + const navigate = useNavigate(); + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + + const validateEmail = (value) => { + const trimmed = value.trim(); + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!trimmed) return "이메일을 입력해주세요."; + if (!emailRegex.test(trimmed)) return "잘못된 이메일 형식입니다."; + return ""; + }; + + const validatePassword = (value) => { + const trimmed = value.trim(); + if (!trimmed) return "비밀번호를 입력해주세요."; + if (trimmed.length < 8) return "비밀번호를 8자 이상 입력해주세요."; + return ""; + }; + + const handleEmailBlur = () => { + setEmailError(validateEmail(email)); + }; + + const handlePasswordBlur = () => { + setPasswordError(validatePassword(password)); + }; + + const handleTogglePassword = () => { + setShowPassword((prev) => !prev); + }; + + const isFormValid = + validateEmail(email) === "" && validatePassword(password) === ""; + + const handleLogin = (e) => { + e.preventDefault(); + if (isFormValid) { + navigate("/items"); + } + }; + + return ( +
+
+
+ + 판다마켓 로고 + +
+
+ +
+
+ + setEmail(e.target.value)} + onBlur={handleEmailBlur} + className={emailError ? "error" : ""} + required + /> +
{emailError}
+ + +
+ setPassword(e.target.value)} + onBlur={handlePasswordBlur} + className={passwordError ? "error" : ""} + required + /> + +
+ +
{passwordError}
+ + +
+ +
+ 간편 로그인하기 + +
+ +

+ 판다마켓이 처음이신가요? 회원가입 +

+
+
+ ); +} diff --git a/14-Sprint-Mission/login/login.css b/src/pages/login/login.css similarity index 99% rename from 14-Sprint-Mission/login/login.css rename to src/pages/login/login.css index bcd0d886..c3f2416b 100644 --- a/14-Sprint-Mission/login/login.css +++ b/src/pages/login/login.css @@ -119,7 +119,6 @@ button:focus { align-items: center; background-color: #e6f2ff; border-radius: 8px; - height: 42px; padding: 16px 23px; margin-bottom: 24px; } diff --git a/14-Sprint-Mission/privacy/privacy.html b/src/pages/privacy/privacy.html similarity index 100% rename from 14-Sprint-Mission/privacy/privacy.html rename to src/pages/privacy/privacy.html diff --git a/14-Sprint-Mission/signup/signup.css b/src/pages/signup/signup.css similarity index 100% rename from 14-Sprint-Mission/signup/signup.css rename to src/pages/signup/signup.css diff --git a/14-Sprint-Mission/signup/signup.html b/src/pages/signup/signup.html similarity index 100% rename from 14-Sprint-Mission/signup/signup.html rename to src/pages/signup/signup.html diff --git a/14-Sprint-Mission/signup/signup.js b/src/pages/signup/signup.js similarity index 100% rename from 14-Sprint-Mission/signup/signup.js rename to src/pages/signup/signup.js diff --git a/src/routes/AppRouter.jsx b/src/routes/AppRouter.jsx new file mode 100644 index 00000000..d035588a --- /dev/null +++ b/src/routes/AppRouter.jsx @@ -0,0 +1,21 @@ +import { Routes, Route } from "react-router-dom"; +import Login from "../pages/login/Login"; +// import LandingPage from "./pages/LandingPage"; +// import Login from "../pages/login/Login"; +// import Signup from "./pages/signup/Signup"; +import Items from "../pages/items/Items"; +// import FAQ from "./pages/faq/FAQ"; +// import PrivacyPolicy from "./pages/privacy/PrivacyPolicy"; + +export default function AppRouter() { + return ( + + {/* } /> */} + } /> + {/* } /> */} + } /> + {/* } /> */} + {/* } /> */} + + ); +}