[김예인] 스프린트 미션 3#36
Hidden character warning
Conversation
ckdqja135
left a comment
There was a problem hiding this comment.
예인님 ~ PR 잘 봤습니다!ㅎㅎㅎ
먼저 코드가 들쑥날쑥해서 죄송하다고 하셨는데 전혀 죄송할 일 아니에요. 이번 미션은 정말 분량이 많고, API 두 종류 * CRUD 5개 + 로그인/회원가입 + 반응형 + 모달 + 비번 토글까지 한 미션에 이 정도 내용이 들어 있는 건 입문자에게 결코 만만한 양이 아닙니다. 끝까지 제출하신 것만으로도 충분히 잘하신 거예요.
먼저 칭찬하고 싶은 건 파일 구조 감각입니다. validation.js, modal.js 같은 작은 모듈을 따로 만들고login/signup 양쪽에서 import해서 쓰는 흐름들은 미션 요구사항을 진짜로 이해해서 만든 결과예요.
단순히 분리하라니까 분리한 게 아니라 어떤 코드가 어디에 들어가야 하는지에 대한 감이 잡혀가는 게 보입니다. 그리고 모바일에서 이미지에 calc(100vw - 32px)를 쓰신 부분, .second-info-card에 column-reverse를 쓰신 부분 모두 왜 이렇게 해야 하는지를 이해하지 못하면 못쓰는 코드예요. 디테일을 챙기는 감각이 좋습니다. 👍
그럼에도 불구하고 이번에 가장 중요하게 봐주셨으면 하는 건 두 가지예요.
첫째 signupstyle.css의 미디어 쿼리가 .login-container를 가리키고 있어서 회원가입 페이지에 반응형이 전혀 안 먹고 있는 상황입니다. 직접 요청해주신 반응형이 제대로 구현됐는지의 답이 안타깝게도 랜딩/로그인은 됐는데 회원가입은 안 됐다.입니다. 하지만 이건 한 줄 고치면 해결되는 문제이니 가장 먼저 손봐주세요!
둘째 indexstyle.css의 라인 184~498이 라인 501~826에 거의 통째로 한 번 더 복붙되어 있는 상태예요. 작업 도중 실수로 들어간 것 같은데 절반 정도 분량이 잉여 코드라 아쉬운 포인트였어요 ㅎㅎ
JS 쪽도 같은 맥락이에요. validation.js의 setError가 텍스트만 바꾸고 input에 빨간 테두리는 입히지 않는 점, 그리고 signup.js의 checkPasswordConfirm에 return false가 한 줄 빠져서 빈 값에서도 검증이 통과되는 점 둘 다 화면상으로는 동작하는 것처럼 보이지만 기능의 절반이 빠진 상태라서 꼭 짚어드리고 싶었어요.
마지막으로 주어진 미션만을 보았을 때 제가 캐치했던 출제자의 의도 한 가지만 더 말씀드리자면 Article은 .then()/.catch(), Product는 async/await + try/catch 로 일부러 방식이 다르게 요구된 부분이에요. 지금은 두 서비스 모두 async/await로 통일되어 있는데 두 가지 비동기 처리 방식을 모두 경험해보는 게 이번 미션의 학습 목표라서 Article 쪽만 .then() 체인으로 다시 써보시면 좋겠습니다.
이미 async/await은 능숙하게 쓰시는 게 보이니 두 방식을 직접 비교해보기 딱 좋은 기회가 될 거예요.
전반적으로 코드를 작성할 수 있다는 단계는 충분히 넘어오신 상태이고 지금부터는 작성한 코드를 다시 한 번 점검하는 습관이 큰 도움이 될 거예요. 작업이 끝났을 때 파일을 한 번 위에서 아래로 훑어보고(중복 블록이 있는지), 브라우저 DevTools에서 각 화면 폭(375/744/1200/1920)에서 실제로 어떻게 보이는지만 체크해보시면 이번 미션의 이슈 중 80%는 미리 발견하실 수 있어요.
다음 미션도 화이팅이에요! 어렵다고 느끼시는 부분이 있으면 지금처럼 구체적으로 요청해주세요. 그래야 도움이 더 됩니다. 😃
There was a problem hiding this comment.
미디어 쿼리가 엉뚱한 클래스를 가리킵니다. 회원가입 페이지에 반응형이 적용되지 않고 있어요.
signup.html은 컨테이너로 .signup-container를 쓰고 있는데 signupstyle.css의 미디어 쿼리는 이렇게 되어 있어요.
/* signupstyle.css 라인 377~ */
@media (max-width: 1119px) {
.login-container { /* ← .signup-container 가 아니라 .login-container! */
max-width: 640px;
padding: 0
}
}
@media (max-width: 743px) {
.login-container { /* ← 여기도 마찬가지 */
max-width: 400px;
...
}
}
signup.html에는 .login-container라는 요소가 존재하지 않습니다. 즉, 회원가입 페이지는 어떤 화면 크기에서도 반응형 스타일이 적용되지 않고 있어요. 모바일에서 회원가입 페이지를 열어보면 PC용 레이아웃 그대로 보일 거예요.
왜 문제인지
login용 CSS를 복사해서 signup용으로 만드는 과정에서 클래스명을 바꿔야 하는 곳을 놓치신 것 같습니다. 이건 미션 요구사항의 "회원가입 모바일 좌우 16px / 최대 400px"이 그대로 구현 누락된 셈이에요. ㅎㅎㅎ
signupstyle.css의 미디어 쿼리 두 곳에서 .login-container → .signup-container로 바꿔주세요.
CSS의 클래스 선택자는 HTML과 1:1로 매칭되어야 동작합니다. CSS는 "이 클래스 이름을 가진 요소가 있으면 적용한다"고만
약속하기 때문에 HTML에 없는 이름이면 조용히 무시되고 에러도 안 나요.
이래서 복사-수정 작업에서 가장 잘 새는 버그 유형이고 브라우저 DevTools의 Elements 패널에서 요소를 클릭했을 때 어떤 CSS 규칙이 매칭됐는가를 직접 확인하는 습관을 들이면 빨리 발견할 수 있습니다. ㅎㅎㅎ
There was a problem hiding this comment.
거의 동일한 코드 블록이 두 번 반복됩니다.(라인 184498와 501826)
home-top-image, .info-section, .info-card, .info-text, .second-info-section, .second-info-card, .third-info-section, .third-info-card, .bottom-banner, .banner-inner, .footer, .footer-inner, .footer-links, .sns-links — 이 선택자들의 규칙이 사실상 그대로 한 번 더 정의되어 있어요. @media (min-width: 120rem) 블록도 라인 485와 813에 똑같이 두 번 있습니다.
이 부분이 왜 문제냐면
- CSS는 나중에 선언된 규칙이 이긴다(cascade)는 특성 때문에, 두 블록의 값이 살짝 달라지는 순간 어느 쪽이 적용되는지 추적이 매우 어려워집니다. 미래에 한쪽만 수정하면 다른 쪽이 남아서 의도와 다른 결과가 나옵니다.
- 파일 크기가 거의 두 배가 돼서 가독성과 유지보수 비용이 큽니다 (현재 972줄 중 절반 가까이가 잉여 코드).
- 채점자/리뷰어가 보면 "왜 이렇게 됐지?" 라는 의문이 생겨서 안 좋은 인상이 생깁니다.
따라서 라인 501826 블록 전체와, 두 번째 @media (min-width: 120rem) 블록(라인 813)을 삭제하시면 됩니다. 삭제 후 페이지가 정상으로 보이는지 한 번 확인해주세요.
CSS의 cascade(폭포) 규칙은 "동일한 specificity면 나중에 쓰인 게 이긴다"입니다. 따라서 의도치 않은 중복은 디버깅을 매우
어렵게 만듭니다. 코드를 잘라 붙이다가 실수로 두 번 붙여넣은 것 같은데 이런 사고를 막으려면 작업 후 파일 길이를 한 번 훑고 Ctrl+F로 같은 셀렉터가 여러 번 등장하는지 검색하는 습관이 도움이 됩니다. ㅎㅎㅎ
There was a problem hiding this comment.
추가로 indexstyle.css에 PC(1200~1919) 구간을 따로 정의한 부분이 없어서 기본값으로 굴러갑니다. 기본 padding이
.info-section { padding: 8.625rem 21.5rem } (좌우 344px씩) 인데 1200~1500px 화면에서는 콘텐츠 공간이 거의 안 남아요. PC 기본값을 1200px 화면에서도 자연스럽게 보이도록 손보면 더 완성도가 높아질거에요 ㅎㅎ
There was a problem hiding this comment.
빨간 테두리가 적용되지 않고 있어용
미션 요구사항은 빈 값/잘못된 형식이면 빨간 테두리 + 에러 메시지인데 현재 validation.js의 setError 함수는 텍스트만
바꾸고 끝납니다.
export function setError(messageEl, message) {
messageEl.textContent = message // ← 텍스트만 변경
}
loginstyle.css에는 .input-error { border: 1px solid var(--error-red) !important; } 클래스가 정의되어 있는데
이걸 input에 추가하는 코드가 어디에도 없어요. 즉 클래스는 만들어두고 사용은 안 한 상태입니다.
그래서 아래와 같이 수정해보세용 ㅎㅎ
function checkEmail() {
const email = emailInput.value;
clearError(emailInput, emailError);
if (!email) {
setError(emailInput, emailError, '이메일을 입력해주세요.');
return false;
}
if (!validateEmail(email)) {
setError(emailInput, emailError, '잘못된 이메일 형식입니다.');
return false;
}
return true;
}
function checkPassword() {
const password = passwordInput.value;
clearError(passwordInput, passwordError);
if (!password) {
setError(passwordInput, passwordError, '비밀번호를 입력해주세요.');
return false;
}
if (!validatePassword(password)) {
setError(passwordInput, passwordError, '비밀번호를 8자 이상 입력해주세요.');
return false;
}
return true;
}
| const product = await getProduct(newProduct.id); | ||
| console.log('아까 생성한 상품:', product); | ||
|
|
||
| const patchproduct = await patchProduct(newProduct.id, '수정한과자이름'); |
There was a problem hiding this comment.
const patchproduct = await patchProduct(newProduct.id, '수정한과자이름');
patchProduct(productId, name, description, price, tags, images) 시그니처인데 name만 전달하면 나머지는 undefined로 채워지고 그게 그대로 body에 들어갑니다. PATCH 의도는 바꿀 필드만 전달이라서 이 경우 객체에서 undefined 값을 빼주거나 body를 객체로 받도록 함수 시그니처를 바꿔주는 게 더 안전해요. (예: patchProduct(id, partialData) 형태)
아래와 같이 수정해주세요~
const patchproduct = await patchProduct(newProduct.id, {
name: '수정한과자이름'
});
console.log('수정한 상품: ', patchproduct);
요렇게 객체 기반으로 바꾸는 방식이 가장 깔끔해요~ ㅎㅎ
다른 곳도 이런 포맷으로 바꾸면 더욱 좋아용
| keyword = '', | ||
| orderBy = 'recent') { | ||
| try { | ||
| const query = |
There was a problem hiding this comment.
쿼리스트링은 URLSearchParams 사용하는 것을 권장합니다.
아래와 같이요~ ㅎㅎㅎ
async function getArticleList(
page = 1,
pageSize = 10,
keyword = '',
orderBy = 'recent') {
try {
const query = new URLSearchParams({
page,
pageSize,
keyword,
orderBy,
});
const response = await fetch(`${BASE_URL}/articles?${query}`);
이렇게 하면 예를 들어 keyword에 한글이나 특수문자가 들어왔을 때 자동으로 변환됩니다.
// keyword: '안녕 & 반가워'
// 기존 방식: ?keyword=안녕 & 반가워 ← URL 깨짐 (공백, & 가 그대로 들어감)
// URLSearchParams: ?keyword=%EC%95%88%EB%85%95+%26+%EB%B0%98%EA%B0%80%EC%9B%8C
서버가 정확히 "안녕 & 반가워" 로 디코딩해서 받습니다. keyword에 검색어 &page=1처럼 악의적인 문자열이 들어와도 다른 파라미터를 덮어쓰지 못해요. 인코딩 + 보안 + 가독성 세 가지가 한 번에 해결 되는 것이지용~
확인하실 때는 콘솔에 console.log(${BASE_URL}/articles?${query}) 한 줄 찍어보시면 실제로 어떤 URL이 만들어지는지 보입니다만, 지금 단계에서는 '이런 도구가 있다' 정도만 인지하고 다음에 시도해보셔도 충분합니다.
There was a problem hiding this comment.
indexstyle.css 라인 82의 width: 9.5625rem !important; — !important는 cascade를 강제로 깨버려서 나중에 반응형 등으로 로고 크기를 바꾸려고 할 때 다른 규칙이 안 먹히게 만들어요. 우선순위가 필요하면 셀렉터의 specificity(예: .gnb .logoimg)로 해결하시는 편이 안전합니다.
There was a problem hiding this comment.
indexstyle.css는 rem을 주로 쓰지만 loginstyle.css와 signupstyle.css는 px를 주로 씁니다.
한 프로젝트 안에서는 한 단위로 통일하는 게 가독성과 유지보수에 좋아요. (특히 사용자 글자 크기 설정을 고려하면 rem이 더 접근성 친화적입니다.)
스프린트 미션 요구사항 체크리스트
v3
공통 (API 구현 및 비동기 처리)
getArticleList(): GET 메서드를 사용해 주세요. (page, pageSize, keyword 쿼리 파라미터 이용)getArticle(): GET 메서드를 사용해 주세요.createArticle(): POST 메서드를 사용해 주세요. (request body에 title, content, image 포함)patchArticle(): PATCH 메서드를 사용해 주세요.deleteArticle(): DELETE 메서드를 사용해 주세요..then()메서드를 이용하여 비동기 처리를 해주세요..catch()를 이용하여 오류 처리를 해주세요.getProductList(): GET 메서드를 사용해 주세요. (page, pageSize, keyword 쿼리 파라미터 이용)getProduct(): GET 메서드를 사용해 주세요.createProduct(): POST 메서드를 사용해 주세요. (request body에 name, description, price, tags, images 포함)patchProduct(): PATCH 메서드를 사용해 주세요.deleteProduct(): DELETE 메서드를 사용해 주세요.async/await을 이용하여 비동기 처리를 해주세요.try/catch를 이용하여 오류 처리를 해주세요.ProductService.js파일에 Product API 관련 함수들을 작성해 주세요.ArticleService.js파일에 Article API 관련 함수들을 작성해 주세요.main.js파일에 작성하고 import를 활용해 주세요.로그인 / 회원가입 페이지 공통 (유효성 검증)
#3692FF입니다.로그인 페이지
USER_DATA)에 없거나 비밀번호가 틀린 경우, '비밀번호가 일치하지 않습니다.' alert 표시회원가입 페이지
USER_DATA)에 존재하는 경우, '사용 중인 이메일입니다' alert 표시심화 요구사항
랜딩 페이지 & 공통 메타 태그
반응형 디자인 (랜딩 페이지)
1199px / Mobile: 375px743px)반응형 디자인 및 컴포넌트 기능 (로그인 / 회원가입)
배포 링크
멘토님에게 남길 메시지
아직 코드 작성에 미숙하다보니 놓치는 부분이 많았는데 세세한 부분까지 다 피드백해주셔서 도움이 정말 많이 됐습니다! 이번 미션이 워낙 어려웠어서 코드가 들쑥날쑥한 점 미리 죄송하고 이번에도 잘부탁드립니다!
반응형 디자인이 제대로 구현이 되었는지 자세히 봐주시면 감사하겠습니다!