Skip to content
23 changes: 23 additions & 0 deletions public/assets/svg/empty-document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

const EmptyDocumentIcon = ({ size = 24, ...props }) => (
Copy link

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 타입이 정의되지 않았습니다. 타입 안전성을 위해 인터페이스를 추가해주세요.

+interface EmptyDocumentIconProps {
+  size?: number;
+  [key: string]: any;
+}

-const EmptyDocumentIcon = ({ size = 24, ...props }) => (
+const EmptyDocumentIcon = ({ size = 24, ...props }: EmptyDocumentIconProps) => (
📝 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.

Suggested change
const EmptyDocumentIcon = ({ size = 24, ...props }) => (
interface EmptyDocumentIconProps {
size?: number;
[key: string]: any;
}
const EmptyDocumentIcon = ({ size = 24, ...props }: EmptyDocumentIconProps) => (
🤖 Prompt for AI Agents
In public/assets/svg/empty-document.tsx at line 3, the component
EmptyDocumentIcon lacks TypeScript type definitions for its props. Define an
interface specifying the expected prop types, including the optional size as a
number and any other props, then apply this interface to the component's props
parameter to ensure type safety.

<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
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

접근성을 위한 대체 텍스트가 필요합니다.

스크린 리더 사용자를 위해 SVG에 대체 텍스트를 제공해야 합니다. title 요소나 aria-label 속성을 추가해주세요.

  <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

‼️ 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.

Suggested change
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
{...props}
>
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
role='img'
aria-label='빈 문서 아이콘'
{...props}
>
🧰 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
In public/assets/svg/empty-document.tsx around lines 4 to 11, the SVG element
lacks accessible alternative text for screen readers. Add a <title> element
inside the SVG with a descriptive text or include an aria-label attribute on the
SVG element to provide meaningful alternative text for accessibility.

<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;
21 changes: 21 additions & 0 deletions public/assets/svg/star-empty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { SvgProps } from '@/types/svgType';

const StarEmpty = ({ size = 20, color = '#DDDDDD', ...props }: SvgProps) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
viewBox='0 0 20 20'
fill="none"
stroke={color}
strokeWidth="1"
{...props}
>
<path
d='M12.3122 15.0005C12.2069 15.0009 12.1042 14.9681 12.0187 14.9067L7.99966 11.993L3.9806 14.9067C3.89474 14.969 3.79129 15.0024 3.68522 15.002C3.57915 15.0016 3.47595 14.9675 3.39054 14.9046C3.30513 14.8417 3.24193 14.7532 3.21009 14.6521C3.17825 14.5509 3.17942 14.4422 3.21341 14.3417L4.78091 9.69891L0.718413 6.91298C0.630416 6.8527 0.564002 6.76586 0.528872 6.66515C0.493743 6.56444 0.491741 6.45513 0.523158 6.3532C0.554575 6.25127 0.617764 6.16206 0.703494 6.0986C0.789224 6.03514 0.893001 6.00076 0.999663 6.00048H6.01154L7.52404 1.34579C7.55662 1.2453 7.6202 1.15771 7.70564 1.09558C7.79109 1.03346 7.89402 1 7.99966 1C8.10531 1 8.20823 1.03346 8.29368 1.09558C8.37913 1.15771 8.44271 1.2453 8.47529 1.34579L9.98779 6.00204H14.9997C15.1065 6.00199 15.2105 6.03613 15.2964 6.09947C15.3824 6.16281 15.4459 6.25201 15.4775 6.35402C15.5091 6.45603 15.5072 6.56548 15.4721 6.66634C15.437 6.76721 15.3706 6.85419 15.2825 6.91454L11.2184 9.69891L12.785 14.3405C12.8104 14.4156 12.8175 14.4957 12.8058 14.5742C12.7941 14.6526 12.7639 14.7272 12.7177 14.7917C12.6715 14.8561 12.6107 14.9087 12.5401 14.945C12.4696 14.9813 12.3915 15.0003 12.3122 15.0005Z'
/>
</svg>
);
Comment on lines +4 to +19
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선을 위한 title 요소 추가 필요

정적 분석 도구가 지적한 대로, SVG 요소에 대체 텍스트가 없어 스크린 리더 사용자의 접근성이 떨어집니다.

다음과 같이 title 요소를 추가하여 접근성을 개선하세요:

const StarEmpty = ({ size = 20, color = '#DDDDDD', ...props }: SvgProps) => (
  <svg
    xmlns='http://www.w3.org/2000/svg'
    width={size}
    height={size}
    viewBox='0 0 20 20'
    fill="none"
    stroke={color}
    strokeWidth="1"
    {...props}
  >
+   <title>빈 별</title>
    <path
      d='M12.3122 15.0005C12.2069 15.0009 12.1042 14.9681 12.0187 14.9067L7.99966 11.993L3.9806 14.9067C3.89474 14.969 3.79129 15.0024 3.68522 15.002C3.57915 15.0016 3.47595 14.9675 3.39054 14.9046C3.30513 14.8417 3.24193 14.7532 3.21009 14.6521C3.17825 14.5509 3.17942 14.4422 3.21341 14.3417L4.78091 9.69891L0.718413 6.91298C0.630416 6.8527 0.564002 6.76586 0.528872 6.66515C0.493743 6.56444 0.491741 6.45513 0.523158 6.3532C0.554575 6.25127 0.617764 6.16206 0.703494 6.0986C0.789224 6.03514 0.893001 6.00076 0.999663 6.00048H6.01154L7.52404 1.34579C7.55662 1.2453 7.6202 1.15771 7.70564 1.09558C7.79109 1.03346 7.89402 1 7.99966 1C8.10531 1 8.20823 1.03346 8.29368 1.09558C8.37913 1.15771 8.44271 1.2453 8.47529 1.34579L9.98779 6.00204H14.9997C15.1065 6.00199 15.2105 6.03613 15.2964 6.09947C15.3824 6.16281 15.4459 6.25201 15.4775 6.35402C15.5091 6.45603 15.5072 6.56548 15.4721 6.66634C15.437 6.76721 15.3706 6.85419 15.2825 6.91454L11.2184 9.69891L12.785 14.3405C12.8104 14.4156 12.8175 14.4957 12.8058 14.5742C12.7941 14.6526 12.7639 14.7272 12.7177 14.7917C12.6715 14.8561 12.6107 14.9087 12.5401 14.945C12.4696 14.9813 12.3915 15.0003 12.3122 15.0005Z'
    />
  </svg>
);
📝 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.

Suggested change
const StarEmpty = ({ size = 20, color = '#DDDDDD', ...props }: SvgProps) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
viewBox='0 0 20 20'
fill="none"
stroke={color}
strokeWidth="1"
{...props}
>
<path
d='M12.3122 15.0005C12.2069 15.0009 12.1042 14.9681 12.0187 14.9067L7.99966 11.993L3.9806 14.9067C3.89474 14.969 3.79129 15.0024 3.68522 15.002C3.57915 15.0016 3.47595 14.9675 3.39054 14.9046C3.30513 14.8417 3.24193 14.7532 3.21009 14.6521C3.17825 14.5509 3.17942 14.4422 3.21341 14.3417L4.78091 9.69891L0.718413 6.91298C0.630416 6.8527 0.564002 6.76586 0.528872 6.66515C0.493743 6.56444 0.491741 6.45513 0.523158 6.3532C0.554575 6.25127 0.617764 6.16206 0.703494 6.0986C0.789224 6.03514 0.893001 6.00076 0.999663 6.00048H6.01154L7.52404 1.34579C7.55662 1.2453 7.6202 1.15771 7.70564 1.09558C7.79109 1.03346 7.89402 1 7.99966 1C8.10531 1 8.20823 1.03346 8.29368 1.09558C8.37913 1.15771 8.44271 1.2453 8.47529 1.34579L9.98779 6.00204H14.9997C15.1065 6.00199 15.2105 6.03613 15.2964 6.09947C15.3824 6.16281 15.4459 6.25201 15.4775 6.35402C15.5091 6.45603 15.5072 6.56548 15.4721 6.66634C15.437 6.76721 15.3706 6.85419 15.2825 6.91454L11.2184 9.69891L12.785 14.3405C12.8104 14.4156 12.8175 14.4957 12.8058 14.5742C12.7941 14.6526 12.7639 14.7272 12.7177 14.7917C12.6715 14.8561 12.6107 14.9087 12.5401 14.945C12.4696 14.9813 12.3915 15.0003 12.3122 15.0005Z'
/>
</svg>
);
const StarEmpty = ({ size = 20, color = '#DDDDDD', ...props }: SvgProps) => (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
viewBox='0 0 20 20'
fill="none"
stroke={color}
strokeWidth="1"
{...props}
>
<title>빈 별</title>
<path
d='M12.3122 15.0005C12.2069 15.0009 12.1042 14.9681 12.0187 14.9067L7.99966 11.993L3.9806 14.9067C3.89474 14.969 3.79129 15.0024 3.68522 15.002C3.57915 15.0016 3.47595 14.9675 3.39054 14.9046C3.30513 14.8417 3.24193 14.7532 3.21009 14.6521C3.17825 14.5509 3.17942 14.4422 3.21341 14.3417L4.78091 9.69891L0.718413 6.91298C0.630416 6.8527 0.564002 6.76586 0.528872 6.66515C0.493743 6.56444 0.491741 6.45513 0.523158 6.3532C0.554575 6.25127 0.617764 6.16206 0.703494 6.0986C0.789224 6.03514 0.893001 6.00076 0.999663 6.00048H6.01154L7.52404 1.34579C7.55662 1.2453 7.6202 1.15771 7.70564 1.09558C7.79109 1.03346 7.89402 1 7.99966 1C8.10531 1 8.20823 1.03346 8.29368 1.09558C8.37913 1.15771 8.44271 1.2453 8.47529 1.34579L9.98779 6.00204H14.9997C15.1065 6.00199 15.2105 6.03613 15.2964 6.09947C15.3824 6.16281 15.4459 6.25201 15.4775 6.35402C15.5091 6.45603 15.5072 6.56548 15.4721 6.66634C15.437 6.76721 15.3706 6.85419 15.2825 6.91454L11.2184 9.69891L12.785 14.3405C12.8104 14.4156 12.8175 14.4957 12.8058 14.5742C12.7941 14.6526 12.7639 14.7272 12.7177 14.7917C12.6715 14.8561 12.6107 14.9087 12.5401 14.945C12.4696 14.9813 12.3915 15.0003 12.3122 15.0005Z'
/>
</svg>
);
🧰 Tools
🪛 Biome (2.1.2)

[error] 5-14: 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
In public/assets/svg/star-empty.tsx around lines 4 to 19, the SVG element lacks
a title element for accessibility, which reduces screen reader support. Add a
<title> element inside the SVG with a descriptive text like "Empty star icon" to
provide alternative text for screen readers and improve accessibility.


export default StarEmpty;
64 changes: 64 additions & 0 deletions src/apis/reservations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { privateInstance } from './privateInstance';
import {
MyReservationsResponse,
GetMyReservationsParams,
UpdateReservationRequest,
Reservation,
CreateReviewRequest,
ReviewResponse,
} 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
Copy link

Choose a reason for hiding this comment

The 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

‼️ 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.

Suggested change
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)])
);
🤖 Prompt for AI Agents
In src/apis/reservations.ts around lines 16 to 27, the query parameter appending
uses multiple if conditions to check each parameter before appending. Simplify
this by iterating over the params object keys and appending only those with
defined values, removing explicit if statements to make the code more concise
and maintainable.

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;
};

/**
* 내 예약 리뷰 작성
* POST /api/reservations/{reservationId}/reviews
*/
export const createReview = async (
reservationId: number,
data: CreateReviewRequest,
): Promise<ReviewResponse> => {
const response = await privateInstance.post(
`/reservations/${reservationId}/reviews`,
data,
);
return response.data;
};
Comment on lines +15 to +64
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

API 함수에 에러 처리 추가 권장

모든 API 함수에서 에러를 catch하고 의미 있는 에러 메시지로 변환하면 좋겠습니다.

에러 처리 래퍼 함수 추가:

const handleApiError = (error: any, context: string) => {
  if (error.response?.data?.message) {
    throw new Error(error.response.data.message);
  }
  throw new Error(`${context} 중 오류가 발생했습니다.`);
};

export const getMyReservations = async (
  params: GetMyReservationsParams,
): Promise<MyReservationsResponse> => {
  try {
    // 기존 코드
  } catch (error) {
    handleApiError(error, '예약 목록 조회');
  }
};
🤖 Prompt for AI Agents
In src/apis/reservations.ts from lines 15 to 64, the API functions lack error
handling which can lead to unclear error messages. Add a helper function
handleApiError that extracts meaningful error messages from the API response or
provides a generic context-specific message. Wrap the existing API calls in
try-catch blocks and call handleApiError in the catch to throw improved error
messages for getMyReservations, updateMyReservation, and createReview functions.

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='!w-298px !h-184 !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',
}}
>
{/* 체크 아이콘 */}
<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-38px w-80px 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>
Comment on lines +44 to +59
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

버튼 스타일링 불일치 및 접근성 개선 필요

  1. 클래스 명명이 일관되지 않습니다 (h-38px vs h-38, w-80px vs w-80)
  2. 로딩 상태에서 접근성을 위한 aria-label이 부족합니다
  3. Primary 버튼에 중복 스타일이 있습니다
           <Button
             variant='secondary'
-            className='text-md h-38px w-80px rounded-lg border border-gray-300 font-medium'
+            className='text-md h-38px w-80px rounded-lg border border-gray-300 font-medium'
             onClick={onCancel}
             disabled={isLoading}
+            aria-label='예약 취소 거부'
           >
             아니오
           </Button>
           <Button
             variant='primary'
-            className='text-md bg-nomad h-38 w-80 rounded-lg font-medium text-white'
+            className='text-md h-38px w-80px rounded-lg font-medium'
             onClick={onConfirm}
             disabled={isLoading}
+            aria-label={isLoading ? '예약 취소 처리 중' : '예약 취소 확인'}
           >
             {isLoading ? '취소 중...' : '취소하기'}
           </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.

Suggested change
<Button
variant='secondary'
className='text-md h-38px w-80px 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>
<Button
variant='secondary'
className='text-md h-38px w-80px rounded-lg border border-gray-300 font-medium'
onClick={onCancel}
disabled={isLoading}
aria-label='예약 취소 거부'
>
아니오
</Button>
<Button
variant='primary'
className='text-md h-38px w-80px rounded-lg font-medium'
onClick={onConfirm}
disabled={isLoading}
aria-label={isLoading ? '예약 취소 처리 중' : '예약 취소 확인'}
>
{isLoading ? '취소 중...' : '취소하기'}
</Button>
🤖 Prompt for AI Agents
In
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx
between lines 44 and 59, fix the button styling inconsistencies by using
consistent class naming conventions (e.g., use either h-38 or h-38px uniformly
for height and w-80 or w-80px for width). Add appropriate aria-label attributes
to the buttons to improve accessibility, especially reflecting the loading state
on the primary button. Remove redundant or duplicate styles from the primary
button's className to keep the styling clean and maintainable.

</div>
</div>
</Modal.Content>
</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-120'>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

접근성 개선 제안.

현재 구현은 기능적으로 올바르지만, 접근성을 위해 의미적 HTML 구조 개선을 고려해보세요.

다음과 같이 의미적 구조로 개선할 수 있습니다:

-    <div className='flex flex-col items-center justify-center py-120'>
+    <section className='flex flex-col items-center justify-center py-120' role="status" aria-label="예약 내역 없음">
📝 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.

Suggested change
<div className='flex flex-col items-center justify-center py-120'>
<section className='flex flex-col items-center justify-center py-120' role="status" aria-label="예약 내역 없음">
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/EmptyReservations.tsx at
line 5, the current div used for layout lacks semantic HTML elements which can
improve accessibility. Replace or wrap the div with more meaningful semantic
tags such as <main>, <section>, or <article> depending on the context, and
ensure appropriate ARIA roles or landmarks are used to enhance screen reader
navigation and overall accessibility.

{/* 빈 상태 아이콘 */}
<div className='mb-24'>
<EmptyDocumentIcon size={131} />
Copy link

Choose a reason for hiding this comment

The 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

‼️ 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.

Suggested change
<EmptyDocumentIcon size={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={EMPTY_ICON_SIZE} />
</div>
{/* …rest of the component */}
</div>
);
}
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/EmptyReservations.tsx at
line 8, the icon size is hardcoded as 131. To improve maintainability, define a
constant for the icon size at the top of the file and replace the hardcoded
value with this constant in the EmptyDocumentIcon component.

</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,111 @@
'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;
const showReviewCompleted = isCompleted && reviewSubmitted;

return (
<div className='rounded-24 flex h-204 w-792 overflow-hidden border border-gray-300 bg-white'>
{/* 이미지 영역 */}
<div className='relative h-204 w-204 flex-shrink-0'>
<Image
src={activity.bannerImageUrl}
alt={activity.title}
fill
className='rounded-l-24 object-cover'
/>
</div>

{/* 콘텐츠 영역 */}
<div className='flex flex-1 flex-col justify-start py-21 pr-24 pl-24'>
{/* 상태 라벨 */}
<div>
<span className={cn('text-lg font-bold', STATUS_COLORS[status])}>
{STATUS_LABELS[status]}
</span>
</div>

{/* 제목 */}
<div className='mt-8'>
<h3 className='text-nomad text-xl font-bold'>{activity.title}</h3>
</div>

{/* 날짜 및 인원 정보 */}
<div className='mt-12'>
<p className='text-2lg text-nomad'>
{date} · {startTime} - {endTime} · {headCount}명
</p>
</div>

{/* 가격 + 버튼 */}
<div className='mt-21 flex items-center justify-between'>
{/* 가격 */}
<p className='text-2xl font-bold text-black'>
₩{totalPrice.toLocaleString()}
</p>

{/* 버튼/상태 */}
<div className='flex h-43 w-144 items-center justify-center'>
{showCancelButton && (
<Button
variant='secondary'
className='h-43 w-144 rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
{showReviewButton && (
<Button
variant='primary'
className='bg-nomad h-43 w-144 rounded-md text-lg font-bold'
onClick={() => onReview?.(id)}
>
후기 작성
</Button>
)}
{showReviewCompleted && (
<div className='text-lg font-bold text-gray-500'>후기 완료</div>
)}
</div>
</div>
</div>
</div>
);
}
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
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

라벨-옵션 변환 로직에 안전 장치를 추가해보세요.

현재 find 연산이 일치하는 항목을 찾지 못할 경우에 대한 처리가 없습니다. 더 안전한 구현을 위해 예외 상황을 처리하는 것이 좋겠습니다.

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

‼️ 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.

Suggested change
const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
if (selectedOption !== undefined) {
onChange(selectedOption);
}
};
const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
if (selectedOption) {
onChange(selectedOption);
} else {
console.warn(`Unknown filter label: ${selectedLabel}`);
}
};
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationFilter.tsx
around lines 23 to 30, the handleChange function does not handle the case when
find returns undefined if no matching label is found. Add a safety check or
fallback to handle this scenario gracefully, such as logging a warning or
ignoring the change, to prevent potential runtime errors.


return (
<Dropdown
options={labelOptions}
value={FILTER_LABELS[value]}
onChange={handleChange}
placeholder='필터'
className='h-[53px] w-[160px]'
disableScroll={true}
/>
);
}
Loading