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
55 changes: 27 additions & 28 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@

### 기본

공통

- [o] 브라우저에 현재 보이는 화면의 영역(viewport) 너비를 기준으로 분기되는 반응형 디자인을 적용합니다.
PC: 1200px 이상
Tablet: 768px 이상 ~ 1199px 이하
Mobile: 375px 이상 ~ 767px 이하
375px 미만 사이즈의 디자인은 고려하지 않습니다

### 랜딩 페이지

- [o] Tablet 사이즈로 작아질 때 “판다마켓” 로고의 왼쪽에 여백 24px, “로그인” 버튼 오른쪽 여백 24px을 유지할 수 있도록 “판다마켓” 로고와 “로그인" 버튼의 간격이 가까워집니다.
- [o] Mobile 사이즈로 작아질 때 “판다마켓” 로고의 왼쪽에 여백 16px, “로그인” 버튼 오른쪽 여백 16px을 유지할 수 있도록 “판다마켓” 로고와 “로그인" 버튼의 간격이 가까워집니다.
- [o] 화면 영역이 줄어들면 “Privacy Policy”, “FAQ”, “codeit-2024”이 있는 영역과 SNS 아이콘들이 있는 영역의 간격이 줄어듭니다.

### 로그인, 회원가입 페이지 성공

- [o] Tablet 사이즈에서 내부 디자인은 PC사이즈와 동일합니다.
- [o] Mobile 사이즈에서 좌우 여백 16px 제외하고 내부 요소들이 너비를 모두 차지합니다.
- [o] Mobile 사이즈에서 내부 요소들의 너비는 기기의 너비가 커지는 만큼 커지지만 400px을 넘지 않습니다.
### 로그인

- [o] 이메일 input에서 focus out 할 때, 값이 없을 경우 input에 빨강색 테두리와 아래에 "이메일을 입력해주세요." 빨강색 에러 메세지를 보입니다.
- [o] 이메일 input에서 focus out 할 때, 이메일 형식에 맞지 않는 경우 input에 빨강색 테두리와 아래에 "잘못된 이메일 형식입니다" 빨강색 에러 메세지를 보입니다.
- [o] 비밀번호 input에서 focus out 할 때, 값이 없을 경우 아래에 "비밀번호를 입력해주세요." 에러 메세지를 보입니다
- [o] 비밀번호 input에서 focus out 할 때, 값이 8자 미만일 경우 아래에 "비밀번호를 8자 이상 입력해주세요." 에러 메세지를 보입니다.
- [o] input 에 빈 값이 있거나 에러 메세지가 있으면 '로그인' 버튼은 비활성화 됩니다.
- [o] input 에 유효한 값을 입력하면 '로그인' 버튼이 활성화 됩니다.
- [o] 활성화된 '로그인' 버튼을 누르면 "/items" 로 이동합니다

### 회원가입

- [o] 이메일 input에서 focus out 할 때, 값이 없을 경우 input에 빨강색 테두리와 아래에 "이메일을 입력해주세요." 빨강색 에러 메세지를 보입니다.
- [o] 이메일 input에서 focus out 할 때, 이메일 형식에 맞지 않는 경우 input에 빨강색 테두리와 아래에 "잘못된 이메일 형식입니다" 빨강색 에러 메세지를 보입니다.
- [o] 닉네임 input에서 focus out 할 때, 값이 없을 경우 input에 빨강색 테두리와 아래에 "닉네임을 입력해주세요." 빨강색 에러 메세지를 보입니다.
- [o] 비밀번호 input에서 focus out 할 때, 값이 없을 경우 아래에 "비밀번호를 입력해주세요." 에러 메세지를 보입니다
- [o] 비밀번호 input에서 focus out 할 때, 값이 8자 미만일 경우 아래에 "비밀번호를 8자 이상 입력해주세요." 에러 메세지를 보입니다.
- [o] 비밀번호 input과 비밀번호 확인 input의 값이 다른 경우, 비밀번호 확인 input 아래에 "비밀번호가 일치하지 않습니다.." 에러 메세지를 보입니다.
- [o] input 에 빈 값이 있거나 에러 메세지가 있으면 '회원가입' 버튼은 비활성화 됩니다.
- [o] input 에 유효한 값을 입력하면 '회원가입' 버튼이 활성화 됩니다.
- [o] 활성화된 '회원가입' 버튼을 누르면 로그인 페이지로 이동합니다

### 심화

- [o] 페이스북, 카카오톡, 디스코드, 트위터 등 SNS에서 Linkbrary 랜딩 페이지(“/”) 공유 시 좌측 예시와 같은 미리보기를 볼 수 있도록 랜딩 페이지 메타 태그를 설정해 주세요.
- [o] 미리보기에서 제목은 “판다 마켓”, 설명은 “일상의 모든 물건을 거래해보세요”로 설정합니다.
- [o] 주소와 이미지는 자유롭게 설정하세요.
- [o] 눈 모양 아이콘 클릭시 비밀번호의 문자열이 보이기도 하고, 가려지기도 합니다.
- [o] 비밀번호의 문자열이 가려질 때는 눈 모양 아이콘에는 사선이 그어져있고, 비밀번호의 문자열이 보일 때는 사선이 없는 눈 모양 아이콘이 보이도록 합니다.

## 주요 변경사항

Expand All @@ -36,9 +37,7 @@

## 멘토에게

- 피드백 받은거 수정하려고 했으나 시간이..그래서 일단 스프린트 미션3 위주로 했습니다
- 확실히 강의 참고하고 구글링을 통해 코드 작성을 하였으나 아직은 여전히 css가 제가 원하는 대로 조작이 힘듭니다..그러다보니 마지막 최종 수정은 gpt를 통해서 했지만 그래도 전에보다 좋아진건 gpt가 준 코드를 gpt설명없이 이해를 할 수 있다라는 부분에서 굉장히 큰 성취감을 느낍니다
- 반응형 디자인을 하고 개발자 도구를 통해 확인해보니 맞는 것 같긴한데 확실히 맞는지 잘 모르겠습니다..이게 반응형이 처음이다보니 확실한 기준이 없는 것 같습니다 혹시 알 수 있는 팁이 있을까요??
- 이번 코딩할 때는 솔직히 구글링의 한계를 많이 느껴서 gpt 도움을 많이 받았습니다..하나하나 적용할 때 마다 자꾸 css가 이상하게 바껴서 눈물 많이 흘렸습니다..그래도 시간안에 만들어보자의 의의를 두고 했습니다
- 이왕 gpt쓴거 심화도 해봐야겠다하고 했구요 주소는 디스코드하고 이미지는 그냥 판다마켓 로고로 했습니다..!!
- 코딩 재밌다..
- 진짜 너무너무 어려웠습니다..세상이 무너지는 줄 알았습니다
- 구글링의 한계를 느끼는 경험이였습니다..혹시 저는 그냥 velog나 mdn참고 많이 하는데 혹시 이 방법말고 멘토님이라면 어떻게 질문하고 찾는지 선생님만에 꿀팁이 있을까요...?
- 처음으로 주석을 달아보았습니다..이게 코드양이 길어지고 Js파일을 제 스스로가 봐도 보기 힘들다보니 gpt한테 이때 어떤 방법을 쓰니?? 라고 물어보니 주석을 쓰라고 하더라고요..네 다음부터 주석을 적절히 잘 활용해야겠다라는 생각이 들었습니다
- gpt도움 많이 받았습니다..근데 코드를 그냥 복붙하고 따라쳤다기보다는 일단 이해는 하고 그때그때 마다 찾아쓸 수 있을 정도로 이해했습니다 혹시 추가적으로 추천하는 방법이 있나요..??
4 changes: 4 additions & 0 deletions css/auth.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,7 @@
max-width: 640px;
margin: 0 auto;
}

.input-error {
border: 1px solid red !important;
}
2 changes: 2 additions & 0 deletions login.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,7 @@ <h3>간편 로그인하기</h3>
<a href="signup.html">회원가입</a>
</div>
</div>

<script src="scripts/auth.js"></script>
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안

아마 script 태그를 HTML의 하단에 배치하신 이유가 script가 문서의 렌더링을 막지 않도록 하기 위해서이실 것 같아요.
하지만, script 태그에 defer나 async 속성을 사용하면 이런 문제를 해결할 수 있기 때문에 반드시 하단에 배치할 필요는 없습니다!
또한 script 태그는 상단에 있는게 구조 파악에서도 유리하기 때문에 상단 head 태그에 두시는 것을 추천드려요~

script async
script defer

지금과 같은 경우 DOM을 조작하는 JS 이니 defer 속성을 추가하시면 되겠습니다~

</body>
</html>
184 changes: 184 additions & 0 deletions scripts/auth.js
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
한 파일에서 로그인, 회원가입 공용으로 JS를 작성해주셨네요~
둘이 공통되는 부분이 많이 이렇게 해주신 것 같아요.
다만 그렇게 되면 코드가 복잡해져서 유지보수성이 떨어지고 로그인 페이지의 경우 불필요한 코드까지 알아야합니다~
가능하면 공통 로직들은 분리해주시고 각 페이지별 JS를 따로 만드시는 것을 추천드려요!

우선은 간단하게 아래처럼 분리하셔도 좋을 것 같아요.

auth.js // 로그인, 회원가입에서 필요한 유효성 검사 로직들이 위치

login.js // auth.js에서 필요한 로직을 가지고와 필요한 태그에 연결
signup.js

Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
document.addEventListener("DOMContentLoaded", function () {
const form = document.querySelector("form.auth-form");
if (!form) return;
const button = form.querySelector("button.auth-button");
button.disabled = true;
Comment on lines +4 to +5
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
JS 가 아니라 html 에서 button에 disabled 속성을 true로 주고 시작하는게 더 좋을 것 같아요.
JS에서 이렇게 해야할 이유를 잘 모르겠고 이렇게 되면 HTML 에서 button이 활성화된채로 렌더링됩니다~


// 전체 폼 유효성 검사
function checkFormValidity() {
let valid = true;
const inputs = form.querySelectorAll("input");
inputs.forEach(function (input) {
if (input.value.trim() === "") {
valid = false;
}
});
if (form.querySelectorAll(".error-message").length > 0) {
valid = false;
}
button.disabled = !valid;
}
Comment on lines +8 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
폼의 유효성을 검사하는 로직을 따로 분리해주신 것은 좋습니다.
다만 이미 validateEmail과 같은 각 인풋별 유효성 검사 함수를 선언해두셨으니 이를 이용해서 유효성 검사를 하시거나
유효하지 않다면 error-message 라는 클래스를 가진 태그가 폼안에 존재할테니 그냥 해당 여부만 확인하시면 될 것 같습니다!

Suggested change
function checkFormValidity() {
let valid = true;
const inputs = form.querySelectorAll("input");
inputs.forEach(function (input) {
if (input.value.trim() === "") {
valid = false;
}
});
if (form.querySelectorAll(".error-message").length > 0) {
valid = false;
}
button.disabled = !valid;
}
function checkFormValidity() {
const isValid = form.querySelectorAll(".error-message").length > 0;
button.disabled = !isValid;
}


// 이메일
const emailInput = document.getElementById("email");
if (emailInput) {
emailInput.addEventListener("blur", validateEmail);
emailInput.addEventListener("input", validateEmail);
}
function validateEmail() {
const emailValue = emailInput.value.trim();
let errorMessage = emailInput.closest(".input-group").querySelector(".error-message");
if (!emailValue) {
emailInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "이메일을 입력해주세요.";
emailInput.closest(".input-group").appendChild(errorMessage);
Comment on lines +34 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
JS 를 통해 스타일을 변경해주셔도 괜찮지만, error와 관련된 클래스를 만들어두시고 해당 클래스를 통해 스타일링하시면
코드도 줄고 css관련은 css 파일에 적으니 더 좋을 것 같네요!

Suggested change
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "이메일을 입력해주세요.";
emailInput.closest(".input-group").appendChild(errorMessage);
errorMessage = document.createElement("span");
errorMessage.className = "error-message"; // css에서 .error-message { color: red; font-size: 14px; display: block; margin-top: 4px; } 와 같은 식으로 스타일링하세요~
errorMessage.innerText = "이메일을 입력해주세요.";
emailInput.closest(".input-group").appendChild(errorMessage);

} else {
errorMessage.innerText = "이메일을 입력해주세요.";
}
} else {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(emailValue)) {
emailInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "잘못된 이메일 형식입니다";
emailInput.closest(".input-group").appendChild(errorMessage);
} else {
errorMessage.innerText = "잘못된 이메일 형식입니다";
}
} else {
emailInput.classList.remove("input-error");
if (errorMessage) errorMessage.remove();
}
}
checkFormValidity();
}

// 비밀번호
const passwordInput = document.getElementById("password");
if (passwordInput) {
passwordInput.addEventListener("blur", validatePassword);
passwordInput.addEventListener("input", validatePassword);
}
function validatePassword() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
승민님이 너무 어려우셨다고 하셔서 기능적으로 크게 문제가 없는 부분은 피드백을 안 드리려고 하는데,
if문이 많이 중첩되면 로직 파악이 어려워집니다~
이러한 경우 함수로 많이 분리하면 흐름 따라가기가 더 쉬워집니다~
어려우시면 참고만해보세요.

  function validateEmail() {
    const email = emailInput.value.trim();
    const errorMessage = getErrorMessageEl(
      emailInput,
      "이메일을 입력해주세요."
    );

    if (!email) {
      showError(emailInput, errorMessage, "이메일을 입력해주세요.");
    } else if (!isValidEmail(emailValue)) {
      showError(emailInput, errorMessage, "잘못된 이메일 형식입니다");
    } else {
      hideError(emailInput, errorMessage);
    }

    checkFormValidity();
  }

  function isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  function getErrorMessageEl(input, defaultMessage) {
    const errorMessage =
      input.closest(".input-group").querySelector(".error-message") ??
      document.createElement("span");

    errorMessage.className = "error-message";
    errorMessage.innerText = defaultMessage;
    input.closest(".input-group").appendChild(errorMessage);

    return errorMessage;
  }

  function showError(input, errorMessage, message) {
    input.classList.add("input-error");
    errorMessage.innerText = message;
  }

  function hideError(input, errorMessage) {
    input.classList.remove("input-error");
    if (errorMessage) errorMessage.remove();
  }

const passwordValue = passwordInput.value.trim();
let errorMessage = passwordInput.closest(".input-group").querySelector(".error-message");
if (!passwordValue) {
passwordInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "비밀번호를 입력해주세요.";
passwordInput.closest(".input-group").appendChild(errorMessage);
} else {
errorMessage.innerText = "비밀번호를 입력해주세요.";
}
} else if (passwordValue.length < 8) {
passwordInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "비밀번호를 8자 이상 입력해주세요.";
passwordInput.closest(".input-group").appendChild(errorMessage);
} else {
errorMessage.innerText = "비밀번호를 8자 이상 입력해주세요.";
}
} else {
passwordInput.classList.remove("input-error");
if (errorMessage) errorMessage.remove();
}
if (passwordCheckInput) {
validatePasswordCheck();
}
checkFormValidity();
}

// 닉네임
const nicknameInput = document.getElementById("nickname");
if (nicknameInput) {
nicknameInput.addEventListener("blur", validateNickname);
nicknameInput.addEventListener("input", validateNickname);
}
function validateNickname() {
const nicknameValue = nicknameInput.value.trim();
let errorMessage = nicknameInput.closest(".input-group").querySelector(".error-message");
if (!nicknameValue) {
nicknameInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "닉네임을 입력해주세요.";
nicknameInput.closest(".input-group").appendChild(errorMessage);
} else {
errorMessage.innerText = "닉네임을 입력해주세요.";
}
} else {
nicknameInput.classList.remove("input-error");
if (errorMessage) errorMessage.remove();
}
checkFormValidity();
}

// 비밀번호 - 회원가입
const passwordCheckInput = document.getElementById("passwordCheck");
if (passwordCheckInput) {
passwordCheckInput.addEventListener("blur", validatePasswordCheck);
passwordCheckInput.addEventListener("input", validatePasswordCheck);
}
function validatePasswordCheck() {
if (passwordInput && passwordCheckInput) {
const passwordValue = passwordInput.value;
const passwordCheckValue = passwordCheckInput.value;
let errorMessage = passwordCheckInput.closest(".input-group").querySelector(".error-message");
if (passwordValue !== passwordCheckValue) {
passwordCheckInput.classList.add("input-error");
if (!errorMessage) {
errorMessage = document.createElement("span");
errorMessage.className = "error-message";
errorMessage.style.cssText = "color: red; font-size: 14px; display: block; margin-top: 4px;";
errorMessage.innerText = "비밀번호가 일치하지 않습니다.";
passwordCheckInput.closest(".input-group").appendChild(errorMessage);
} else {
errorMessage.innerText = "비밀번호가 일치하지 않습니다.";
}
} else {
passwordCheckInput.classList.remove("input-error");
if (errorMessage) errorMessage.remove();
}
checkFormValidity();
}
}

// 비밀번호 보이게 하기
const passwordWrappers = document.querySelectorAll(".password-wrapper");
passwordWrappers.forEach(function (wrapper) {
const toggleButton = wrapper.querySelector("button");
const input = wrapper.querySelector("input");
if (toggleButton && input) {
toggleButton.addEventListener("click", function (e) {
e.preventDefault();
input.type = input.type === "password" ? "text" : "password";
});
}
});

// 폼 제출 처리
form.addEventListener("submit", function (e) {
e.preventDefault();
if (button.disabled) return;
if (nicknameInput || passwordCheckInput) {
window.location.href = "login.html";
} else {
window.location.href = "/items";
}
});
Comment on lines +175 to +183
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
form이 유효하지 않으면 submit button이 disabled 상태라 제출이 되지 않으므로 관련 코드는 없어도 될 것 같아요~

Suggested change
form.addEventListener("submit", function (e) {
e.preventDefault();
if (button.disabled) return;
if (nicknameInput || passwordCheckInput) {
window.location.href = "login.html";
} else {
window.location.href = "/items";
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
if (nicknameInput || passwordCheckInput) {
window.location.href = "login.html";
} else {
window.location.href = "/items";
}
});

});
6 changes: 4 additions & 2 deletions signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<label for="password">비밀번호</label>
<div class="password-wrapper">
<input id="password" name="password" type="password" placeholder="비밀번호를 입력해 주세요" required />
<button>
<button type="button">
<img src="images/login_icon/eye-invisible.svg" alt="비밀번호 감추기" class="toggle-password" />
</button>
</div>
Expand All @@ -52,7 +52,7 @@
type="password"
placeholder="비밀번호를 다시 입력해 주세요"
required />
<button>
<button type="button">
<img src="images/login_icon/eye-invisible.svg" alt="비밀번호 감추기" class="toggle-password" />
</button>
</div>
Expand All @@ -78,5 +78,7 @@ <h3>간편 로그인하기</h3>
<a href="login.html">로그인</a>
</div>
</div>

<script src="scripts/auth.js"></script>
</body>
</html>
Loading