Skip to content

[노현지] Sprint7 #69

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Conversation

hyeonjiroh
Copy link
Collaborator

@hyeonjiroh hyeonjiroh commented Feb 24, 2025

요구사항

기본

상품 상세

  • 상품 상세 페이지 주소는 "/items/{productId}" 입니다.
  • response 로 받은 아래의 데이터로 화면을 구현합니다.
    • favoriteCount : 하트 개수
    • images : 상품 이미지
    • tags : 상품태그
    • name : 상품 이름
    • description : 상품 설명
  • 목록으로 돌아가기 버튼을 클릭하면 중고마켓 페이지 주소인 "/items" 으로 이동합니다

상품 문의 댓글

  • 문의하기에 내용을 입력하면 등록 버튼의 색상은 "3692FF"로 변합니다.
  • response 로 받은 아래의 데이터로 화면을 구현합니다
    • image : 작성자 이미지
    • nickname : 작성자 닉네임
    • content : 작성자가 남긴 문구
    • description : 상품 설명
    • updatedAt : 문의글 마지막 업데이트 시간

심화

  • 모든 버튼에 자유롭게 Hover효과를 적용하세요.

주요 변경사항

  • App.jsx
    • 현재 경로를 기반으로 인증 페이지 및 홈 페이지 여부를 판별하는 변수 추가
  • AllItemsSection.jsx
    • handleLoad 함수를 useEffect 안에 선언
    • 매직 넘버들을 상수로 정의
    • 장식용 이미지에는 alt=""로 수정
  • BestItemsSection.jsx
    • handleLoad 함수를 useEffect 안에 선언
    • 매직 넘버들을 상수로 정의
  • ItemCard.jsx
    • 장식용 이미지에는 alt=""로 수정
  • RegisterItemPage.jsx
    • 장식용 이미지에는 alt=""로 수정
    • 컴포넌트 분리
      • ImageUploader.jsx
    • image 등록되었을 때 등록 버튼 활성화되도록 변경
  • 공통 컴포넌트 생성
    • PrimaryButton.jsx
    • DeleteButton.jsx
    • MenuButton.jsx
  • 유틸 파일 생성
    • breakpoints를 저장해놓은 breakpoints.js
    • 유효성 검사 함수들을 모아놓은 validation.js
    • dateTimeUtils.js에 시간 계산 함수 추가
  • 랜딩 페이지 생성
  • 로그인 페이지 생성
  • 가입 페이지 생성

멘토에게

  • 랜딩 페이지와 로그인, 가입 페이지는 이후에 컴포넌트 분리 시도해보겠습니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@hyeonjiroh hyeonjiroh requested a review from kiJu2 February 24, 2025 05:57
@hyeonjiroh hyeonjiroh self-assigned this Feb 24, 2025
@hyeonjiroh hyeonjiroh added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Feb 24, 2025
@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 25, 2025

스프리트 미션 하시느라 수고 많으셨어요.
학습에 도움 되실 수 있게 꼼꼼히 리뷰 하도록 해보겠습니다. 😊

}

export async function getItemComments(productId = "", { cursor = 0 }) {
const query = `limit=${COMMENTS_LIMIT}&cursor=${cursor}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

쿼리는 URLSearchParams로 손쉽게 사용할 수 있어요 !

Suggested change
const query = `limit=${COMMENTS_LIMIT}&cursor=${cursor}`;
const query = new URLSearchParams({cursor, limit: COMMENTS_LIMIT, });

URLSearchParams와 함께 객체로 손쉽게 핸들링할 수 있습니다 !
객체로 구성할 수 있어 가독성이 좋고, URL 인코딩을 자동으로 처리하여 특수 문자나 공백이 포함된 값에서도 안전하게 동작합니다 !

URLSearchParams: URLSearchParams 인터페이스는 URL의 쿼리 문자열을 대상으로 작업할 수 있는 유틸리티 메서드를 정의합니다.

Comment on lines +4 to +16
function PrimaryButton({
children,
className = "",
onClick = () => {},
disabled = false,
}) {
const buttonClass = `${styles.button} ${className}`;
return (
<button className={buttonClass} onClick={onClick} disabled={disabled}>
{children}
</button>
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

(심화/제안/고민해보기) 혹시 버튼 컴포넌트들을 공통 버튼 컴포넌트로 리팩토링 할 수는 없을까요?

MenuButton, DeleteButton, PrimaryButton.. 더 나아가서 SecondaryButton, UpdateButton 등등 목적에 따라 버튼이 생겨날 수 있을 것 같아요.
만약 디자인 시스템이 바뀌어서 일괄적으로 버튼의 radius를 바꿔야하는 상화이 온다면 유지보수하기가 어려워질 수 있겠지요?

예를 들어서 다음과 같은 컴포넌트를 만들어볼 수 있을 것 같아요:

import styles from "./Button.module.css";

function Button({
  children,
  variant = "primary",
  icon,
//  onClick = () => {},
  className = "",
//  disabled = false,
  ...rest
}) {
  return (
    <button
      className={`${styles.button} ${styles[variant]} ${className}`}
//      onClick={onClick}
//      disabled={disabled} 해당 내용들은 `rest`에 자동 등재
      {...rest}
    >
      {icon && <img src={icon} alt="" className={styles.icon} />}
      {children}
    </button>
  );
}

export default Button;

Copy link
Collaborator

Choose a reason for hiding this comment

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

방금 제안드린 내용에서 추가

사실, 여기서 icon 또한 buttonchildren으로 포함시킬 수도 있긴 하겠네요 😊
예를 들어서 다음과 같이요 !:

<Button variant="error" className="flex justify-between w-24 items-center">삭제<DeleteIcon /></Button>

@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import debounce from "lodash.debounce";

function useWindowSize() {
function useWindowSize(delay = 300) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 ~ 피드백 반영 좋습니다 현지님 👍

Comment on lines +52 to +58
<h2 className={styles.featureTag}>Search</h2>
<h3 className={styles.bold}>구매를 원하는 상품을 검색하세요</h3>
<p className={styles.featureDescription}>
구매하고 싶은 물품은 검색해서
<br />
쉽게 찾아보세요
</p>
Copy link
Collaborator

Choose a reason for hiding this comment

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

h 시리즈가 순차적으로 잘 들어갔네요 👍

Comment on lines +37 to +49
<div className={`${styles.features} ${styles.wrapper}`}>
<div className={styles.feature}>
<img src={HomeImg1} width="68.5%" alt="인기 상품" />
<div className={styles.featureContent}>
<h2 className={styles.featureTag}>Hot item</h2>
<h3 className={styles.bold}>인기 상품을 확인해 보세요</h3>
<p className={styles.featureDescription}>
가장 HOT한 중고거래 물품을
<br />
판다 마켓에서 확인해 보세요
</p>
</div>
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안/선택) section을 사용하셔도 될 것 같아요.

Suggested change
<div className={`${styles.features} ${styles.wrapper}`}>
<div className={styles.feature}>
<img src={HomeImg1} width="68.5%" alt="인기 상품" />
<div className={styles.featureContent}>
<h2 className={styles.featureTag}>Hot item</h2>
<h3 className={styles.bold}>인기 상품을 확인해 보세요</h3>
<p className={styles.featureDescription}>
가장 HOT한 중고거래 물품을
<br />
판다 마켓에서 확인해 보세요
</p>
</div>
</div>
<section className={`${styles.features} ${styles.wrapper}`}>
<div className={styles.feature}>
<img src={HomeImg1} width="68.5%" alt="인기 상품" />
<div className={styles.featureContent}>
<h2 className={styles.featureTag}>Hot item</h2>
<h3 className={styles.bold}>인기 상품을 확인해 보세요</h3>
<p className={styles.featureDescription}>
가장 HOT한 중고거래 물품을
<br />
판다 마켓에서 확인해 보세요
</p>
</div>
</section>

영역이 구분되며 h로 각 섹션의 정체성이 명확하므로 section태그도 괜찮은 의미일 것 같아서 제안드려요:

다음은 MDN<section>에 대한 설명 중 첫 문장입니다.

The <section> HTML element represents a generic standalone section of a document, which doesn't have a more specific semantic element to represent it. Sections should always have a heading, with very few exceptions.

<section> HTML 요소는 문서의 일반적인 독립형 섹션을 나타내며 이를 나타내는 더 구체적인 의미 요소가 없습니다. 섹션에는 거의 예외를 제외하고 항상 제목이 있어야 합니다.

Comment on lines +8 to +14
function ItemCommentCard({
comment: {
content,
updatedAt,
writer: { nickname, image },
},
}) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 이렇게 props를 받으니까 comment가 어떤 구성으로 되어있는지 파악하기 좋네요 👍👍

Comment on lines +95 to +115
{isEditMode ? (
<div className={styles.buttonContainer}>
<button
type="button"
className={styles.cancelButton}
onClick={handleEditCancel}
>
취소
</button>
<PrimaryButton
type="submit"
className={styles.editCompleteButton}
disabled={!editAvailable}
onClick={handleEditComplete}
>
수정 완료
</PrimaryButton>
</div>
) : (
""
)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 작성해볼 수 있습니다 !:

Suggested change
{isEditMode ? (
<div className={styles.buttonContainer}>
<button
type="button"
className={styles.cancelButton}
onClick={handleEditCancel}
>
취소
</button>
<PrimaryButton
type="submit"
className={styles.editCompleteButton}
disabled={!editAvailable}
onClick={handleEditComplete}
>
수정 완료
</PrimaryButton>
</div>
) : (
""
)}
{isEditMode && (
<div className={styles.buttonContainer}>
<button
type="button"
className={styles.cancelButton}
onClick={handleEditCancel}
>
취소
</button>
<PrimaryButton
type="submit"
className={styles.editCompleteButton}
disabled={!editAvailable}
onClick={handleEditComplete}
>
수정 완료
</PrimaryButton>
</div>
)}

Comment on lines +19 to +30
const handleLoad = async () => {
try {
const { list, nextCursor } = await getItemComments(productId, {
cursor,
});
setComments((prev) => [...prev, ...list]);
setCursor(nextCursor);
} catch (error) {
alert(error.message);
console.error("ERROR: ", error);
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 함수는 useCallback을 사용해볼 수 있습니다 !:

const handleLoad = useCallback(async () => {
  try {
    const { list, nextCursor } = await getItemComments(productId, { cursor });
    setComments((prev) => [...prev, ...list]);
    setCursor(nextCursor);
  } catch (error) {
    alert(error.message);
    console.error("ERROR: ", error);
  }
}, [productId, cursor]); // ✅ 의존성 추가

@kiJu2
Copy link
Collaborator

kiJu2 commented Feb 25, 2025

수고하셨습니다 현지님 !
언제나 꾸준히 성장하시는 현지님 😊
피드백을 항상 잘 수용하시기에 이번엔 약간 심화가 될 수 있을만한 공통 컴포넌트를 제안드려봤어요 !
재밌는 도전이 될 수 있을 것 같아요 !

미션 수행하시느라 수고 많으셨습니다 현지님 ! 😊👍

@kiJu2 kiJu2 merged commit d9fb42d into codeit-bootcamp-frontend:React-노현지 Feb 25, 2025
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants