diff --git a/js/auth.js b/js/auth.js deleted file mode 100644 index a3b0b4109..000000000 --- a/js/auth.js +++ /dev/null @@ -1,60 +0,0 @@ -const btnsTogglePw = document.querySelectorAll(".btn-toggle"); -const inputs = document.querySelectorAll(".input-area > input"); - -/** - * 비밀번호와 비밀번호 확인의 type을 변경하는 함수 - * @param {*} e - */ -function changeTypePw(e) { - const inputPw = e.target.closest(".password").querySelector(".pw"); // 비밀번호 | 비밀번호 확인 input 요소 - const type = inputPw.getAttribute("type"); // 비밀번호 | 비밀번호 확인 input의 type 속성 - - if (type === "password") { - inputPw.setAttribute("type", "text"); // type이 password면 text로 변경 - } else { - inputPw.setAttribute("type", "password"); // type이 text면 password로 변경 - } -} - -/** - * form의 모든 input 태그들이 공백인지 확인하는 함수 - * @param {*} e - */ -function checkIsFilled(e) { - const form = e.target.closest("form"); - const inputs = form.querySelectorAll("input"); - const btnSubmit = form.querySelector("button[type=submit]"); // 로그인 | 회원가입 버튼 - const isFormValid = Array.from(inputs).every( - (input) => input.value.trim() !== "" - ); // form의 모든 input 요소의 값이 공백 아닌지 확인 - const pwConfirm = form.querySelector("#input_password_confirm"); - - if (pwConfirm) { - // 비밀번호 확인이 있는 경우(회원가입 페이지) - const pwValue = form.querySelector("#input_password").value.trim(); - const pwConfirmValue = pwConfirm.value.trim(); - const isPasswordMath = pwValue === pwConfirmValue; - - if (isFormValid && isPasswordMath) { - // 회원가입 페이지 : 비밀번호와 비밀번호 확인도 일치해야 - btnSubmit.disabled = false; // 전송 버튼 활성화 - } else { - btnSubmit.disabled = true; // 전송 버튼 비활성화 - } - } else { - // 로그인 페이지 : input 요소가 모두 채워지기만 하면 - if (isFormValid) { - btnSubmit.disabled = false; // 전송 버튼 활성화 - } else { - btnSubmit.disabled = true; // 전송 버튼 비활성화 - } - } -} - -btnsTogglePw.forEach((btn) => { - btn.addEventListener("click", changeTypePw); -}); - -inputs.forEach((input) => { - input.addEventListener("input", checkIsFilled); -}); diff --git a/js/login.js b/js/login.js new file mode 100644 index 000000000..5e80a162d --- /dev/null +++ b/js/login.js @@ -0,0 +1,20 @@ +import { togglePwVisibility } from "./togglePwVisible.js"; +import { checkInputValidity, validateForm } from "./validation.js"; + +const pwVisibilityToggleBtn = document.querySelector(".btn-toggle"); // [sprint3 리뷰 반영] 변수명 직관적으로 변경 +const inputs = document.querySelectorAll(".input-area > input"); +const loginButton = document.querySelector(".btn.login"); + +pwVisibilityToggleBtn.addEventListener("click", togglePwVisibility); + +inputs.forEach((input) => { + input.addEventListener("focusout", (e) => { + checkInputValidity(e); + validateForm(e); + }); +}); + +loginButton.addEventListener("click", (e) => { + e.preventDefault(); + location.href = "/pages/items.html"; +}); diff --git a/js/signup.js b/js/signup.js new file mode 100644 index 000000000..cdd3047f1 --- /dev/null +++ b/js/signup.js @@ -0,0 +1,22 @@ +import { togglePwVisibility } from "./togglePwVisible.js"; +import { checkInputValidity, validateForm } from "./validation.js"; +const signupButton = document.querySelector(".btn.signup"); + +const pwVisibilityToggleBtn = document.querySelectorAll(".btn-toggle"); // [sprint3 리뷰 반영] 변수명 직관적으로 변경 +const inputs = document.querySelectorAll(".input-area > input"); + +pwVisibilityToggleBtn.forEach((btn) => { + btn.addEventListener("click", togglePwVisibility); +}); + +inputs.forEach((input) => { + input.addEventListener("focusout", (e) => { + checkInputValidity(e); + validateForm(e); + }); +}); + +signupButton.addEventListener("click", (e) => { + e.preventDefault(); + location.href = "/pages/signin.html"; +}); diff --git a/js/togglePwVisible.js b/js/togglePwVisible.js new file mode 100644 index 000000000..e4d7abe3f --- /dev/null +++ b/js/togglePwVisible.js @@ -0,0 +1,14 @@ +/** + * 비밀번호와 비밀번호 확인의 type을 변경하는 함수 + * @param {*} e + */ +function togglePwVisibility(e) { + // [sprint3 리뷰 반영] 함수명 직관적으로 변경 + const inputPw = e.target.closest(".password").querySelector(".pw"); // 비밀번호 input 요소 + const type = inputPw.getAttribute("type"); // 비밀번호 | 비밀번호 확인 input의 type 속성 + + // [sprint3 리뷰 반영] if문 3항 연산자 사용해 코드 간결하게 수정 + inputPw.setAttribute("type", type === "password" ? "text" : "password"); +} + +export { togglePwVisibility }; diff --git a/js/validation.js b/js/validation.js new file mode 100644 index 000000000..b77e43e05 --- /dev/null +++ b/js/validation.js @@ -0,0 +1,125 @@ +/** + * input이 공백인지 검사 + * @param {*} target 이벤트 발생한 타겟 요소 + * @param {*} errorMessage 에러를 표시할 태그 + * @returns input의 입력이 공백이면 true, 공백이 아니면 false 반환 + */ +function handleEmptyInput(target, errorMessage) { + if (target.value.trim("") === "") { + errorMessage.textContent = target.placeholder; + target.classList.add("invalid"); + target.dataset.valid = false; + return true; + } else { + target.classList.remove("invalid"); + target.dataset.valid = true; + return false; + } +} + +/** + * 이메일이 [최소 한글자 이상의 영문 대소문자, 숫자]@[최소 한글자 이상의 영문 대소문자, 숫자].[최소 2글자 이상의 영문 대소문자]의 정규 표현식에 적합한지 검사 + * @param {*} email 공백을 제거한 이메일 문자열 + * @returns 이메일이 정규 표현식에 적합하면 true, 적합하지 않으면 false 반환 + */ +function validateEmailFormat(email) { + const emailRegex = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/; + + return emailRegex.test(email); +} + +/** + * 이메일 입력의 유효성 검사 + * @param {*} target 이벤트 발생한 타겟 요소 + * @param {*} errorMessage 에러를 표시할 태그 + */ +function validateEmail(target, errorMessage) { + const isEmailValid = validateEmailFormat(target.value.trim("")); + if (!isEmailValid) { + errorMessage.textContent = "잘못된 이메일 형식입니다."; + target.classList.add("invalid"); + target.dataset.valid = false; + } else { + target.classList.remove("invalid"); + target.dataset.valid = true; + } +} + +/** + * 비밀번호 입력의 유효성 검사 + * @param {*} target 이벤트 발생한 타겟 요소 + * @param {*} errorMessage 에러를 표시할 태그 + */ +function validatePassword(target, errorMessage) { + if (target.value.trim("").length < 8) { + errorMessage.textContent = "비밀번호를 8자리 이상 입력해주세요."; + target.classList.add("invalid"); + target.dataset.valid = false; + } else { + target.classList.remove("invalid"); + target.dataset.valid = true; + } +} + +/** + * 비밀번호 확인 입력의 유효성 검사 + * @param {*} target 이벤트 발생한 타겟 요소 + * @param {*} errorMessage 에러를 표시할 태그 + */ +function validatePasswordConfirm(target, errorMessage) { + const password = target + .closest("form") + .querySelector("#input_password") + .value.trim(""); + + if (target.value.trim("") !== password) { + errorMessage.textContent = "비밀번호가 일치하지 않습니다."; + target.classList.add("invalid"); + target.dataset.valid = false; + } else { + validatePassword(target, errorMessage); + } +} + +/** + * form의 input 요소들의 유효성 검사 + */ +function checkInputValidity({ target }) { + const errorMessage = target + .closest(".input-area") + .querySelector(".msg-error"); + const isEmpty = handleEmptyInput(target, errorMessage); + + if (!isEmpty) { + switch (target.id) { + case "input_email": + validateEmail(target, errorMessage); + break; + case "input_password": + validatePassword(target, errorMessage); + break; + case "input_password_confirm": + validatePasswordConfirm(target, errorMessage); + break; + default: + break; + } + } +} + +/** + * 폼 태그의 모든 입력이 모두 유효성 검사를 통과했는지 체크 + */ +function validateForm({ target }) { + const form = target.closest("form"); + const inputs = form.querySelectorAll("input"); + const btnSubmit = form.querySelector("button[type=submit]"); // 로그인 | 회원가입 버튼 + const isFormValid = Array.from(inputs).every( + // (input) => input.value.trim() !== "" + (input) => input.dataset.valid === "true" + ); // form의 모든 input 요소의 data-valid가 'true'인지 체크 + + btnSubmit.disabled = isFormValid === true ? false : true; // 모든 입력이 공백이 아니면 로그인 버튼 활성화 +} + +export { checkInputValidity, validateForm }; diff --git a/pages/login.html b/pages/login.html index 0047ef30c..461193b88 100644 --- a/pages/login.html +++ b/pages/login.html @@ -24,8 +24,10 @@

type="email" id="input_email" placeholder="이메일을 입력해 주세요" + data-valid="false" required /> + 에러 메시지
@@ -34,8 +36,10 @@

id="input_password" class="pw" placeholder="비밀번호를 입력해주세요" + data-valid="false" required /> + 에러 메시지 @@ -70,6 +74,6 @@

- + diff --git a/pages/signin.html b/pages/signin.html new file mode 100644 index 000000000..24ca14d5b --- /dev/null +++ b/pages/signin.html @@ -0,0 +1,9 @@ + + + + + + Signin + + + diff --git a/pages/signup.html b/pages/signup.html index 9659a9ae0..e95602862 100644 --- a/pages/signup.html +++ b/pages/signup.html @@ -24,8 +24,10 @@

type="email" id="input_email" placeholder="이메일을 입력해 주세요" + data-valid="false" required /> + 에러 메시지
@@ -33,8 +35,10 @@

type="text" id="input_name" placeholder="닉네임을 입력해 주세요" + data-valid="false" required /> + 에러 메시지

@@ -43,8 +47,10 @@

id="input_password" class="pw" placeholder="비밀번호를 입력해주세요" + data-valid="false" required /> + 에러 메시지 @@ -56,8 +62,10 @@

id="input_password_confirm" class="pw" placeholder="비밀번호를 다시 한 번 입력해주세요" + data-valid="false" required /> + 에러 메시지 @@ -92,6 +100,6 @@

- + diff --git a/style/auth.css b/style/auth.css index d9bb98baf..9084ab4ce 100644 --- a/style/auth.css +++ b/style/auth.css @@ -58,15 +58,21 @@ .container form .input-area input:focus { outline-color: var(--primary100); } -.container form .input-area input.pw[type=password] + .btn-toggle { +.container form .input-area input.pw[type=text] ~ .btn-toggle { background-image: url(../assets/images/icons/ic_visibility_on.png); } -.container form .input-area input.pw[type=text] + .btn-toggle { +.container form .input-area input.pw[type=password] ~ .btn-toggle { background-image: url(../assets/images/icons/ic_visibility_off.png); } +.container form .input-area input.invalid { + border: 0.1rem solid var(--error); +} +.container form .input-area input.invalid ~ .msg-error { + display: block; +} .container form .input-area .btn-toggle { position: absolute; - bottom: 1.6rem; + top: 5.8rem; right: 2.4rem; width: 2.4rem; height: 2.4rem; @@ -81,6 +87,14 @@ width: 100%; height: 100%; } +.container form .input-area .msg-error { + display: none; + margin: 0.8rem 0 0 1.6rem; + font-size: 1.4rem; + font-weight: var(--semiBold); + line-height: 2.4rem; + color: var(--error); +} .container form .btn { height: 5.6rem; padding: 0;