Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
32829f0
chore: commit 생성
Oct 29, 2024
25ea232
chore : Project 디렉토리 삭제 및 템플릿 파일 복구
Oct 30, 2024
f81abbc
fix : 오타 수정 및 img 요소 alt 속성 명시
Nov 2, 2024
4082fd9
refactor : 시맨틱 태그 수정, CSS 업데이트는 후속 작업으로 예정
Nov 2, 2024
34cfdb9
chore : h1 태그 미사용으로 분리
Nov 2, 2024
24f6681
refactor : CSS 업데이트
Nov 2, 2024
cd395f2
docs : README.md 양식 변경
Nov 2, 2024
f952bee
feat : 로그인 페이지 HTML 작성
Nov 2, 2024
1ea6eff
feat : 로그인 페이지 CSS 생성
Nov 2, 2024
e4e9f18
chore : 로그인 버튼 색상 변경 및 hover, focus 추가
Nov 2, 2024
9f4ac74
feat : 회원가입 페이지 생성 및 CSS 업데이트
Nov 2, 2024
73b1971
chore : 이미지 수정
Nov 2, 2024
a109c25
chore : 메인 페이지 header 고정 및 오타 수정
Nov 2, 2024
9414283
chore : gray 및 blue 색상 변수 등록 및 적용
Nov 2, 2024
0605656
feat : Media Query 적용 완료
Nov 2, 2024
4f95a6b
Chore : Merge branch 'Basic-김도균' of https://github.com/codeit-bootcam…
Nov 13, 2024
aa0787a
chore : 유효성 검사 및 이벤트 등록 포맷 작성
Nov 13, 2024
67c79ae
chore : 버튼 초기 비활성화
Nov 13, 2024
f54bb88
feat : 로그인 페이지 및 회원가입 페이지 input 필드 유효성 검사 추가
Nov 13, 2024
1b62f16
feat : form 유효성 검사 통과 후 submit 버튼 활성화 기능 추가
Nov 13, 2024
e80928a
feat : submit전 비밀번호 관련 input 필드 타입 password로 변경 기능 추가
Nov 14, 2024
fa0b680
feat : 비밀번호 토글 기능 추가
Nov 14, 2024
51d742c
chore : script 로딩 방식 수정
Nov 14, 2024
b81d1f0
refactor : return문 간소화
Nov 14, 2024
eb0d91a
chore : css vw단위 %로 수정
Nov 14, 2024
6d036b6
refactor : 에러 메시지 리터럴 상수화 모듈로 별도 관리
Nov 15, 2024
7a3e357
refactor : 유효성 검사의 통과 return 타입 true로 수정 및 removeError함수 리팩토링
Nov 15, 2024
5678b4e
fix : 비밀번호 확인란 먼저 작성 시 오류 메시지 추가 로직 버그 수정
Nov 15, 2024
180ac95
docs : 4주차 Troubleshooting 내용 추가
Nov 15, 2024
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
47 changes: 44 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
# Troubleshooting
# Troubleshooting

# Tools
## 4주차 스프린트

### 이벤트를 input 요소 하나마다 다 등록해야 하는가?

- 처음에는 `email`, `nickname`, `password`, `password confirm`란에 `focusout` 이벤트가 발생하니깐 개별 등록할 생각이었다.
- 전부 등록하는 방식이 비효율적이라고 생각해서 어떻게 할지 고민하다가 이벤트 위임 방식을 채택하였다.
- 현재까지 로그인 및 회원가입 페이지의 이벤트는 `form`요소 안에서만 동작하기 때문에, `form` 요소에 이벤트를 위임하고, 자식 요소의 `target`에 대해 이벤트 핸들러를 동작시켰다.

### 비밀번호 확인을 먼저 작성한 경우 어떻게 되는가?

- 사용자가 회원가입할 때 가입할 비밀번호를 비밀번호 확인 `input field`에 먼저 입력했을 때 동작부분에서 오류가 있었다.
- 먼저 비밀번호 확인란에 사용자가 원하는 비밀번호를 입력한 후, 비밀번호란에 일치하지 않는 비밀번호를 입력했을 경우 정상적으로 에러 요소가 추가되지 않았다.
- 이 부분에 대해서 비밀번호 `field`가 `focusout`될 때 비밀번호를 비교하는 함수를 호출하여 다를 경우 비밀번호 확인란에 에러 요소가 추가되도록 로직을 구성하였다.

### 에러 메시지를 문자열 리터럴로 줘야 하는가?

- 처음에는 에러 메시지에 대해 문자열 리터럴로 직접 타이핑하여 `return`하였지만, 생각해보니 단점이 있을 수 밖에 없다는 생각이 들었다.
- 만약, 에러 메시지를 수정하고 싶을 경우에는 자바스크립트 코드를 다시 찾아서 리터럴을 직접 수정해야 하는데, `account.js` 파일의 로직이 길어질 경우 에러 메시지를 찾기 힘들 수도 있고, 수정할 때 실수도 유발할 것 같았다.
- 때문에 상수로 `enum`처럼 관리하는게 좋겠다고 생각하여 `ACCOUT_CONST.js` 모듈에 상수 변수를 `export`하는 방식으로 수정하였다.
- 이메일의 정규표현식도 상수 모듈로 관리하였다.

### `input field`가 전부 유효성 검사를 통과했다는 것을 어떻게 표현할 것인가?

- `form`에서 `focusout`이벤트 발생 시 유효성 검사에 관한 함수를 관리하는 객체를 순회하면서 `input field`를 전부 검사하는 방식으로 로직을 구현했다.
- 당장 아이디어로 떠오른 것이 해당 방식이지만, 상태를 관리하는 객체를 활용하면 좋겠다고 생각한다.

### 스크립트 파일을 불러오는 방식을 어떤걸 채택할 것인가?

- 조사한 바로는, `window.onload`, `DOMContentLoaded`, `defer`, `async` 이 4가지 방법이 대표적인 방식이었다.
- `window.onload`, `DOMContentLoaded` 방식은 이벤트 핸들러로 사용하는 방식으로, DOM 요소가 완전히 로드가 되어야 실행되도록 하는 방식이다.
- 두 방식의 차이는 이미지나 비디오 등 외부 리소스 로딩을 기다리는 방식이다. 첫번째 방식은 외부 리소스 로딩도 기다리기에, 상대적으로 느리지만 안정적이고, 두번째 방식은 외부 리소스가 로드 되기 전에 DOM을 완성하고 싶을 때 사용한다.
- `defer`, `async` 방식은 `script` 태그에 속성을 부여하여, HTML 파싱과 무관하게 비동기로 로드된다.
- `defer` 방식은 `DOMContentLoaded` 이벤트가 발생한 후에 실행된다. 얼핏 보면 비슷해보이지만, `DOMContentLoaded` 방식은 HTML 파싱 완료 후에 바로 실행되기에 스크립트가 로딩 유무와 상관없이 DOM이 생성되면 바로 실행되는 이벤트이기에 이벤트 처리 순서대로 진행하므로 실행순서가 보장되지 않는다. `defer`는 이와 달리 순서를 보장한다.
- `async` 방식은 스크립트 로딩이 완료된 순서대로 실행하기에, 순서가 보장되지 않는다.
- 주로 사용되는 경우를 정리하면 다음과 같다.
- `window.onload` : 모든 리소스가 로드된 후 실행해야 할 작업 (외부 리소스 대기 O)
- `DOMContentLoaded` : DOM이 준비된 후 빠르게 실행해야 할 작업, 순서 보장 X (외부 리소스 대기 X)
- `defer` : HTML 로딩을 방해하지 않으면서 스크립트를 순차적으로 실행 (외부 리소스 대기 X)
- `async` : 페이지의 다른 요소와 상관없이 독립적인 스크립트 실행, 순서 보장 X (외부 리소스 대기 X)
- 4주차 때는 위 방식 중 defer를 채택하였다.

# 💻 Tools

- Text Editor : VS Code
- Formatting : Prettier
- Formatting : Prettier
24 changes: 24 additions & 0 deletions faq.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>판다마켓</title>
<link rel="icon" href="img/favicon.svg" type="image/svg" />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
/>
</head>
<body>
<h1>FAQ 페이지입니다.</h1>
</body>
</html>
24 changes: 24 additions & 0 deletions items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>판다마켓</title>
<link rel="icon" href="img/favicon.svg" type="image/svg" />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
/>
</head>
<body>
<h1>아이템 페이지입니다.</h1>
</body>
</html>
11 changes: 9 additions & 2 deletions login.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
/></a>
</header>
<main>
<form class="login-info">
<form class="login-info auth-form" method="post" action="./items.html">
<label for="email" class="email">이메일</label>
<div class="input-id">
<input
type="email"
name="email"
id="email"
placeholder="이메일을 입력해주세요"
required
aria-required="true"
/>
</div>

Expand All @@ -44,6 +46,8 @@
name="password"
id="password"
placeholder="비밀번호를 입력해주세요"
required
aria-required="true"
/>
<img
src="img/img_closed_eye.png"
Expand All @@ -52,7 +56,9 @@
/>
</div>

<button type="submit" class="login-btn">로그인</button>
<button type="submit" class="login-btn submitButton" disabled>
로그인
</button>
</form>

<nav class="simple-login">
Expand All @@ -74,5 +80,6 @@
<span>판다마켓이 처음이신가요? </span>
<a href="./signup.html">회원가입</a>
</footer>
<script src="./scripts/account.js" type="module"></script>
</body>
</html>
24 changes: 24 additions & 0 deletions privacy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>판다마켓</title>
<link rel="icon" href="img/favicon.svg" type="image/svg" />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
/>
</head>
<body>
<h1>PRIVACY 페이지입니다.</h1>
</body>
</html>
14 changes: 14 additions & 0 deletions scripts/ACCOUNT_CONST.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const ERROR_MESSAGE = {
NOT_INPUT_EMAIL: "이메일을 입력해 주세요",
INVALID_EMAIL: "잘못된 이메일입니다.",
NOT_INPUT_NICKNAME: "닉네임을 입력해 주세요",
NOT_INPUT_PASSWORD: "비밀번호를 입력해 주세요",
MORE_THEN_8DIGIT: "비밀번호를 8자리 이상 입력해 주세요",
NOT_MATCH_PASSWORD: "비밀번호가 일치하지 않습니다.",
};

const VAILD_REGEX = {
EMAIL_REGEX: /^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,
};

export { ERROR_MESSAGE, VAILD_REGEX };
131 changes: 131 additions & 0 deletions scripts/account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { ERROR_MESSAGE, VAILD_REGEX } from "./ACCOUNT_CONST.js";

const authForm = document.querySelector(".auth-form");

const addErrorMessage = (target, errorMessage) => {
const nextNode = target.nextElementSibling;

if (!errorMessage) return;

target.classList.add("error-input-border");
if (!nextNode || nextNode.classList.contains("toggle-password")) {
const errorElement = document.createElement("span");
errorElement.textContent = errorMessage;
errorElement.classList.add("error-message");
target.after(errorElement);
return;
}
if (nextNode.textContent !== errorMessage) {
nextNode.textContent = errorMessage;
}
};

const removeErrorMessage = (target) => {
const nextNode = target.nextElementSibling;
if (nextNode && nextNode.classList.contains("error-message"))
nextNode.remove();
target.classList.remove("error-input-border");
};

const comparePassword = (target) => {
const prevPassword = document.querySelector("#password");
console.log(prevPassword.value === target.value);
return prevPassword.value === target.value;
};

const changePasswordType = () => {
document.querySelectorAll(".password-field").forEach((element) => {
element.type = "password";
});
};

const activatedButton = (validCheckResults) => {
const submitButton = document.querySelector(".submitButton");
validCheckResults.every((value) => value === true)
? (submitButton.disabled = false)
: (submitButton.disabled = true);
};

const validCheckForm = () => {
return Object.entries(validCheckInput).map(
([validInputName, validCheckFunc]) => {
const inputElement = document.querySelector(`#${validInputName}`);
if (inputElement === null) return true;
return validCheckFunc(inputElement);
}
);
};

const togglePassword = (target) => {
target.classList.toggle("visible");
if (target.classList.contains("visible")) {
target.src = "./img/img_opend_eye.png";
target.alt = "opend eye";
target.parentNode.children[0].type = "text";
} else {
target.src = "./img/img_closed_eye.png";
target.alt = "closed eye";
target.parentNode.children[0].type = "password";
}
};

const validCheckInput = {
email: (target) => {
const emailRegex = VAILD_REGEX.EMAIL_REGEX;
if (target.value.length === 0) return ERROR_MESSAGE.NOT_INPUT_EMAIL;
if (!emailRegex.test(target.value)) return ERROR_MESSAGE.INVALID_EMAIL;
removeErrorMessage(target);
return true;
},
nickname: (target) => {
if (target.value.length === 0) return ERROR_MESSAGE.NOT_INPUT_NICKNAME;
removeErrorMessage(target);
return true;
},
password: (target) => {
if (target.value.length === 0) return ERROR_MESSAGE.NOT_INPUT_PASSWORD;
if (target.value.length < 8) return ERROR_MESSAGE.MORE_THEN_8DIGIT;
//비밀번호 확인을 기준으로 비밀번호 란을 채울 경우 고려
const passwordConfirm = document.querySelector("#password-confirm");
if (passwordConfirm)
addErrorMessage(
passwordConfirm,
validCheckInput[passwordConfirm.name](passwordConfirm)
);
removeErrorMessage(target);
return true;
},
"password-confirm": (target) => {
if (target.value.length === 0) return ERROR_MESSAGE.NOT_INPUT_PASSWORD;
if (target.value.length < 8) return ERROR_MESSAGE.MORE_THEN_8DIGIT;
if (!comparePassword(target)) return ERROR_MESSAGE.NOT_MATCH_PASSWORD;
removeErrorMessage(target);
return true;
},
};

const focusoutHandler = ({ target }) => {
if (validCheckInput[target.name]) {
addErrorMessage(target, validCheckInput[target.name](target));
const result = validCheckForm();
activatedButton(result);
}
};

const clickHandler = ({ target }) => {
if (target.classList.contains("toggle-password")) togglePassword(target);
};

authForm.addEventListener("focusout", focusoutHandler);
authForm.addEventListener("click", clickHandler);
authForm.addEventListener(
"submit",
(event) => {
event.preventDefault();
changePasswordType();
authForm.removeEventListener("focusout", focusoutHandler);
authForm.removeEventListener("click", clickHandler);
authForm.submit();
},
{ once: true }
);
25 changes: 23 additions & 2 deletions signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@
/></a>
</header>
<main>
<form class="login-info">
<form
class="login-info auth-form signup-info"
method="post"
action="./signup.html"
>
<label for="email" class="email">이메일</label>
<div class="input-id">
<input
type="email"
name="email"
id="email"
placeholder="이메일을 입력해주세요"
required
aria-required="true"
/>
</div>
<label for="nickname" class="nickname">닉네임</label>
Expand All @@ -41,6 +47,8 @@
name="nickname"
id="nickname"
placeholder="닉네임을 입력해주세요"
required
aria-required="true"
/>
</div>

Expand All @@ -51,6 +59,9 @@
name="password"
id="password"
placeholder="비밀번호를 입력해주세요"
class="password-field"
required
aria-required="true"
/>
<img
src="img/img_closed_eye.png"
Expand All @@ -65,6 +76,9 @@
name="password-confirm"
id="password-confirm"
placeholder="비밀번호를 다시 한 번 입력해주세요"
class="password-field"
required
aria-required="true"
/>
<img
src="img/img_closed_eye.png"
Expand All @@ -73,7 +87,13 @@
/>
</div>

<button type="submit" class="login-btn register-btn">회원가입</button>
<button
type="submit"
class="login-btn register-btn submitButton"
disabled
>
회원가입
</button>
</form>

<nav class="simple-login">
Expand All @@ -95,5 +115,6 @@
<span>이미 회원이신가요? </span>
<a href="./login.html">로그인</a>
</footer>
<script src="./scripts/account.js" type="module"></script>
</body>
</html>
Loading