Skip to content

Conversation

@clyde-yoonjae
Copy link
Contributor

@clyde-yoonjae clyde-yoonjae commented Mar 13, 2025

📝 주요 작업 내용

커밋순으로 확인해주시면 감사합니다.!
@dbswl701 내가참여하고있는 모임 명칭변경 확인해주시면 감사하겠습니다!

📺 스크린샷

image

🔗 참고 사항

ex) 의논할 점, 리뷰어가 알 필요가 있는 추가 정보나 문서, 참고 링크를 포함.

💬 리뷰 요구사항

ex) 중점적으로 리뷰해줬으면 하는 부분

📃 관련 이슈

ex) #이슈 번호

Summary by CodeRabbit

  • 신규 기능
    • 모임을 탈퇴할 수 있는 버튼과 대기 상태 취소 기능이 추가되었습니다.
    • 좋아요한 모임을 무한 스크롤 방식으로 확인할 수 있는 새로운 섹션이 도입되었습니다.
  • 향상된 기능
    • 모임 참여 화면이 개선되어 상세 정보와 상태 알림 기능이 강화되었습니다.
    • 인터페이스에 새로운 애니메이션 효과가 추가되어 시각적 경험이 향상되었습니다.
  • 제거된 기능
    • 기존의 모임 멤버 목록 뷰가 제거되어 보다 간소화된 UI를 제공합니다.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2025

Walkthrough

이번 변경사항은 미팅 관련 기능 전반에 걸쳐 여러 개선 및 리팩토링을 포함합니다.

  • CardRightSection에서 isPublic 속성의 타입이 필수에서 선택적으로 변경되고 기본값 false가 추가되었으며, URL 쿼리 파라미터가 수정되었습니다.
  • Joined 컴포넌트가 제거되었고, 대신 미팅 탈퇴 기능을 제공하는 LeaveMeetingButton, 좋아요한 미팅을 무한 스크롤로 보여주는 Likes 컴포넌트, 참여 미팅의 상태를 관리하는 Participated 컴포넌트가 업데이트되었습니다.
  • 관련 API 호출, 쿼리 로직, 타입 정의 및 Tailwind 애니메이션 설정에도 변화가 이루어졌습니다.

Changes

파일 변경 내용
src/app/(user-page)/my-meeting/_features/CardRightSection.tsx isPublic 속성 타입을 필수에서 선택적으로 변경하고 기본값 false 추가, URL 쿼리 파라미터 'joined' -> 'participated' 변경
src/app/(user-page)/my-meeting/_features/Joined.tsx 기존 미팅 리스트 및 멤버 모달 기능을 포함한 컴포넌트 삭제
src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx 미팅 탈퇴를 위한 신규 컴포넌트 추가 (로딩 상태, mutation 호출 포함)
src/app/(user-page)/my-meeting/_features/Likes.tsx 좋아요한 미팅을 무한 스크롤로 불러오는 신규 컴포넌트 추가 (React Query, Skeleton 로딩 포함)
src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx 대기 상태 취소 기능을 제공하는 신규 컴포넌트 추가 (mutation hook 사용)
src/app/(user-page)/my-meeting/_features/Participated.tsx 미팅 참여 관련 로직 업데이트: handleCardClick으로 이벤트 처리, IStatusOverlay 인터페이스 도입, 상태 칩 및 탈퇴 버튼 조건부 렌더링 추가
src/app/(user-page)/my-meeting/my/page.tsx 미팅 참여 관련 타입을 IMyMeetingParticipated로 변경
src/app/(user-page)/my-meeting/likes/page.tsx Likes 페이지를 비동기로 전환하고 React Query를 통해 데이터를 사전 가져오도록 수정
src/hooks/mutations/useMyMeetingMutation.ts 신규 mutation hooks (useQuitMeetingMutation, useCancelPendingMutation) 추가 및 export 업데이트
src/hooks/queries/useMyMeetingQueries.ts 신규 useInfiniteMyMeetingLikesQueries 함수 추가 및 participated 쿼리의 타입 수정
src/service/api/meeting.ts API 엔드포인트들을 하드코딩 대신 상수(endpoints 모듈) 사용으로 리팩토링
src/service/api/mymeeting.ts 미팅 참여 타입을 IMyMeetingParticipated로 변경, getMyMeetingLikes, deleteQuit, deleteCancel 등 신규 API 함수 추가
src/types/myMeeting.ts 신규 인터페이스 IMyMeetingParticipatedIMyMeetingLikes 추가
tailwind.config.ts theme.extendanimation 속성 추가, delay-100, delay-200, delay-300 애니메이션 클래스 도입

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant LMButton as LeaveMeetingButton
    participant QuitHook as useQuitMeetingMutation
    participant API as API 서버
    participant Toast as 알림 토스트

    User->>LMButton: 미팅 탈퇴 버튼 클릭
    LMButton->>QuitHook: handleClick() 호출 (meetingId 전달)
    QuitHook->>API: deleteQuit(meetingId) 호출
    API-->>QuitHook: 성공 응답 반환
    QuitHook->>Toast: 성공 토스트 메시지 표시
    QuitHook->>LMButton: 로딩 상태 업데이트 (false)
Loading
sequenceDiagram
    participant User as 사용자
    participant LikesComp as Likes Component
    participant QueryHook as useInfiniteMyMeetingLikesQueries
    participant API as API 서버

    User->>LikesComp: 스크롤 액션 발생 (무한 스크롤 트리거)
    LikesComp->>QueryHook: 다음 페이지 데이터 요청
    QueryHook->>API: getMyMeetingLikes(lastMeetingId) 호출
    API-->>QueryHook: 페이징된 좋아요 미팅 데이터 반환
    QueryHook->>LikesComp: 새로운 데이터 업데이트 및 렌더링
Loading

Suggested labels

🐛 fix, ♻️ refactor

Suggested reviewers

  • dbswl701
  • Lee-Dong-Seok

Poem

나는 작은 토끼, 변화의 숲을 달려
새로운 컴포넌트와 리팩토링, 코드가 춤을 춰
오류들은 멀리 사라지고, 수정의 빛이 반짝여
당근을 씹으며 기뻐하는 내 마음, 코딩의 노래 속에
혁신의 발자국 남기며, 한 걸음씩 앞으로!
🥕🐇✨

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2eeb44 and d9f467d.

📒 Files selected for processing (3)
  • src/app/(user-page)/my-meeting/_features/Likes.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/my/page.tsx (2 hunks)
  • src/service/api/meeting.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/(user-page)/my-meeting/my/page.tsx
  • src/service/api/meeting.ts
🔇 Additional comments (5)
src/app/(user-page)/my-meeting/_features/Likes.tsx (5)

1-11: 적절한 import 구조 및 모듈 분리

필요한 모듈과 컴포넌트를 잘 import하고 있습니다. 특히 useInfiniteMyMeetingLikesQueriesuseInfiniteScroll 같은 커스텀 훅을 사용하여 로직을 분리한 것은 좋은 접근 방식입니다.


12-28: React 훅 활용이 잘 되어 있습니다

React 훅을 효과적으로 사용하여 상태 관리와 무한 스크롤 기능을 구현했습니다. useInfiniteMyMeetingLikesQueries의 반환값을 구조 분해 할당하여 필요한 데이터와 함수를 가져오는 방식이 명확합니다.


29-31: 로딩 상태 처리가 적절합니다

데이터 로딩 중에 스켈레톤 컴포넌트를 표시하는 방식은 사용자 경험을 향상시키는 좋은 패턴입니다.


113-120: 무한 스크롤 구현이 적절합니다

무한 스크롤을 위한 Observer 요소를 명확하게 구현했습니다. id="infinite-scroll-trigger"를 추가하여 디버깅이 용이하게 한 점도 좋습니다.


122-133: 상태 처리와 사용자 메시지가 적절합니다

추가 데이터 로딩 상태와 빈 데이터 상태에 대한 처리가 잘 되어 있습니다. 특히 "찜한 모임이 없어요. 원하는 모임을 찜해보세요!"와 같은 친근한 메시지는 사용자 경험을 향상시킵니다.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@netlify
Copy link

netlify bot commented Mar 13, 2025

Deploy Preview for deving-together ready!

Name Link
🔨 Latest commit d9f467d
🔍 Latest deploy log https://app.netlify.com/sites/deving-together/deploys/67d516e076e40f0008d4d04a
😎 Deploy Preview https://deploy-preview-68--deving-together.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 70
Accessibility: 95
Best Practices: 92
SEO: 100
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ✅ success
  • Tests: ✅ success
  • Build: ✅ success

✅ 모든 검사가 통과되었습니다.

@codecov
Copy link

codecov bot commented Mar 13, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dbswl701
Copy link
Contributor

dbswl701 commented Mar 13, 2025

내가 주최한 모임의 경우에도 내가 참여하고 있는 모임에 보여지고 있습니다. 이 부분 백엔드쪽에 요청하거나 조건문 처리하여 보여지지 않도록 하는 작업이 필요해 보입니다.
(추가) 문의드리니 백엔드분이 의도적으로 넣으신 부분 같네요. 찜하기만 없어지면 그대로 동작해도 될 것 같아요. 아니면 본인이 주최한 표시가 들어가도 좋을 것 같습니다!

@dbswl701
Copy link
Contributor

모임 취소, 탈퇴의 경우 누르자마자 바로 api가 호출이 되어 모달 등의 확인 처리가 필요할 것 같습니다. 머지가 된다면 이 부분 제가 처리해두겠습니다.

@dbswl701
Copy link
Contributor

내가 참여하고 있는 모임에서 찜하기 버튼 숨김처리 부탁드립니다.

@dbswl701
Copy link
Contributor

찜한 모임에서 좋아요(찜 취소)를 눌러도 모임 리스트에는 보이지만 하트가 변경되지 않고 있습니다. 확인 부탁드립니다!

Copy link
Contributor

@dbswl701 dbswl701 left a comment

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.

<PublicSelect isPublic={isPublic ?? false} meetingId={meetingId} />

이렇게 ?? 연산자를 넣는 것 보다

const CardRightSection = ({
  memberList,
  isPublic,
  className,
  meetingId,
  showPublicSelect = false,
}

이 부분에서 isPublic=false 기본값을 지정하는 방안은 어떨까요? 더 자연스러운 흐름일 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오호 좋습니당 반영하겠습니다

meetingId,
className = '',
}: LeaveMeetingButtonProps) => {
const { mutate: quitMeeting, isPending: isLoading } =
Copy link
Contributor

Choose a reason for hiding this comment

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

isLoading을 사용하지 않으시고 isPending을 isLoading으로 바꿔사용하시는 이유가 있으실까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저희 프로젝트에선 전부 isLoading을 사용하고 있더라구요. 어제 문득 isPending과 isLoading중 어떤 거 사용해야할지 좀 고민이 있었다가 TanStack/query#6297 요고보구 v5에선 isPending을 사용한다기에, 통일성을 위해서 일단은 isPending 쓰고 isLoading으로 맞춰넣었슴니다. isLoading으로 일단 통일하겠슴니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

윤지님 해당 부분 리엑트쿼리 v5에서 useMutation의 반환값에서 isLoading이 삭제되고 isPending으로 대체되엇다고합니다. 따라 isPending이 isLoading 대신 사용된다고 합니다. 그래서 기존 코드 보았을 때 isLoading을 사용하는 부분이 인피니티 쿼리와 useQuery부분임니당 해당 부분에서의 isLoading은 첫 페이지 데이터를 로딩 중일때만 true가 되는 상태라고 합니다!

Comment on lines +24 to +32
<button
type="button"
onClick={handleClick}
className={`absolute bottom-2 right-2 z-10 flex items-center rounded-md px-3 py-1.5 ${
isLoading ? 'opacity-70' : ''
} ${className}`}
aria-label="모임 탈퇴하기"
disabled={isLoading}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

className이 여기에만 적용이 되어있는데, 상세 조회에서는 이 컴포넌트 커스텀을 어떻게 하면 될까요? 아이콘이나 글씨 크기 등도 고려가 필요할 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

요부분 prop추가하겠슴니다

Comment on lines 131 to 136
{/* 데이터가 없는 경우 표시 */}
{meetingData.pages[0].content.length === 0 && (
<div className="text-gray-500 py-8 text-center text-white">
찜한 모임이 없습니다.
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

해당 부분의 스타일은
image

시안 참고 부탁드립니다.

Comment on lines 138 to 143
{/* 더 이상 데이터가 없음을 표시 */}
{!hasNextPage && meetingData.pages[0].content.length > 0 && (
<div className="text-gray-500 py-4 text-center text-white">
모든 모임을 불러왔습니다.
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분은 푸터가 들어가면 나타나지 않아도 될 것 같은데 어떠신가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 좋습니다

Comment on lines +34 to +35
const res = await axiosInstance.get(
`${myMeetingURL.likes}?lastMeetingId=${lastMeetingId}&size=${6}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

제작하신 url 호출 함수 사용해도 좋을 것 같아요

Comment on lines 102 to 109
const res = await axiosInstance.delete(`${myMeetingURL.quit(meetingId)}`);
return res.data.data;
};

// 승인 대기중인 모임 취소하기
const DeleteCancel = async (meetingId: number) => {
const res = await axiosInstance.delete(`${myMeetingURL.cancel(meetingId)}`);
return res.data.data;
Copy link
Contributor

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.

타입은 겹치는 부분이 많아서 extends를 활용하는 방향이 좋아보여요. 저도 이 부분 수정할 예정이라 제가 수정해두겠습니다!

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.

이 부분은 이미 util/searchFilter.ts에 위치해 있습니다. 제거 부탁드립니다.

Comment on lines +13 to +17
animation: {
'delay-100': 'bounce 1s infinite 100ms',
'delay-200': 'bounce 1s infinite 200ms',
'delay-300': 'bounce 1s infinite 300ms',
},
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분은 어디에 사용되는 애니메이션인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

요부분 승인대기중에 사용되비다!

@clyde-yoonjae
Copy link
Contributor Author

내가 주최한 모임의 경우에도 내가 참여하고 있는 모임에 보여지고 있습니다. 이 부분 백엔드쪽에 요청하거나 조건문 처리하여 보여지지 않도록 하는 작업이 필요해 보입니다. (추가) 문의드리니 백엔드분이 의도적으로 넣으신 부분 같네요. 찜하기만 없어지면 그대로 동작해도 될 것 같아요. 아니면 본인이 주최한 표시가 들어가도 좋을 것 같습니다!

로드시점에 주최여부를 판단할 수 있는 부분이없어서 추후 백엔드분과 다시 얘기해봐야겠네용

@clyde-yoonjae
Copy link
Contributor Author

모임 취소, 탈퇴의 경우 누르자마자 바로 api가 호출이 되어 모달 등의 확인 처리가 필요할 것 같습니다. 머지가 된다면 이 부분 제가 처리해두겠습니다.

넵 감사합니다 윤지님 체고

@clyde-yoonjae
Copy link
Contributor Author

찜한 모임에서 좋아요(찜 취소)를 눌러도 모임 리스트에는 보이지만 하트가 변경되지 않고 있습니다. 확인 부탁드립니다!

찜한모임 기능동작은 해당 브랜치 머지되면 한나님께서 작업해주시기로 어제 전달받았슴니다!

@clyde-yoonjae clyde-yoonjae force-pushed the feat/refactor/i-participated-meeting/DEVING-79 branch from 3b5d166 to 6b9542d Compare March 14, 2025 14:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
src/types/myMeeting.ts (2)

1-1: 새로운 인터페이스 추가에 대한 제안.

IMyMeetingParticipated 인터페이스를 추가하고 CategoryTitle 타입을 가져오는 것은 좋은 시도입니다. 하지만 이전 리뷰에서 언급된 것처럼 여러 인터페이스 간에 중복되는 속성이 많습니다.

-interface IMyMeetingParticipated {
-  categoryTitle: CategoryTitle;
-  meetingId: number;
-  title: string;
-  thumbnail: string;
-  location: string;
-  memberCount: number;
-  maxMember: number;
-  likesCount: number;
-  myMemberStatus: 'APPROVED' | 'REJECTED' | 'PENDING' | 'EXPEL';
-  memberList: Member[];
-  isMeetingManager: boolean;
-}
+// 기본 인터페이스 정의
+interface IBaseMeeting {
+  meetingId: number;
+  title: string;
+  thumbnail: string;
+  location: string;
+  memberCount: number;
+  maxMember: number;
+  likesCount: number;
+}
+
+// 참여 모임 인터페이스
+interface IMyMeetingParticipated extends IBaseMeeting {
+  categoryTitle: CategoryTitle;
+  myMemberStatus: 'APPROVED' | 'REJECTED' | 'PENDING' | 'EXPEL';
+  memberList: Member[];
+  isMeetingManager: boolean;
+}

이런 방식으로 타입을 확장하여 중복을 줄이는 것을 고려해보세요.

Also applies to: 34-46, 104-105


48-57: 타입 확장을 통한 코드 개선 제안.

IMyMeetingLikes 인터페이스도 마찬가지로 상당 부분 다른 인터페이스와 중복됩니다. 앞서 제안된 IBaseMeeting을 확장하여 사용할 수 있습니다:

-interface IMyMeetingLikes {
-  meetingId: number;
-  categoryTitle: CategoryTitle;
-  title: string;
-  thumbnail: string;
-  location: string;
-  memberCount: number;
-  maxMember: number;
-  likesCount: number;
-}
+interface IMyMeetingLikes extends IBaseMeeting {
+  categoryTitle: CategoryTitle;
+}

이러한 접근 방식은 타입 정의를 보다 유지보수하기 쉽게 만들고, 확장성을 향상시킵니다.

src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx (1)

24-46: UI 구현 및 사용자 피드백이 우수합니다.

로딩 상태에 따른 시각적 피드백과 애니메이션이 잘 구현되어 있습니다. 특히 애니메이션 딜레이를 통한 점 표시는 사용자에게 대기 중임을 효과적으로 전달합니다.

하지만 모달 확인 대화상자를 추가하는 것을 고려해 보세요:

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation();
  if (window.confirm('정말로 승인 대기를 취소하시겠습니까?')) {
    cancelPending(meetingId);
  }
};

PR 코멘트에서 언급된 것처럼 즉각적인 API 호출 대신 확인 과정을 추가하는 것이 좋을 것 같습니다.

src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx (1)

27-32: 접근성 및 UI 개선

aria-label을 적절하게 사용한 것은 좋습니다. 또한 className prop을 받아서 컴포넌트가 재사용 가능하도록 구현한 점도 좋습니다. 하지만 버튼의 스타일링이 더 명확하게 구분될 수 있습니다.

다음과 같이 스타일을 개선해 보세요:

-className={`absolute bottom-2 right-2 z-10 flex items-center rounded-md px-3 py-1.5 ${
-  isLoading ? 'opacity-70' : ''
-} ${className}`}
+className={`absolute bottom-2 right-2 z-10 flex items-center rounded-md px-3 py-1.5 text-red-500 bg-white border border-red-500 hover:bg-red-50 transition-colors ${
+  isLoading ? 'opacity-70 cursor-not-allowed' : ''
+} ${className}`}
src/app/(user-page)/my-meeting/_features/Likes.tsx (1)

126-133: 빈 상태 UI 개선

빈 상태(empty state) UI가 기본적으로 구현되어 있지만, 사용자 경험을 더 향상시킬 수 있습니다.

빈 상태에 아이콘이나 이미지를 추가하고, 사용자에게 모임 페이지로 이동할 수 있는 버튼을 제공하는 것이 좋습니다:

-      {meetingData.pages[0].content.length === 0 && (
-        <div className="flex h-[60vh] w-full items-center justify-center text-center text-Cgray500">
-          <div>
-            <p className="mb-2">찜한 모임이 없어요.</p>
-            <p>원하는 모임을 찜해보세요!</p>
-          </div>
-        </div>
-      )}
+      {meetingData.pages[0].content.length === 0 && (
+        <div className="flex h-[60vh] w-full items-center justify-center text-center">
+          <div className="flex flex-col items-center">
+            <div className="mb-4 text-Cgray400">
+              {/* 아이콘 또는 이미지 추가 */}
+              <svg width="64" height="64" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="currentColor"/>
+              </svg>
+            </div>
+            <p className="mb-2 text-Cgray500">찜한 모임이 없어요.</p>
+            <p className="mb-6 text-Cgray500">원하는 모임을 찜해보세요!</p>
+            <button 
+              onClick={() => router.push('/meeting')}
+              className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors"
+            >
+              모임 둘러보기
+            </button>
+          </div>
+        </div>
+      )}
src/app/(user-page)/my-meeting/likes/page.tsx (1)

23-28: HTML 구조 최적화

추가적인 div 요소가 HydrationBoundary를 감싸고 있지만, 이 div는 특별한 스타일이나 속성이 없어 불필요합니다.

불필요한 div 요소를 제거하여 DOM 구조를 더 간결하게 만드세요:

-  return (
-    <div>
-      <HydrationBoundary state={dehydrate(queryClient)}>
-        <Likes />
-      </HydrationBoundary>
-    </div>
-  );
+  return (
+    <HydrationBoundary state={dehydrate(queryClient)}>
+      <Likes />
+    </HydrationBoundary>
+  );
src/hooks/mutations/useMyMeetingMutation.ts (1)

45-67: 승인 대기 취소 기능 추가

승인 대기 취소를 위한 뮤테이션 훅이 잘 구현되었습니다. 에러 처리 부분에서 조건문(if (error.response?.status))은 항상 true로 평가될 가능성이 있습니다. 에러 코드에 따른 더 구체적인 조건을 제공하는 것이 좋을 수 있습니다.

-    onError: (error: AxiosError) => {
-      if (error.response?.status) {
-        showToast('승인 대기 취소에 실패했습니다. 다시 시도해주세요.', 'error');
-      }
-    },
+    onError: (error: AxiosError) => {
+      showToast('승인 대기 취소에 실패했습니다. 다시 시도해주세요.', 'error');
+    },
src/service/api/mymeeting.ts (1)

30-39: 찜한 모임 로드 기능 추가

찜한 모임을 불러오는 API 함수가 잘 구현되었습니다. URL 문자열 구성 방식이 파일 내 다른 함수들과 일관되어 있습니다.

이 부분에서 템플릿 문자열 대신 meeting.ts 파일에서 사용한 방식처럼 myMeetingURL.likes 상수를 활용한다면 코드베이스의 일관성이 더 향상될 수 있습니다.

-  const res = await axiosInstance.get(
-    `${myMeetingURL.likes}?lastMeetingId=${lastMeetingId}&size=${6}`,
-  );
+  const res = await axiosInstance.get(myMeetingURL.likes, {
+    params: { lastMeetingId, size: 6 },
+  });
src/app/(user-page)/my-meeting/_features/Participated.tsx (1)

135-171: 태블릿 및 모바일 뷰의 반응형 구현

태블릿과 모바일 뷰에서도 동일한 조건부 렌더링 로직이 적용되어 있습니다. 다만, 세 가지 뷰(데스크탑, 태블릿, 모바일)에서 상태 관련 컴포넌트 로직이 중복되고 있습니다.

좀 더 DRY한 접근법을 위해, 상태 관련 컴포넌트(PendingStatusChip, LeaveMeetingButton, StatusOverlay)를 렌더링하는 로직을 별도의 함수로 추출하는 것을 고려해 볼 수 있습니다.

예를 들어, 다음과 같은 함수를 만들어 사용할 수 있습니다:

const renderStatusComponents = (meeting: IMyMeetingParticipated, className?: string) => (
  <>
    {meeting.myMemberStatus === 'PENDING' && (
      <PendingStatusChip meetingId={meeting.meetingId} />
    )}
    {meeting.myMemberStatus === 'APPROVED' && !meeting.isMeetingManager && (
      <LeaveMeetingButton
        meetingId={meeting.meetingId}
        className={className || "bg-warning text-white"}
      />
    )}
    {isDisabledStatus(meeting.myMemberStatus) && (
      <StatusOverlay meeting={meeting} />
    )}
  </>
);

그리고 각 뷰에서는 다음과 같이 사용할 수 있습니다:

{/* 데스크탑 */}
<div className="relative w-full">
  <HorizonCard {...props} />
  {renderStatusComponents(meeting)}
</div>

{/* 태블릿 */}
<div className="relative hidden flex-col border-b border-Cgray300 py-[42px] md:flex lg:hidden">
  <HorizonCard {...props} />
  <CardRightSection {...props} />
  {renderStatusComponents(meeting)}
</div>

{/* 모바일 */}
<div className="relative flex flex-col border-b border-Cgray300 py-[42px] md:hidden">
  <HorizonCard {...props} />
  <CardRightSection {...props} />
  {renderStatusComponents(meeting, "bg-warning px-2 py-1 text-xs text-white")}
</div>

Also applies to: 175-211

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b47f33c and b2eeb44.

📒 Files selected for processing (17)
  • src/app/(user-page)/my-meeting/_features/CardRightSection.tsx (2 hunks)
  • src/app/(user-page)/my-meeting/_features/Joined.tsx (0 hunks)
  • src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/_features/Likes.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/_features/Participated.tsx (5 hunks)
  • src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/_features/Tab.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/likes/page.tsx (1 hunks)
  • src/app/(user-page)/my-meeting/my/page.tsx (2 hunks)
  • src/hooks/mutations/useMyMeetingMutation.ts (2 hunks)
  • src/hooks/queries/useMyMeetingQueries.ts (2 hunks)
  • src/hooks/useCard.tsx (1 hunks)
  • src/service/api/meeting.ts (4 hunks)
  • src/service/api/mymeeting.ts (3 hunks)
  • src/types/myMeeting.ts (3 hunks)
  • src/util/CategoryNameToEng.ts (1 hunks)
  • tailwind.config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/(user-page)/my-meeting/_features/Joined.tsx
🔇 Additional comments (40)
src/app/(user-page)/my-meeting/_features/Tab.tsx (1)

8-8: 이름 변경이 일관성 있게 적용되었습니다.

'joined'에서 'participated'로의 변경이 PR 목적과 일치하며, 라우팅 및 관련 컴포넌트에서 일관성 있게 적용되었습니다.

src/util/CategoryNameToEng.ts (1)

1-14: 코드 중복이 발생했습니다.

이 기능은 이미 util/searchFilter.ts에 위치해 있습니다. 코드 중복을 방지하기 위해 이 파일을 제거하고 기존 유틸리티를 사용하는 것이 좋겠습니다.

tailwind.config.ts (1)

13-17: 애니메이션 설정이 적절히 추가되었습니다.

승인 대기 상태에 사용되는 지연 애니메이션이 명확하게 정의되었습니다. 각 애니메이션마다 다른 지연 시간을 적용하여 시각적으로 구분되는 효과를 제공합니다.

src/app/(user-page)/my-meeting/my/page.tsx (2)

15-15: 타입 가져오기가 적절히 추가되었습니다.

IMyMeetingParticipated 타입 import를 추가하여 코드의 타입 안전성을 향상시켰습니다.


50-50: 타입 명시가 정확하게 수정되었습니다.

getNextPageParam 함수의 매개변수 타입을 Paginated<IMyMeetingParticipated>로 명시하여 참여 중인 모임 데이터 구조와 일치시켰습니다. 이전에 잠재적으로 잘못된 타입이 사용될 가능성을 제거하여 타입 안전성을 높였습니다.

src/hooks/useCard.tsx (2)

124-124: 라우팅 경로 업데이트가 적절히 이루어졌습니다.

'joined'에서 'participated'로의 용어 변경이 일관되게 적용되었습니다. 이는 모임 참여 상태를 더 명확하게 표현하는 좋은 개선입니다.


128-128: 라우팅 경로 업데이트가 적절히 이루어졌습니다.

'joined'에서 'participated'로의 용어 변경이 여기서도 일관되게 적용되었습니다.

src/app/(user-page)/my-meeting/_features/CardRightSection.tsx (2)

17-17: isPublic 속성에 기본값 설정이 잘 적용되었습니다.

이전 리뷰 피드백에 따라 isPublic 속성에 기본값을 설정하고 타입을 선택적으로 변경한 것은 좋은 개선입니다. 이제 속성을 제공하지 않아도 기본적으로 false로 처리됩니다.

Also applies to: 23-23


64-64: 라우팅 경로의 일관성 있는 업데이트.

'joined'에서 'participated'로의 변경이 라우팅 로직에서도 일관되게 적용되었습니다. 추가로 showPublicSelect를 통한 조건부 처리도 적절하게 구현되었습니다.

src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx (3)

6-9: Props 인터페이스 정의가 명확합니다.

PendingStatusChipProps 인터페이스의 속성 정의가 명확하고 간결합니다. text 속성을 선택적으로 만든 것은 컴포넌트의 유연성을 높이는 좋은 방법입니다.


15-16: 비동기 상태 관리가 잘 되어 있습니다.

useCancelPendingMutation 훅을 사용하여 비동기 상태(isPending)를 관리하고 있어 사용자 경험이 향상됩니다.


18-21: 이벤트 처리가 적절히 구현되었습니다.

stopPropagation()을 사용하여 버블링을 방지하고, 클릭 이벤트가 상위 컴포넌트에 영향을 주지 않도록 처리한 것은 좋은 방법입니다.

src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx (2)

15-16: React Query의 속성명 변경과 일관성 유지

isPendingisLoading으로 변경하신 것이 확인됩니다. 이전 리뷰 코멘트에서 언급되었듯이 React Query v5에서는 isPendingisLoading을 대체하지만, 프로젝트 전체의 일관성을 위해 isLoading으로 사용하는 것이 좋겠습니다.


18-21: 이벤트 처리 및 모달 확인 추가 필요

이벤트 전파 방지는 적절하게 구현되었습니다. 그러나 PR 코멘트에서 언급된 것처럼 모임 탈퇴 시 사용자에게 확인을 요청하는 모달이 필요합니다. 현재는 버튼 클릭 시 바로 API가 호출됩니다.

사용자가 실수로 버튼을 클릭했을 때 의도치 않게 모임에서 탈퇴되는 것을 방지하기 위해 확인 모달을 추가해주세요.

src/app/(user-page)/my-meeting/_features/Likes.tsx (1)

56-56: isLike prop 하드코딩 확인 필요

isLike 속성이 하드코딩되어 있습니다. 이는 좋아요 목록이므로 항상 true일 수 있지만, 데이터 소스에서 실제 상태를 가져오는 것이 더 정확합니다.

실제 API 응답에 좋아요 상태가 포함되어 있는지 확인하고, 포함되어 있다면, 하드코딩 대신 해당 값을 사용하세요.

src/app/(user-page)/my-meeting/likes/page.tsx (1)

16-22: 서버 컴포넌트에서 데이터 프리페칭 구현

서버 컴포넌트에서 React Query를 사용하여 데이터를 프리페칭하는 구현이 잘 되어 있습니다. 이는 클라이언트에서의 초기 로딩 상태를 방지하여 사용자 경험을 개선합니다.

src/hooks/queries/useMyMeetingQueries.ts (3)

21-21: 키 구조 일관성 유지

myMeetingKeys 객체에 likes 키를 추가하여 일관된 구조를 유지하고 있습니다. 이는 좋은 패턴입니다.


41-51: 타입 업데이트 확인

useInfiniteMyMeetingParticipatedQueries 함수에서 lastPage 타입이 IMyMeetingManage에서 IMyMeetingParticipated로 변경되었습니다. 이는 더 정확한 타입 정의를 제공합니다.

다만, API 응답 구조가 실제로 변경되었는지 확인하는 것이 좋습니다.

API 응답 구조가 IMyMeetingParticipated 타입에 맞는지 확인하세요.


53-63: 새로운 쿼리 함수 구현

useInfiniteMyMeetingLikesQueries 함수가 기존 패턴을 따라 잘 구현되어 있습니다. 무한 스크롤을 위한 설정과 페이징 처리가 적절합니다.

src/service/api/meeting.ts (5)

11-11: 상수 임포트를 통한 코드 개선

엔드포인트 URL을 상수로 관리하여 코드 가독성과 유지보수성을 향상시켰습니다. 이는 하드코딩된 URL 문자열 대신 명명된 상수를 사용함으로써 오타 가능성을 줄이고 유지보수를 쉽게 만드는 좋은 접근법입니다.


18-18: URL 상수화로 코드 품질 개선

하드코딩된 URL 대신 meetingURL.top 상수를 사용하여 코드의 가독성과 유지보수성이 향상되었습니다.


34-34: URL 상수 사용으로 일관성 확보

템플릿 문자열 내에 meetingURL.search 상수를 사용하여 API 엔드포인트 참조 방식의 일관성을 유지했습니다.


42-42: 좋아요 관련 엔드포인트 상수화

likesURL.createlikesURL.delete 함수를 통해 좋아요 관련 엔드포인트를 상수화하여 코드 일관성과 유지보수성을 향상시켰습니다.

Also applies to: 48-48


78-78: 모든 엔드포인트의 일관된 상수화

모임 상세정보, 매니저 정보, 모임 등록 및 탈퇴 엔드포인트가 모두 일관되게 상수화되었습니다. 이는 코드베이스 전체의 일관성을 유지하고 향후 URL 구조 변경 시 유지보수를 용이하게 합니다.

Also applies to: 83-83, 95-95, 103-103

src/hooks/mutations/useMyMeetingMutation.ts (3)

2-2: 필요한 의존성을 올바르게 가져옴

필요한 모듈들을 명확히 가져와서 코드의 가독성과 의존성 관리가 향상되었습니다.

Also applies to: 6-6, 8-9


11-43: 모임 탈퇴 기능 추가

모임 탈퇴를 위한 뮤테이션 훅이 잘 구현되었습니다. 특히 주최자가 모임을 탈퇴할 수 없는 경우에 대한 명확한 에러 처리가 잘 되어 있습니다.

성공 및 실패 케이스에 대한 토스트 메시지, 관련 쿼리 무효화 등이 적절하게 구현되어 있습니다.


159-165: 내보내기 문 업데이트

새로운 훅들을 내보내기 문에 추가하여 외부에서 사용할 수 있도록 적절히 구성했습니다.

src/service/api/mymeeting.ts (5)

4-4: 필요한 타입 임포트 추가

필요한 타입들을 올바르게 가져와서 코드의 타입 안전성이 향상되었습니다.


19-22: 내가 참여한 모임 API 함수의 리턴 타입 변경

getMyMeetingParticipated 함수의 반환 타입이 IMyMeetingManage에서 IMyMeetingParticipated로 변경되었습니다. 이는 다른 파일들의 변경사항과 일치하며 타입의 일관성을 유지합니다.


100-104: 모임 탈퇴 기능 추가

모임 탈퇴 API 함수가 잘 구현되었습니다. myMeetingURL.quit(meetingId) 상수를 사용하여 일관된 방식으로 엔드포인트를 참조하고 있습니다.


106-110: 승인 대기 취소 기능 추가

승인 대기 취소 API 함수가 잘 구현되었습니다. myMeetingURL.cancel(meetingId) 상수를 사용하여 일관된 방식으로 엔드포인트를 참조하고 있습니다.


112-122: 내보내기 문 업데이트

새로운 함수들을 내보내기 문에 추가하여 외부에서 사용할 수 있도록 적절히 구성했습니다.

src/app/(user-page)/my-meeting/_features/Participated.tsx (8)

6-9: 필요한 의존성 및 타입 가져오기

카테고리 이름 변환 유틸리티, Next.js Link 컴포넌트, 그리고 필요한 타입을 적절히 가져왔습니다.


12-13: 새로운 컴포넌트 임포트

모임 탈퇴 버튼과 대기 상태 칩 컴포넌트를 가져와 UI 기능을 확장했습니다.


16-18: 상태 오버레이를 위한 인터페이스 정의

IStatusOverlay 인터페이스를 통해 StatusOverlay 컴포넌트의 props 타입을 명확하게 정의했습니다.


45-49: 비활성화된 상태 확인 함수 구현

모임 상태가 'REJECTED'나 'EXPEL'인 경우를 확인하는 함수가 잘 구현되었습니다. 이는 오버레이 표시 여부를 결정하는 데 사용됩니다.


51-57: 모임 상세 페이지 URL 생성 및 클릭 핸들러

모임 상세 페이지 URL을 생성하는 함수와 카드 클릭 핸들러 함수가 잘 구현되었습니다. 카테고리 제목을 영어로 변환하는 유틸리티 함수를 활용하여 URL을 동적으로 생성합니다.


60-82: 상태 오버레이 컴포넌트 구현

모임 참여 상태가 'REJECTED'나 'EXPEL'인 경우에 대한 시각적 피드백을 제공하는 오버레이 컴포넌트가 잘 구현되었습니다. 사용자가 모임 상세 페이지로 이동할 수 있는 링크도 포함되어 있습니다.


92-131: 데스크탑 뷰의 상태 기반 UI 구현

데스크탑 뷰에서 모임 카드, 상태 칩, 탈퇴 버튼, 그리고 오버레이가 모임 상태에 따라 조건부로 렌더링되도록 잘 구현되었습니다.

모임 카드에 showLikeButton={false}를 추가한 것은 PR 코멘트에서 언급된 "찜하기 버튼을 숨겨달라"는 요청을 반영한 것으로 보입니다.


219-230: 로딩 및 빈 상태 처리 개선

무한 스크롤 트리거, 추가 데이터 로딩 표시, 그리고 데이터가 없는 경우의 메시지가 잘 구현되었습니다. 특히 빈 상태에 대한 메시지가 사용자에게 더 명확한 정보를 제공하도록 개선되었습니다.

Comment on lines +38 to +108
<div key={meeting.meetingId}>
{/* 데스크탑 */}
<div className="hidden border-b border-Cgray300 py-[42px] lg:flex">
<HorizonCard
onClick={() =>
router.push(
`/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
)
}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
className="flex-row"
meetingId={meeting.meetingId}
category={translateCategoryNameToEng(meeting.categoryTitle)}
isLike={true}
likesCount={meeting.likesCount}
></HorizonCard>
</div>

{/* 태블릿 */}
<div className="hidden flex-col border-b border-Cgray300 py-[42px] md:flex lg:hidden">
<HorizonCard
onClick={() =>
router.push(
`/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
)
}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
thumbnailHeight={160}
thumbnailWidth={160}
className=""
meetingId={meeting.meetingId}
category={translateCategoryNameToEng(meeting.categoryTitle)}
isLike={true}
likesCount={meeting.likesCount}
/>
</div>

{/* 모바일 */}
<div className="flex flex-col border-b border-Cgray300 py-[42px] md:hidden">
<HorizonCard
onClick={() =>
router.push(
`/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
)
}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
thumbnailHeight={80}
thumbnailWidth={80}
className=""
meetingId={meeting.meetingId}
category={translateCategoryNameToEng(meeting.categoryTitle)}
isLike={true}
likesCount={meeting.likesCount}
/>
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

코드 중복 및 최적화 제안

데스크탑, 태블릿, 모바일 레이아웃 코드가 중복되어 있습니다. 반응형 UI를 구현하는 더 효율적인 방법이 있습니다.

반복되는 HorizonCard 컴포넌트를 하나의 함수로 추출하고 화면 크기에 따라 속성값만 변경하는 방식으로 리팩토링하세요:

-              {/* 데스크탑 */}
-              <div className="hidden border-b border-Cgray300 py-[42px] lg:flex">
-                <HorizonCard
-                  onClick={() =>
-                    router.push(
-                      `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
-                    )
-                  }
-                  key={meeting.meetingId}
-                  title={meeting.title}
-                  thumbnailUrl={meeting.thumbnail}
-                  location={meeting.location}
-                  total={meeting.maxMember}
-                  value={meeting.memberCount}
-                  className="flex-row"
-                  meetingId={meeting.meetingId}
-                  category={translateCategoryNameToEng(meeting.categoryTitle)}
-                  isLike={true}
-                  likesCount={meeting.likesCount}
-                ></HorizonCard>
-              </div>
-
-              {/* 태블릿 */}
-              <div className="hidden flex-col border-b border-Cgray300 py-[42px] md:flex lg:hidden">
-                <HorizonCard
-                  onClick={() =>
-                    router.push(
-                      `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
-                    )
-                  }
-                  key={meeting.meetingId}
-                  title={meeting.title}
-                  thumbnailUrl={meeting.thumbnail}
-                  location={meeting.location}
-                  total={meeting.maxMember}
-                  value={meeting.memberCount}
-                  thumbnailHeight={160}
-                  thumbnailWidth={160}
-                  className=""
-                  meetingId={meeting.meetingId}
-                  category={translateCategoryNameToEng(meeting.categoryTitle)}
-                  isLike={true}
-                  likesCount={meeting.likesCount}
-                />
-              </div>
-
-              {/* 모바일 */}
-              <div className="flex flex-col border-b border-Cgray300 py-[42px] md:hidden">
-                <HorizonCard
-                  onClick={() =>
-                    router.push(
-                      `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
-                    )
-                  }
-                  key={meeting.meetingId}
-                  title={meeting.title}
-                  thumbnailUrl={meeting.thumbnail}
-                  location={meeting.location}
-                  total={meeting.maxMember}
-                  value={meeting.memberCount}
-                  thumbnailHeight={80}
-                  thumbnailWidth={80}
-                  className=""
-                  meetingId={meeting.meetingId}
-                  category={translateCategoryNameToEng(meeting.categoryTitle)}
-                  isLike={true}
-                  likesCount={meeting.likesCount}
-                />
-              </div>
+              <div className="border-b border-Cgray300 py-[42px]">
+                {/* 데스크탑 */}
+                <div className="hidden lg:flex">
+                  <HorizonCard
+                    onClick={() =>
+                      router.push(
+                        `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
+                      )
+                    }
+                    key={meeting.meetingId}
+                    title={meeting.title}
+                    thumbnailUrl={meeting.thumbnail}
+                    location={meeting.location}
+                    total={meeting.maxMember}
+                    value={meeting.memberCount}
+                    className="flex-row"
+                    meetingId={meeting.meetingId}
+                    category={translateCategoryNameToEng(meeting.categoryTitle)}
+                    isLike={true}
+                    likesCount={meeting.likesCount}
+                  />
+                </div>
+
+                {/* 태블릿 */}
+                <div className="hidden md:flex lg:hidden flex-col">
+                  <HorizonCard
+                    onClick={() =>
+                      router.push(
+                        `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
+                      )
+                    }
+                    key={meeting.meetingId}
+                    title={meeting.title}
+                    thumbnailUrl={meeting.thumbnail}
+                    location={meeting.location}
+                    total={meeting.maxMember}
+                    value={meeting.memberCount}
+                    thumbnailHeight={160}
+                    thumbnailWidth={160}
+                    meetingId={meeting.meetingId}
+                    category={translateCategoryNameToEng(meeting.categoryTitle)}
+                    isLike={true}
+                    likesCount={meeting.likesCount}
+                  />
+                </div>
+
+                {/* 모바일 */}
+                <div className="flex flex-col md:hidden">
+                  <HorizonCard
+                    onClick={() =>
+                      router.push(
+                        `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
+                      )
+                    }
+                    key={meeting.meetingId}
+                    title={meeting.title}
+                    thumbnailUrl={meeting.thumbnail}
+                    location={meeting.location}
+                    total={meeting.maxMember}
+                    value={meeting.memberCount}
+                    thumbnailHeight={80}
+                    thumbnailWidth={80}
+                    meetingId={meeting.meetingId}
+                    category={translateCategoryNameToEng(meeting.categoryTitle)}
+                    isLike={true}
+                    likesCount={meeting.likesCount}
+                  />
+                </div>
+              </div>

더 나아가, renderMeetingCard 함수를 만들어 중복을 더 줄일 수 있습니다:

const renderMeetingCard = (meeting, props = {}) => (
  <HorizonCard
    onClick={() =>
      router.push(
        `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`,
      )
    }
    key={meeting.meetingId}
    title={meeting.title}
    thumbnailUrl={meeting.thumbnail}
    location={meeting.location}
    total={meeting.maxMember}
    value={meeting.memberCount}
    meetingId={meeting.meetingId}
    category={translateCategoryNameToEng(meeting.categoryTitle)}
    isLike={true}
    likesCount={meeting.likesCount}
    {...props}
  />
);

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ❌ failure
  • Tests: ✅ success
  • Build: ❌ failure

❌ 일부 검사가 실패했습니다.

@github-actions
Copy link

CI Status Report

검사 결과

  • Lint: ✅ success
  • Format: ✅ success
  • Type Check: ✅ success
  • Tests: ✅ success
  • Build: ✅ success

✅ 모든 검사가 통과되었습니다.

@clyde-yoonjae clyde-yoonjae merged commit 2449a67 into dev Mar 15, 2025
9 checks passed
@clyde-yoonjae clyde-yoonjae deleted the feat/refactor/i-participated-meeting/DEVING-79 branch March 15, 2025 06:10
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.

4 participants