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
Binary file added .DS_Store
Binary file not shown.
26 changes: 26 additions & 0 deletions login.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@
border-radius: 12px;
}

.error {
border: 1px solid #F74747;
}

.error-message {
margin-top: 8px;
font-size: 14px;
font-weight: 600;
line-height: 24px;
color: #F74747;
}

.visibility-icon {
width: 24px;
height: 24px;
Expand All @@ -68,6 +80,14 @@
cursor: pointer;
}

.on {
background-image: url("./assets/images/btn_visibility_on_24px.svg");
}

.off {
background-image: url("./assets/images/btn_visibility_off_24px.svg");
}
Comment on lines +83 to +89
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호 background image로 on/off를 만드셨군요 ?


.styled-button {
margin-bottom: 24px;
padding: 12px 0;
Expand All @@ -78,6 +98,12 @@
font-weight: 600;
line-height: 32px;
cursor: pointer;
background-color: var(--blue);
}

.styled-button[disabled]{
background-color: var(--gray-400);
color: var(--gray-100);
}

.sns-container {
Expand Down
12 changes: 8 additions & 4 deletions login.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<link rel="stylesheet" href="login.css" />
<link rel="stylesheet" href="palette.css" />
<link rel="stylesheet" href="reset.css" />
<script type="text/javascript" src="login.js"></script>
<script type="text/javascript" src="password-check.js"></script>
</head>
<body>
<main class="main-container">
Expand All @@ -20,18 +22,20 @@
<form class="form-container">
<div class="input-container">
<label class="text-gray800 label-text">이메일</label>
<input class="text-gray800 gray100 styled-input" placeholder="이메일을 입력해 주세요" type="email"/>
<input class="text-gray800 gray100 styled-input email-input" placeholder="이메일을 입력해 주세요" type="email" name="email" required/>
<p class="error-message" id="emailError"></p>
</div>

<div class="input-container">
<label class="text-gray800 label-text">비밀번호</label>
<div class="password-container">
<input class="text-gray800 gray100 styled-input" placeholder="비밀번호를 입력해 주세요" type="password"/>
<img src="./assets/images/btn_visibility_on_24px.svg" class="visibility-icon">
<input class="text-gray800 gray100 styled-input password-input" placeholder="비밀번호를 입력해 주세요" type="password" name="password" required/>
<div class="visibility-icon off"></div>
</div>
<p class="error-message" id="passwordError"></p>
</div>

<button class="text-gray100 blue styled-button">로그인</button>
<button class="text-gray100 styled-button login-button" disabled>로그인</button>

<div class="sns-container">
<span class="text-gray800 sns-login-message">간편 로그인하기</span>
Expand Down
166 changes: 166 additions & 0 deletions login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
document.addEventListener("DOMContentLoaded", () => {
const emailInput = document.querySelector(".email-input");
const nicknameInput = document.querySelector(".nickname-input");
const passwordInput = document.querySelector(".password-input");
const passwordCheckInput = document.querySelector(".password-check-input");
const signUpButton = document.querySelector(".signup-button");
const loginButton = document.querySelector(".login-button");

const emailError = document.getElementById("emailError");
const passwordError = document.getElementById("passwordError");
const passwordCheckError = document.getElementById("passwordCheckError");
const nicknameError = document.getElementById("nicknameError");

const state = {
email: false,
nickname: false,
password: false,
passwordCheck: false
};

function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
Comment on lines +21 to +24
Copy link
Collaborator

Choose a reason for hiding this comment

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

굿굿 ! 순수함수를 작성하셨군요 !

순수함수는 안정성 있는 함수로 꼽힙니다. 😊

순수 함수는 외부의 상태를 변경하지 않으면서 동일한 인자에 대해 항상 똑같은 값을 리턴하는 함수다.


function validateInput(inputElement) {
const inputType = inputElement.getAttribute("type");
const inputName = inputElement.getAttribute("name");
let isValid = true;

if (inputType === "email") {
const emailValue = emailInput.value.trim();

if (!emailValue) {
emailError.textContent = "이메일을 입력해주세요.";
inputElement.classList.add("error");
isValid = false;
} else if (!isValidEmail(emailValue)) {
emailError.textContent = "잘못된 이메일 형식입니다.";
inputElement.classList.add("error");
isValid = false;
} else {
emailError.textContent = "";
inputElement.classList.remove("error");
}

state.email = isValid;
}
Comment on lines +31 to +48
Copy link
Collaborator

@kiJu2 kiJu2 Mar 17, 2025

Choose a reason for hiding this comment

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

(심화/의견/선택) validation 때문에 조건 분기를 필연적으로 해야되는 상황이군요 !

이러할 경우 validation 함수 자체를 분리해볼 수 있습니다 !

매핑?: 객체 인덱스를 활용하여 분기하는 방법입니다 ! (이어서)

Copy link
Collaborator

Choose a reason for hiding this comment

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

validationRules과 element를 인덱싱하여 선언해볼 수 있어요:

  const formElements = {
    email: document.querySelector(".email-input"),
    nickname: document.querySelector(".nickname-input"),
    password: document.querySelector(".password-input"),
    passwordCheck: document.querySelector(".password-check-input"),
    signUpButton: document.querySelector(".signup-button"),
    loginButton: document.querySelector(".login-button"),
  };

  const errorMessages = {
    email: document.getElementById("emailError"),
    nickname: document.getElementById("nicknameError"),
    password: document.getElementById("passwordError"),
    passwordCheck: document.getElementById("passwordCheckError"),
  };

  const validationRules = {
    email: (value) => {
      if (!value) return "이메일을 입력해주세요.";
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return "잘못된 이메일 형식입니다.";
      return "";
    },
    nickname: (value) => (!value ? "닉네임을 입력해주세요." : ""),
    password: (value) => {
      if (!value) return "비밀번호를 입력해주세요.";
      if (value.length < 8) return "비밀번호를 8자 이상 입력해주세요.";
      return "";
    },
    passwordCheck: (value) => {
      if (!value) return "비밀번호를 입력해주세요.";
      if (value !== formElements.password.value.trim()) return "비밀번호가 일치하지 않습니다.";
      return "";
    },
  };

Copy link
Collaborator

@kiJu2 kiJu2 Mar 17, 2025

Choose a reason for hiding this comment

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

그리고 다음과 같이 validate 함수를 만들어볼 수 있습니다 😊:

  function validateInput(type) {
    const inputElement = formElements[type];
    const errorElement = errorMessages[type];
    const value = inputElement.value.trim();
    const errorMessage = validationRules[type](value);

    if (errorMessage) {
      errorElement.textContent = errorMessage;
      inputElement.classList.add("error");
      state[type] = false;
    } else {
      errorElement.textContent = "";
      inputElement.classList.remove("error");
      state[type] = true;
    }

    updateButtonState();
  }

GPT랑 논의하고 제 의견이 잘 포함되었다고 사료되어 추가한 내용이며 실제로 동작되지 않을 수 있습니다 ! 만약 궁금하신 사항이 있다면 DM 주세요 ㅎㅎㅎ


if (inputType === "text") {
const nicknameValue = nicknameInput.value.trim();

if (!nicknameValue) {
nicknameError.textContent = "닉네임을 입력해주세요.";
inputElement.classList.add("error");
isValid = false;
} else {
nicknameError.textContent = "";
inputElement.classList.remove("error");
}

state.nickname = isValid;
}

if (inputType === "password" && inputName === "password") {
const passwordValue = passwordInput.value.trim();

if (!passwordValue) {
passwordError.textContent = "비밀번호를 입력해주세요.";
inputElement.classList.add("error");
isValid = false;
} else if (passwordValue.length < 8) {
passwordError.textContent = "비밀번호를 8자 이상 입력해주세요.";
inputElement.classList.add("error");
isValid = false;
} else {
passwordError.textContent = "";
inputElement.classList.remove("error");
}

state.password = isValid;
}

if (inputType === "password" && inputName === "passwordDoubleCheck") {
const passwordValue = passwordInput.value.trim();
const passwordCheckValue = passwordCheckInput.value.trim();

if (!passwordCheckValue) {
passwordCheckError.textContent = "비밀번호를 입력해주세요.";
inputElement.classList.add("error");
isValid = false;
} else if (passwordValue !== passwordCheckValue) {
passwordCheckError.textContent = "비밀번호가 일치하지 않습니다.";
inputElement.classList.add("error");
isValid = false;
} else {
passwordCheckError.textContent = "";
inputElement.classList.remove("error");
}

state.passwordCheck = isValid;
}
updateButtonState();
}

function updateButtonState() {
const isLoginValid = state.email && state.password;
const isSignUpValid = state.email && state.nickname && state.password && state.passwordCheck;

if (loginButton) {
if (isLoginValid) {
loginButton.classList.add("active");
loginButton.removeAttribute("disabled");
} else {
loginButton.classList.remove("active");
loginButton.setAttribute("disabled", true);
}
}

if (signUpButton) {
if (isSignUpValid) {
signUpButton.classList.add("active");
signUpButton.removeAttribute("disabled");
} else {
signUpButton.classList.remove("active");
signUpButton.setAttribute("disabled", true);
}
}
}

if (emailInput) {
emailInput.addEventListener("focusout", () => validateInput(emailInput));
}
if (passwordInput) {
passwordInput.addEventListener("focusout", () => validateInput(passwordInput));
}
if (passwordCheckInput) {
passwordCheckInput.addEventListener("focusout", () => validateInput(passwordCheckInput));
}
if (nicknameInput) {
nicknameInput.addEventListener("focusout", () => validateInput(nicknameInput));
}

if (loginButton) {
loginButton.addEventListener("click", function(e) {
e.preventDefault();
const isEmailValid = state.email;
const isPasswordValid = state.password;

if (isEmailValid && isPasswordValid) {
window.location.href = "items.html";
}
});
}

if (signUpButton) {
signUpButton.addEventListener("click", function (e) {
e.preventDefault();
const isFormValid = state.email && state.nickname && state.password && state.passwordCheck;

if (isFormValid) {
window.location.href = "signup.html";
}
});
}
});
48 changes: 48 additions & 0 deletions password-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
document.addEventListener("DOMContentLoaded", () => {
const passwordInput = document.querySelector(".password-input");
const passwordCheckInput = document.querySelector(".password-check-input");
const visiblePasswordIcon = document.querySelector(".visibility-icon");
const visiblePasswordCheckIcon = document.querySelector(".password-check-visibility-icon");

let passwordVisible = true;
let passwordCheckVisible = true;

function togglePasswordType (type) {

if (type === "password") {
if (!passwordVisible) {
visiblePasswordIcon.classList.remove('on');
visiblePasswordIcon.classList.add('off');
passwordInput.setAttribute("type", "password");
} else {
visiblePasswordIcon.classList.remove('off');
visiblePasswordIcon.classList.add('on');
passwordInput.setAttribute("type", "text");
}

passwordVisible = !passwordVisible;
}
Comment on lines +12 to +24
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 변경해볼 수 있어요 😊:

visiblePasswordIcon.addEventListener("click", () => {
  passwordVisible = !passwordVisible;
  passwordInput.type = passwordVisible ? "text" : "password";

  // ✅ 이미지 src 직접 변경
  visiblePasswordIcon.src = passwordVisible
    ? "./assets/images/btn_visibility_on_24px.svg"
    : "./assets/images/btn_visibility_off_24px.svg";
});

클래스 이름을 설정하고, 클래스에 css 코드를 넣을 필요 없이 src를 직접 변경해볼 수 있습니다. 😊


if (type === "passwordCheck") {
if (!passwordCheckVisible) {
visiblePasswordCheckIcon.classList.remove('on');
visiblePasswordCheckIcon.classList.add('off');
passwordCheckInput.setAttribute("type", "password");
} else {
visiblePasswordCheckIcon.classList.remove('off');
visiblePasswordCheckIcon.classList.add('on');
passwordCheckInput.setAttribute("type", "text");
}

passwordCheckVisible = !passwordCheckVisible;
}

}

if (visiblePasswordIcon) {
visiblePasswordIcon.addEventListener("click", () => togglePasswordType("password"));
}
if (visiblePasswordCheckIcon) {
visiblePasswordCheckIcon.addEventListener("click", () => togglePasswordType("passwordCheck"));
}
});
25 changes: 25 additions & 0 deletions signup.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@
border-radius: 12px;
}

.error {
border: 1px solid #F74747;
}

.error-message {
margin-top: 8px;
font-size: 14px;
font-weight: 600;
line-height: 24px;
color: #F74747;
}

.visibility-icon {
width: 24px;
height: 24px;
Expand All @@ -68,6 +80,14 @@
cursor: pointer;
}

.on {
background-image: url("./assets/images/btn_visibility_on_24px.svg");
}

.off {
background-image: url("./assets/images/btn_visibility_off_24px.svg");
}

.styled-button {
margin-bottom: 24px;
padding: 12px 0;
Expand All @@ -80,6 +100,11 @@
cursor: pointer;
}

.styled-button[disabled]{
background-color: var(--gray-400);
color: var(--gray-100);
}

.sns-container {
display: flex;
align-items: center;
Expand Down
20 changes: 13 additions & 7 deletions signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<link rel="stylesheet" href="signup.css" />
<link rel="stylesheet" href="palette.css" />
<link rel="stylesheet" href="reset.css" />
<script type="text/javascript" src="login.js"></script>
<script type="text/javascript" src="password-check.js"></script>
</head>
<body>
<main class="main-container">
Expand All @@ -20,31 +22,35 @@
<form class="form-container">
<div class="input-container">
<label class="text-gray800 label-text">이메일</label>
<input class="text-gray800 gray100 styled-input" placeholder="이메일을 입력해 주세요" type="email"/>
<input class="text-gray800 gray100 styled-input email-input" placeholder="이메일을 입력해 주세요" type="email" name="email" required/>
<p class="error-message" id="emailError"></p>
</div>

<div class="input-container">
<label class="text-gray800 label-text">닉네임</label>
<input class="text-gray800 gray100 styled-input" placeholder="닉네임을 입력해 주세요"/>
<input class="text-gray800 gray100 styled-input nickname-input" placeholder="닉네임을 입력해 주세요" type="text" name="nickname" required/>
<p class="error-message" id="nicknameError"></p>
</div>

<div class="input-container">
<label class="text-gray800 label-text">비밀번호</label>
<div class="password-container">
<input class="text-gray800 gray100 styled-input" placeholder="비밀번호를 입력해 주세요" type="password"/>
<img src="./assets/images/btn_visibility_on_24px.svg" class="visibility-icon">
<input class="text-gray800 gray100 styled-input password-input" placeholder="비밀번호를 입력해 주세요" type="password" name="password" required/>
<div class="visibility-icon off"></div>
</div>
<p class="error-message" id="passwordError"></p>
</div>

<div class="input-container">
<label class="text-gray800 label-text">비밀번호 확인</label>
<div class="password-container">
<input class="text-gray800 gray100 styled-input" placeholder="비밀번호를 다시 입력해 주세요" type="password"/>
<img src="./assets/images/btn_visibility_on_24px.svg" class="visibility-icon">
<input class="text-gray800 gray100 styled-input password-check-input" placeholder="비밀번호를 다시 입력해 주세요" type="password" name="passwordDoubleCheck" required/>
<div class="visibility-icon password-check-visibility-icon off"></div>
</div>
<p class="error-message" id="passwordCheckError"></p>
</div>

<button class="text-gray100 blue styled-button">회원 가입</button>
<button class="text-gray100 blue styled-button signup-button" disabled>회원 가입</button>

<div class="sns-container">
<span class="text-gray800 sns-login-message">간편 로그인하기</span>
Expand Down
Loading