diff --git a/page/login.html b/page/login.html index 0f8339c2..9d04fe5c 100644 --- a/page/login.html +++ b/page/login.html @@ -26,7 +26,7 @@

autocomplete="email" placeholder="이메일을 입력해주세요" /> - +
- - + +
@@ -63,8 +63,9 @@

판다마켓이 처음이신가요? - + 회원가입
+ diff --git a/page/signup.html b/page/signup.html index f51616f7..02350a67 100644 --- a/page/signup.html +++ b/page/signup.html @@ -25,7 +25,7 @@

autocomplete="email" placeholder="이메일을 입력해주세요" /> - + class="input" placeholder="닉네임을 입력해주세요" /> - +
placeholder="비밀번호를 입력해주세요" />
+
class="input" placeholder="비밀번호를 다시 한 번 입력해주세요" /> - +
+ - +
@@ -84,8 +90,9 @@

이미 회원이신가요? - + 로그인
+ diff --git a/script/login.js b/script/login.js new file mode 100644 index 00000000..28909c17 --- /dev/null +++ b/script/login.js @@ -0,0 +1,45 @@ +import { + validInput, + updateValidationUI, + togglePasswordVisibility, + checkButtonActivation, +} from "./utils.js"; + +// 요소 선택 +const formElements = { + email: document.querySelector("#email"), + pw: document.querySelector("#pw"), + warnings: document.querySelectorAll(".warning-text"), + eyeIcon: document.querySelector(".eye-icon"), + loginBtn: document.querySelector(".form-btn"), +}; + +// 공통 유효성 검사 핸들러 +const handleValidation = (type, inputEl, warningEl) => { + const result = validInput[type](inputEl); + updateValidationUI(inputEl, warningEl, result); + checkButtonActivation(formElements.loginBtn, "login"); +}; + +// 이벤트 바인딩 +formElements.email.addEventListener("blur", () => { + handleValidation("email", formElements.email, formElements.warnings[0]); +}); + +formElements.pw.addEventListener("input", () => { + handleValidation("pw", formElements.pw, formElements.warnings[1]); +}); + +// 비밀번호 보기 토글 +formElements.eyeIcon.addEventListener("click", () => { + togglePasswordVisibility(formElements.eyeIcon, formElements.pw); +}); + +// 로그인 버튼 클릭 +formElements.loginBtn.addEventListener("click", (e) => { + e.preventDefault(); + checkButtonActivation(formElements.loginBtn, "login"); + if (!formElements.loginBtn.disabled) { + window.location.href = "../page/items.html"; + } +}); diff --git a/script/signUp.js b/script/signUp.js new file mode 100644 index 00000000..a5f9c29d --- /dev/null +++ b/script/signUp.js @@ -0,0 +1,62 @@ +import { + validInput, + updateValidationUI, + togglePasswordVisibility, + checkButtonActivation, +} from "./utils.js"; + +// 엘리먼트 모음 +const formElements = { + email: document.querySelector("#email"), + nickname: document.querySelector("#nickname"), + pw: document.querySelector("#pw"), + pwChk: document.querySelector("#pw-chk"), +}; +const warningText = document.querySelectorAll(".warning-text"); +const signUpBtn = document.querySelector(".form-btn"); +const eyesIcons = document.querySelectorAll(".eye-icon"); + +// 유효성 검사 핸들러 +const handleValidation = (type, index) => { + const result = validInput[type](formElements[type], formElements.pw?.value); + updateValidationUI(formElements[type], warningText[index], result); + + // 비밀번호 blur 시 pwChk도 함께 검사 + if (type === "pw" && formElements.pwChk.value) { + const chkResult = validInput.pwChk( + formElements.pwChk, + formElements.pw.value + ); + updateValidationUI(formElements.pwChk, warningText[3], chkResult); + } + checkButtonActivation(signUpBtn, "signup"); +}; + +// 이벤트 연결 +formElements.email.addEventListener("blur", () => handleValidation("email", 0)); +formElements.nickname.addEventListener("blur", () => + handleValidation("nickname", 1) +); +formElements.pw.addEventListener("input", () => handleValidation("pw", 2)); +formElements.pwChk.addEventListener("input", () => + handleValidation("pwChk", 3) +); + +// 비밀번호 토글 +eyesIcons.forEach((icon) => { + icon.addEventListener("click", () => { + const target = icon.classList.contains("pw") + ? formElements.pw + : formElements.pwChk; + togglePasswordVisibility(icon, target); + }); +}); + +// 회원가입 버튼 +signUpBtn.addEventListener("click", (e) => { + e.preventDefault(); + checkButtonActivation(signUpBtn, "signup"); + if (!signUpBtn.disabled) { + window.location.href = "../page/login.html"; + } +}); diff --git a/script/utils.js b/script/utils.js new file mode 100644 index 00000000..e0846c26 --- /dev/null +++ b/script/utils.js @@ -0,0 +1,92 @@ +// 상태 객체 +const state = { + emailValid: false, + pwValid: false, + passwordMatchValid: false, + nickNameValid: false, +}; + +// 상태 업데이트 +const setValidationState = (key, isValid) => { + state[key] = isValid; +}; + +// 이메일 유효성 확인 +const checkValidEmail = (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val); + +// input 조건 확인 함수 +export const validInput = { + email: (input) => { + const val = input.value.trim(); + if (!val) { + setValidationState("emailValid", false); + return { isValid: false, msg: "이메일을 입력해주세요." }; + } + if (!checkValidEmail(val)) { + setValidationState("emailValid", false); + return { isValid: false, msg: "잘못된 이메일 형식입니다." }; + } + setValidationState("emailValid", true); + return { isValid: true, msg: "" }; + }, + + pw: (input) => { + const val = input.value; + if (!val) { + setValidationState("pwValid", false); + return { isValid: false, msg: "비밀번호를 입력해주세요." }; + } + if (val.length < 8) { + setValidationState("pwValid", false); + return { isValid: false, msg: "비밀번호를 8자 이상 입력해주세요." }; + } + setValidationState("pwValid", true); + return { isValid: true, msg: "" }; + }, + + pwChk: (input, pwValue) => { + const val = input.value; + if (val.length < 8) { + setValidationState("passwordMatchValid", false); + return { isValid: false, msg: "비밀번호를 8자 이상 입력해주세요." }; + } + if (val !== pwValue) { + setValidationState("passwordMatchValid", false); + return { isValid: false, msg: "비밀번호가 일치하지 않습니다." }; + } + setValidationState("passwordMatchValid", true); + return { isValid: true, msg: "" }; + }, + + nickname: (input) => { + const val = input.value.trim(); + if (!val) { + setValidationState("nickNameValid", false); + return { isValid: false, msg: "닉네임을 입력해주세요." }; + } + setValidationState("nickNameValid", true); + return { isValid: true, msg: "" }; + }, +}; + +// UI 업데이트 함수 +export const updateValidationUI = (inputEle, warningMsg, { isValid, msg }) => { + inputEle.classList.toggle("input-error", !isValid); + warningMsg.innerHTML = msg; +}; + +// 비밀번호 보기 토글 +export const togglePasswordVisibility = (buttonEl, inputEl) => { + const isActive = buttonEl.classList.toggle("on"); + inputEl.setAttribute("type", isActive ? "text" : "password"); + return isActive; +}; + +// 버튼 활성화 체크 함수 +export const checkButtonActivation = (targetEle, pageChk) => { + const keys = + pageChk === "login" ? ["emailValid", "pwValid"] : Object.keys(state); + + const isAllValid = keys.every((key) => state[key]); + targetEle.disabled = !isAllValid; +}; diff --git a/style/auth.css b/style/auth.css index e5048f3f..e264c327 100644 --- a/style/auth.css +++ b/style/auth.css @@ -1,4 +1,16 @@ /* 로그인 & 회원가입 */ +.warning-text { + font-size: 13px; + color: #f74747; + font-weight: 500; + padding-left: 5px; + height: 13px; + margin: 8px 0 15px; +} +.input-error { + border: 1px solid red; +} + .auth-container { width: var(--auth-container-width); margin: 0 auto; @@ -37,7 +49,6 @@ height: 56px; background-color: var(--gray100); border-radius: 12px; - margin-bottom: 24px; padding-left: 24px; color: var(--gray800); width: 100%; @@ -47,14 +58,12 @@ } .auth-container .form-wrap .pw-wrap { position: relative; - margin-bottom: 24px; } .auth-container .form-wrap .pw-wrap .input { margin: 0; } .auth-container .form-wrap .pw-wrap .eye-icon { content: ""; - display: block; background-image: url(../images/eyes_close.png); background-position: center; background-repeat: no-repeat; @@ -69,12 +78,20 @@ } .auth-container .form-wrap .pw-wrap .eye-icon.on { background-image: url(../images/eyes.png); + width: 21px; + height: 21px; + right: 26px; + top: 48%; } .auth-container .form-wrap .input::placeholder { color: var(--gray400); } -.auth-container .form-wrap .form-btn { +.auth-container .form-wrap .form-btn:disabled { + cursor: default; background-color: var(--gray400); +} +.auth-container .form-wrap .form-btn { + background-color: var(--main-color); color: #fff; height: 56px; border-radius: 50px; @@ -104,10 +121,10 @@ margin-top: 24px; font-size: 14px; } -.auth-container .signup-wrap .goto-signup { +.auth-container .signup-wrap .goto-page { color: var(--main-color); text-decoration: underline; } -.auth-container .signup-wrap .goto-signup:visited { +.auth-container .signup-wrap .goto-page:visited { color: var(--main-color); }