Skip to content

Conversation

@suuuuya
Copy link
Collaborator

@suuuuya suuuuya commented Sep 21, 2025

URL

배포 주소 | 판다마켓

요구사항

기본

중고마켓

  • 중고마켓 페이지 주소는 “/items” 입니다.
  • 페이지 주소가 “/items” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다.
  • 상단 네비게이션 바는 이전 미션에서 구현한 랜딩 페이지와 동일한 스타일로 만들어 주세요.
  • 상품 데이터 정보는 https://panda-market-api.vercel.app/docs/#/ 에 명세된 GET 메소드 “/products” 를 사용해주세요.
  • '상품 등록하기' 버튼을 누르면 “/additem” 로 이동합니다. ( 빈 페이지 )
  • 전체 상품에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.

중고마켓 반응형

  • 베스트 상품
    Desktop : 4개 보이기
    Tablet : 2개 보이기
    Mobile : 1개 보이기
  • 전체 상품
    Desktop : 12개 보이기
    Tablet : 6개 보이기
    Mobile : 4개 보이기

심화

  • 페이지 네이션 기능을 구현합니다.

주요 변경사항

  • 페이지별 타이틀 관리
  • og 이미지, 파비콘, 메타 태그 정리
  • hover, click(드롭다운) 인터랙션 효과

스크린샷

pc tablet mobile
image image image

멘토에게

  • 검토해주셔서 감사합니다. ☺️
  • .gitignore에 .env 파일을 추가하는 걸 깜빡해서 .env 파일이 실수로 레포지토리에 올라갔습니다 ㅠㅠ

@suuuuya suuuuya requested a review from kiJu2 September 21, 2025 01:47
@suuuuya suuuuya self-assigned this Sep 21, 2025
@suuuuya suuuuya added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Sep 21, 2025
@kiJu2
Copy link
Collaborator

kiJu2 commented Sep 22, 2025

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

@kiJu2
Copy link
Collaborator

kiJu2 commented Sep 22, 2025

검토해주셔서 감사합니다. ☺️

😊 🙇‍♂️🙇‍♂️

.gitignore에 .env 파일을 추가하는 걸 깜빡해서 .env 파일이 실수로 레포지토리에 올라갔습니다 ㅠㅠ

하핳 괜찮습니다.
요거 이미 깃이 트래킹중이어서 뒤늦게 .gitignore에 추가하셔도 계속 트래킹할거예요 !
쉽게 하시려면 .env 삭제하시고 커밋하신 후에 다시 .env 추가하시면 될겁니다 😊

Comment on lines +10 to +19
<meta property="og:type" content="website" />
<meta property="og:site_name" content="판다마켓" />
<meta property="og:title" content="판다마켓" />
<meta property="og:description" content="일상의 모든 물건을 거래해보세요" />
<meta property="og:image" content="/og-img.png" />
<!-- Favicon -->
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png" />
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 +3 to +18
const getItems = async ({ page = 1, pageSize = 10, orderBy = "recent", keyword = "" }) => {
try {
const { data } = await instance.get("/products", {
params: {
page,
pageSize,
orderBy,
keyword,
},
});
return data;
} catch (error) {
console.error(error);
throw error;
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

훌륭합니다 ! 더 할 말이 없군요 ! 👍

파라메터의 기본값도 적절하고, 받아야 할 파라메터도 필요한 것들만 잘 받고 있네요.
axiosparams 활용도 좋고 try ... catch를 통한 예외처리도 ! ㅎㅎㅎ

너무 칭찬만 했는데.. 칭찬 받을 명분이 충분하셨습니다 👍

Comment on lines +7 to +56
const CardItemWrapper = styled.li`
position: relative;
width: 100%;
`;

const CardThumbnail = styled.div`
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 16px;
overflow: hidden;
transition: all 0.2s;
& img {
width: 100%;
height: 100%;
object-fit: cover;
}
${CardItemWrapper}:hover & {
opacity: 0.8;
}
${Midia("sm")} {
border-radius: 12px;
}
`;

const CardInfo = styled.div`
margin-top: 16px;
display: grid;
gap: 6px 0;
word-break: break-all;
`;

const CardTitle = styled.div`
${typography["text-md-medium"]};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

const CardPrice = styled.div`
${typography["text-lg-bold"]};
`;

const CardLikes = styled.div`
display: flex;
align-items: center;
gap: 0 4px;
& .count {
${typography["text-xs-medium"]};
}
`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 .. 이런게 styled-components의 매력인 것 같아요.

스타일과 컴포넌트의 영역이 명확히 구분되어서 가독성도 좋고, 자바스크립트를 유연하게 사용할 수 있는 점이 참 매력적으로 느껴지네요.
잘 활용하고 계십니다 👍

Comment on lines +62 to +63
const CardItem = ({ images, name, price, favoriteCount }) => {
const thumbnailUrl = images && images.length > 0 ? images[0] : defaultImg;
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 컴포넌트는 images가 필요한 것이 아닌 thumbnail이 필요한 것 같군요?

그렇다면 다음과 같이 바꿔볼 수 있을거예요:

Suggested change
const CardItem = ({ images, name, price, favoriteCount }) => {
const thumbnailUrl = images && images.length > 0 ? images[0] : defaultImg;
const CardItem = ({ thumbnail = defaultImg, name, price, favoriteCount }) => {

그리고 해당 컴포넌트를 호출하는 곳에서 결정해줄 수 있어요 😉:

   <CardItem thumbnail={(images && images.length > 0) && images[0]}

이렇게 하면 카드 컴포넌트가 진짜 필요한 매개인자로 구성할 수 있겠군요 !

Comment on lines +3 to +19
const useClickOutside = (ref, onClickOutside) => {
useEffect(() => {
const handleClickOutside = e => {
if (!ref.current || ref.current.contains(e.target)) {
return;
}
onClickOutside(e);
};

document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("touchstart", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("touchstart", handleClickOutside);
};
}, [ref, onClickOutside]);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으~~~~ 재사용성을 고려한 커스텀 훅 !

이렇게 인터랙션을 훅으로 담아두니까 모달, 드롭다운, 팝오버 등 여러 곳에서 사용되기 너무 좋을 것 같아요.
재사용성을 위해서 커스텀 훅을 설계하시다니.
미션 5를 진행하시는게 아까울 정도네요. 😂😂😂

Comment on lines +8 to +33
const useResponsiveView = () => {
const [view, setView] = useState(() => {
const width = window.innerWidth;
if (width <= BREAKPOINTS.mobileMax) return "mobile";
if (width <= BREAKPOINTS.tabletMax) return "tablet";
return "desktop";
});

useEffect(() => {
const handleResize = () => {
const width = window.innerWidth;
if (width <= BREAKPOINTS.mobileMax) {
setView("mobile");
} else if (width <= BREAKPOINTS.tabletMax) {
setView("tablet");
} else {
setView("desktop");
}
};

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

return view;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

useResponsiveView도 우리 판다마켓에서 재사용성이 좋아보이네요 👍👍👍

Comment on lines +6 to +8
const toggle = useCallback(() => setState(prev => !prev), []);
const setOn = useCallback(() => setState(true), []);
const setOff = useCallback(() => setState(false), []);
Copy link
Collaborator

Choose a reason for hiding this comment

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

setter(setState)를 그대로 반환하지 않고 핸들러로 감싸서 반환했군요 ! 👍

훌륭합니다. 이렇게 콜백으로 감싸서 전달하면 추 후 유지관리에 용이할거예요. 👍👍👍

Comment on lines +10 to +14
const ITEMS_DISPLAY_COUNT = {
desktop: 10,
tablet: 6,
mobile: 4,
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

굿굿. 컴포넌트와 관련없는 상수 및 변수를 컴포넌트 바깥에 선언하셨군요 !

훌륭합니다. 전반적으로 연수님은 리액트 활용에 대해서 잘 적응하신 것으로 보이는군요 ! 👍

@kiJu2
Copy link
Collaborator

kiJu2 commented Sep 22, 2025

간만에 연수님 코드를 보네요 ㅎㅎㅎ 반가운 마음으로 리뷰하였습니다. 🤣
칭찬만 하다가 끝났군요.... 더 도움 드릴 팁이 있을까 열심히 찾아봤는데 잘한 점만 눈에 띄었습니다 😊😊😊
지금처럼 정진합시다 ! 미션 수행하시느라 정말 수고 많으셨어요 ㅎㅎㅎ

@kiJu2 kiJu2 merged commit f21bde2 into codeit-bootcamp-frontend:React-이연수 Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants