Skip to content

Conversation

@fiivxyxxng
Copy link
Collaborator

@fiivxyxxng fiivxyxxng commented Mar 16, 2025

요구사항

기본

공통

  • Github에 스프린트 미션 PR을 만들어 주세요.
  • React, Express를 사용해 진행합니다.

프론트엔드 구현 요구사항

랜딩 페이지

  • HTML과 CSS로 구현한 랜딩페이지를 React로 마이그레이션하세요.
  • 랜딩 페이지 url path는 "/"로 설정하세요.

중고마켓 페이지

  • 중고마켓 페이지 url path를 "/items"으로 설정하세요.
  • 페이지 주소가 "/items" 일 때 상단내비게이션바의 "중고마켓" 버튼의 색상은 "3692FF"입니다.
  • 중고마켓 페이지 판매 중인 상품은 본인이 만든 GET 메서드를 사용해 주세요.
    • 다만 좋아요 순 정렬 기능은 제외해 주세요.
    • 사진은 디폴트 이미지로 프론트엔드에서 처리해주세요.
    • 베스트 상품 목록 조회는 구현하지 않습니다.
  • '상품 등록하기' 버튼을 누르면 "/registration" 로 이동합니다. ( 빈 페이지 )

상품 등록 페이지

  • PC, Tablet, Mobile 디자인에 해당하는 상품 등록 페이지를 만들어 주세요.
  • 상품 등록 url path는 "/registration"입니다.
  • 상품 등록은 본인이 만든 POST 메서드를 사용해 주세요.
  • 등록 성공 시, 해당 상품 상세 페이지로 이동합니다. (빈페이지)

백엔드 구현 요구사항

중고마켓

  • Product 스키마를 작성해 주세요.
    • id, name, description, price, tags, createdAt, updatedAt필드를 가집니다.
    • 필요한 필드가 있다면 자유롭게 추가해 주세요.
  • 상품 등록 API를 만들어 주세요.
    • name, description, price, tags를 입력하여 상품을 등록합니다.
  • 상품 상세 조회 API를 만들어 주세요.
    • id, name, description, price, tags, createdAt를 조회합니다.
  • 상품 수정 API를 만들어 주세요.
    • PATCH 메서드를 사용해 주세요.
  • 상품 삭제 API를 만들어 주세요.
  • 상품 목록 조회 API를 만들어 주세요.
    • id, name, price, createdAt를 조회합니다.
    • offset 방식의 페이지네이션 기능을 포함해 주세요.
    • 최신순(recent)으로 정렬할 수 있습니다.
    • name, description에 포함된 단어로 검색할 수 있습니다.
  • 각 API에 적절한 에러 처리를 해 주세요.
  • 각 API 응답에 적절한 상태 코드를 리턴하도록 해 주세요.
  • .env 파일에 환경 변수를 설정해 주세요.
  • CORS를 설정해 주세요.
  • render.com로 배포해 주세요.
  • MongoDB를 활용해 주세요.

심화

프론트엔드 구현 요구사항

상품 등록 페이지

  • 모든 입력 input box에 빈 값이 있을 경우, 등록 버튼이 비활성화됩니다.
  • 태그를 입력한 후 엔터키를 누르면, 그 태그가 칩 형태로 쌓입니다.
  • 상품명, 상품 소개, 판매 가격, 태그에 대한 유효성 검사 Custom Hook을 만들어주세요. 유효성 검사를 통과하지 않을 경우, 각 input에 빨간색 테두리와, 각각의 Input 아래에 빨간색 에러 메시지를 보여주세요.
    • 유효한 조건
      • 상품명: 1자 이상, 10자 이내
      • 상품 소개: 10자 이상, 100자 이내
      • 판매 가격: 1자 이상, 숫자
      • 태그: 5글자 이내

주요 변경사항

스크린샷

image image image

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@fiivxyxxng fiivxyxxng requested a review from reach0908 March 16, 2025 15:47
@fiivxyxxng fiivxyxxng changed the title [오하영] Sprint 6 [오하영] Sprint6 Mar 16, 2025
Copy link

@reach0908 reach0908 left a comment

Choose a reason for hiding this comment

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

미션하시느라 고생많으셨습니다!

백엔드는 별도의 레포지터리로 옮기시면 좋을 것 같습니다!

추가로 이렇게 폴더를 옮기는 경우 변경사항들이 기록되지 않는 경우가 있는데, 폴더 옮기기만 진행한 후 커밋을 한번하고 그 뒤에 파일들을 수정하시면 파일 diff가 잘 기록됩니다!

모바일에서 상단 헤더때문에 레이아웃이 깨지는 문제가 발생하고 있습니다!

Comment on lines 3 to 15
export const handleError = async (url, options = {}) => {
try {
const res = await fetch(url, options);
if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`);
}
const body = await res.json();
return body;
} catch (e) {
console.error("Failed to fetch products:", e);
throw e;
}
};

Choose a reason for hiding this comment

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

[P1]
공통되는 로직을 분리한 것은 좋았으나 네이밍을 변경하면 좋을 것 같습니다.
해당 로직에서 에러만 핸들링하는 것이 아닌 fetch의 기능도 포함하고 있어 오해할 수도 있을 것 같습니다!

};

return (
<form onSubmit={handleAddItem}>

Choose a reason for hiding this comment

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

form 활용 좋습니다!

Comment on lines 55 to 60
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="상품명을 입력해주세요"
/>

Choose a reason for hiding this comment

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

[P2]
input 태그의 속성을 활용한 validation 방법에 대해서 알아보시면 좋을 것 같습니다!

Comment on lines 10 to 27
useEffect(() => {
const updatePageSize = () => {
if (window.matchMedia("(max-width: 743px)").matches) {
setPageSize(BEST_PAGE_SIZE.mobile);
} else if (window.matchMedia("(max-width: 1199px)").matches) {
setPageSize(BEST_PAGE_SIZE.tablet);
} else {
setPageSize(BEST_PAGE_SIZE.desktop);
}
};

updatePageSize();

window.addEventListener("resize", updatePageSize);
return () => {
window.removeEventListener("resize", updatePageSize);
};
}, []);

Choose a reason for hiding this comment

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

[P1]
커스텀 훅으로 빼보면 좋을 것 같습니다!

Comment on lines 37 to 49
{bestItems.map((item) => (
<div key={item.id}>
<img className={styles.img} src={defaultImg} alt={item.name} />
<div className={styles.description}>
<div className={styles.name}>{item.name}</div>
<div className={styles.price}>{item.price}원</div>
<div className={styles.favoriteCount}>
<img src={unheartIcon} alt="좋아요 아이콘" />{" "}
{item.favoriteCount}
</div>
</div>
</div>
))}

Choose a reason for hiding this comment

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

[P0]
bestItems의 배열의 길이가 0일때의 처리를 해주면 좋을 것 같습니다.

@@ -0,0 +1,79 @@
import { useEffect, useState } from "react";
import { getProducts } from "../../api/index";
import { PAGE_SIZE, ORDER } from "../../constants";

Choose a reason for hiding this comment

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

good!

Comment on lines 42 to 55
useEffect(() => {
updatePageSize();
fetchSortedData({ page, pageSize, order, keyword });

window.addEventListener("resize", updatePageSize);
return () => {
window.removeEventListener("resize", updatePageSize);
};
}, [page, pageSize, order, keyword]);

const onPageChange = (pageNum) => {
setPage(pageNum);
fetchSortedData({ page: pageNum, pageSize, order, keyword });
};

Choose a reason for hiding this comment

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

[P0]
이 부분이 어차피 page가 바뀌면서 useEffect로 인해 fetchSortedData 한번 요청이 될 것 같은데, 그 과정에서 onPageChage 하는 부분에서도 또 fetch를 하고 있어 중복으로 요청이 되지 않는지 확인이 필요합니다!

@@ -0,0 +1,16 @@
export const PAGE_SIZE = {

Choose a reason for hiding this comment

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

[P2]
별도의 파일로 분리 잘하신 것 같습니다. 다만 도메인 단위로 constants/page.constant.js 등과 같이 별도의 파일로 관리해보는 것도 좋을 것 같아요!


const app = express();
app.use(express.json());
app.use(cors());

Choose a reason for hiding this comment

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

[P0]
이렇게 설정하면 모든 URL에 대해 허용을 해주게 됩니다. 자신이 사용하는 프론트엔드 레포만 허용해줄 수 있도록 설정해주세요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants