-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/71 예약 내역 페이지, 후기 작성 구현(UI 및 API 연동) #94
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
Changes from 8 commits
2ee457b
4e27143
9c83f59
ca01044
a04a6b4
9fd63c0
624e574
87b488d
b659067
e808e82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const EmptyDocumentIcon = ({ size = 24, ...props }) => ( | ||||||||||||||||||||||||||||||||||||||
| <svg | ||||||||||||||||||||||||||||||||||||||
| xmlns='http://www.w3.org/2000/svg' | ||||||||||||||||||||||||||||||||||||||
| width={size} | ||||||||||||||||||||||||||||||||||||||
| height={size} | ||||||||||||||||||||||||||||||||||||||
| fill='none' | ||||||||||||||||||||||||||||||||||||||
| viewBox='0 0 131 178' | ||||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 접근성을 위한 대체 텍스트가 필요합니다. 스크린 리더 사용자를 위해 SVG에 대체 텍스트를 제공해야 합니다. <svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
+ role='img'
+ aria-label='빈 문서 아이콘'
{...props}
>📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[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) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| <path | ||||||||||||||||||||||||||||||||||||||
| fill='#DDD' | ||||||||||||||||||||||||||||||||||||||
| d='M106.397 0c13.255 0 24 10.745 24 24v129.259c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V44.974L44.974 0z' | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
| <path | ||||||||||||||||||||||||||||||||||||||
| fill='#79747E' | ||||||||||||||||||||||||||||||||||||||
| d='M41.752 2.108c1.895-1.873 5.11-.53 5.11 2.134v18.62c0 13.254-10.746 24-24 24H3.781c-2.68 0-4.015-3.25-2.109-5.134z' | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
| </svg> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export default EmptyDocumentIcon; | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||||||
| import { privateInstance } from './privateInstance'; | ||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||
| MyReservationsResponse, | ||||||||||||||||||||||||||||||||||
| GetMyReservationsParams, | ||||||||||||||||||||||||||||||||||
| UpdateReservationRequest, | ||||||||||||||||||||||||||||||||||
| Reservation, | ||||||||||||||||||||||||||||||||||
| } from '@/types/reservationTypes'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * 내 예약 리스트 조회 | ||||||||||||||||||||||||||||||||||
| * GET /api/reservations | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export const getMyReservations = async ( | ||||||||||||||||||||||||||||||||||
| params: GetMyReservationsParams, | ||||||||||||||||||||||||||||||||||
| ): Promise<MyReservationsResponse> => { | ||||||||||||||||||||||||||||||||||
| const queryParams = new URLSearchParams(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (params.cursorId) { | ||||||||||||||||||||||||||||||||||
| queryParams.append('cursorId', params.cursorId.toString()); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (params.size) { | ||||||||||||||||||||||||||||||||||
| queryParams.append('size', params.size.toString()); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (params.status) { | ||||||||||||||||||||||||||||||||||
| queryParams.append('status', params.status); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 쿼리 파라미터 생성 로직 간소화 가능 조건문을 제거하고 더 간결하게 작성할 수 있습니다. - const queryParams = new URLSearchParams();
-
- if (params.cursorId) {
- queryParams.append('cursorId', params.cursorId.toString());
- }
- if (params.size) {
- queryParams.append('size', params.size.toString());
- }
- if (params.status) {
- queryParams.append('status', params.status);
- }
+ const queryParams = new URLSearchParams(
+ Object.entries(params)
+ .filter(([_, value]) => value !== undefined)
+ .map(([key, value]) => [key, String(value)])
+ );📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| const response = await privateInstance.get( | ||||||||||||||||||||||||||||||||||
| `/reservations?${queryParams.toString()}`, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * 내 예약 수정(취소) | ||||||||||||||||||||||||||||||||||
| * PATCH /api/reservations/{reservationId} | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export const updateMyReservation = async ( | ||||||||||||||||||||||||||||||||||
| reservationId: number, | ||||||||||||||||||||||||||||||||||
| data: UpdateReservationRequest, | ||||||||||||||||||||||||||||||||||
| ): Promise<Reservation> => { | ||||||||||||||||||||||||||||||||||
| const response = await privateInstance.patch( | ||||||||||||||||||||||||||||||||||
| `/reservations/${reservationId}`, | ||||||||||||||||||||||||||||||||||
| data, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import Modal from '@/components/Modal'; | ||||||||||||||||||||||||
| import Button from '@/components/Button'; | ||||||||||||||||||||||||
| import CheckIcon from '@assets/svg/check'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| interface CancelReservationModalProps { | ||||||||||||||||||||||||
| isOpen: boolean; | ||||||||||||||||||||||||
| onCancel: () => void; | ||||||||||||||||||||||||
| onConfirm: () => void; | ||||||||||||||||||||||||
| isLoading?: boolean; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default function CancelReservationModal({ | ||||||||||||||||||||||||
| isOpen, | ||||||||||||||||||||||||
| onCancel, | ||||||||||||||||||||||||
| onConfirm, | ||||||||||||||||||||||||
| isLoading = false, | ||||||||||||||||||||||||
| }: CancelReservationModalProps) { | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <Modal isOpen={isOpen} onOpenChange={(open) => !open && onCancel()}> | ||||||||||||||||||||||||
| <Modal.Content className='!h-[184px] !w-[298px] !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-[184px] !w-[298px] !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-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0 shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)]'> | |
| <div className='flex h-full w-full flex-col items-center justify-center gap-24 rounded-xl bg-white p-16'> |
🤖 Prompt for AI Agents
In
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx
around lines 22 to 30, the styles borderRadius, background, and boxShadow are
redundantly defined both in the className and the style attribute. To fix this,
consolidate the styling by either converting all these styles into appropriate
Tailwind CSS classes or by removing them from className and keeping them only in
the style object, ensuring no duplication and consistent styling approach.
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.
Popup이 아닌 Modal을 선택해서 사용한 이유가 isLoading 상태 처리 및 동적 텍스트가 필요하기에 Popup에는 props에 isLoading?:boolean이 존재하지 않는 반면 Modal 컴포넌트의 경우 isLoading?:boolean이 존재하여 Modal 컴포넌트를 선택해서 사용했습니다.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||||||
| import EmptyDocumentIcon from '@assets/svg/empty-document'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function EmptyReservations() { | ||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className='flex flex-col items-center justify-center py-[120px]'> | ||||||||||||||||||||||||||||||
| {/* 빈 상태 아이콘 */} | ||||||||||||||||||||||||||||||
| <div className='mb-[24px]'> | ||||||||||||||||||||||||||||||
| <EmptyDocumentIcon size={131} /> | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 아이콘 크기 상수화 제안. 하드코딩된 아이콘 크기(131)를 상수로 분리하면 유지보수성이 향상됩니다. 다음과 같이 상수로 분리할 수 있습니다: +const EMPTY_ICON_SIZE = 131;
+
export default function EmptyReservations() {
return (
<div className='flex flex-col items-center justify-center py-120'>
{/* 빈 상태 아이콘 */}
<div className='mb-24'>
- <EmptyDocumentIcon size={131} />
+ <EmptyDocumentIcon size={EMPTY_ICON_SIZE} />
</div>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| {/* 빈 상태 메시지 */} | ||||||||||||||||||||||||||||||
| <p className='text-2xl font-normal text-gray-700'> | ||||||||||||||||||||||||||||||
| 아직 등록한 체험이 없어요 | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Image from 'next/image'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Button from '@/components/Button'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Reservation } from '@/types/reservationTypes'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { STATUS_LABELS, STATUS_COLORS } from '@/constants/reservationConstants'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import cn from '@/lib/cn'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ReservationCardProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reservation: Reservation; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCancel?: (reservationId: number) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onReview?: (reservationId: number) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 체험 완료 여부 확인 (현재 시간이 체험 종료 시간을 지났는지) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isExperienceCompleted = (date: string, endTime: string): boolean => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const experienceEndDateTime = new Date(`${date}T${endTime}+09:00`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Date() > experienceEndDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function ReservationCard({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reservation, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCancel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onReview, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ReservationCardProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reviewSubmitted, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalPrice, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = reservation; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isCompleted = isExperienceCompleted(date, endTime); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const showCancelButton = status === 'pending'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const showReviewButton = isCompleted && !reviewSubmitted; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex h-[204px] w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex h-[204px] w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'> | |
| <div className='flex h-[204px] w-full max-w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'> |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx at
line 43, the card container has a fixed width of 792px which can cause layout
issues on smaller screens. Replace the fixed width with a responsive width
approach, such as using relative units like percentages or max-width, and ensure
the container adapts fluidly to different screen sizes to improve responsive
design.
Outdated
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.
요 부분은 h-204 이런 식으로 사용하셔도 됩니다!
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.
🧹 Nitpick (assertive)
이미지 오류 처리 개선 필요
이미지 로딩 실패 시에 대한 처리가 없습니다. 사용자 경험 향상을 위해 오류 처리를 추가하는 것이 좋습니다.
<Image
src={activity.bannerImageUrl}
alt={activity.title}
fill
className='rounded-l-[24px] object-cover'
+ onError={(e) => {
+ e.currentTarget.src = '/images/placeholder.jpg'; // 기본 이미지로 대체
+ }}
/>📝 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.
| <Image | |
| src={activity.bannerImageUrl} | |
| alt={activity.title} | |
| fill | |
| className='rounded-l-[24px] object-cover' | |
| /> | |
| <Image | |
| src={activity.bannerImageUrl} | |
| alt={activity.title} | |
| fill | |
| className='rounded-l-[24px] object-cover' | |
| onError={(e) => { | |
| e.currentTarget.src = '/images/placeholder.jpg'; // 기본 이미지로 대체 | |
| }} | |
| /> |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 46 to 51, the Image component lacks error handling for image
loading failures. To improve user experience, add an onError handler to the
Image component that sets a fallback image or handles the error gracefully,
ensuring the UI remains visually consistent even if the original image fails to
load.
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)
접근성 개선 필요
버튼에 aria-label이나 더 구체적인 접근성 속성을 추가하면 스크린 리더 사용자에게 더 나은 경험을 제공할 수 있습니다.
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
+ aria-label={`${activity.title} 예약 취소`}
>
예약 취소
</Button>📝 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.
| {showCancelButton && ( | |
| <Button | |
| variant='secondary' | |
| className='h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onCancel?.(id)} | |
| > | |
| 예약 취소 | |
| </Button> | |
| )} | |
| {showCancelButton && ( | |
| <Button | |
| variant='secondary' | |
| className='h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onCancel?.(id)} | |
| aria-label={`${activity.title} 예약 취소`} | |
| > | |
| 예약 취소 | |
| </Button> | |
| )} |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 84 to 92, the cancel button lacks accessibility attributes. Add an
appropriate aria-label to the Button component that clearly describes its
action, such as "예약 취소 버튼" or a more specific label including the reservation
id, to improve screen reader usability.
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)
버튼 영역 구현 검토
가격 표시와 조건부 버튼 렌더링이 적절히 구현되었습니다. toLocaleString()을 사용한 가격 포맷팅도 좋습니다. 단, 두 버튼이 동시에 표시될 경우 레이아웃을 고려해야 합니다.
버튼들이 동시에 표시될 경우를 대비해 간격 조정이 필요할 수 있습니다:
<div>
+ <div className='flex gap-2'>
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
{showReviewButton && (
<Button
variant='primary'
className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onReview?.(id)}
>
후기 작성
</Button>
)}
+ </div>
</div>📝 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.
| {/* 가격 + 버튼 */} | |
| <div className='mt-[21px] flex items-center justify-between'> | |
| {/* 가격 */} | |
| <p className='text-2xl font-bold text-black'> | |
| ₩{totalPrice.toLocaleString()} | |
| </p> | |
| {/* 버튼 */} | |
| <div> | |
| {showCancelButton && ( | |
| <Button | |
| variant='secondary' | |
| className='h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onCancel?.(id)} | |
| > | |
| 예약 취소 | |
| </Button> | |
| )} | |
| {showReviewButton && ( | |
| <Button | |
| variant='primary' | |
| className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onReview?.(id)} | |
| > | |
| 후기 작성 | |
| </Button> | |
| )} | |
| </div> | |
| </div> | |
| {/* 가격 + 버튼 */} | |
| <div className='mt-[21px] flex items-center justify-between'> | |
| {/* 가격 */} | |
| <p className='text-2xl font-bold text-black'> | |
| ₩{totalPrice.toLocaleString()} | |
| </p> | |
| {/* 버튼 */} | |
| <div> | |
| <div className='flex gap-2'> | |
| {showCancelButton && ( | |
| <Button | |
| variant='secondary' | |
| className='h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onCancel?.(id)} | |
| > | |
| 예약 취소 | |
| </Button> | |
| )} | |
| {showReviewButton && ( | |
| <Button | |
| variant='primary' | |
| className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold' | |
| onClick={() => onReview?.(id)} | |
| > | |
| 후기 작성 | |
| </Button> | |
| )} | |
| </div> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 75 to 103, the price and conditional buttons are rendered
correctly, but when both cancel and review buttons appear together, their layout
spacing needs adjustment. Add appropriate horizontal spacing (e.g., margin or
gap) between the two buttons inside their container div to ensure they don't
appear too close and maintain a clean layout.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import Dropdown from '@/components/Dropdown'; | ||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||
| FilterOption, | ||||||||||||||||||||||||||||||||||||||
| FILTER_OPTIONS, | ||||||||||||||||||||||||||||||||||||||
| FILTER_LABELS, | ||||||||||||||||||||||||||||||||||||||
| } from '@/constants/reservationConstants'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| interface ReservationFilterProps { | ||||||||||||||||||||||||||||||||||||||
| value: FilterOption; | ||||||||||||||||||||||||||||||||||||||
| onChange: (value: FilterOption) => void; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export default function ReservationFilter({ | ||||||||||||||||||||||||||||||||||||||
| value, | ||||||||||||||||||||||||||||||||||||||
| onChange, | ||||||||||||||||||||||||||||||||||||||
| }: ReservationFilterProps) { | ||||||||||||||||||||||||||||||||||||||
| // 표시용 라벨 옵션 배열 | ||||||||||||||||||||||||||||||||||||||
| const labelOptions = FILTER_OPTIONS.map((option) => FILTER_LABELS[option]); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 라벨 선택 시 실제 값으로 변환 | ||||||||||||||||||||||||||||||||||||||
| const handleChange = (selectedLabel: string) => { | ||||||||||||||||||||||||||||||||||||||
| const selectedOption = FILTER_OPTIONS.find( | ||||||||||||||||||||||||||||||||||||||
| (option) => FILTER_LABELS[option] === selectedLabel, | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| if (selectedOption !== undefined) { | ||||||||||||||||||||||||||||||||||||||
| onChange(selectedOption); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 라벨-옵션 변환 로직에 안전 장치를 추가해보세요. 현재 const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
- if (selectedOption !== undefined) {
+ if (selectedOption) {
onChange(selectedOption);
+ } else {
+ console.warn(`Unknown filter label: ${selectedLabel}`);
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||
| <Dropdown | ||||||||||||||||||||||||||||||||||||||
| options={labelOptions} | ||||||||||||||||||||||||||||||||||||||
| value={FILTER_LABELS[value]} | ||||||||||||||||||||||||||||||||||||||
| onChange={handleChange} | ||||||||||||||||||||||||||||||||||||||
| placeholder='필터' | ||||||||||||||||||||||||||||||||||||||
| className='h-[53px] w-[160px]' | ||||||||||||||||||||||||||||||||||||||
| disableScroll={true} | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
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 타입 정의가 필요합니다.
props에 대한 TypeScript 타입이 정의되지 않았습니다. 타입 안전성을 위해 인터페이스를 추가해주세요.
📝 Committable suggestion
🤖 Prompt for AI Agents