-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/100 내 체험 관리 페이지(UI 및 API연동) #115
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
Conversation
Walkthrough내 체험 관리 페이지가 새로 구현되었습니다. 레이아웃, 활동 카드, 삭제 모달, 비어있는 상태 컴포넌트가 추가되었고, API 연동 및 무한 스크롤, 삭제 기능이 적용되었습니다. 관련 커스텀 훅과 API 모듈도 새로 도입되었습니다. 일부 스타일 클래스가 수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MyActivitiesPage
participant useMyActivitiesInfinite
participant useDeleteMyActivity
participant API
User->>MyActivitiesPage: 페이지 접속
MyActivitiesPage->>useMyActivitiesInfinite: 활동 목록 요청
useMyActivitiesInfinite->>API: GET /my-activities (cursorId, size)
API-->>useMyActivitiesInfinite: 활동 목록 반환
useMyActivitiesInfinite-->>MyActivitiesPage: 데이터 전달
MyActivitiesPage-->>User: 활동 목록/비어있는 상태/로딩/에러 UI 표시
User->>MyActivitiesPage: 삭제 버튼 클릭
MyActivitiesPage->>DeleteActivityModal: 삭제 확인 모달 표시
User->>DeleteActivityModal: 삭제 확정 클릭
DeleteActivityModal->>useDeleteMyActivity: 삭제 요청
useDeleteMyActivity->>API: DELETE /deleteActivity/{id}
API-->>useDeleteMyActivity: 성공/실패 응답
useDeleteMyActivity-->>MyActivitiesPage: 쿼리 무효화 및 알림
MyActivitiesPage-->>User: 갱신된 목록/알림 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes해당 이슈의 목적 외 변경사항은 발견되지 않았습니다. Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
public/assets/svg/moreOptionsIcon.tsx(1 hunks)src/apis/myActivities.ts(1 hunks)src/app/(with-header)/myactivity/layout.tsx(1 hunks)src/app/(with-header)/mypage/activities/components/ActivityCard.tsx(1 hunks)src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx(1 hunks)src/app/(with-header)/mypage/activities/components/EmptyActivities.tsx(1 hunks)src/app/(with-header)/mypage/activities/page.tsx(1 hunks)src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx(2 hunks)src/hooks/useMyActivitiesQueries.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/app/(with-header)/mypage/activities/components/EmptyActivities.tsx (2)
src/app/(with-header)/mypage/dashboard/components/EmptyDashboard.tsx (1)
EmptyDashboard(3-17)src/app/(with-header)/mypage/reservations/components/EmptyReservations.tsx (1)
EmptyReservations(3-17)
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx (3)
src/types/modalTypes.ts (1)
ModalProps(9-13)src/components/Button.tsx (1)
Button(18-49)src/components/Modal/Content.tsx (1)
ModalContent(19-45)
🪛 Biome (2.1.2)
public/assets/svg/moreOptionsIcon.tsx
[error] 4-11: Alternative text title element cannot be empty
For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.
(lint/a11y/noSvgWithoutTitle)
src/app/(with-header)/mypage/activities/components/ActivityCard.tsx
[error] 76-81: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 76-81: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
[error] 67-71: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 84-88: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 90-94: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🔇 Additional comments (6)
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx (2)
22-22: Tailwind CSS 클래스 일관성 개선을 승인합니다
px단위를 제거하여 Tailwind의 표준 유틸리티 클래스 명명 규칙과 일치시킨 것은 좋은 변경사항입니다.
46-46: 클래스명 일관성 개선을 승인합니다버튼의 크기 클래스에서
px단위를 제거한 것이 일관성 있는 개선사항입니다.src/app/(with-header)/mypage/activities/components/EmptyActivities.tsx (1)
3-17: 일관된 빈 상태 컴포넌트 구현을 승인합니다기존
EmptyReservations와EmptyDashboard컴포넌트와 동일한 패턴을 따라 일관성을 유지하고 있습니다. 구조와 스타일링이 적절합니다.src/apis/myActivities.ts (1)
26-28: 삭제 API 함수 구현을 승인합니다간단하고 명확한 삭제 API 구현입니다.
src/hooks/useMyActivitiesQueries.ts (1)
19-30: 무한 스크롤 구현을 승인합니다React Query의
useInfiniteQuery를 적절히 활용한 무한 스크롤 구현입니다. 커서 기반 페이지네이션과 적절한 캐싱 전략을 사용하고 있습니다.src/app/(with-header)/myactivity/layout.tsx (1)
1-74: 레이아웃 구현이 잘 되어있습니다로딩 상태, 에러 처리, 반응형 디자인이 적절히 구현되었습니다. 스켈레톤 UI가 실제 UI와 일치하여 사용자 경험이 향상됩니다.
| @@ -0,0 +1,18 @@ | |||
| import React from 'react'; | |||
|
|
|||
| const MoreOptionsIcon = ({ size = 24, ...props }) => ( | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
TypeScript 타입 정의와 접근성 개선이 필요합니다
컴포넌트에 적절한 타입 정의가 없고, 접근성을 위한 속성이 누락되어 있습니다.
다음과 같이 개선하세요:
+interface MoreOptionsIconProps extends React.SVGProps<SVGSVGElement> {
+ size?: number;
+}
+
-const MoreOptionsIcon = ({ size = 24, ...props }) => (
+const MoreOptionsIcon = ({ size = 24, ...props }: MoreOptionsIconProps) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 40 40'
+ role='img'
+ aria-label='더보기 옵션'
{...props}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const MoreOptionsIcon = ({ size = 24, ...props }) => ( | |
| interface MoreOptionsIconProps extends React.SVGProps<SVGSVGElement> { | |
| size?: number; | |
| } | |
| const MoreOptionsIcon = ({ size = 24, ...props }: MoreOptionsIconProps) => ( | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width={size} | |
| height={size} | |
| fill="none" | |
| viewBox="0 0 40 40" | |
| role="img" | |
| aria-label="더보기 옵션" | |
| {...props} | |
| > | |
| {/* existing icon paths/circles */} | |
| </svg> | |
| ); |
🤖 Prompt for AI Agents
In public/assets/svg/moreOptionsIcon.tsx at line 3, the MoreOptionsIcon
component lacks TypeScript type definitions and accessibility attributes. Define
proper TypeScript types for the component props, including size and any other
SVG attributes. Add accessibility attributes such as role="img" and an
appropriate aria-label or title to improve screen reader support.
| export const getMyActivitiesWithPagination = async (params?: { | ||
| cursorId?: number; | ||
| size?: number; | ||
| }): Promise<MyActivitiesResponse> => { | ||
| const queryParams = new URLSearchParams(); | ||
| if (params?.cursorId) | ||
| queryParams.append('cursorId', params.cursorId.toString()); | ||
| if (params?.size) queryParams.append('size', params.size.toString()); | ||
|
|
||
| const url = `/my-activities${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; | ||
| const response = await privateInstance.get(url); | ||
| return response.data; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
API 함수 구현을 승인하며 쿼리 파라미터 처리 개선을 제안합니다
전반적인 구현이 잘 되어 있습니다. 쿼리 파라미터 생성 부분을 약간 개선할 수 있습니다.
더 간결한 구현을 위해 다음과 같이 개선할 수 있습니다:
export const getMyActivitiesWithPagination = async (params?: {
cursorId?: number;
size?: number;
}): Promise<MyActivitiesResponse> => {
- const queryParams = new URLSearchParams();
- if (params?.cursorId)
- queryParams.append('cursorId', params.cursorId.toString());
- if (params?.size) queryParams.append('size', params.size.toString());
-
- const url = `/my-activities${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
+ const queryParams = new URLSearchParams();
+ if (params?.cursorId !== undefined) {
+ queryParams.append('cursorId', params.cursorId.toString());
+ }
+ if (params?.size !== undefined) {
+ queryParams.append('size', params.size.toString());
+ }
+
+ const queryString = queryParams.toString();
+ const url = `/my-activities${queryString ? `?${queryString}` : ''}`;
const response = await privateInstance.get(url);
return response.data;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getMyActivitiesWithPagination = async (params?: { | |
| cursorId?: number; | |
| size?: number; | |
| }): Promise<MyActivitiesResponse> => { | |
| const queryParams = new URLSearchParams(); | |
| if (params?.cursorId) | |
| queryParams.append('cursorId', params.cursorId.toString()); | |
| if (params?.size) queryParams.append('size', params.size.toString()); | |
| const url = `/my-activities${queryParams.toString() ? `?${queryParams.toString()}` : ''}`; | |
| const response = await privateInstance.get(url); | |
| return response.data; | |
| }; | |
| export const getMyActivitiesWithPagination = async (params?: { | |
| cursorId?: number; | |
| size?: number; | |
| }): Promise<MyActivitiesResponse> => { | |
| const queryParams = new URLSearchParams(); | |
| if (params?.cursorId !== undefined) { | |
| queryParams.append('cursorId', params.cursorId.toString()); | |
| } | |
| if (params?.size !== undefined) { | |
| queryParams.append('size', params.size.toString()); | |
| } | |
| const queryString = queryParams.toString(); | |
| const url = `/my-activities${queryString ? `?${queryString}` : ''}`; | |
| const response = await privateInstance.get(url); | |
| return response.data; | |
| }; |
🤖 Prompt for AI Agents
In src/apis/myActivities.ts around lines 8 to 20, the current query parameter
construction manually appends each parameter, which can be simplified. Refactor
the code to build the URLSearchParams object more concisely by conditionally
adding parameters in a cleaner way or by using a helper function to filter and
append only defined parameters. This will make the code more readable and
maintainable without changing its behavior.
| onClick={() => setIsMenuOpen(!isMenuOpen)} | ||
| className='flex h-40 w-40 items-center justify-center rounded-full hover:bg-gray-100' | ||
| > | ||
| <MoreOptionsIcon size={40} /> | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
버튼에 명시적인 type 속성을 추가하세요
모든 버튼에 type="button" 속성을 추가하여 의도하지 않은 폼 제출을 방지하세요.
<button
+ type="button"
onClick={() => setIsMenuOpen(!isMenuOpen)}
className='flex h-40 w-40 items-center justify-center rounded-full hover:bg-gray-100'
>
<button
+ type="button"
onClick={handleEdit}
className='flex h-62 w-full items-center justify-center border-b border-gray-300 px-46 py-18 text-center text-lg font-medium text-gray-900 hover:bg-gray-50'
>
<button
+ type="button"
onClick={handleDelete}
className='flex h-62 w-full items-center justify-center px-46 py-18 text-center text-lg font-medium text-gray-900 hover:bg-gray-50'
>Also applies to: 83-87, 89-93
🧰 Tools
🪛 Biome (2.1.2)
[error] 67-71: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/ActivityCard.tsx around
lines 67 to 71, 83 to 87, and 89 to 93, the button elements lack an explicit
type attribute, which can cause unintended form submissions. Add type="button"
to each button element in these line ranges to clearly specify their behavior
and prevent accidental form submissions.
| {isMenuOpen && ( | ||
| <> | ||
| <div | ||
| className='fixed inset-0 z-40' | ||
| onClick={() => setIsMenuOpen(false)} | ||
| /> | ||
|
|
||
| {/* 드롭다운 메뉴 */} | ||
| <div className='absolute top-full right-0 z-50 w-160 rounded-md border border-gray-300 bg-white shadow-lg'> | ||
| <button | ||
| onClick={handleEdit} | ||
| className='flex h-62 w-full items-center justify-center border-b border-gray-300 px-46 py-18 text-center text-lg font-medium text-gray-900 hover:bg-gray-50' | ||
| > | ||
| 수정하기 | ||
| </button> | ||
| <button | ||
| onClick={handleDelete} | ||
| className='flex h-62 w-full items-center justify-center px-46 py-18 text-center text-lg font-medium text-gray-900 hover:bg-gray-50' | ||
| > | ||
| 삭제하기 | ||
| </button> | ||
| </div> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
🛠️ Refactor suggestion
드롭다운 메뉴의 접근성을 개선하세요
현재 구현은 키보드 네비게이션을 지원하지 않습니다. 접근성을 위해 다음을 고려하세요:
- ESC 키로 메뉴 닫기
- 포커스 트랩 구현
- 적절한 ARIA 속성 추가
Headless UI의 Menu 컴포넌트나 Radix UI의 DropdownMenu를 사용하는 것을 추천합니다. 이미 구현된 접근성 기능을 활용할 수 있습니다.
🧰 Tools
🪛 Biome (2.1.2)
[error] 76-81: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 76-81: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
[error] 84-88: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
[error] 90-94: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/ActivityCard.tsx around
lines 73 to 96, the dropdown menu lacks keyboard accessibility features such as
closing with the ESC key, focus trapping, and ARIA attributes. To fix this,
replace the current custom dropdown implementation with a well-supported
accessible component like Headless UI's Menu or Radix UI's DropdownMenu, which
provide built-in keyboard navigation, focus management, and ARIA roles. This
will ensure the dropdown is fully accessible without manually implementing these
features.
| }: DeleteActivityModalProps) { | ||
| return ( | ||
| <Modal isOpen={isOpen} onOpenChange={(open) => !open && onCancel()}> | ||
| <Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
🛠️ Refactor suggestion
!important 사용을 최소화하세요
Modal.Content의 className에 많은 !important가 사용되고 있습니다. 이는 CSS 우선순위 문제를 나타내며, Modal 컴포넌트의 기본 스타일과 충돌이 있을 수 있습니다. Modal 컴포넌트의 기본 스타일을 수정하거나 더 구체적인 CSS 선택자를 사용하는 것이 좋습니다.
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx at
line 22, the className for Modal.Content uses multiple !important flags, which
indicates CSS specificity conflicts. To fix this, remove the !important
declarations and instead adjust the CSS by either modifying the Modal
component's base styles or applying more specific CSS selectors to override
styles without using !important.
| <Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'> | ||
| <div | ||
| className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16' | ||
| style={{ | ||
| borderRadius: '12px', | ||
| background: '#FFFFFF', | ||
| boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)', | ||
| overflow: 'hidden', | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
인라인 스타일과 className을 혼용하지 마세요
className과 style 속성을 함께 사용하는 것은 유지보수성을 저하시킵니다. Tailwind CSS 클래스로 통일하거나 CSS 모듈을 사용하세요.
<Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'>
<div
- className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16'
- style={{
- borderRadius: '12px',
- background: '#FFFFFF',
- boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)',
- overflow: 'hidden',
- }}
+ className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16 rounded-xl shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)] overflow-hidden'
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'> | |
| <div | |
| className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16' | |
| style={{ | |
| borderRadius: '12px', | |
| background: '#FFFFFF', | |
| boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)', | |
| overflow: 'hidden', | |
| }} | |
| <Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'> | |
| <div | |
| className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16 rounded-xl shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)] overflow-hidden' | |
| > |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx
around lines 22 to 30, avoid mixing inline styles with className attributes.
Replace the inline style object with equivalent Tailwind CSS utility classes or
move the styles to a CSS module and apply them via className to improve
maintainability and consistency.
| <div className='w-full max-w-none lg:max-w-792'> | ||
| <div className='mb-24 flex items-center justify-between'> | ||
| <h1 className='text-nomad text-3xl leading-42 font-bold'> | ||
| 내 체험 관리 | ||
| </h1> | ||
| <Button | ||
| variant='primary' | ||
| onClick={handleCreateActivity} | ||
| className='h-48 w-120 rounded-sm px-16 py-8 text-base font-bold whitespace-nowrap' | ||
| > | ||
| 체험 등록하기 | ||
| </Button> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
중복된 헤더 섹션을 컴포넌트로 추출하세요
헤더 섹션(제목과 버튼)이 로딩, 에러, 성공 상태에서 반복됩니다. 별도의 컴포넌트로 추출하여 코드 중복을 줄이세요.
+const ActivityHeader = ({ onCreateActivity }: { onCreateActivity: () => void }) => (
+ <div className='mb-24 flex items-center justify-between'>
+ <h1 className='text-nomad text-3xl leading-42 font-bold'>
+ 내 체험 관리
+ </h1>
+ <Button
+ variant='primary'
+ onClick={onCreateActivity}
+ className='h-48 w-120 rounded-sm px-16 py-8 text-base font-bold whitespace-nowrap'
+ >
+ 체험 등록하기
+ </Button>
+ </div>
+);
export default function MyActivitiesPage() {
// ... existing code ...
if (isLoading) {
return (
<div className='w-full max-w-none lg:max-w-792'>
- <div className='mb-24 flex items-center justify-between'>
- <h1 className='text-nomad text-3xl leading-42 font-bold'>
- 내 체험 관리
- </h1>
- <Button
- variant='primary'
- onClick={handleCreateActivity}
- className='h-48 w-120 rounded-sm px-16 py-8 text-base font-bold whitespace-nowrap'
- >
- 체험 등록하기
- </Button>
- </div>
+ <ActivityHeader onCreateActivity={handleCreateActivity} />
// ... rest of loading stateAlso applies to: 104-115, 128-139
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/page.tsx around lines 75 to 87, 104
to 115, and 128 to 139, the header section containing the title and button is
duplicated across loading, error, and success states. Extract this header
section into a separate reusable component that accepts necessary props like the
button click handler. Replace the repeated header code in all these sections
with this new component to reduce duplication and improve maintainability.
| </div> | ||
| <div className='w-full max-w-none lg:max-w-792'> | ||
| {/* 제목 */} | ||
| <div className='mb-48 flex items-center justify-between'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
헤더 하단 여백이 일관되지 않습니다
성공 상태에서 헤더의 하단 여백이 mb-48로 설정되어 있지만, 로딩과 에러 상태에서는 mb-24입니다. 의도적인 것이 아니라면 일관성을 위해 통일하세요.
- <div className='mb-48 flex items-center justify-between'>
+ <div className='mb-24 flex items-center justify-between'>🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/page.tsx at line 128, the bottom
margin class on the header container is set to mb-48 for the success state but
mb-24 for loading and error states, causing inconsistent spacing. Update the
margin class in the loading and error state containers to mb-48 to match the
success state, ensuring consistent bottom margin across all states.
| <DeleteActivityModal | ||
| isOpen={deleteModal.isOpen} | ||
| onCancel={handleDeleteClose} | ||
| onConfirm={handleDeleteConfirm} | ||
| isLoading={deleteActivityMutation.isPending} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
모달과 헤더 간의 z-index 충돌을 확인하세요
PR 설명에서 언급된 z-index 충돌 문제를 해결해야 합니다. 모달이 헤더 위에 제대로 표시되는지 확인하세요.
🏁 Script executed:
#!/bin/bash
# 헤더와 모달 관련 z-index 값 확인
echo "=== Header z-index values ==="
rg -A 2 "z-" --glob "**/*header*" --glob "**/*Header*"
echo -e "\n=== Modal z-index values ==="
rg -A 2 "z-" --glob "**/Modal*" --glob "**/modal*"
echo -e "\n=== DeleteActivityModal의 부모 컴포넌트들 확인 ==="
fd -e tsx -e jsx | xargs rg "DeleteActivityModal" -lLength of output: 794
🏁 Script executed:
#!/bin/bash
# DeleteActivityModal 컴포넌트 내 z-index 정의 및 className 확인
MODAL_FILE=$(fd 'DeleteActivityModal.tsx')
echo "DeleteActivityModal 파일 위치: $MODAL_FILE"
echo "=== z-index 정의 확인 ==="
rg -n "z-" -A3 "$MODAL_FILE" || echo "→ z-index 클래스 없음"
echo -e "\n=== className 사용 라인 ==="
rg -n "className" -A2 "$MODAL_FILE"Length of output: 1623
🏁 Script executed:
#!/bin/bash
# DeleteActivityModal 파일 상단의 import 문 확인
FILE='src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx'
echo "=== ${FILE} 상단 1-60줄 ==="
sed -n '1,60p' "$FILE"Length of output: 2158
🏁 Script executed:
#!/bin/bash
# Modal 컴포넌트 파일 위치 확인 및 z-index 정의 점검
MODAL_FILE=$(fd 'Modal.tsx' 2>/dev/null | head -n 1)
echo "Modal 파일 위치: $MODAL_FILE"
echo -e "\n=== 상단 1-200줄 확인 ==="
sed -n '1,200p' "$MODAL_FILE"
echo -e "\n=== z-index 클래스 및 style 내 z-index 검색 ==="
rg -n "z-" "$MODAL_FILE" || echo "→ z-index 클래스 없음"
rg -n "zIndex" "$MODAL_FILE" || echo "→ style 속성 내 zIndex 없음"Length of output: 2676
🏁 Script executed:
#!/bin/bash
# Modal 컴포넌트 파일 위치 및 내용 확인
echo "== Modal.tsx 위치 찾기 =="
find src/components -type f -name "Modal.tsx"
echo -e "\n== Modal.tsx 상단 1-200줄 확인 =="
find src/components -type f -name "Modal.tsx" | head -n1 | xargs sed -n '1,200p'
echo -e "\n== Modal.tsx에서 z-index 클래스 및 style 내 zIndex 검색 =="
find src/components -type f -name "Modal.tsx" | head -n1 | xargs rg -n "z-" || echo "→ z-index 클래스 없음"
find src/components -type f -name "Modal.tsx" | head -n1 | xargs rg -n "zIndex" || echo "→ style 속성 내 zIndex 없음"Length of output: 4230
모달이 헤더(z-100) 위에 제대로 표시되도록 z-index 설정 추가 필요
헤더 컴포넌트(fixed z-100) 위에 모달이 렌더링되려면, 현재 Modal.Content에 z-index가 없어 헤더 뒤로 가려질 수 있습니다.
수정 위치:
- src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx
- 22행(
\<Modal.Content>): 클래스에 헤더보다 높은 z-index 유틸리티(예:!z-[200])를 추가해주세요.
- 22행(
- <Modal.Content className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'>
+ <Modal.Content className='!z-[200] !h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0'>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx at
line 22, the Modal.Content element lacks a z-index, causing the modal to appear
behind the header which has z-100. To fix this, add a Tailwind CSS class with a
higher z-index such as !z-[200] to the Modal.Content's className to ensure the
modal displays above the header.
| export const useDeleteMyActivity = () => { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: deleteMyActivity, | ||
| onSuccess: () => { | ||
| // 내 체험 관련 쿼리들 무효화 | ||
| queryClient.invalidateQueries({ | ||
| queryKey: MY_ACTIVITIES_QUERY_KEYS.ALL, | ||
| }); | ||
| alert('체험이 삭제되었습니다.'); | ||
| }, | ||
| onError: (error) => { | ||
| alert(`체험 삭제 실패: ${error.message}`); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
삭제 기능 구현을 승인하며 사용자 피드백 개선을 제안합니다
뮤테이션 로직과 쿼리 무효화 처리가 적절합니다. 하지만 사용자 경험 개선을 위해 alert() 대신 토스트 알림 시스템 사용을 권장합니다.
더 나은 사용자 경험을 위해 다음과 같이 개선하는 것을 고려해보세요:
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: MY_ACTIVITIES_QUERY_KEYS.ALL,
});
- alert('체험이 삭제되었습니다.');
+ // Toast 알림 시스템 사용 권장
+ // toast.success('체험이 삭제되었습니다.');
},
onError: (error) => {
- alert(`체험 삭제 실패: ${error.message}`);
+ // Toast 알림 시스템 사용 권장
+ // toast.error(`체험 삭제 실패: ${error.message}`);
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const useDeleteMyActivity = () => { | |
| const queryClient = useQueryClient(); | |
| return useMutation({ | |
| mutationFn: deleteMyActivity, | |
| onSuccess: () => { | |
| // 내 체험 관련 쿼리들 무효화 | |
| queryClient.invalidateQueries({ | |
| queryKey: MY_ACTIVITIES_QUERY_KEYS.ALL, | |
| }); | |
| alert('체험이 삭제되었습니다.'); | |
| }, | |
| onError: (error) => { | |
| alert(`체험 삭제 실패: ${error.message}`); | |
| }, | |
| }); | |
| }; | |
| export const useDeleteMyActivity = () => { | |
| const queryClient = useQueryClient(); | |
| return useMutation({ | |
| mutationFn: deleteMyActivity, | |
| onSuccess: () => { | |
| // 내 체험 관련 쿼리들 무효화 | |
| queryClient.invalidateQueries({ | |
| queryKey: MY_ACTIVITIES_QUERY_KEYS.ALL, | |
| }); | |
| // Toast 알림 시스템 사용 권장 | |
| // toast.success('체험이 삭제되었습니다.'); | |
| }, | |
| onError: (error) => { | |
| // Toast 알림 시스템 사용 권장 | |
| // toast.error(`체험 삭제 실패: ${error.message}`); | |
| }, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In src/hooks/useMyActivitiesQueries.ts around lines 33 to 49, replace the
alert() calls in onSuccess and onError handlers with a toast notification system
to improve user experience. Import and use a toast library (e.g., react-toastify
or similar) to show non-blocking, styled notifications instead of blocking alert
dialogs. This change will provide smoother feedback without interrupting user
interaction.
BokyungCodes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이제 마이페이지 거의 다 완성됐네요! 수고하셨습니다🤲
|
구현 수고하셨습니다! |
minimo-9
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만드시느라 고생 많으셨습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx (1)
23-23: !important 사용을 최소화하세요
Modal.Content의 className에 많은!important가 사용되고 있습니다. 이는 CSS 우선순위 문제를 나타내며, Modal 컴포넌트의 기본 스타일과 충돌이 있을 수 있습니다. Modal 컴포넌트의 기본 스타일을 수정하거나 더 구체적인 CSS 선택자를 사용하는 것이 좋습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx(1 hunks)src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx(2 hunks)src/components/Modal/Content.tsx(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/Modal/Content.tsx (3)
src/types/modalTypes.ts (1)
ModalProps(9-13)src/contexts/ModalContext.tsx (1)
useModalContext(20-26)src/lib/utils.ts (1)
cn(4-6)
🔇 Additional comments (6)
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx (3)
27-27: 인라인 스타일을 Tailwind CSS로 잘 변환했습니다
rounded-xl,bg-white,shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)]등으로 인라인 스타일을 Tailwind 클래스로 깔끔하게 변환했습니다. 일관된 스타일링 방식으로 유지보수성이 향상되었습니다.
42-42: !important 제거가 적절합니다버튼 클래스에서 불필요한 !important를 제거하여 CSS 특이성 문제를 방지했습니다. 깔끔한 개선입니다.
22-26: CancelReservationModal의 z-index 설정은 올바르게 적용됩니다CancelReservationModal에서 사용된
backdropClassName='!z-[999]'는 Tailwind CSS의 arbitrary value 문법에 맞으므로 문제없이 작동합니다.
추가로, DeleteActivityModal에서는!z-999대신!z-[999]형태로 통일하시면 일관성을 유지할 수 있습니다.src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx (1)
27-27: 스타일링이 일관되게 잘 적용되었습니다인라인 스타일 대신 Tailwind CSS 클래스를 사용하여 일관된 스타일링을 구현했습니다. 이는 유지보수성 향상에 도움이 됩니다.
src/components/Modal/Content.tsx (2)
20-23: 인터페이스 확장이 적절합니다
ExtendedModalContentProps로 기존ModalProps를 확장하여zIndex와backdropClassName지원을 추가한 것이 좋습니다. 타입 안전성을 유지하면서 기능을 확장했습니다.
46-52: 백드롭 스타일링 개선이 잘 구현되었습니다
cn유틸리티를 사용하여 백드롭의 기본 클래스, z-index 클래스, 커스텀 클래스를 적절히 조합했습니다. 조건부 클래스 적용이 깔끔하게 처리되었습니다.
| export default function DeleteActivityModal({ | ||
| isOpen, | ||
| onCancel, | ||
| onConfirm, | ||
| isLoading = false, | ||
| }: DeleteActivityModalProps) { | ||
| return ( | ||
| <Modal isOpen={isOpen} onOpenChange={(open) => !open && onCancel()}> | ||
| <Modal.Content | ||
| className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0' | ||
| zIndex={999} | ||
| backdropClassName='!z-999' | ||
| > | ||
| <div className='flex h-full w-full flex-col items-center justify-center gap-24 rounded-xl bg-white p-16 shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)]'> | ||
| {/* 체크 아이콘 */} | ||
| <div className='flex justify-center'> | ||
| <CheckIcon size={24} /> | ||
| </div> | ||
|
|
||
| {/* 메시지 */} | ||
| <p className='text-nomad text-center text-lg font-medium'> | ||
| 체험을 삭제하시겠어요? | ||
| </p> | ||
|
|
||
| {/* 버튼 */} | ||
| <div className='flex gap-12'> | ||
| <Button | ||
| variant='secondary' | ||
| className='text-md h-38 w-80 rounded-lg border border-gray-300 font-medium' | ||
| onClick={onCancel} | ||
| disabled={isLoading} | ||
| > | ||
| 아니오 | ||
| </Button> | ||
| <Button | ||
| variant='primary' | ||
| className='text-md bg-nomad h-38 w-80 rounded-lg font-medium text-white' | ||
| onClick={onConfirm} | ||
| disabled={isLoading} | ||
| > | ||
| {isLoading ? '삭제 중...' : '삭제하기'} | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </Modal.Content> | ||
| </Modal> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
코드 중복을 줄이기 위한 공통 컴포넌트 고려
DeleteActivityModal과 CancelReservationModal이 거의 동일한 구조를 가지고 있습니다. 메시지와 버튼 텍스트만 다를 뿐입니다. 공통 ConfirmationModal 컴포넌트를 만들어 재사용성을 높이는 것을 고려해보세요.
공통 컴포넌트 예시:
interface ConfirmationModalProps {
isOpen: boolean;
onCancel: () => void;
onConfirm: () => void;
isLoading?: boolean;
message: string;
confirmText: string;
cancelText?: string;
loadingText?: string;
}🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx
from lines 14 to 61, the DeleteActivityModal component shares almost identical
structure with CancelReservationModal except for message and button texts. To
reduce code duplication, create a reusable ConfirmationModal component that
accepts props for isOpen, onCancel, onConfirm, isLoading, message, confirmText,
cancelText, and loadingText. Replace DeleteActivityModal and
CancelReservationModal with this common component by passing the appropriate
texts as props.
| <Modal.Content | ||
| className='!h-184 !w-298 !max-w-none !min-w-0 !rounded-xl !p-0' | ||
| zIndex={999} | ||
| backdropClassName='!z-999' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘못된 Tailwind 클래스명을 수정하세요
backdropClassName='!z-999'는 올바르지 않은 Tailwind 클래스명입니다. 대괄호 표기법을 사용해야 합니다.
다음과 같이 수정하세요:
- backdropClassName='!z-999'
+ backdropClassName='!z-[999]'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| backdropClassName='!z-999' | |
| backdropClassName='!z-[999]' |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx at
line 25, the Tailwind class 'backdropClassName="!z-999"' is invalid. Replace it
with the correct bracket notation for arbitrary values by changing it to
'backdropClassName="!z-[999]"' to ensure proper styling.
| const { isOpen } = useModalContext(); | ||
| const [isMounted, setIsMounted] = useState(false); | ||
|
|
||
| const zIndexClass = zIndex ? `z-[${zIndex}]` : 'z-50'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
동적 z-index 클래스 생성의 잠재적 문제
z-[${zIndex}] 형태의 동적 클래스 생성은 Tailwind CSS의 JIT 컴파일러가 해당 클래스를 인식하지 못할 수 있습니다. 런타임에 생성되는 클래스는 CSS에 포함되지 않을 위험이 있습니다.
다음 스크립트로 사용되는 z-index 값들을 확인하고 safelist에 추가하거나 미리 정의된 클래스를 사용하는 것을 고려해보세요:
미리 정의된 z-index 클래스를 사용하는 방향으로 개선:
- const zIndexClass = zIndex ? `z-[${zIndex}]` : 'z-50';
+ const getZIndexClass = (z?: number) => {
+ switch (z) {
+ case 999: return 'z-[999]';
+ case 50: return 'z-50';
+ default: return 'z-50';
+ }
+ };
+ const zIndexClass = getZIndexClass(zIndex);🏁 Script executed:
#!/bin/bash
# 코드베이스에서 사용되는 zIndex 값들 찾기
rg "zIndex=\{[0-9]+\}" --type tsx -oLength of output: 66
현재 rg --type tsx 옵션이 지원되지 않아 스크립트가 실패했습니다. .tsx 파일 내에서 실제로 zIndex={숫자} 형태로 사용된 곳이 있는지 아래 스크립트로 재확인해 주세요.
이 후 사용되는 zIndex 값 목록에 따라 safelist 추가 여부나 미리 정의된 클래스 사용으로 전환할지 결정합시다.
🏁 Script executed:
#!/bin/bash
# .tsx 파일 전체에서 zIndex={숫자} 사용 위치 찾기
rg --glob '*.tsx' "zIndex\s*=\s*{[0-9]+}" -n .Length of output: 166
아래 스크립트로 다시 한 번 .tsx 파일 전체에서 zIndex prop 사용 위치를 확인해 주세요.
만약 위 결과에 숫자 리터럴이 포함되어 있다면, 다음 스크립트로 순수 숫자 리터럴만 추출해 보세요.
이후 실제로 사용되는 zIndex 값 목록을 기반으로 Tailwind safelist 추가 또는 미리 정의된 클래스 전환 여부를 결정합시다.
🏁 Script executed:
#!/bin/bash
# .tsx 파일 전체에서 zIndex prop 사용 위치 확인
rg --glob '*.tsx' -n 'zIndex\s*=\s*{'Length of output: 131
🏁 Script executed:
#!/bin/bash
# zIndex={숫자} 형태 리터럴 사용 위치 확인
grep -RPn --include="*.tsx" 'zIndex\s*=\s*\{\d+\}' .Length of output: 261
Tailwind JIT에서 ‘z-[${zIndex}]’ 동적 클래스가 누락되지 않도록 설정 필요
현재 코드에서 사용된 zIndex는 다음 두 곳에서 숫자 리터럴로만 사용되고 있습니다:
- src/app/(with-header)/mypage/activities/components/DeleteActivityModal.tsx:24 (zIndex={999})
- src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx:24 (zIndex={999})
제안 방안:
- tailwind.config.js에 safelist에 해당 클래스를 추가
// tailwind.config.js module.exports = { // … safelist: ['z-[999]'], }
- 또는 theme.extend로 zIndex 스케일을 확장하고, 코드에서 브라켓 문법이 아닌 ‘z-999’ 형태를 사용
// tailwind.config.js module.exports = { theme: { extend: { zIndex: { 999: '999', }, }, }, }
// src/components/Modal/Content.tsx
- const zIndexClass = zIndex ?
z-[${zIndex}]: 'z-50';
- const zIndexClass = zIndex ?
z-${zIndex}: 'z-50';
위 중 하나를 적용해 JIT가 해당 클래스를 누락 없이 컴파일하도록 보장해주세요.
🤖 Prompt for AI Agents
In src/components/Modal/Content.tsx at line 34, the dynamic Tailwind class
`z-[${zIndex}]` may be omitted by Tailwind JIT compiler. To fix this, update
tailwind.config.js by either adding 'z-[999]' to the safelist array to ensure
the class is always included, or extend the theme's zIndex scale with a key
'999' and use the class 'z-999' in the code instead of bracket notation. Apply
one of these approaches to guarantee the zIndex class is compiled correctly.
📌 변경 사항 개요
내 체험 관리 페이질르 구현했습니다. 사용자가 등록한 체험 목록 조회 + 수정 및 삭제 연동, myactivity 프로필 섹션 레이아웃까지 추가 완료했습니다.
📝 상세 내용
체험 목록
🔗 관련 이슈
🖼️ 스크린샷(선택사항)
💡 참고 사항
Summary by CodeRabbit
신규 기능
스타일