diff --git a/index.html b/index.html index 222d09ed..0ac2c59e 100644 --- a/index.html +++ b/index.html @@ -33,7 +33,7 @@ /> - 로그인 + 로그인 diff --git a/login.html b/signin.html similarity index 66% rename from login.html rename to signin.html index 0563b9b4..12d34bf7 100644 --- a/login.html +++ b/signin.html @@ -9,7 +9,7 @@
-
+
-
+ diff --git a/signup.html b/signup.html index 5415eb22..e18f4ea1 100644 --- a/signup.html +++ b/signup.html @@ -9,7 +9,7 @@
-
+
-
+ diff --git a/src/assets/icons/btn_visibility_off_24px.svg b/src/assets/icons/ic_closed_eye.svg similarity index 100% rename from src/assets/icons/btn_visibility_off_24px.svg rename to src/assets/icons/ic_closed_eye.svg diff --git a/src/assets/icons/ic_opened_eye.svg b/src/assets/icons/ic_opened_eye.svg new file mode 100644 index 00000000..35a75305 --- /dev/null +++ b/src/assets/icons/ic_opened_eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/js/common/auth.mjs b/src/js/common/auth.mjs new file mode 100644 index 00000000..33ec9790 --- /dev/null +++ b/src/js/common/auth.mjs @@ -0,0 +1,14 @@ +function toggleInputType(e) { + const eventCurrentTarget = e.currentTarget; + const inputElement = eventCurrentTarget.parentElement.firstElementChild; + const [closeEye, openEye] = eventCurrentTarget.children; + + openEye.classList.toggle("none"); + closeEye.classList.toggle("none"); + inputElement.setAttribute( + "type", + inputElement.type === "password" ? "text" : "password" + ); +} + +export { toggleInputType }; diff --git a/src/js/common/validate.mjs b/src/js/common/validate.mjs new file mode 100644 index 00000000..25745cdd --- /dev/null +++ b/src/js/common/validate.mjs @@ -0,0 +1,65 @@ +function addErrorMessage(el, message) { + const target = el.parentElement.parentElement; + const pElement = document.createElement("p"); + pElement.classList.add("error-message"); + pElement.textContent = message; + target.append(pElement); +} + +function onInputError(el, { message }) { + const parentElement = el.parentElement; + parentElement.classList.add("red-border"); + parentElement.classList.remove("gray-border"); + + const hasError = + parentElement.parentElement.lastElementChild.classList.contains( + "error-message" + ); + + if (!hasError) { + addErrorMessage(el, message); + } +} + +function offInputError(el) { + el.parentElement.classList.add("gray-border"); + el.parentElement.classList.remove("red-border"); +} + +function checkValidation({ el, isValidate, message }) { + if (isValidate === true) { + offInputError(el); + } else { + onInputError(el, { message }); + } +} + +function validate(e, { targetEl, message, validator }) { + const el = e?.target ? e.target : e; + const value1 = el.value; + const value2 = targetEl?.value; + const isValidate = validator(value1, value2); + + checkValidation({ el, isValidate, message }); +} + +function resetErrorMessage(e) { + const errorCandidateElement = + e.target.parentElement.parentElement.lastElementChild; + const hasError = errorCandidateElement.classList.contains("error-message"); + + if (hasError) { + errorCandidateElement.remove(); + } +} + +function changeButtonStatus(el, { checkEmptyInputElements }) { + const isEmpty = checkEmptyInputElements.some( + (inputElement) => inputElement.value === "" + ); + const isError = document.querySelectorAll(".error-message").length > 0; + + el.disabled = isEmpty || isError; +} + +export { resetErrorMessage, validate, changeButtonStatus }; diff --git a/src/js/common/validator.mjs b/src/js/common/validator.mjs new file mode 100644 index 00000000..af034a2f --- /dev/null +++ b/src/js/common/validator.mjs @@ -0,0 +1,12 @@ +const MINIMUM_PASSWORD_LENGTH = 8; +const EMAIL_REG_EXP = + /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i; + +const authValidator = { + isWrongEmailFormat: (value) => EMAIL_REG_EXP.test(value), + isEmptyInput: (value) => value !== "", + isMoreThanEight: (value) => value.length >= MINIMUM_PASSWORD_LENGTH, + isMatch: (value1, value2) => value1 !== "" && value1 === value2, +}; + +export { authValidator }; diff --git a/src/js/constants/contants.mjs b/src/js/constants/contants.mjs new file mode 100644 index 00000000..8c0d4807 --- /dev/null +++ b/src/js/constants/contants.mjs @@ -0,0 +1,11 @@ +const ERROR_MESSAGE = { + IS_EMPTY_EMAIL: "이메일을 입력해 주세요.", + IS_WRONG_EMAIL_FORMAT: "잘못된 이메일입니다.", + IS_EMPTY_NICKNAME: "닉네임을 입력해 주세요.", + IS_EMPTY_PASSWORD: "비밀번호를 입력해 주세요.", + IS_EMPTY_REPASSWORD: "비밀번호 확인을 입력해 주세요.", + IS_MORE_THAN_EIGHT_PASSWORD: "비밀번호를 8자 이상 입력해주세요.", + IS_NOT_MATCH_PASSWORD: "비밀번호가 일치하지 않습니다.", +}; + +export { ERROR_MESSAGE }; diff --git a/src/js/signin.mjs b/src/js/signin.mjs new file mode 100644 index 00000000..1257a9a8 --- /dev/null +++ b/src/js/signin.mjs @@ -0,0 +1,53 @@ +import { + validate, + resetErrorMessage, + changeButtonStatus, +} from "./common/validate.mjs"; +import { toggleInputType } from "./common/auth.mjs"; +import { authValidator } from "./common/validator.mjs"; +import { ERROR_MESSAGE } from "./constants/contants.mjs"; + +const $emailInput = document.querySelector("#email"); +const $passwordInput = document.querySelector("#password"); +const $submitButton = document.querySelector(".auth-button"); +const $eyeIcon = document.querySelector(".eye"); + +const checkEmptyInputElements = [$emailInput, $passwordInput]; + +function onFocusoutEmail(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_EMAIL, + }); + validate(e, { + validator: authValidator.isWrongEmailFormat, + message: ERROR_MESSAGE.IS_WRONG_EMAIL_FORMAT, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function onFocusoutPassword(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_PASSWORD, + }); + validate(e, { + validator: authValidator.isMoreThanEight, + message: ERROR_MESSAGE.IS_MORE_THAN_EIGHT_PASSWORD, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function onClickButton(e) { + e.preventDefault(); + location.href = "/items.html"; +} + +$emailInput.addEventListener("focusout", onFocusoutEmail); +$passwordInput.addEventListener("focusout", onFocusoutPassword); +$eyeIcon.addEventListener("click", toggleInputType); +$submitButton.addEventListener("click", onClickButton); diff --git a/src/js/signup.mjs b/src/js/signup.mjs new file mode 100644 index 00000000..1d693569 --- /dev/null +++ b/src/js/signup.mjs @@ -0,0 +1,91 @@ +import { + validate, + resetErrorMessage, + changeButtonStatus, +} from "./common/validate.mjs"; +import { toggleInputType } from "./common/auth.mjs"; +import { authValidator } from "./common/validator.mjs"; +import { ERROR_MESSAGE } from "./constants/contants.mjs"; + +const $emailInput = document.querySelector("#email"); +const $nicknameInput = document.querySelector("#nickname"); +const $passwordInput = document.querySelector("#password"); +const $repasswordInput = document.querySelector("#repassword"); +const $submitButton = document.querySelector(".auth-button"); +const $eyeIcon = document.querySelectorAll(".eye"); + +const checkEmptyInputElements = [ + $emailInput, + $nicknameInput, + $passwordInput, + $repasswordInput, +]; + +function onFocusoutEmail(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_EMAIL, + }); + validate(e, { + validator: authValidator.isWrongEmailFormat, + message: ERROR_MESSAGE.IS_WRONG_EMAIL_FORMAT, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function checkFocusoutNickname(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_NICKNAME, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function onFocusoutPassWord(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_PASSWORD, + }); + validate(e, { + validator: authValidator.isMoreThanEight, + message: ERROR_MESSAGE.IS_MORE_THAN_EIGHT_PASSWORD, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function onFocusoutRepassWord(e) { + resetErrorMessage(e); + validate(e, { + validator: authValidator.isEmptyInput, + message: ERROR_MESSAGE.IS_EMPTY_REPASSWORD, + }); + validate(e, { + validator: authValidator.isMoreThanEight, + message: ERROR_MESSAGE.IS_MORE_THAN_EIGHT_PASSWORD, + }); + validate(e.target, { + targetEl: $passwordInput, + validator: authValidator.isMatch, + message: ERROR_MESSAGE.IS_NOT_MATCH_PASSWORD, + }); + + changeButtonStatus($submitButton, { checkEmptyInputElements }); +} + +function onClickButton(e) { + e.preventDefault(); + location.href = "/signin.html"; +} + +$emailInput.addEventListener("focusout", onFocusoutEmail); +$nicknameInput.addEventListener("focusout", checkFocusoutNickname); +$passwordInput.addEventListener("focusout", onFocusoutPassWord); +$repasswordInput.addEventListener("focusout", onFocusoutRepassWord); +$eyeIcon.forEach(($eyeEl) => $eyeEl.addEventListener("click", toggleInputType)); +$submitButton.addEventListener("click", onClickButton); diff --git a/src/styles/auth.css b/src/styles/auth.css index 9defb8de..65212990 100644 --- a/src/styles/auth.css +++ b/src/styles/auth.css @@ -4,7 +4,7 @@ body { height: 100vh; } -.login-main { +.auth-main { position: absolute; top: 10%; left: 50%; @@ -12,25 +12,25 @@ body { width: 640px; } -.login-main .logo { +.auth-main .logo { display: flex; justify-content: center; margin-bottom: 40px; } -.login-main .logo img { +.auth-main .logo img { width: 396px; height: 132px; } -.login-form { +.auth-form { display: flex; flex-direction: column; gap: 24px; margin-bottom: 24px; } -.login-form .login-label span { +.auth-form .auth-label span { display: inline-block; margin-bottom: 16px; font-size: 18px; @@ -39,24 +39,44 @@ body { color: var(--gray-800); } -.login-form .login-label .login-input-box { +.auth-form .auth-label .auth-input-box { display: flex; justify-content: space-between; padding: 15px 24px; background-color: var(--gray-100); - border: 1px solid var(--gray-100); border-radius: 12px; } -.login-form .login-label img { +.auth-form .auth-label .eye { + display: flex; margin-left: 5px; } -.login-form .login-label .login-input-box:focus-within { +.none { + display: none; +} + +.gray-border { + border: 1px solid var(--gray-100); +} + +.red-border { + border: 1px solid var(--error-red); +} + +.auth-form .auth-label .auth-input-box.gray-border:focus-within { border: 1px solid var(--blue); } -.login-form .login-label input { +.error-message { + margin-top: 8px; + padding: 0 16px; + font-size: 14px; + font-weight: 600; + color: var(--error-red); +} + +.auth-form .auth-label input { width: 100%; background: none; border: none; @@ -65,14 +85,14 @@ body { line-height: 26px; } -.login-form .login-label input::placeholder { +.auth-form .auth-label input::placeholder { color: var(--gray-400); } -.login-form button { - display: block; +.auth-form .auth-button { + display: inline-block; padding: 12px 0; - background-color: var(--gray-400); + background-color: var(--blue); border: none; border-radius: 40px; font-size: 20px; @@ -80,9 +100,15 @@ body { line-height: 32px; text-align: center; color: var(--gray-100); + cursor: pointer; +} + +.auth-form .auth-button:disabled { + background-color: var(--gray-400); + cursor: default; } -.convenient-login-box { +.convenient-auth-box { display: flex; align-items: center; justify-content: space-between; @@ -96,13 +122,13 @@ body { color: var(--gray-800); } -.convenient-login-box .convenient-login-icon-box { +.convenient-auth-box .convenient-auth-icon-box { display: flex; align-items: center; gap: 16px; } -.convenient-login-box .convenient-login-icon-box a { +.convenient-auth-box .convenient-auth-icon-box a { height: 42px; } @@ -120,23 +146,23 @@ body { /** 모바일 반응형 */ @media (min-width: 375px) and (max-width: 767px) { - .login-main { + .auth-main { top: 80px; width: 100%; max-width: 400px; padding: 0 16px; } - .login-main .logo { + .auth-main .logo { margin-bottom: 24px; } - .login-main .logo img { + .auth-main .logo img { width: 198px; height: 66px; } - .login-form .login-label span { + .auth-form .auth-label span { display: inline-block; margin-bottom: 8px; font-size: 14px; diff --git a/src/styles/common.css b/src/styles/common.css index 3967f907..afd5bd89 100644 --- a/src/styles/common.css +++ b/src/styles/common.css @@ -43,4 +43,5 @@ --gray-100: #f3f4f6; --gray-50: #f9fafb; --blue: #3682ff; + --error-red: #f74747; }