diff --git a/login/login-main.js b/login/login-main.js
new file mode 100644
index 00000000..06892c9a
--- /dev/null
+++ b/login/login-main.js
@@ -0,0 +1,7 @@
+import { createValidRule, ruleObj, emailInput, passwordInput } from '../modules/validation-rule.mjs';
+import { initValidation } from '../modules/validate.mjs';
+import { initPasswordVisibility, visibilityPw } from '../modules/toggle-visibility-pw.mjs';
+
+const loginValidationState = createValidRule({ emailInput, passwordInput }, ruleObj); //네이밍구조 페이지 기반으로 바꾸기
+initValidation(loginValidationState);
+initPasswordVisibility({ visibilityPw });
diff --git a/login/login.css b/login/login.css
index bfa1c4fa..d9f2989b 100644
--- a/login/login.css
+++ b/login/login.css
@@ -1,5 +1,9 @@
@import url(../global.css);
+.container__position-relative {
+ position: relative;
+}
+
main {
padding: 0 16px;
}
@@ -28,12 +32,11 @@ fieldset {
flex-direction: column;
width: 100%;
border: none;
- position: relative;
}
label[for="toggle-visibility-pw"] {
position: absolute;
- top: 152px;
+ top: 52px;
right: 24px;
display: inline-block;
width: 24px;
@@ -67,22 +70,19 @@ input {
background-color: var(--secondary-gray100);
height: 56px;
padding: 12px 24px;
+ width: 100%;
}
button {
height: 56px;
padding: 16px 124px;
border-radius: 40px;
- background-color: var(--primary-color);
+ background-color: var(--secondary-gray400);
color: var(--white-color);
margin-bottom: 24px;
font-size: 20px;
}
-button:hover {
- background-color: var(--primary-hover);
-}
-
.login__alert {
height: 74px;
background-color: #e6f2ff;
@@ -119,6 +119,31 @@ button:hover {
color: var(--primary-color);
}
+/* validation err 관련 스타일 */
+.error-message {
+ /* err메세지 */
+ color: red;
+ font-size: 14px;
+ line-height: 26px;
+ padding-left: 24px;
+ display: block;
+ margin-top: -16px;
+ margin-bottom: 16px;
+}
+
+.error-Line {
+ /* 인풋 빨간 라인*/
+ border: 1px solid red;
+}
+
+.pass-button {
+ background-color: var(--primary-color);
+}
+
+.pass-button:hover {
+ background-color: var(--primary-hover);
+}
+
@media screen and (min-width: 768px) {
.main__login {
max-width: 640px;
@@ -131,7 +156,7 @@ button:hover {
}
label[for="toggle-visibility-pw"] {
- top: 180px;
+ top: 60px;
}
form label {
diff --git a/modules/toggle-visibility-pw.mjs b/modules/toggle-visibility-pw.mjs
new file mode 100644
index 00000000..2fc70583
--- /dev/null
+++ b/modules/toggle-visibility-pw.mjs
@@ -0,0 +1,23 @@
+// 비밀번호 보기 토글버튼들(chekBox)
+export const visibilityPw = document.querySelector("#toggle-visibility-pw");
+export const visibilityPwCheck = document.querySelector("#toggle-visibility-pwcheck");
+
+function togglePasswordVisibility(targetInput, targetLabel) {
+ const isPassword = targetInput.type === "password";
+ targetInput.type = isPassword ? "text" : "password";
+ targetLabel.setAttribute('aria-checked', isPassword ? 'true' : 'false');
+}
+
+export function initPasswordVisibility(toggleBtns) {
+ Object.values(toggleBtns).forEach((toggleBtn) => {
+ if (!toggleBtn) return;
+
+ const targetInput = toggleBtn.previousElementSibling;
+ const targetLabel = toggleBtn.nextElementSibling;
+ if (!targetInput || targetInput.type !== "password") return;
+
+ toggleBtn.addEventListener("click", () => {
+ togglePasswordVisibility(targetInput, targetLabel);
+ });
+ });
+}
diff --git a/modules/validate.mjs b/modules/validate.mjs
new file mode 100644
index 00000000..081dfa8b
--- /dev/null
+++ b/modules/validate.mjs
@@ -0,0 +1,54 @@
+import { removeMessage, appendErr, clearErr, updateButton } from './validation-ui.mjs';
+
+const userForm = document.querySelector("form");
+
+// 검사 통과 못한 거 있나요?
+export function hasInvalidInput(validationState) {
+ return Object.values(validationState).some(validator => validator.passed === false);
+}
+
+//폼 전송 막기
+function preventInvalidSubmit(e, validationState) {
+ //서브밋 누르면 통과 못한 인풋에 경고 보여줘
+ Object.values(validationState).forEach((validator) => {
+ updateValidation({ currentTarget: validator.input }, validationState);
+ });
+
+ if (hasInvalidInput(validationState)) {
+ e.preventDefault();
+ }
+}
+
+//스타일 추가할지 말지 판단
+function updateValidation(e, validationState) {
+ const { name } = e.currentTarget;
+ const validator = validationState[name];
+
+ removeMessage(name)
+
+ if (!validator.condition()) {
+ appendErr(validator);
+ validator.passed = false;
+ } else {
+ clearErr(validator);
+ validator.passed = true;
+ }
+
+ updateButton(validationState, hasInvalidInput);
+}
+
+//리스너 추가
+export function initValidation(validationState) {
+ for (let validator in validationState) {
+ validationState[validator].input.addEventListener("focusout", e => updateValidation(e, validationState));
+ }
+
+ userForm.addEventListener("submit", e => preventInvalidSubmit(e, validationState));
+
+ if (validationState['user-password-check']) {
+ validationState['user-password'].input.addEventListener("focusout", () => {
+ // 비밀번호 변경되었을 때 비밀번호 확인도 같이
+ updateValidation({ currentTarget: validationState['user-password-check'].input }, validationState);
+ });
+ }
+}
diff --git a/modules/validation-rule.mjs b/modules/validation-rule.mjs
new file mode 100644
index 00000000..09c2d23a
--- /dev/null
+++ b/modules/validation-rule.mjs
@@ -0,0 +1,60 @@
+export const emailInput = document.querySelector("#user-email");
+export const passwordInput = document.querySelector("#user-password");
+export const nameInput = document.querySelector("#user-name");
+export const passwordCheckInput = document.querySelector("#user-password-check");
+
+// 전체 검사 조건
+export const ruleObj = {
+ 'user-email': {
+ input: emailInput,
+ isValid() {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.input.value);
+ },
+ getErrorMessage() {
+ return this.input.value ? "잘못된 이메일입니다." : "이메일을 입력해주세요"
+ },
+ },
+ 'user-password': {
+ input: passwordInput,
+ isValid() {
+ return this.input.value.length >= 8;
+ },
+ getErrorMessage() {
+ return (this.input.value.length == 0) ? "비밀번호를 입력해주세요." : "비밀번호를 8자 이상 입력해주세요."
+ },
+ },
+ 'user-name': {
+ input: nameInput,
+ isValid() {
+ return this.input.value;
+ },
+ getErrorMessage() {
+ return "닉네임을 입력해주세요";
+ },
+ },
+ 'user-password-check': {
+ input: passwordCheckInput,
+ isValid() {
+ return (passwordInput.value === this.input.value) && passwordCheckInput.value.length != 0;
+ },
+ getErrorMessage() {
+ return "비밀번호가 일치하지 않습니다."
+ },
+ }
+}
+
+// 해당 페이지에 있는 인풋만 규칙 만들기
+export function createValidRule(inputElement, ruleObj) {
+ const validRule = {}
+
+ Object.values(inputElement).forEach((input) => {
+ validRule[input.name] = {
+ input,
+ condition: ruleObj[input.name].isValid,
+ createMsg: ruleObj[input.name].getErrorMessage,
+ passed: false,
+ }
+ });
+
+ return validRule;
+}
\ No newline at end of file
diff --git a/modules/validation-ui.mjs b/modules/validation-ui.mjs
new file mode 100644
index 00000000..eeafb724
--- /dev/null
+++ b/modules/validation-ui.mjs
@@ -0,0 +1,26 @@
+const submitBtn = document.querySelector("button");
+
+//존재하는 실패 메세지 삭제
+export function removeMessage(name) {
+ const msg = document.querySelector(`#${name}+.error-message`);
+ if (msg) msg.remove();
+}
+
+// 검사 실패 메세지 추가
+export function appendErr(validator) {
+ validator.input.classList.add("error-Line");
+ const err = document.createElement("span");
+ err.classList.add("error-message");
+ err.textContent = validator.createMsg();
+ validator.input.insertAdjacentElement("afterend", err);
+}
+
+// 검사 실패 스타일 삭제
+export function clearErr(validator) {
+ validator.input.classList.remove("error-Line");
+}
+
+// 버튼 스타일 추가
+export function updateButton(validationState, hasInvalidInput) {
+ submitBtn.classList.toggle("pass-button", !hasInvalidInput(validationState));
+}
diff --git a/sign_up/index.html b/sign_up/index.html
index bb5e0bdc..a3029de9 100644
--- a/sign_up/index.html
+++ b/sign_up/index.html
@@ -7,6 +7,7 @@
+
@@ -16,20 +17,24 @@
-