Skip to content

[백은결] sprint5#75

Open
eungyeolbaek wants to merge 7 commits intocodeit-sprint-fullstack:react-백은결from
eungyeolbaek:react-백은결-sprint5

Hidden character warning

The head ref may contain hidden characters: "react-\ubc31\uc740\uacb0-sprint5"
Open

[백은결] sprint5#75
eungyeolbaek wants to merge 7 commits intocodeit-sprint-fullstack:react-백은결from
eungyeolbaek:react-백은결-sprint5

Conversation

@eungyeolbaek
Copy link
Collaborator

@eungyeolbaek eungyeolbaek commented Jan 18, 2026

요구사항

기본

공통

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

프론트엔드 구현 요구사항

랜딩 페이지

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

중고마켓 페이지

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

상품 등록 페이지

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

심화

프론트엔드 구현 요구사항

상품 등록 페이지

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

🔧 주요 변경사항

  • 기존 HTML/CSS 랜딩 페이지를 React 컴포넌트로 마이그레이션 (HomePage)

  • /items 페이지 구현 (ItemPage) – 판매 중인 상품 목록, 검색, 페이지네이션 포함

  • /registration 상품 등록 페이지 구현 (RegistrationPage) – 입력 폼, 태그 입력, 등록 버튼 UI 포함

  • CSS 모듈 기반 스타일 적용 및 반응형 레이아웃 구성

  • 상품 등록 폼 유효성 검사 훅(useFormValidation) 적용 및 에러 표시

  • 등록 버튼 활성/비활성 상태 처리 및 스타일 적용


💬 멘토님께 남기는 메시지

  • 도메인 기준으로 폴더구조를 정리해 보았습니다.
  • 상품 등록 페이지(<RegistrationPage>)에서는 요구사항에 따라 직접 구현한 POST 메서드를 사용해 상품 등록 로직을 작성했습니다. 다만 현재 로그인 기능이 구현되지 않아 인증 토큰이 없어 서버에서 401 에러가 발생하는 상황입니다. 요구사항인 “등록 성공 시 상품 상세 페이지로 이동”을 만족하기 위해, 임시로 fakeProductId를 사용해 상세 페이지(빈 페이지)로 이동하도록 처리해 두었습니다.
  • 태그 입력은 필수 사항이 아니라는 판단이 들어, 태그가 없더라도 다른 입력값이 모두 유효할 경우 등록 버튼이 활성화되도록 구현했습니다.
  • 반응형 구현 과정에서 clamp, flex-basis, order 등의 CSS 속성을 처음 사용해 보았습니다. 반응형 레이아웃을 구현할 때 실무에서 자주 사용되는 CSS 속성이나, 알아두면 좋은 패턴에는 어떤 것들이 있는지 궁금합니다.

@eungyeolbaek eungyeolbaek self-assigned this Jan 18, 2026
@eungyeolbaek eungyeolbaek added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요! labels Jan 18, 2026
@@ -0,0 +1,52 @@
const BASE_URL = 'https://panda-market-api.vercel.app';
Copy link
Collaborator

Choose a reason for hiding this comment

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

BASE_URL을 환경변수로 분리하면 배포 환경에 따라 API 서버 주소를 쉽게 변경할 수 있습니다.

const BASE_URL = import.meta.env.VITE_API_URL || 'https://panda-market-api.vercel.app';

const { productName, productDescription, productPrice } = form;

const errors = {};
let isNotError = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

isNotError는 이중 부정(not + error)으로 가독성이 떨어집니다. isValid로 변경하면 더 직관적입니다.

return { errors, isValid: Object.keys(errors).length === 0 };

const errors = {};
let isNotError = true;

if (productName && productName.length > 10) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

상품명 유효성 검사에서 1자 이상 조건이 누락되어 있습니다. (요구사항: 1자 이상, 10자 이내)

@@ -0,0 +1,39 @@
import clsx from 'clsx';
Copy link
Collaborator

Choose a reason for hiding this comment

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

파일명에 오타가 있습니다: OrderByButtton.jsxOrderByButton.jsx (t가 3개)

파일명, CSS 모듈명, index.js export도 함께 수정해주세요.

export function OrderByButtton() {
const [isOpen, setIsOpen] = useState(false);

const handleSelect = () => setIsOpen(false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

handleSelect 함수가 인자를 받지만 사용하지 않고 있습니다. 정렬 기능이 미구현 상태입니다.

<div className={styles.imgWrapper}>
<img src={defaultProductImg} alt={title} className={styles.productImg} />
</div>

Copy link
Collaborator

Choose a reason for hiding this comment

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

상품 클릭 시 상세 페이지로 이동해야 하지만 href가 비어있습니다. id prop을 받아서 /items/${id}로 이동하도록 수정해주세요.

const { currentPage, totalPages, setTotalItems, goToPage, next, prev } =
usePagination();

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

API 호출 실패 시 에러 처리가 없습니다. try-catch로 감싸고 에러 상태를 관리해주세요.

try {
  const result = await getProductList({ ... });
  setProducts(result.list);
} catch (error) {
  console.error('상품 목록 조회 실패:', error);
}

return;
}

setTags((prev) => [...prev, value]); // 배열에 태그 추가
Copy link
Collaborator

Choose a reason for hiding this comment

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

동일한 태그가 중복으로 추가될 수 있습니다. 중복 체크 로직을 추가해주세요.

if (tags.includes(value)) {
  setForm((prev) => ({ ...prev, productTag: '' }));
  return;
}

const navigate = useNavigate();

const handleSubmit = async () => {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

주석 처리된 코드가 남아있습니다. TODO 주석으로 명확히 표시해주세요.

// TODO: 로그인 기능 구현 후 실제 API 연동 필요
const fakeProductId = 1234;

return !isTextarea ? (
<div className={clsx(styles.container, error && styles.error)}>
<label htmlFor={id}>{label}</label>
<input
Copy link
Collaborator

Choose a reason for hiding this comment

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

inputtextarea의 wrapper 코드가 중복됩니다. 동적 컴포넌트로 리팩토링하면 코드가 간결해집니다.

const InputComponent = isTextarea ? 'textarea' : 'input';

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

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 최종제출 스프린트 미션 최종 제출 PR입니다. 코드리뷰 및 평가해주세요!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants