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
8 changes: 7 additions & 1 deletion .gitignore
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
Expand Up @@ -8,4 +8,10 @@ node_modules/
.env

# macOS의 경우 생성되는 파일들
.DS_Store
.DS_Store

# vscode 설정 파일
.vscode/

# mission_template_codes 공부용
mission_template_codes/
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

51 changes: 51 additions & 0 deletions README.md
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,51 @@
<!-- 추후 추가 수정 예정 -->

# 코드잇 스프린트 미션

## 미션 목록

| 미션 | 날짜 | PR | 주요 내용 |
| ---- | ---------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| 1 | 2025-02-24 | [#10](https://github.com/codeit-bootcamp-frontend/15-Sprint-Mission/pull/10) | 랜딩 페이지의 HTML 및 CSS 구현 |
| 2 | 2025-03-05 | [#44](https://github.com/codeit-bootcamp-frontend/15-Sprint-Mission/pull/44) | 회원가입 및 로그인 페이지의 HTML, CSS 구현 |
| 3 | 2025-03-07 | [#60](https://github.com/codeit-bootcamp-frontend/15-Sprint-Mission/pull/60) | 반응형 디자인 구현(desktop-first, 1920px 이상 큰 모니터 기준), breakpoint: 1919px, 1199px, 767px |
| 4 | 2025-03-18 | [#](https://github.com/codeit-bootcamp-frontend/15-Sprint-Mission/pull/) | JS기능 추가(DOM 요소 조작 및 이벤트 리스너), 회원가입, 로그인 폼 유효성 검사 |

---

## 컨벤션

### 명명 규칙

1. **이미지 파일**:

- 이미지 파일 이름은 **소문자**로 작성하고, **언더스코어(\_)**를 사용하여 단어를 구분합니다.

2. **HTML, CSS, JS 파일, 폴더명**:

- 파일, 폴더 이름은 **소문자**로 작성하고, **하이픈(-)**을 사용하여 단어를 구분합니다.(kebab-case)

3. **변수명, 함수명, 프로퍼티 키**:
- camelCase

### 함수 규칙

**화살표 함수**를 사용하되, this바인딩 고려 시 필요한 경우(이벤트 리스너의 콜백함수, 메소드 정의 등) 일반 함수도 사용 가능 합니다.

### 커밋 규칙

1. 커밋 메시지는 소문자로 작성합니다.
2. 커밋 메시지 본문 작성은 선택사항입니다.
3. 타입: 내용
| **타입** | **내용** |
|------ ---|-----------|
| **feat** | 새로운 기능 추가 |
| **fix** | 버그 수정 |
| **docs** | 문서 변경 (README, Wiki 등) |
| **style** | 코드 스타일 변경 (세미콜론, 공백, 들여쓰기 등) |
| **refactor** | 코드 리팩토링 (기능 변경 없이 코드 구조나 가독성 개선) |
| **perf** | 성능 개선 |
| **test** | 테스트 코드 추가 및 수정 |
| **chore** | 기타 일들 (빌드 스크립트, 환경 설정 등) |

## 폴더 구조
151 changes: 151 additions & 0 deletions auth/auth-validation.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,151 @@
import { ERROR_MESSAGES } from '../constants/auth-validation-messages.js';

document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('.auth-form');
const authSubmitButton = document.querySelector('.auth-button');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const nicknameInput = document.getElementById('nickname');
const confirmPasswordInput = document.getElementById('password-confirm');
const authType = form.dataset.authType;

let emailValue;
let nicknameValue;
let passwordValue;
let confirmPasswordValue;

// 이메일 형식 검증
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(String(email));
};
Comment on lines +18 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
에러메시지를 분리하신 것처럼 이런 정규식과 검증하는 로직을 따로 분리하셔도 될 것 같아요~
일반적으로 정규식들은 여러 페이지에서 사용되니까요!


// 에러 메시지 표시
const toggleError = (targetInput, message, isInputValid) => {
const inputContainer = targetInput.closest('.input-container');
if (!inputContainer) return;
const errorContainer = inputContainer.querySelector(
'.validation-error-message',
);
Comment on lines +25 to +29
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
inputContainer 변수가 한번만 사용되고 있어 변수로 저장할 필요성이 낮아보여요~
아래처럼 작성하시면 더 가독성측면에서 좋을 것 같아요~
변수는 최소 2번 이상 사용하거나 변수로 선언했을 때 가독성에서 유리할 것 같은 경우만 선언하시는 것을 추천드려요!

Suggested change
const inputContainer = targetInput.closest('.input-container');
if (!inputContainer) return;
const errorContainer = inputContainer.querySelector(
'.validation-error-message',
);
const errorContainer = targetInput.closest('.input-container')?.querySelector('.validation-error-message');
if (!errorContainer) return;

if (!isInputValid) {
targetInput.classList.add('error-input');
errorContainer.textContent = message;
errorContainer.classList.add('active');
} else {
targetInput.classList.remove('error-input');
errorContainer.textContent = '';
errorContainer.classList.remove('active');
}
};

// 개별 인풋 검증: 포커스아웃된 input만 검증하고 에러 메시지 표시
const validateInput = (target) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
하나의 함수가 너무 많은 역할을 하고 있어요~
모든 input을 하나의 함수로 검증하기보다 input들을 나눠서 작성해주시면 더 깔끔할 것 같아요!

if (target.id === 'email') {
emailValue = emailInput.value.trim();

if (emailValue === '') {
toggleError(target, ERROR_MESSAGES.emailRequired, false);
} else if (!validateEmail(emailValue)) {
toggleError(target, ERROR_MESSAGES.invalidEmail, false);
} else {
toggleError(target, '', true);
}
}

if (target.id === 'nickname' && authType === 'signup') {
nicknameValue = nicknameInput.value.trim();
if (nicknameValue === '') {
toggleError(target, ERROR_MESSAGES.nicknameRequired, false);
} else {
toggleError(target, '', true);
}
}

if (target.id === 'password') {
passwordValue = passwordInput.value.trim();
if (passwordValue === '') {
toggleError(target, ERROR_MESSAGES.passwordRequired, false);
} else if (passwordValue.length < 8) {
toggleError(target, ERROR_MESSAGES.passwordLength, false);
} else {
toggleError(target, '', true);
}
}

if (target.id === 'password-confirm' && authType === 'signup') {
confirmPasswordValue = confirmPasswordInput.value.trim();
if (confirmPasswordValue === '') {
toggleError(target, ERROR_MESSAGES.confirmPasswordRequired, false);
} else if (passwordValue !== confirmPasswordValue) {
toggleError(target, ERROR_MESSAGES.passwordMismatch, false);
} else {
toggleError(target, '', true);
}
}
};

// 전체 폼 유효성 검사
const validateForm = () => {
let isFormValid = true;

emailValue = emailInput.value.trim();
passwordValue = passwordInput.value.trim();
nicknameValue = nicknameInput ? nicknameInput.value.trim() : '';
confirmPasswordValue = confirmPasswordInput
? confirmPasswordInput.value.trim()
: '';

if (emailValue === '' || !validateEmail(emailValue)) isFormValid = false;
if (passwordValue === '' || passwordValue.length < 8) isFormValid = false;
if (authType === 'signup') {
if (nicknameValue === '') isFormValid = false;
if (confirmPasswordValue === '' || passwordValue !== confirmPasswordValue)
isFormValid = false;
}
return isFormValid;
};

// 제출 버튼 활성화 상태
const updateSubmitButtonState = () => {
authSubmitButton.disabled = !validateForm();
};

// 포커스아웃 시 인풋 유효성 검사
form.addEventListener('focusout', function (event) {
if (event.target.matches('input')) {
validateInput(event.target);
}
});
Comment on lines +109 to +118
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
한 파일에서 최상단에 변수 선언들을 위치시키는 것처럼 일정한 순서로 코드가 작성되는 것이 가독성 측면에서 좋습니다~
지금 변수와 이벤트 바인딩문이 번갈아가며 작성되어 있고 그렇게 위치해야하는 이유도 잘 모르겠어요~
가능하면 이벤트 바인딩문은 변수 선언문보다 하단에 위치시키는 것을 추천드립니다!


// input 이벤트에 적용(input이 변할때마다 이벤트가 발생하는 것을 막기 위해 넣었지만 원리는 이해 더 필요)
const debounce = (func, delay) => {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};

// 인풋 입력 시 버튼 상태 업데이트(form이 유효성 검사를 통과하면 focus를 옮기지 않아도 자동 버튼 활성화되도록)
form.addEventListener(
'input',
debounce((event) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
event 인자를 해당 함수내에서 사용하지 않으니 선언하지 않으시는 것이 좋겠습니다~

Suggested change
debounce((event) => {
debounce(() => {

updateSubmitButtonState();
}, 300),
);

// 폼 제출 시 전체 폼 검증 후 페이지 이동 처리
form.addEventListener('submit', function (event) {
event.preventDefault();

if (validateForm()) {
if (authType === 'signup') {
Comment on lines +143 to +144
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
아래처럼 작성하시면 if 문의 중첩을 피하실 수 있어요~

Suggested change
if (validateForm()) {
if (authType === 'signup') {
if (!validateForm()) return;
if (authType === 'signup') {

window.location.href = '/signup';
} else if (authType === 'login') {
window.location.href = '/items';
}
}
});
});
51 changes: 40 additions & 11 deletions css/login-signup.css → auth/auth.css
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
@@ -1,9 +1,9 @@
.login-signup-body {
.auth-body {
display: flex;
justify-content: center;
}

.login-signup-container {
.auth-container {
display: flex;
flex-direction: column;
width: 64rem;
Expand All @@ -12,26 +12,29 @@
gap: 2.4rem;
}

/* login-signup header */
.login-signup-header {
/* auth header */
.auth-header {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 4rem;
width: 100%;
}

.login-signup-header .logo {
.auth-header .logo {
width: 10rem;
}

.login-signup-header h1 a {
.auth-header .logo-typo {
width: 30rem;
}

.auth-header h1 a {
font-size: 6rem;
margin-left: 2rem;
}

/* login-signup form */
.login-signup-form {
/* auth form */
.auth-form {
display: flex;
flex-direction: column;
gap: 2.4rem;
Expand All @@ -56,7 +59,7 @@ input {
width: 100%;
}

.login-signup-button {
.auth-button {
font-size: 2rem;
font-weight: 600;
line-height: 3.2rem;
Expand Down Expand Up @@ -127,10 +130,36 @@ input {
text-underline-offset: 2px;
}

.validation-error-message {
color: var(--error);
font-size: 1.4rem;
font-weight: 600;
line-height: 2.4rem;
display: none;
}

.validation-error-message.active {
display: block;
padding: 0.8rem 1.6rem;
}

.error-input {
border: 1px solid var(--error);
}

/* responsive */
@media (max-width: 767px) {
.login-signup-container {
.auth-container {
margin: 0 16px;
max-width: 400px;
width: 100%;
}

.auth-header .logo {
width: 9rem;
}

.auth-header .logo-typo {
width: 25rem;
}
}
Loading
Loading