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 .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"liveServer.settings.port": 5501
"liveServer.settings.port": 5501,
"github.copilot.enable": {
"*": false,
"plaintext": false,
"markdown": false,
"scminput": false
}
}
37 changes: 30 additions & 7 deletions src/css/auth.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,36 @@ body {
font-style: normal;
font-weight: 400;
line-height: 26px;
width: 100%;
}

.auth__input.error {
border: 1px solid red;
}

.auth__error-message {
font-size: 14px;
color: red;
}

.auth__input.success {
border: 1px solid var(--blue);
}

.auth__input:focus {
.hidden {
display: none;
}

/* .auth__input:focus {
border: 2px solid #3692ff;
outline: none;
}
} */

.auth__visible {
width: 24px;
height: 24px;
position: absolute;
right: 24px;
top: 71%;
transform: translateY(-50%);
right: 12px;
cursor: pointer;
top: 58px;
}

.auth__button {
Expand All @@ -88,6 +103,10 @@ body {
cursor: pointer;
}

.auth__button:disabled {
background: var(--gray-400);
}

.auth__social {
margin-top: 24px;
width: 100%;
Expand Down Expand Up @@ -155,4 +174,8 @@ body {
padding: 0 16px;
max-width: 400px;
}

.auth__label {
font-size: 14px;
}
}
25 changes: 18 additions & 7 deletions src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,11 @@
.banner__container--bottom {
gap: 0;
}

.highlight:nth-child(3) .highlight__container {
flex-direction: column;
align-items: flex-end;
}
}

/* 태블릿 */
Expand All @@ -337,9 +342,12 @@
padding: 24px;
}

.highlight:nth-child(3) .highlight__container {
flex-direction: column;
align-items: flex-end;
.highlight__title {
font-size: 32px;
}

.highlight__subtitle {
font-size: 18px;
}

.feature-content {
Expand Down Expand Up @@ -370,17 +378,20 @@
padding: 52px 15px;
}

.highlight:nth-child(3) .highlight__container {
flex-direction: column;
align-items: flex-end;
.highlight__title {
font-size: 24px;
}

.highlight__subtitle {
font-size: 18px;
}

.br-highlight {
display: none;
}

.footer {
padding: 32px 100px;
padding: 32px;
}

.footer__section {
Expand Down
11 changes: 11 additions & 0 deletions src/html/items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>items</title>
</head>
<body>
items
</body>
</html>
7 changes: 6 additions & 1 deletion src/html/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<link rel="stylesheet" href="../css/global.css" />
<link rel="stylesheet" href="../css/auth.css" />
<link rel="stylesheet" href="../css/palette.css" />
<script type="module" src="../js/login.js" defer></script>
<script type="module" src="../js/toggle.js" defer></script>
</head>
<body>
<div class="auth auth--login">
Expand All @@ -30,6 +32,7 @@
placeholder="이메일을 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
</div>

<div class="auth__field">
Expand All @@ -39,7 +42,9 @@
id="password"
class="auth__input"
placeholder="비밀번호를 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
<button
type="button"
class="auth__visible"
Expand All @@ -52,7 +57,7 @@
</button>
</div>

<button type="submit" class="auth__button">로그인</button>
<button type="submit" class="auth__button" disabled>로그인</button>
</form>

<div class="auth__social">
Expand Down
10 changes: 8 additions & 2 deletions src/html/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<link rel="stylesheet" href="../css/global.css" />
<link rel="stylesheet" href="../css/auth.css" />
<link rel="stylesheet" href="../css/palette.css" />
<script type="module" src="../js/signup.js" defer></script>
<script type="module" src="../js/toggle.js" defer></script>
</head>
<body>
<div class="auth auth--login">
Expand All @@ -30,6 +32,7 @@
placeholder="이메일을 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
</div>

<div class="auth__field">
Expand All @@ -42,6 +45,7 @@
placeholder="닉네임을 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
</div>

<div class="auth__field">
Expand All @@ -54,6 +58,7 @@
placeholder="비밀번호를 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
<button
type="button"
class="auth__visible"
Expand All @@ -76,19 +81,20 @@
placeholder="비밀번호를 다시 입력해주세요"
required
/>
<p class="auth__error-message hidden">에러 메시지</p>
<button
type="button"
class="auth__visible"
aria-label="비밀번호 보기"
>
<img
src="../assets/images/visibility_on.svg"
src="../assets/images/visibility_off.svg"
alt="비밀번호 보이기"
/>
</button>
</div>

<button type="submit" class="auth__button">회원가입</button>
<button type="submit" class="auth__button" disabled>회원가입</button>
</form>

<div class="auth__social">
Expand Down
67 changes: 67 additions & 0 deletions src/js/formvalidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
Copy link
Collaborator

Choose a reason for hiding this comment

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

정규식을 쓰셨군요! 👍


/* 에러 메시지 */
export function showError(input, error, message) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

기능별로 함수를 잘 나눠 주셨네요! 👍

input.classList.remove('success');
input.classList.add('error');
error.textContent = message;
error.classList.remove('hidden');
}

/* 성공 메시지 */
export function showSuccess(input, error) {
input.classList.add('success');
input.classList.remove('error');
error.classList.add('hidden');
}

/* 이메일 유효성 검사 */
export function validationEmail(input, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

로그인/회원가입에서 공통되는 유효성 검사를 formvalidation.js로 분리하고, 비밀번호 토글 기능도 toggle.js로 따로 나눴습니다! 그런데 validation 함수뿐 아니라 버튼 상태 업데이트 함수나 페이지 이동 함수까지 같이 넣은 게 괜찮은 설계인지 궁금합니다. 혹시 지금 구조보다 더 나은 방식이 있다면 어떻게 나눌 수 있을지도 조언해주시면 감사하겠습니다!
-> 물론 지금 보다 더 나뉠 여지는 충분이 있습니다만, 지금처럼 하나의 모듈로 묶는 것도 규모가 크지 않다면 충분히 합리적인 선택입니다. 정답이 있지는 않아요 🤣 지금 처럼 쓰다가 규모가 커지면 분리해도 괜찮습니다. 다만 여러 기능이 모인 파일인 만큼 formvalidation.js보다는 조금 더 범용적인 느낌의 이름이 좋겠죠 :)

const value = input.value.trim();

if (value === '') {
showError(input, error, '이메일을 입력해주세요');
} else if (!emailRegex.test(value)) {
showError(input, error, '잘못된 이메일 형식입니다');
} else {
showSuccess(input, error);
}
}

/* 비밀번호 유효성 검사 */
export function validationPassword(input, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

또 구현하면서 이메일, 닉네임, 비밀번호 등 유효성 검사 함수 내부 로직이 거의 비슷해서, 조건과 메시지만 다르게 공통 함수로 추출하는 게 더 나은 구조일까 고민이 들었습니다. 각각의 역할이 명확한 게 유지보수나 SOLID 원칙에 더 부합할지, 아니면 중복을 줄이기 위해 하나로 합치는 게 더 실무적으로 좋은 선택일지 궁금합니다!
->
음 제가 정확하게 잘 이해를 못했는데, 유효성 검사를 하나의 함수로 합친다는 말씀이 맞을까요? 만약 그렇다면 공통화는 가능하긴 하지만, 함수가 너무 커질 거 같아요 (해당 부분은 생각하시는 코드를 예시로 보여주시면 더 이야기 할 수 있을 거 같습니다)🤔

오히려 좀 더 나누는 것은 가능할 거 같아요. 지금의 경우 함수가 유효성 검사도 하고 에러 핸들링도 다루고 있습니다. 그런데 만약 이메일 찾기 같은 페이지가 추가되고, 이메일 찾기 페이지에서 이메일 입력 에러 발생시 로그인 페이지와 다른 UI를 보여줘야 한다면 이 함수는 수정이 필요하겠죠..! 🤣

하지만 늘 정답은 없습니다..! 지금 상황에서는 충분히 잘 나누셨다고 생각해요. 만약 이 프로젝트가 그냥 여기서 끝난다면? 혹은 시간이 너무 부족하다면? 어느정도 단순하고 빠르게 개발하고 나중에 필요할 때 리팩토링 하는 것도 방법이 되겠죠 :)

const value = input.value.trim();

if (value === '') {
showError(input, error, '비밀번호를 입력해주세요');
} else if (value.length < 8) {
showError(input, error, '비밀번호를 8자 이상 입력해주세요');
} else {
showSuccess(input, error);
}
}

/* 모든 입력값이 유효한지 확인 */
export function inputsValid(input) {
for (let i = 0; i < input.length; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

for of, forEach 등을 활용하시는 것이 실수를 줄이고, 가독성에 조금 더 도움이 됩니다 :)

if (!input[i].classList.contains('success')) {
return false;
}
}
return true;
}

/* 버튼 활성화 */
export function updateButtonState(input, button) {
button.disabled = !inputsValid(input);
}

/* 버튼 누르면 페이지 이동 */
export function goToPage(input, button, url) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 부분은 공통으로 쓰이는 로직은 아닐 거 같아요~ 페이지 이동이기보다는 로그인, 회원 가입 등의 요청을 하고 응답을 처리하므로 각각 구현하게 될 거 같습니다 :)

button.addEventListener('click', (e) => {
e.preventDefault();
if (inputsValid(input)) {
window.location.href = url;
}
});
}
33 changes: 33 additions & 0 deletions src/js/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
validationEmail,
validationPassword,
updateButtonState,
goToPage,
} from './formvalidation.js';
const emailInput = document.getElementById('email');
const pwInput = document.getElementById('password');
const emailError = emailInput.nextElementSibling;
const pwError = pwInput.nextElementSibling;
const loginButton = document.querySelector('.auth__button');

const inputs = [emailInput, pwInput];

/* 이메일 검증 */
function validateEmail() {
validationEmail(emailInput, emailError);
updateButtonState(inputs, loginButton);
}

/* 비밀번호 검증 */
function validatePassword() {
validationPassword(pwInput, pwError);
updateButtonState(inputs, loginButton);
}

/* 페이지 이동 */
goToPage(inputs, loginButton, '../html/items.html');

emailInput.addEventListener('blur', validateEmail);
Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 유효성 검사를 각각의 input마다 blur/input 이벤트에 addEventListener로 바인딩하고 있는데, 이 구조가 너무 반복적이고 지저분하다는 느낌이 들어서 더 깔끔하게 작성할 수 있는 방법이 있을지 궁금합니다!
-> 이벤트 버블링을 활용해 보실 수 있을 거 같네요! 🤔

const inputValidations = {
  email: validateEmail,
  password: validatePassword,
};

const $form = document.querySelector("form");
$form.addEventListener("input", (e) => {
  const validation = inputValidations[e.target.id]; 
  validation();
  updateButtonState(inputs, loginButton);
});

emailInput.addEventListener('input', validateEmail);
pwInput.addEventListener('blur', validatePassword);
pwInput.addEventListener('input', validatePassword);
Loading
Loading