-
Notifications
You must be signed in to change notification settings - Fork 39
[김진선] sprint4 #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "Basic-\uAE40\uC9C4\uC120-sprint4"
[김진선] sprint4 #129
Changes from all commits
0a08a04
a667298
c7dc861
7866188
a412edd
e4081f9
81c8a4e
5df58ed
db7e5c4
697af19
96725f4
a93d803
ce75913
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,77 +9,103 @@ | |
| <meta property="og:title" content="판다 마켓" /> | ||
| <meta property="og:description" content="일상의 모든 물건을 거래해보세요" /> | ||
| <meta property="og:image" content="/images/og_img.png" /> | ||
| <meta name="twitter:card" content="summary_large_image" /> | ||
| <meta name="twitter:title" content="판다 마켓" /> | ||
| <meta name="twitter:description" content="일상의 모든 물건을 거래해보세요" /> | ||
| <meta name="twitter:image" content="/images/og_img.png" /> | ||
| <link rel="stylesheet" as="style" crossorigin | ||
| href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard-dynamic-subset.min.css" /> | ||
| <title>판다마켓</title> | ||
| <link rel="stylesheet" href="styles/reset.css"> | ||
| <link rel="stylesheet" href="styles/global.css"> | ||
| <link rel="stylesheet" href="styles/variables.css"> | ||
| <link rel="stylesheet" href="styles/mobile.css" media="(max-width: 767px)"> | ||
| <link rel="stylesheet" href="styles/tablet.css" media="(min-width: 768px) and (max-width: 1199px)"> | ||
| <link rel="stylesheet" href="styles/desktop.css" media="(min-width: 1200px)"> | ||
| <link rel="stylesheet" href="styles/index.css"> | ||
| </head> | ||
|
|
||
| <body> | ||
| <!-- header start --> | ||
| <header> | ||
| <!-- nav start --> | ||
| <nav> | ||
| <a href="/" aria-label="홈으로 이동"> | ||
| <!-- <a href="/" aria-label="홈으로 이동"> | ||
| <img src="/images/Property1-2=sm.png" | ||
| srcset="/images/Property1-2=Typo.png 743w, /images/Property1-2=sm.png 744w" | ||
| sizes="(max-width: 743px) 100vw, 744px" alt="판다마켓 로고" /> --> | ||
| <picture> | ||
| <source srcset="/images/Property1-2=Typo.png" media="(max-width: 743px)" /> | ||
| <img src="/images/Property 1-2=sm.png" alt="판다마켓 로고" /> | ||
| <img src="/images/Property1-2=sm.png" alt="판다마켓 로고" /> | ||
| </picture> | ||
| </a> | ||
| <a href="/login.html" class="btn-small-40" aria-label="로그인으로 이동">로그인</a> | ||
| </nav> | ||
| <!-- nav end --> | ||
| </header> | ||
| <!-- header end --> | ||
|
|
||
| <!-- main start --> | ||
| <main> | ||
| <section id="hero" class="banner"> | ||
| <!-- hero section start --> | ||
| <section class="banner"> | ||
| <div class="wrapper"> | ||
| <h1>일상의 모든 물건을<br>거래해 보세요</h1> | ||
| <a href="items.html"><button class="btn" aria-label="아이템으로 이동">구경하러 가기</button></a> | ||
| <div class="hero-title"> | ||
| <h1>일상의 모든 물건을 <span class="heading-line-break">거래해 보세요</span></h1> | ||
| <a href="items.html"> | ||
| <button class="btn-large" aria-label="아이템으로 이동">구경하러 가기</button> | ||
| </a> | ||
| </div> | ||
| <img src="/images/Img_home_top.png" alt="" aria-hidden="true" /> | ||
| </div> | ||
| </section> | ||
| <!-- hero section end --> | ||
|
|
||
| <!-- features section start --> | ||
| <section class="features wrapper wrapper--narrow"> | ||
| <!-- feature div start --> | ||
| <div class="feature"> | ||
| <img src="images/Img_home_01.png" alt="아이템 콘텐츠 이미지" /> | ||
| <div class="feature-content"> | ||
| <p class="feature-tag">Hot item</p> | ||
| <h1>인기 상품을<br />확인해 보세요</h1> | ||
| <h1>인기 상품을<span class="heading-line-break"> 확인해 보세요</span></h1> | ||
| <p class="feature-description"> | ||
| 가장 HOT한 중고거래 물품을<br />판다마켓에서 확인해 보세요 | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <!-- feature div end --> | ||
| <!-- feature div start --> | ||
| <div class="feature"> | ||
| <img src="images/Img_home_02.png" alt="아이템 콘텐츠 이미지"> | ||
| <div class="feature-content"> | ||
| <p class="feature-tag">Search</p> | ||
| <h1>구매를 원하는<br>상품을 검색하세요</h1> | ||
| <h1>구매를 원하는<span class="heading-line-break"> 상품을 검색하세요</span></h1> | ||
| <p class="feature-description">구매하고 싶은 물품은 검색해서<br>쉽게 찾아보세요</p> | ||
| </div> | ||
| <img src="images/Img_home_02.png" alt="아이템 콘텐츠 이미지"> | ||
| </div> | ||
| <!-- feature div end --> | ||
| <!-- feature div start --> | ||
| <div class="feature"> | ||
| <img src="images/Img_home_03.png" alt="아이템 콘텐츠 이미지" /> | ||
| <div class="feature-content"> | ||
| <p class="feature-tag">Register</p> | ||
| <h1 class="font-fat">판매를 원하는<br>상품을 등록하세요</h1> | ||
| <h1 class="font-fat">판매를 원하는<span class="heading-line-break"> 상품을 등록하세요</span></h1> | ||
| <p class="feature-description"> | ||
| 어떤 물건이든 판매하고 싶은 상품을<br>쉽게 등록하세요</p> | ||
| </div> | ||
| </div> | ||
| <!-- feature div end --> | ||
| </section> | ||
| <section id="closing-banner" class="banner"> | ||
| <!-- features section end --> | ||
|
|
||
| <!-- closing banner start --> | ||
| <section class="banner"> | ||
| <div class="wrapper"> | ||
| <h1>믿을 수 있는<br>판다마켓 중고 거래</h1> | ||
| <h1>믿을 수 있는<span class="heading-line-break"> 판다마켓 중고 거래</span></h1> | ||
| <img src="/images/Img_home_bottom.png" alt="" aria-hidden="true" /> | ||
| </div> | ||
| </section> | ||
| <!-- closing banner end --> | ||
| </main> | ||
| <!-- main end --> | ||
|
|
||
| <!-- footer start --> | ||
| <footer> | ||
| <div class="footer-copyright">© codeit - 2024</div> | ||
| <div class="footer-copyright">© codeit - 2024</div> | ||
| <div id="footer--menu"> | ||
| <a href="/privacy.html" aria-label="Privacy Policy로 이동">Privacy Policy</a> | ||
| <a href="/faq.html" aria-label="FAQ로 이동">FAQ</a> | ||
|
|
@@ -91,6 +117,7 @@ <h1>믿을 수 있는<br>판다마켓 중고 거래</h1> | |
| <a href="https://www.instagram.com/" aria-label="인스타그램으로 이동"><img src="/images/ic_instagram.svg"></a> | ||
| </div> | ||
| </footer> | ||
| <!-- footer end --> | ||
| </body> | ||
|
|
||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { | ||
| validateInputs, | ||
| checkAllInputsValid, | ||
| togglePasswordVisibility, | ||
| debounce, | ||
| } from "./utils.js"; | ||
| /** | ||
| * 변수 정의 | ||
| */ | ||
| const inputArr = document.querySelectorAll("[data-validate]"); | ||
| const submitBtn = document.querySelector(".btn"); | ||
| const togglePasswordBtns = document.querySelectorAll(".toggle-password"); | ||
| const form = document.querySelector(".auth-form"); | ||
| const debouncedCheckAll = debounce(() => { | ||
| checkAllInputsValid(inputArr, submitBtn); | ||
| }, 120); | ||
| /** | ||
| * 이벤트 리스너 | ||
| */ | ||
| // 입력란(input) | ||
| form.addEventListener("input", (e) => { | ||
| const input = e.target.closest("[data-validate]"); | ||
| if (!input) return; | ||
| validateInputs(input); | ||
| debouncedCheckAll(); | ||
| }); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수 선언부 블락끼리는 그 사이에 한칸씩 띄워주세요! |
||
| form.addEventListener( | ||
| "blur", | ||
| (e) => { | ||
| const input = e.target.closest("[data-validate]"); | ||
| if (!input) return; | ||
| validateInputs(input); | ||
| checkAllInputsValid(inputArr, submitBtn); | ||
| }, | ||
| true | ||
| ); | ||
| // 패스워드 토글 | ||
| togglePasswordBtns.forEach((button) => { | ||
| button.addEventListener("click", () => togglePasswordVisibility(button)); | ||
| }); | ||
| // submit | ||
| submitBtn.addEventListener("click", (e) => { | ||
| e.preventDefault(); | ||
| if (!submitBtn.disabled) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굳굳! 예외처리 좋고, redirectMap 객체를 조건문 안에서 선언한것도 괜찮네요 👍 // constants.js
export const REDIRECT_MAP = {
"/login.html": "/items.html",
"/signup.html": "/login.html",
}; |
||
| const redirectMap = { | ||
| "/login.html": "/items.html", | ||
| "/signup.html": "/login.html", | ||
| }; | ||
| const target = redirectMap[window.location.pathname]; | ||
| if (target) window.location.href = target; | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,60 @@ | ||||||||||||||
| import { validators } from "./validators.js"; | ||||||||||||||
| /** | ||||||||||||||
| * debounce code | ||||||||||||||
| */ | ||||||||||||||
| export function debounce(fn, delay = 300) { | ||||||||||||||
| let timer; | ||||||||||||||
| return function (...args) { | ||||||||||||||
| clearTimeout(timer); | ||||||||||||||
| timer = setTimeout(() => { | ||||||||||||||
| fn.apply(this, args); | ||||||||||||||
| }, delay); | ||||||||||||||
| }; | ||||||||||||||
| }; | ||||||||||||||
| /** | ||||||||||||||
| * toggle password | ||||||||||||||
| */ | ||||||||||||||
| export const togglePasswordVisibility = (button) => { | ||||||||||||||
| const targetId = button.getAttribute("data-target"); | ||||||||||||||
| const passwordInput = document.getElementById(targetId); | ||||||||||||||
| if (!passwordInput) return; | ||||||||||||||
| const img = button.querySelector("img"); | ||||||||||||||
| if (passwordInput.type === "password") { | ||||||||||||||
| passwordInput.type = "text"; | ||||||||||||||
| img.src = "/images/btn_visibility_on_24px.png"; | ||||||||||||||
| img.alt = "비밀번호 보이는 중"; | ||||||||||||||
| } else { | ||||||||||||||
| passwordInput.type = "password"; | ||||||||||||||
| img.src = "/images/btn_none_visibility_on_24px.png"; | ||||||||||||||
| img.alt = "비밀번호 숨겨진 상태"; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+22
to
+30
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if else문을 써서 처리해주는것도 나쁘지않지만, 구조를 생각해봤을때 조건이 여러개 붙는게 아닌 둘중에 하나로 토글되는 방식이니까, 이런 방식을 사용하면 의도가 좀 더 명확히 파악되고 코드도 간결해질수있겠네요 :) const isPasswordVisible = passwordInput.type === "password";
passwordInput.type = isPasswordVisible ? "text" : "password";
img.src = isPasswordVisible
? "/images/btn_visibility_on_24px.png"
: "/images/btn_none_visibility_on_24px.png";
img.alt = isPasswordVisible ? "비밀번호 보이는 중" : "비밀번호 숨겨진 상태"; |
||||||||||||||
| }; | ||||||||||||||
| /* | ||||||||||||||
| * Validate User Input | ||||||||||||||
| */ | ||||||||||||||
| export const validateInputs = (inputEl) => { | ||||||||||||||
| const validateType = inputEl.dataset.validate; | ||||||||||||||
| const { value, id } = inputEl; | ||||||||||||||
| if (!validateType || !validators[validateType]) return; | ||||||||||||||
|
Comment on lines
+37
to
+38
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 코드 흐름을 좀 더 개선해보려면 38번째라인을 37번째 라인보다 위로 갈 수 있게 순서를 바꾸면 더 좋을것같아요. |
||||||||||||||
| const { isValid, message } = validators[validateType](value); | ||||||||||||||
| const errMsg = document.getElementById(`${id}-error`); | ||||||||||||||
| if (errMsg) { | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 40, 41사이에 한칸 띄워주세요 :) |
||||||||||||||
| errMsg.textContent = isValid ? "" : message; | ||||||||||||||
| } | ||||||||||||||
| if (isValid) { | ||||||||||||||
| inputEl.classList.remove("input-error"); | ||||||||||||||
| } else { | ||||||||||||||
| inputEl.classList.add("input-error"); | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+44
to
+48
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 조건문도 classList.toggle을 사용하면 이렇게 간소화할수있답니다!
Suggested change
|
||||||||||||||
| return isValid; | ||||||||||||||
| }; | ||||||||||||||
| /* | ||||||||||||||
| * 모든 입력값이 유효한지 확인하는 함수 | ||||||||||||||
| */ | ||||||||||||||
| export const checkAllInputsValid = (inputArr, submitBtn) => { | ||||||||||||||
| // 1) 모든 필드에 대해 validateInputs 호출 → 에러 메시지 업데이트 | ||||||||||||||
| const results = Array.from(inputArr).map((input) => validateInputs(input)); | ||||||||||||||
| // 2) map 결과로만 버튼 활성화/비활성화 결정 | ||||||||||||||
| const allValid = results.every(Boolean); | ||||||||||||||
| submitBtn.disabled = !allValid; | ||||||||||||||
| }; | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굳굳! 유효성 검사 로직은 프로그램내에서 UI와 결합될 필요없이 재사용될 수 있으니, 따로 분리해두고 모듈화하시는게 좋죠 👍 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // 함수 정의 | ||
| /* | ||
| Validators | ||
| */ | ||
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
| export const validators = { | ||
| email: (value) => { | ||
| if (!value) return { isValid: false, message: "이메일을 입력해주세요." }; | ||
| if (!emailRegex.test(value)) | ||
| return { isValid: false, message: "이메일 형식에 맞지 않습니다." }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| nickname: (value) => { | ||
| if (!value) return { isValid: false, message: "닉네임을 입력해주세요." }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| password: (value) => { | ||
| if (!value) return { isValid: false, message: "비밀번호를 입력해주세요." }; | ||
| if (value.length < 8) | ||
| return { isValid: false, message: "비밀번호를 8자리 이상 입력해주세요." }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| confirmPassword: (value) => { | ||
| const passwordValue = document.getElementById("password").value; | ||
| if (!value) | ||
| return { isValid: false, message: "비밀번호를 한번 더 입력해주세요." }; | ||
| if (value.length < 8) | ||
| return { isValid: false, message: "비밀번호를 8자리 이상 입력해주세요." }; | ||
| if (value !== passwordValue) | ||
| return { isValid: false, message: "비밀번호가 일치하지 않습니다." }; | ||
| return { isValid: true, message: "" }; | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 상황에서는 Debounce를 사용하는 것이 적절하지 않습니다.
이유는: