Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[FE] feat: 리뷰 그룹 목록 조회 API 연동 및 MSW 핸들러 수정 #1090

Open
wants to merge 36 commits into
base: develop
Choose a base branch
from

Conversation

soosoo22
Copy link
Contributor


🚀 어떤 기능을 구현했나요 ?

  • 리뷰 링크 페이지에서 회원이 생성한 리뷰 그룹 목록을 불러오는 API를 연동했습니다. 링크 생성 폼에서 새로운 링크를 생성하면 msw 환경에서도 이를 반영하도록 핸들러와 목 데이터를 수정했습니다.
  • API 요청했을 때, 성공적으로 리뷰 그룹 목록을 불러오는지에 대한 테스트를 작성했으며, 회원과 비회원에 따라 revieweeId 값이 달라지므로, 비회원일 경우 revieweeIdnull로 반환되는지 확인하는 테스트를 추가했습니다.
  • NavigationTab은 로그인 상태일 때만 표시되도록 처리했으며, Breadcrumb은 로그인 상태에서는 보이지 않도록 수정했습니다. ‼️ 아직 로그인 기능이 완전히 구현되지 않아, 로그인 상태를 가정하는 임시 변수를 만들어 진행했습니다. ‼️
  NavigationTab Breadcrumb BackButton
로그인 상태 O(홈, 연결 페이지 제외하고 모든 페이지에서 표시) X O (목록, 모아보기, 상세 페이지에서 표시)
비로그인 상태 X O X
스크린샷 2025-02-12 오후 5 32 23

ReviewLinkItem

  • 목록 페이지작성한 리뷰 확인 페이지에서 사용하는 리뷰 아이템은 리뷰이가 포함되거나 키워드가 들어가는 등 차이점이 있어, 공통 컴포넌트로 재사용하기보다는 리뷰 링크 페이지에서 쓰이는 ReviewLinkItem 컴포넌트를 별도로 구현했습니다.
  • ResizeObserver를 사용해 ReviewLinkItem을 감싸는 컴포넌트에 스크롤이 생기면 padding-right: 2rem 추가했습니다.
스크린샷 2025-02-12 오후 6 14 37

🔥 어떻게 해결했나요 ?

  • 리뷰 링크 생성 폼에서 새로운 리뷰가 생성되면, 리뷰 링크 페이지에서 최신 리뷰 그룹 목록을 불러올 수 있도록 props로 함수를 전달했습니다. 사용자가 리뷰 링크를 생성하면 refetch()를 실행해서 최신 데이터를 불러오도록 했으며 생성된 리뷰 링크가 오른쪽에 ReviewLinkItem으로 즉시 추가되도록 구현했습니다.
    const ReviewLinkDashboard = () => {
  const { data: reviewLinks, refetch } = useGetReviewLinks();

  const navigate = useNavigate();

  // 새로운 리뷰 링크가 생성된 후, 최신 데이터를 다시 불러오기 위해 refetch() 실행
  const handleNewReviewLink = () => {
    refetch();
  };

  const handleReivewLinkItemClick = (reviewRequestCode: string) => {
    navigate(`/${ROUTE.reviewList}/${reviewRequestCode}`);
  };

  return (
    <S.ReviewLinkDashboardContainer>
      <S.FormSection>
        <URLGeneratorForm isMember={true} handleNewReviewLink={handleNewReviewLink} />
      </S.FormSection>
2025-02-12.5.52.09.mov

리뷰가 없을 경우

  • 생성한 리뷰 링크가 없을 경우, 단순히 돋보기 컴포넌트만 두게 되면 딱 봤을 때 좌우 여백 차이가 더 두드러져 보여서 border를 추가했습니다. props로 isBorder를 받게끔 수정했습니다.
  • 목록, 모아보기 페이지도 받은 리뷰가 없을 시, 널~이 아닌 돋보기 컴포넌트로 수정했습니다.
스크린샷 2025-02-12 오후 5 57 27 스크린샷 2025-02-12 오후 5 55 42

📝 어떤 부분에 집중해서 리뷰해야 할까요?

📚 참고 자료, 할 말

BadaHertz52 and others added 30 commits January 22, 2025 16:05
…up-api' into fe/feat/1080-reivew-link-page-api
@soosoo22 soosoo22 added this to the 7차 스프린트 milestone Feb 12, 2025
@soosoo22 soosoo22 self-assigned this Feb 12, 2025
@soosoo22 soosoo22 linked an issue Feb 12, 2025 that may be closed by this pull request
3 tasks
@soosoo22 soosoo22 changed the title feat: 리뷰 링크 페이지 API 연동 feat: 리뷰 그룹 목록 조회 API 연동 및 MSW 핸들러 수정 Feb 12, 2025
gettingSectionList: `${serverUrl}/${VERSION2}/sections`,
gettingGroupedReviews: (sectionId: number) =>
`${REVIEW_GROUP_API_URL}?${REVIEW_GROUP_API_PARAMS.queryString.sectionId}=${sectionId}`,
postingHighlight: `${serverUrl}/${VERSION2}/highlight`,
gettingReviewLinks: REVIEW_GROUP_DATA_API_URL,
Copy link
Contributor

Choose a reason for hiding this comment

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

gettingReviewLinks가 로그인한 사용자가 자신이 만든 리뷰 그룹 목록을 조회하는게 아닌가요?
자신이 만든 리뷰 그룹 목록을 조회하는거라면 다른 url로 요청을 보내야할 것 같은데요
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

리뷰 그룹 생성, 리뷰 그룹 목록 조회 url이 같아서 저 상수를 사용했어요.
REVIEW_GROUP_DATA_API_URL === https://api.review-me.page/v2/groups

Copy link
Contributor

@BadaHertz52 BadaHertz52 left a comment

Choose a reason for hiding this comment

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

쑤쑤 고생했어요.
굵직한 기능 위주로 코드를 봤어요. 스타일링 부분은 쑤쑤가 워낙 잘 하니 믿고 맡길게요 ㅎㅎㅎ

로그인 사용자 리뷰 링크 api url이 맞는 지 확인 부탁해요

refetch();
};

const handleReivewLinkItemClick = (reviewRequestCode: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

오타를 발견했어요 (Reivew -> Review)

Comment on lines +18 to +20
const handleNewReviewLink = () => {
refetch();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

리뷰 링크를 다시 불러오는 함수의 기능이 함수명에 담겼으면 좋겠어요. (예시: refetchReviewLinks)
리뷰 생성 폼에서 확인 시, 새로운 링크로 뭘한다는 거지?하고 의미 파악이 쉽지 않았어요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

제가 봐도 의미 파악이 너무 안 되네요.... 수정하겠습니다!🙋‍♀️

Copy link
Contributor

Choose a reason for hiding this comment

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

덧) refetch만 호출할 거라면 중괄호로 감싸지 않고 한 줄로 처리해도 좋을 것 같습니다~

};

// 새로 생성된 리뷰 링크를 목 데이터에 추가
REVIEW_LINKS.reviewGroups.push(newReviewLink);
Copy link
Contributor

Choose a reason for hiding this comment

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

대문자 스네이크는 코드 컨벤션에서 변하지 않는 상수를 의미하기 때문에, 배열을 변형하는 push 동작과 맞지 않아요.
리뷰 링크 생성에 따른, 리뷰 목록의 변경을 확인하고 싶어서 넣은 것 같아요.
그렇다면 ' REVIEW_LINKS'의 변수명을 바꿔야할 것 같아요.

Copy link
Contributor Author

@soosoo22 soosoo22 Feb 13, 2025

Choose a reason for hiding this comment

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

아....!!!! 맞네요... 꼼꼼하십니다!
수정하겠습니다!


// 비회원일 경우, revieweeId를 null로 변경
if (reviewRequestCode === nonMemberReviewRequestCode) {
REVIEW_GROUP_DATA.revieweeId = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

이것도 앞에서 언급한 이유로, 변수명을 바꿔야겠어요.

const url = `${window.origin}/${ROUTE.reviewZone}/${reviewRequestCode}`;

return (
<S.Layout onClick={handleClick}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Layout 태그가 div라서, 스크린 리더기를 사용하는 사용자 입장에서 클릭이 가능한지, 클릭하면 어떤 동작이 일어나는 지에 대한 정보가 없어요. 그리고 키보드 조작만으로는, 리뷰 카드 자체 클릭이 안돼요.

Copy link
Contributor

@ImxYJL ImxYJL Feb 14, 2025

Choose a reason for hiding this comment

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

상위 태그가 ul이라서 div -> li로 변경해야 할 것 같아요. 그리고 UndraggableWrapper로 감싸보면 어떨까요?

observer.observe(element);

return () => observer.disconnect();
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

cardListRef.current 을 useEffect의 deps에 넣지 않아도 괜찮은가요?

@chysis chysis changed the title feat: 리뷰 그룹 목록 조회 API 연동 및 MSW 핸들러 수정 [FE] feat: 리뷰 그룹 목록 조회 API 연동 및 MSW 핸들러 수정 Feb 14, 2025
Copy link
Contributor

@ImxYJL ImxYJL left a comment

Choose a reason for hiding this comment

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

여러 가지 코멘트를 남겼지만ㅋㅋㅋ 당장 반영해야 할 것은 별로 없고 금방 할 수 있는 것들이라 가볍게 봐주시고, 확인 후에 재요청 주세요!
로그인 여부 처리가 까다로웠을 것 같은데 수고 많았어요~~~👍

Comment on lines 12 to 16
export const REVIEW_EMPTY = {
noReviewInTotal: '아직 받은 리뷰가 없어요!',
noReviewInQuestion: '이 질문은 아직 받은 답변이 없어요!',
noReviewInTotal: '아직 받은 리뷰가 없어요...',
noReviewLink: '생성한 리뷰 링크가 없어요...',
noReviewInQuestion: '이 질문은 아직 받은 답변이 없어요...',
};
Copy link
Contributor

Choose a reason for hiding this comment

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

(원래 있었던 상수긴 하지만) 이 값들을 상수로 관리할 필요가 있을까요?
각 페이지나 컴포넌트에 특화된 값들이고 재사용성도 딱히 고려할 필요 없을 것 같아서 공통 상수로 뺄 필요가 없다는 생각입니다. 또 당장 EmptyContent 컴포넌트도 안내 문구를 string으로만 받지 않고 children으로 받으니까요.

당장 수정하자는 건 아닙니다ㅎㅎ

import { REVIEW_QUERY_KEY } from '@/constants';

const useGetReviewLinks = () => {
const result = useSuspenseQuery({
Copy link
Contributor

Choose a reason for hiding this comment

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

오 리뷰 링크를 받아올 때 infiniteQuery를 쓸 줄 알았는데 일반 query를 쓰네요?!
서버에서 isLastPage나 lastReviewGroupId를 내려주길래 inf query를 쓸 줄 알았어요. 애초에 요청에 id를 보내지 않으니 무한 구현은 안 될 것 같긴 한데 왜 저 값들을 내려주는지는 궁금하네요...

Comment on lines 2 to +3
export { default as useGetReviewList } from './useGetReviewList';
export { default as useGetReviewLinks } from './useGetReviewLinks';
Copy link
Contributor

Choose a reason for hiding this comment

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

맥락을 까먹어서 그러는데, ReviewList 가져오는 훅(외에 한 페이지에서만 쓰는 쿼리 훅들)을 공통 훅으로 뺐던 이유가 뭐였는지 기억하시나요? depth 이슈였나...

Copy link
Contributor

Choose a reason for hiding this comment

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

초반에 만든 훅이라 공통 훅 자리에 있는것 같아요

import useGetReviewLinks from '.';

describe('회원이 생성한 리뷰 그룹 목록 조회 테스트', () => {
it('회원이 생성한 리뷰 그룹 목록을 성공적으로 불러온다.', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

테스트까지 만들어주셨네요 👍
추후에 회원/비회원 쿠키를 나누고 그에 따라 테스트 유틸 함수도 나눠보면 좋겠네요!

Comment on lines +21 to +22
// TODO: NavigationTab은 사용자가 홈 페이지와 연결 페이지가 아닌 다른 페이지에서 로그인 상태일 때만 보여준다.
// 임시로 true 설정 (로그인 기능 추가하면서 여기도 수정해야 한다.)
Copy link
Contributor

Choose a reason for hiding this comment

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

주석 좋아요~~

<S.ReviewLink>{url}</S.ReviewLink>
</S.ContentContainer>
<S.Divider />
<RevieweeName revieweeTitle="🧑‍💻 리뷰이" revieweeName={revieweeName} />
Copy link
Contributor

Choose a reason for hiding this comment

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

이모지는 임시용인가요?ㅎㅎ 리뷰어/리뷰이 이모지를 신중하게 골라서 모든 환경에서 동일해 보이도록 이미지 파일을 사용하자고 했던 것 같아서요. (제 PR에 png 파일이 있으니 두 페이지 모두 머지되면 수정해봅시다!)

Comment on lines +15 to +31
useEffect(() => {
const element = cardListRef.current;
if (!element) return;

const observer = new ResizeObserver(() => {
requestAnimationFrame(() => {
const newPadding = element.scrollHeight > element.clientHeight ? '1rem' : '0';
if (element.style.paddingRight !== newPadding) {
element.style.paddingRight = newPadding;
}
});
});

observer.observe(element);

return () => observer.disconnect();
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

CSS의 scrollbar-gutter: stable 속성으로 대신할 수 있지 않을까요?

그리고 여기서는 requestAnimationFrame의 추가가 오히려 성능에 영향을 줄 수 있다고 해요.
ResizeObserver가 레이아웃 변경 직후 실행돼서 항상 최신 element 정보를 갖고올 수 있는데, 여기다requestAnimationFrame을 추가하면 이미 최신 값을 갖고 있음에도 다음 repaint가 일어날 때까지 1프레임을 더 기다린다고 하네요!
만약 이 로직을 사용한다면 ResizeObserver만으로 충분할 것 같습니다!

@@ -34,6 +29,6 @@ export const DetailedReviewContainer = styled.div`

export const ReviewContentContainer = styled.div`
${media.xSmall} {
padding: 0 2rem;
padding: 0 1rem;
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

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

DetailedReview 스타일을 수정한 이유가 있을까요?

작성한 리뷰 페이지에서 기존 DetailedReview를 재활용하면서 공통 컴포넌트로 뺐고, 그래서 제 PR에서는 DetailedReviewPage에서도 더 이상 기존 DetailedReviewPageContents 컴포넌트를 사용하지 않거든요. (다만 파일 삭제는 안 했습니다... ㅎㅎ 그래서 충돌은 안 날지도?) 그리고 반응형을 공통 DetailedReview 스타일을 보면서 작업해서 나중에 서로 생각하지 못한 결과물이 나올 수도 있을 것 같아요.


import NavItem from './NavItem';
import * as S from './styles';
export interface Tab {
label: '리뷰 링크 관리' | '작성한 리뷰 확인';
Copy link
Contributor

Choose a reason for hiding this comment

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

공통 컴포넌트라면 label에 올 수 있는 타입을 열어둬야 하지 않을까요?!

Comment on lines +29 to +30
const isShowBreadCrumb = !isUserLoggedIn && isNeedBreadCrumb && breadcrumbPathList.length > 1;
const isShowNavigationTab = isUserLoggedIn && pathname !== '/' && !pathname.includes(ROUTE.reviewZone);
Copy link
Contributor

Choose a reason for hiding this comment

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

렌더링 로직 빡세네요...ㅋㅋㅋㅋㅋ 개발자살려~!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

[FE] 회원이 생성한 리뷰 그룹 목록 조회 API를 연동한다.
3 participants