Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions page/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h1 class="title">
autocomplete="email"
placeholder="이메일을 입력해주세요"
/>

<span class="warning-text"></span>
<label class="label" for="pw">비밀번호</label>
<div class="pw-wrap">
<input
Expand All @@ -40,11 +40,11 @@ <h1 class="title">
<span
class="eye-icon"
aria-label="비밀번호 표시"
,aria-pressed="false"
aria-pressed="false"
></span>
</div>

<button class="form-btn" type="submit">로그인</button>
<span class="warning-text"></span>
<button class="form-btn" type="submit" disabled>로그인</button>
</form>
<div class="easy-login-wrap">
<p class="login-info">간편 로그인하기</p>
Expand All @@ -63,8 +63,9 @@ <h1 class="title">
</div>
<div class="signup-wrap">
<span>판다마켓이 처음이신가요?</span>
<span><a class="goto-signup" href="./signup.html">회원가입</a></span>
<span><a class="goto-page" href="./signup.html">회원가입</a></span>
</div>
</section>
<script src="../script/login.js" type="module" defer></script>
</body>
</html>
21 changes: 14 additions & 7 deletions page/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ <h1 class="title">
autocomplete="email"
placeholder="이메일을 입력해주세요"
/>

<span class="warning-text"></span>
<label class="label" for="nickname">닉네임</label>
<input
type="text"
Expand All @@ -35,7 +35,7 @@ <h1 class="title">
class="input"
placeholder="닉네임을 입력해주세요"
/>

<span class="warning-text"></span>
<label class="label" for="pw">비밀번호</label>
<div class="pw-wrap">
<input
Expand All @@ -47,11 +47,12 @@ <h1 class="title">
placeholder="비밀번호를 입력해주세요"
/>
<span
class="eye-icon"
class="eye-icon pw"
aria-label="비밀번호 표시"
,aria-pressed="false"
aria-pressed="false"
></span>
</div>
<span class="warning-text"></span>
<label class="label" for="pw-chk">비밀번호 확인</label>
<div class="pw-wrap">
<input
Expand All @@ -62,10 +63,15 @@ <h1 class="title">
class="input"
placeholder="비밀번호를 다시 한 번 입력해주세요"
/>
<span class="eye-icon"></span>
<span
class="eye-icon pw-chk"
aria-label="비밀번호 표시"
aria-pressed="false"
></span>
</div>
<span class="warning-text"></span>

<button class="form-btn" type="submit">회원가입</button>
<button class="form-btn" type="submit" disabled>회원가입</button>
</form>
<div class="easy-login-wrap">
<p class="login-info">간편 로그인하기</p>
Expand All @@ -84,8 +90,9 @@ <h1 class="title">
</div>
<div class="signup-wrap">
<span>이미 회원이신가요?</span>
<span><a class="goto-signup" href="./login.html">로그인</a></span>
<span><a class="goto-page" href="./login.html">로그인</a></span>
</div>
</section>
<script src="../script/signUp.js" type="module" defer></script>
</body>
</html>
45 changes: 45 additions & 0 deletions script/login.js
Original file line number Diff line number Diff line change
@@ -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]);
});
Comment on lines +24 to +31
Copy link
Collaborator

@addiescode-sj addiescode-sj May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 각 입력 필드마다 별도의 이벤트 리스너를 설정하고 있는데, 하나의 이벤트 핸들러로 통합하고 이벤트 위임(Event Delegation) 패턴을 사용하면 코드를 더 효율적으로 만들 수 있을것같아요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

먼저 폼 설정과 관련된 객체를 만들고, 초기화를 처리해서 관련있는 코드끼리 응집도를 높일수있게 처리해볼까요?

// 폼 설정 객체
const formConfig = {
  email: {
    selector: '#email',
    warningIndex: 0,
    validationType: 'email',
    eventType: 'blur'
  },
  pw: {
    selector: '#pw',
    warningIndex: 1,
    validationType: 'pw',
    eventType: 'input'
  }
};

// 폼 요소 초기화
const formElements = {
  form: document.querySelector('form'),
  warnings: document.querySelectorAll('.warning-text'),
  eyeIcon: document.querySelector('.eye-icon'),
  loginBtn: document.querySelector('.form-btn'),
  fields: {}
};

// 폼 필드 초기화
Object.entries(formConfig).forEach(([key, config]) => {
  formElements.fields[key] = document.querySelector(config.selector);
});

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고, 아래와 같이 하나의 이벤트 핸들러에서 위임 방식으로 이벤트를 처리해보면:

// 이벤트 리스너 설정
Object.entries(formConfig).forEach(([fieldName, config]) => {
  const inputEl = formElements.fields[fieldName];
  const warningEl = formElements.warnings[config.warningIndex];
  
  inputEl.addEventListener(config.eventType, () => {
    handleValidation(fieldName, inputEl, warningEl, config.validationType);
  });
});

개별 입력 필드마다 별도의 이벤트 리스너를 설정하지않아도되고,
이런 장점들이 생길 수 있어요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개선으로 생길 수 있는 장점들

  • 확장성: 새로운 폼 필드를 추가할 때 formConfig에 설정만 추가하면돼서, 확장에 용이해요.
  • 각 필드의 설정이 명확하게 구조화되어 있어 유지보수에 용이해요.
  • 이벤트 리스너 설정을 하나로 통합해두면, 이후 수정이 필요할 때 한 곳에서만 변경하면 되니까 추적도 쉬워지고 수정에도 용이해요.
  • 각 입력 필드의 설정이 명확하게 분리되어 있어 디버깅이 쉬워져요.
  • 코드 중복이 줄어들 수 있어요.


// 비밀번호 보기 토글
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";
}
});
62 changes: 62 additions & 0 deletions script/signUp.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 파일에서도 위 login.js에서 드렸던 피드백 참고해보시고, 리팩토링 진행해볼까요? :)

Original file line number Diff line number Diff line change
@@ -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";
}
});
92 changes: 92 additions & 0 deletions script/utils.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 함수가 적절한 수준으로 모듈화되어있네요. 굳굳! 좋습니다 :)

Original file line number Diff line number Diff line change
@@ -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;
};
29 changes: 23 additions & 6 deletions style/auth.css
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -37,7 +49,6 @@
height: 56px;
background-color: var(--gray100);
border-radius: 12px;
margin-bottom: 24px;
padding-left: 24px;
color: var(--gray800);
width: 100%;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Loading