diff --git a/assets/css/global.css b/assets/css/global.css index c88254b2..11959fc2 100644 --- a/assets/css/global.css +++ b/assets/css/global.css @@ -14,3 +14,30 @@ img { max-width: 100%; height: auto; } + +.input-error { + border: 1px solid var(--color-border-error); +} + +.text-error { + color: var(--color-text-input-error); + font-size: 0.875rem; + margin-top: 4px; + margin-left: 20px; +} + +.error-message { + min-height: 1.2em; + font-size: 0.875rem; + margin-top: 10px; +} + +.form__input-button:disabled { + background-color: var(--color-button-disabled); + color: var(--color-button-disabled-text); + cursor: not-allowed; +} + +.form__input-button.active { + background-color: var(--color-button-active); +} diff --git a/assets/css/pages/login-signup.css b/assets/css/pages/auth.css similarity index 88% rename from assets/css/pages/login-signup.css rename to assets/css/pages/auth.css index 283b6c1a..d5e41d29 100644 --- a/assets/css/pages/login-signup.css +++ b/assets/css/pages/auth.css @@ -5,7 +5,7 @@ flex-direction: column; justify-content: center; align-items: center; - margin: 100px 600px; + margin: 100px 300px; } .header__login, @@ -13,6 +13,14 @@ margin-bottom: 40px; } +.form__field { + margin-bottom: 20px; +} + +.logo-image { + width: 400px; +} + .form__login, .form__signup { display: flex; @@ -23,16 +31,16 @@ .form__label-email, .form__label-nickname, .form__label-password, -.form__label-password-confirm { +.form__label-passwordCheck { color: var(--color-text-label); - margin-bottom: 20px; } .form__input-email, .form__input-nickname, .form__input-password, -.form__input-password-confirm { +.form__input-passwordCheck { background-color: var(--color-surface-input); + margin: 20px 0; padding: 20px; border-radius: 8px; width: 100%; @@ -47,11 +55,10 @@ position: relative; display: flex; align-items: center; - margin-bottom: 20px; } .form__toggle-password, -.form__toggle-password-confirm { +.form__toggle-passwordCheck { position: absolute; right: 20px; cursor: pointer; @@ -91,6 +98,10 @@ margin: 80px 16px 231px; max-width: 400px; } + + .logo-image { + width: 200px; + } } /* login signup Tablet */ diff --git a/assets/css/style.css b/assets/css/style.css index 4957946f..eb1a6d94 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -43,6 +43,14 @@ padding-top: 200px; } +.hero__title { + white-space: pre-line; +} + +.feature-section__headline { + white-space: pre-line; +} + .hero__title, .feature-section__headline, .closing-banner__headline { @@ -140,10 +148,6 @@ /* mobile */ @media (min-width: 375px) and (max-width: 767px) { - br.onlyPC { - display: none; - } - .header__nav { padding: 0 24px; } @@ -195,6 +199,7 @@ .feature-section__headline { font-size: 1.5rem; + white-space: normal; } .feature-section__description { @@ -249,6 +254,7 @@ .hero__title { font-size: 2rem; line-height: 1.4; + white-space: normal; } .hero__link { @@ -264,6 +270,10 @@ padding: 0 24px; } + .feature-section__headline { + white-space: normal; + } + .feature-section__item { display: flex; flex-direction: column; diff --git a/assets/css/variables.css b/assets/css/variables.css index 42340f66..8d15f607 100644 --- a/assets/css/variables.css +++ b/assets/css/variables.css @@ -1,17 +1,22 @@ :root { --color-primary-surface: #e6f2ff; --color-primary: #3692ff; - --color-primary-hover: #1967d6; - --color-primary-active: #1251aa; --color-primary-banner: #cfe5ff; + --color-button-hover: #1251aa; + --color-button-active: #3692ff; + --color-button-disabled: #9ca3af; + --color-button-disabled-text: #f3f4f6; + --color-text-title: var(--gray700); --color-text-label: var(--gray800); --color-text-muted: var(--gray500); --color-text-placeholder: var(--gray400); --color-text-button: var(--gray100); --color-text-subtle: var(--gray50); + --color-text-input-error: #f74747; + --color-border-error: #f74747; --color-border: var(--gray200); --color-surface-input: var(--gray100); --color-footer-background: var(--gray900); diff --git a/assets/images/icons/icon_visibilityOff.svg b/assets/images/icons/icon_visibilityOff.svg new file mode 100644 index 00000000..43cfd033 --- /dev/null +++ b/assets/images/icons/icon_visibilityOff.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/images/icons/icon_visibilityOn.svg b/assets/images/icons/icon_visibilityOn.svg index f08f09e2..43a5af17 100644 --- a/assets/images/icons/icon_visibilityOn.svg +++ b/assets/images/icons/icon_visibilityOn.svg @@ -1,10 +1,3 @@ - - - - - - - - + diff --git a/index.html b/index.html index 89640867..01aff109 100644 --- a/index.html +++ b/index.html @@ -38,8 +38,9 @@ class="header__login-link" href="/login.html" aria-label="로그인으로 이동" - >로그인 + 로그인 +
@@ -75,7 +76,9 @@

인기 상품을 확인해 보세요

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

@@ -89,12 +92,21 @@

인기 상품을 class="feature-section__content feature-section__content--reverse" >

Search

+ <<<<<<< HEAD + +

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

+ =======

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

+ >>>>>>> Basic-홍성현-sprint3

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

@@ -111,7 +123,9 @@

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

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

@@ -119,7 +133,9 @@

판매를 원하는

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

aria-label="facebook으로 이동" target="_blank" rel="noopener noreferrer" - >Facebook + > + Facebook + diff --git a/login.html b/login.html index 296b3186..184dd743 100644 --- a/login.html +++ b/login.html @@ -42,39 +42,55 @@

+ diff --git a/scripts/login.js b/scripts/login.js new file mode 100644 index 00000000..208dba25 --- /dev/null +++ b/scripts/login.js @@ -0,0 +1,43 @@ +import { validateEmail, validatePassword } from "./validation/validators.js"; +import { checkFormValidity } from "./validation/formUtils.js"; +import { setupPasswordToggle } from "./utils/togglePassword.js"; + +document.addEventListener("DOMContentLoaded", () => { + const emailInput = document.querySelector("#email"); + const passwordInput = document.querySelector("#password"); + const emailError = document.querySelector("#emailError"); + const passwordError = document.querySelector("#passwordError"); + const loginButton = document.querySelector("#loginButton"); + + function validateAndCheck() { + validateEmail(emailInput, emailError); + validatePassword(passwordInput, passwordError); + checkFormValidity({ + errors: [emailError, passwordError], + values: [emailInput.value, passwordInput.value], + buttonEl: loginButton, + }); + } + setupPasswordToggle({ + inputSelector: "#password", + buttonSelector: "#togglePassword", + iconSelector: "#eyeIcon", + }); + + emailInput.addEventListener("blur", () => + validateEmail(emailInput, emailError) + ); + passwordInput.addEventListener("blur", () => + validatePassword(passwordInput, passwordError) + ); + + emailInput.addEventListener("input", validateAndCheck); + passwordInput.addEventListener("input", validateAndCheck); + + document.querySelector(".form__login").addEventListener("submit", (e) => { + e.preventDefault(); + if (!loginButton.disabled) { + window.location.href = "/items"; + } + }); +}); diff --git a/scripts/signup.js b/scripts/signup.js new file mode 100644 index 00000000..d4f55d88 --- /dev/null +++ b/scripts/signup.js @@ -0,0 +1,69 @@ +import { + validateEmail, + validatePassword, + validatePasswordCheck, +} from "./validation/validators.js"; +import { checkFormValidity } from "./validation/formUtils.js"; +import { setupPasswordToggle } from "./utils/togglePassword.js"; + +document.addEventListener("DOMContentLoaded", () => { + const emailInput = document.querySelector("#email"); + const passwordInput = document.querySelector("#password"); + const passwordCheckInput = document.querySelector("#passwordCheck"); + const emailError = document.querySelector("#emailError"); + const passwordError = document.querySelector("#passwordError"); + const passwordCheckError = document.querySelector("#passwordCheckError"); + const signupButton = document.querySelector("#signupButton"); + + function validateAll() { + validateEmail(emailInput, emailError); + validatePassword(passwordInput, passwordError); + validatePasswordCheck( + passwordInput, + passwordCheckInput, + passwordCheckError, + ); + checkFormValidity({ + errors: [emailError, passwordError, passwordCheckError], + values: [emailInput.value, passwordInput.value, passwordCheckInput.value], + buttonEl: signupButton, + }); + } + + setupPasswordToggle({ + inputSelector: "#password", + buttonSelector: "#togglePassword", + iconSelector: "#eyeIcon", + }); + + setupPasswordToggle({ + inputSelector: "#passwordCheck", + buttonSelector: "#togglePasswordCheck", + iconSelector: "#eyeIconCheck", + }); + + emailInput.addEventListener("blur", () => + validateEmail(emailInput, emailError), + ); + passwordInput.addEventListener("blur", () => + validatePassword(passwordInput, passwordError), + ); + passwordCheckInput.addEventListener("blur", () => + validatePasswordCheck( + passwordInput, + passwordCheckInput, + passwordCheckError, + ), + ); + + [emailInput, passwordInput, passwordCheckInput].forEach((input) => + input.addEventListener("input", validateAll), + ); + + document.querySelector(".form__signup").addEventListener("submit", (e) => { + e.preventDefault(); + if (!signupButton.disabled) { + window.location.href = "/items"; + } + }); +}); diff --git a/scripts/utils/togglePassword.js b/scripts/utils/togglePassword.js new file mode 100644 index 00000000..e8304691 --- /dev/null +++ b/scripts/utils/togglePassword.js @@ -0,0 +1,19 @@ +export function setupPasswordToggle({ + inputSelector, + buttonSelector, + iconSelector, +}) { + const passwordInput = document.querySelector(inputSelector); + const toggleButton = document.querySelector(buttonSelector); + const eyeIcon = document.querySelector(iconSelector); + + if (!passwordInput || !toggleButton || !eyeIcon) return; + + toggleButton.addEventListener("click", () => { + const isVisible = passwordInput.type === "text"; + passwordInput.type = isVisible ? "password" : "text"; + eyeIcon.src = isVisible + ? "/assets/images/icons/icon_visibilityOff.svg" + : "/assets/images/icons/icon_visibilityOn.svg"; + }); +} diff --git a/scripts/validation/formUtils.js b/scripts/validation/formUtils.js new file mode 100644 index 00000000..2a7146c3 --- /dev/null +++ b/scripts/validation/formUtils.js @@ -0,0 +1,12 @@ +export function updateButtonState({ conditions, buttonEl }) { + const isValid = conditions.every(Boolean); + buttonEl.disabled = !isValid; + buttonEl.classList.toggle("active", isValid); +} + +export function checkFormValidity({ errors, values, buttonEl }) { + const hasErrors = errors.some((el) => el.textContent !== ""); + const hasEmpty = values.some((val) => val.trim() === ""); + buttonEl.disabled = hasErrors || hasEmpty; + buttonEl.classList.toggle("active", !buttonEl.disabled); +} diff --git a/scripts/validation/validation.js b/scripts/validation/validation.js new file mode 100644 index 00000000..24b5ecea --- /dev/null +++ b/scripts/validation/validation.js @@ -0,0 +1,16 @@ +export function showError(inputEl, errorEl, message) { + inputEl.classList.add("input-error"); + errorEl.textContent = message; + errorEl.classList.add("text-error"); +} + +export function clearError(inputEl, errorEl) { + inputEl.classList.remove("input-error"); + errorEl.textContent = ""; + errorEl.classList.remove("text-error"); +} + +export function isValidEmail(email) { + const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + return regex.test(email); +} diff --git a/scripts/validation/validators.js b/scripts/validation/validators.js new file mode 100644 index 00000000..14a5e667 --- /dev/null +++ b/scripts/validation/validators.js @@ -0,0 +1,35 @@ +import { showError, clearError, isValidEmail } from "./validation.js"; + +export function validateEmail(inputEl, errorEl) { + const email = inputEl.value.trim(); + clearError(inputEl, errorEl); + + if (email === "") { + showError(inputEl, errorEl, "이메일을 입력해주세요."); + } else if (!isValidEmail(email)) { + showError(inputEl, errorEl, "잘못된 이메일 형식입니다."); + } +} + +export function validatePassword(inputEl, errorEl) { + const password = inputEl.value.trim(); + clearError(inputEl, errorEl); + + if (password === "") { + showError(inputEl, errorEl, "비밀번호를 입력해주세요."); + } else if (password.length < 8) { + showError(inputEl, errorEl, "비밀번호를 8자 이상 입력해주세요."); + } +} + +export function validatePasswordCheck(passwordEl, checkEl, errorEl) { + const password = passwordEl.value.trim(); + const passwordCheck = checkEl.value.trim(); + clearError(checkEl, errorEl); + + if (passwordCheck === "") { + showError(checkEl, errorEl, "비밀번호 확인을 입력해주세요."); + } else if (password !== passwordCheck) { + showError(checkEl, errorEl, "비밀번호가 일치하지 않습니다."); + } +} diff --git a/signup.html b/signup.html index 03c2bd0e..943ca013 100644 --- a/signup.html +++ b/signup.html @@ -36,73 +36,94 @@
- - - - - -
+
+ - +
- -
+
+ - +
+
+
+
+ +
+ - + +
+
- +