Skip to content

Conversation

@evaain706
Copy link
Contributor

@evaain706 evaain706 commented Jul 27, 2025

📌 변경 사항 개요

-체험 상세페이지 리뷰 페이지네이션 제외 api 연결

📝 상세 내용

  • 체험 상세페이지 정보 받아와서 표시
  • 예약로직 적용
  • 에러메세지 alret으로표시(추후 토스트로 적용예정)
  • 자기가 등록한 체험일경우 예약인터페이스가 보이지않도록 수정
  • 드롭다운 공통컴포넌트 구현

🔗 관련 이슈

🖼️ 스크린샷(선택사항)

2025-07-28.02-12-07.mp4

💡 참고 사항

Summary by CodeRabbit

  • 신규 기능

    • 활동 상세 페이지에 실시간 데이터 기반의 상세 정보, 이미지, 예약 가능한 일정, 지도, 리뷰 등 다양한 정보를 표시하는 기능이 추가되었습니다.
    • 활동 예약 시 실제 API와 연동되어 예약이 가능합니다.
    • 활동 소유자는 예약 버튼이 비활성화되어 본인 활동 예약이 제한됩니다.
    • 예약 인터페이스, 날짜 선택기, 가격 표시 등 주요 UI 컴포넌트가 동적으로 데이터와 연동됩니다.
    • 활동 수정 폼이 추가되어 활동 정보, 일정, 이미지 편집 및 업데이트가 가능합니다.
    • 드롭다운 메뉴 컴포넌트가 도입되어 UI 내에서 편리한 선택 기능을 제공합니다.
  • 버그 수정

    • 일부 컴포넌트에서 null 값으로 인한 오류가 발생하지 않도록 안전하게 처리하였습니다.
  • 개선 및 리팩터링

    • 가격, 일정 등 주요 컴포넌트가 하드코딩된 값 대신 동적 데이터로 동작하도록 개선되었습니다.
    • 예약, 일정 등 관련 타입이 명확하게 정의되어 데이터 구조가 일관성 있게 관리됩니다.
    • 지도 컴포넌트의 최대 너비가 확장되어 더 넓은 화면에서 보기 편해졌습니다.
  • 기타

    • 외부 이미지 도메인 허용이 추가되어 특정 외부 이미지가 정상적으로 표시됩니다.

@evaain706 evaain706 self-assigned this Jul 27, 2025
@evaain706 evaain706 added the ✨ Feature 기능구현 label Jul 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 27, 2025

"""

Walkthrough

이 변경사항은 액티비티 상세 페이지의 전체 구조를 동적으로 재구성하여, 실제 API 연동을 통한 데이터 조회 및 예약 기능을 도입합니다. 기존 mock 데이터 기반의 상세 페이지와 컴포넌트가 제거되고, API 기반의 예약·상세·스케줄 관리 로직과 타입 정의, 관련 UI 컴포넌트가 새롭게 추가 및 수정되었습니다.

Changes

파일/경로 요약 변경 요약
next.config.ts 이미지 외부 도메인 허용 목록에 S3 도메인 추가
src/app/(with-header)/activities/[activitiesId]/page.tsx mock 데이터 기반 기존 상세 페이지 컴포넌트 삭제
src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx, .../Skeleton.tsx, .../Title.tsx 상세 정보 폼, 스켈레톤, 타이틀 등 동적 데이터 기반 신규 컴포넌트 추가 및 prop 구조 변경
src/app/(with-header)/activities/[id]/components/ImageGrid.tsx 파라미터 구조 단순화(기능 변화 없음)
src/app/(with-header)/activities/[id]/page.tsx 상세 폼을 렌더링하는 신규 페이지 컴포넌트 추가
src/app/(with-header)/activities/[id]/utils/MonthFormatChange.ts 월 숫자를 2자리 문자열로 변환하는 유틸 함수 추가
src/app/api/activities/[id]/available-schedule/route.ts, .../reservation/route.ts, .../route.ts 액티비티 상세, 예약, 스케줄 API 라우트 신규 추가
src/components/DatePicker/CalendarBody.tsx import 정리(빈 줄 제거, 기능 변화 없음)
src/components/DatePicker/DatePicker.tsx 스케줄 prop 및 onMonthChange 콜백 도입, 내부 상태/로직 전면 리팩토링
src/components/FloatingBox/BookingInterface.tsx, .../PriceDisplay.tsx, .../TotalPriceDisplay.tsx 예약 UI 컴포넌트 일괄적으로 prop 구조로 변경, 가격/스케줄/소유자 등 외부 데이터 기반 렌더링 및 예약 처리
src/components/FloatingBox/TimeSelector.tsx availableDates nullish fallback 추가로 안정성 개선
src/components/LocationMap.tsx 지도 컨테이너 최대 너비 1200px로 상향
src/types/activityDetailType.ts, .../apiErrorResponseType.ts 액티비티 상세, 스케줄, 서버 에러 등 관련 타입/인터페이스 대거 신규 정의
src/ui/BookingModal.tsx, .../MobileBookingModal.tsx, .../TabletBookingModal.tsx BookingModal 및 하위 모달 컴포넌트 prop 구조로 변경, 스케줄 데이터 전달
src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx 액티비티 수정 폼 컴포넌트 신규 추가, API 연동 및 이미지 업로드, 스케줄 관리 기능 포함
src/components/ActivityDropdown/Dropdown.tsx, DropdownContext.tsx, Item.tsx, index.ts, menu.tsx, trigger.tsx 드롭다운 컴포넌트 구조 신규 추가: 상태관리, 컨텍스트, 트리거, 메뉴, 아이템 컴포넌트 구현 및 통합
src/components/FloatingBox/TabletPopup.tsx TabletPopup 컴포넌트 전체 주석 처리로 비활성화

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ActivityDetailPage
    participant ActivityDetailForm
    participant API_Route
    participant ExternalAPI

    User->>ActivityDetailPage: 상세 페이지 접근
    ActivityDetailPage->>ActivityDetailForm: 컴포넌트 렌더링
    ActivityDetailForm->>API_Route: GET /api/activities/:id
    API_Route->>ExternalAPI: GET /activities/:id (with accessToken)
    ExternalAPI-->>API_Route: 상세 데이터 반환
    API_Route-->>ActivityDetailForm: JSON 상세 데이터 반환
    ActivityDetailForm->>API_Route: GET /api/activities/:id/available-schedule?year&month
    API_Route->>ExternalAPI: GET /activities/:id/available-schedule
    ExternalAPI-->>API_Route: 스케줄 데이터 반환
    API_Route-->>ActivityDetailForm: JSON 스케줄 데이터 반환
    User->>ActivityDetailForm: 예약 시도 (예약 버튼 클릭)
    ActivityDetailForm->>API_Route: POST /api/activities/:id/reservation (selectedTimeId, participants)
    API_Route->>ExternalAPI: POST /activities/:id/reservation
    ExternalAPI-->>API_Route: 예약 결과 반환
    API_Route-->>ActivityDetailForm: 예약 결과 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested reviewers

  • minimo-9
  • BokyungCodes

Poem

🐰
새로운 예약의 길이 열렸네,
API 불러 진짜 정보 가득 채우고,
소유자 구분, 가격도 실시간으로,
토끼는 깡총깡총 코드 속을 달려요!
달력도 똑똑, 예약도 척척,
오늘도 개발자의 꿈은 한 뼘 더 자라요.
🌱✨
"""

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/74

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need 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)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 26

🔭 Outside diff range comments (2)
src/ui/MobileBookingModal.tsx (1)

35-39: 주석 처리된 코드를 정리해주세요.

더 이상 사용하지 않는 handleBooking 함수가 주석 처리되어 있습니다. 향후 사용 계획이 없다면 제거하는 것이 좋겠습니다.

-  // const handleBooking = () => {
-  //   alert('예약이 완료되었습니다!');
-  //   setIsOpen(false);
-  //   setStep('date-time');
-  // };
src/components/FloatingBox/BookingInterface.tsx (1)

114-119: 하드코딩된 가격을 props로 전달받은 값으로 수정하세요.

모바일 섹션에서 ₩ 10,000으로 하드코딩되어 있는데, props로 전달받은 price 값을 사용해야 합니다.

            <div className='mb-1 text-xl font-bold text-gray-900'>
-              ₩ 10,000{' '}
+              ₩ {price.toLocaleString()}{' '}
              <span className='text-sm font-normal text-gray-600'>
                / 총 {participants}인
              </span>
            </div>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8651d64 and d9cdc53.

📒 Files selected for processing (23)
  • next.config.ts (1 hunks)
  • src/app/(with-header)/activities/[activitiesId]/page.tsx (0 hunks)
  • src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx (1 hunks)
  • src/app/(with-header)/activities/[id]/components/ImageGrid.tsx (1 hunks)
  • src/app/(with-header)/activities/[id]/components/Skeleton.tsx (1 hunks)
  • src/app/(with-header)/activities/[id]/components/Title.tsx (2 hunks)
  • src/app/(with-header)/activities/[id]/page.tsx (1 hunks)
  • src/app/(with-header)/activities/[id]/utils/MonthFormatChange.ts (1 hunks)
  • src/app/api/activities/[id]/available-schedule/route.ts (1 hunks)
  • src/app/api/activities/[id]/reservation/route.ts (1 hunks)
  • src/app/api/activities/[id]/route.ts (1 hunks)
  • src/components/DatePicker/CalendarBody.tsx (1 hunks)
  • src/components/DatePicker/DatePicker.tsx (1 hunks)
  • src/components/FloatingBox/BookingInterface.tsx (3 hunks)
  • src/components/FloatingBox/PriceDisplay.tsx (1 hunks)
  • src/components/FloatingBox/TimeSelector.tsx (1 hunks)
  • src/components/FloatingBox/TotalPriceDisplay.tsx (1 hunks)
  • src/components/LocationMap.tsx (1 hunks)
  • src/types/activityDetailType.ts (1 hunks)
  • src/types/apiErrorResponseType.ts (1 hunks)
  • src/ui/BookingModal.tsx (1 hunks)
  • src/ui/MobileBookingModal.tsx (2 hunks)
  • src/ui/TabletBookingModal.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • src/app/(with-header)/activities/[activitiesId]/page.tsx
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/app/(with-header)/activities/[id]/components/ImageGrid.tsx (1)
src/app/(with-header)/activities/[activitiesId]/components/ImageGrid.tsx (2)
  • ImageGrid (7-86)
  • function (21-21)
src/ui/TabletBookingModal.tsx (2)
src/types/activityDetailType.ts (1)
  • SchedulesProps (40-40)
src/components/DatePicker/DatePicker.tsx (1)
  • DatePicker (20-94)
src/ui/MobileBookingModal.tsx (2)
src/types/activityDetailType.ts (1)
  • SchedulesProps (40-40)
src/components/DatePicker/DatePicker.tsx (1)
  • DatePicker (20-94)
src/ui/BookingModal.tsx (4)
src/types/activityDetailType.ts (1)
  • SchedulesProps (40-40)
src/hooks/useDeviceSize.ts (1)
  • useDeviceSize (7-30)
src/ui/MobileBookingModal.tsx (1)
  • MobileModal (13-109)
src/ui/TabletBookingModal.tsx (1)
  • TabletModal (10-51)
src/app/api/activities/[id]/route.ts (2)
src/app/api/activities/[id]/available-schedule/route.ts (1)
  • GET (5-34)
src/types/apiErrorResponseType.ts (1)
  • ServerErrorResponse (1-3)
src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx (2)
src/app/(with-header)/activities/[id]/utils/MonthFormatChange.ts (1)
  • padMonth (1-3)
src/components/FloatingBox/BookingInterface.tsx (1)
  • BookingInterface (16-149)
src/components/DatePicker/DatePicker.tsx (3)
src/types/activityDetailType.ts (1)
  • SchedulesProps (40-40)
src/types/datePickerTypes.ts (2)
  • CalendarHeaderProps (11-14)
  • CalendarBodyProps (3-9)
src/components/DatePicker/CalendarHeader.tsx (1)
  • CalendarHeader (5-35)
src/types/activityDetailType.ts (3)
src/app/(with-header)/activities/[activitiesId]/page.tsx (1)
  • ActivityDetailPage (11-79)
src/types/bookingStoreTypes.ts (1)
  • BookingState (1-23)
src/app/(with-header)/activities/[activitiesId]/components/ReviewCard.tsx (1)
  • ReviewCard (4-25)
🪛 Biome (2.1.2)
src/app/(with-header)/activities/[id]/components/Title.tsx

[error] 34-35: 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 (28)
src/components/LocationMap.tsx (1)

79-79: 레이아웃 개선을 위한 최대 너비 증가 승인

액티비티 상세 페이지의 향상된 UI와 잘 맞는 변경사항입니다. 800px에서 1200px로 증가한 것은 더 넓은 화면에서 더 나은 사용자 경험을 제공할 것입니다.

src/ui/MobileBookingModal.tsx (2)

11-17: 일관된 props 전달 구조가 잘 구현되었습니다.

스케줄 데이터를 DatePicker로 전달하는 prop threading이 올바르게 구현되어 있고, 타입 정의도 적절합니다.


52-52: schedules prop이 DatePicker에 올바르게 전달되었습니다.

DatePicker 컴포넌트가 schedules를 받도록 업데이트된 것과 일치하여 데이터 흐름이 적절합니다.

src/components/FloatingBox/TimeSelector.tsx (1)

16-17: 안전한 null 체크 로직이 추가되었습니다.

availableDates가 null이나 undefined일 때 발생할 수 있는 런타임 에러를 방지하는 좋은 방어적 프로그래밍 사례입니다. nullish coalescing과 optional chaining을 적절히 사용했습니다.

src/ui/TabletBookingModal.tsx (2)

8-14: 일관된 props 구조가 잘 유지되었습니다.

MobileBookingModal과 동일한 패턴으로 schedules prop을 추가하여 일관성 있는 구현을 보여줍니다.


31-31: DatePicker에 schedules가 올바르게 전달되었습니다.

DatePicker 컴포넌트의 업데이트된 인터페이스와 일치하는 적절한 prop 전달입니다.

src/app/(with-header)/activities/[id]/components/ImageGrid.tsx (1)

7-7: 리팩토링 승인

함수 시그니처에서 매개변수 구조 분해를 인라인으로 이동한 것은 코드를 더 간결하고 읽기 쉽게 만드는 좋은 개선사항입니다.

src/components/FloatingBox/PriceDisplay.tsx (1)

1-1: 동적 가격 표시로의 개선 승인

하드코딩된 가격에서 price prop을 받는 방식으로 변경한 것은 컴포넌트의 재사용성을 크게 향상시키는 좋은 개선사항입니다.

src/ui/BookingModal.tsx (1)

3-3: 스케줄 prop 추가 승인

SchedulesProps 타입을 추가하고 하위 모달 컴포넌트들에게 스케줄 데이터를 전달하는 구조가 API 연동 패턴과 일치합니다.

Also applies to: 8-12

src/components/FloatingBox/TotalPriceDisplay.tsx (1)

3-4: 동적 가격 계산으로의 개선 승인

하드코딩된 pricePerPerson 값을 price prop으로 외부화한 것은 컴포넌트의 유연성을 크게 향상시키는 훌륭한 개선사항입니다.

src/app/(with-header)/activities/[id]/components/Title.tsx (1)

13-14: 소유자 기반 조건부 렌더링 승인

isDropDown에서 isOwner로 변경하여 액티비티 소유자 기반의 UI 제어로 개선한 것은 비즈니스 로직에 더 적합한 접근방식입니다.

src/app/api/activities/[id]/route.ts (1)

6-32: API 라우트 구현이 잘 되어 있습니다.

인증, 에러 처리, 응답 형식이 일관성 있게 구현되어 있고, Next.js 15의 async params 패턴을 올바르게 사용하고 있습니다. 다른 API 라우트들과 동일한 패턴을 따라 유지보수성이 좋습니다.

src/app/api/activities/[id]/available-schedule/route.ts (1)

5-34: API 라우트 구현이 잘 되어 있습니다.

인증, 에러 처리, URL 구성이 올바르게 구현되어 있고, 다른 API 라우트들과 일관된 패턴을 따르고 있습니다.

src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx (2)

104-109: setTimeout(0) 사용 목적을 명확히 하거나 제거하세요.

0ms 딜레이의 setTimeout이 사용된 목적이 명확하지 않습니다. 상태 업데이트를 다음 틱으로 연기할 필요가 있다면 주석으로 설명하거나, 불필요하다면 제거하세요.

이 setTimeout이 정말 필요한지 확인해보세요. React의 상태 업데이트는 이미 비동기적으로 배치 처리됩니다.

              onMonthChange={(year, month) => {
-                setTimeout(() => {
                  setYear(year);
                  setMonth(month);
-                }, 0);
              }}

22-29: 전반적인 컴포넌트 구조가 잘 설계되어 있습니다.

React Query 사용, 조건부 렌더링, props 전달 방식이 모두 적절하게 구현되어 있습니다. 특히 ownership 로직과 그에 따른 UI 조건부 렌더링이 잘 되어 있습니다.

Also applies to: 82-123

src/components/FloatingBox/BookingInterface.tsx (2)

16-45: props 기반 리팩토링과 API 연동이 잘 구현되어 있습니다.

컴포넌트가 props를 받도록 리팩토링되었고, 비동기 예약 처리와 포괄적인 에러 핸들링이 잘 구현되어 있습니다. 소유자 기반 예약 제한 로직도 적절합니다.


52-56: 예약 가능 조건에 소유자 체크가 잘 추가되었습니다.

!isOwner 조건이 추가되어 활동 소유자가 자신의 활동을 예약할 수 없도록 적절히 제한되어 있습니다.

src/components/DatePicker/DatePicker.tsx (6)

13-13: 새로운 타입 import 확인

SchedulesProps 타입이 올바르게 import되어 있습니다.


32-34: viewDate 초기화 로직 확인

selectedDate가 있을 때 dayjs로 변환하는 로직이 올바르게 구현되어 있습니다.


65-72: 월 변경 콜백 함수 구현 확인

onMonthChange 콜백에서 월을 1-based로 전달하는 로직이 올바르게 구현되어 있습니다.


76-78: 날짜 선택 핸들러 구현 확인

날짜 선택 시 Date 객체로 변환하는 로직이 올바릅니다.


80-80: null 체크 안전장치 확인

availableDates에 대한 null 체크가 올바르게 구현되어 있습니다.


40-51: 자동 날짜 선택 로직의 의존성 배열 문제

setSelectedDate가 의존성 배열에 누락되어 있고, 무한 루프 가능성이 있습니다.

  useEffect(() => {
    if (!selectedDate && schedules.length > 0) {
      const firstSchedule = schedules.find(
        (item) =>
          dayjs(item.date).year() === viewDate.year() &&
          dayjs(item.date).month() === viewDate.month(),
      );
      if (firstSchedule) {
        setSelectedDate(dayjs(firstSchedule.date).toDate());
      }
    }
-  }, [schedules, viewDate]);
+  }, [schedules, viewDate, selectedDate, setSelectedDate]);

Likely an incorrect or invalid review comment.

src/types/activityDetailType.ts (5)

22-27: ActivitySchedule 인터페이스 정의 확인

스케줄 정보를 위한 기본 인터페이스가 올바르게 정의되어 있습니다.


29-33: TimeSlot 타입 정의 확인

시간대 정보를 위한 타입이 적절하게 정의되어 있습니다.


35-38: GroupedSchedule 타입 정의 확인

날짜별로 그룹화된 스케줄 구조가 잘 설계되어 있습니다.


40-40: SchedulesProps 타입 별칭 확인

GroupedSchedule 배열에 대한 타입 별칭이 명확합니다.


42-44: ActivitySubImage 인터페이스 정의 확인

서브 이미지를 위한 간단한 인터페이스가 적절합니다.

Comment on lines +6 to +8
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
},
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

외부 이미지 도메인 설정 승인 및 보안 고려사항

S3 버킷의 이미지 최적화를 위한 올바른 설정입니다.

보안과 환경 분리를 위해 환경변수 사용을 고려해보세요:

  images: {
-    domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
+    domains: [process.env.NEXT_PUBLIC_IMAGE_DOMAIN || 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
  },
📝 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
images: {
domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
},
images: {
domains: [process.env.NEXT_PUBLIC_IMAGE_DOMAIN || 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
},
🤖 Prompt for AI Agents
In next.config.ts around lines 6 to 8, the external image domain is hardcoded
for S3 bucket access. To improve security and environment separation, replace
the hardcoded domain with an environment variable. Define the domain in an
environment variable and reference it in the images.domains array to allow
different domains per environment and avoid exposing sensitive info in code.

Comment on lines +16 to +17
const [year, setYear] = useState(2025);
const [month, setMonth] = useState(7);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

하드코딩된 년도와 월을 현재 날짜로 초기화하세요.

2025년 7월로 하드코딩되어 있어, 다른 시기에 사용할 때 문제가 될 수 있습니다.

-  const [year, setYear] = useState(2025);
-  const [month, setMonth] = useState(7);
+  const [year, setYear] = useState(new Date().getFullYear());
+  const [month, setMonth] = useState(new Date().getMonth() + 1);
📝 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 [year, setYear] = useState(2025);
const [month, setMonth] = useState(7);
const [year, setYear] = useState(new Date().getFullYear());
const [month, setMonth] = useState(new Date().getMonth() + 1);
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx
around lines 16 to 17, the year and month state variables are hardcoded to 2025
and 7. Replace these hardcoded values by initializing year and month with the
current date's year and month using JavaScript's Date object to ensure the
component uses the current date dynamically.

useEffect(() => {
if (currentUserId && currentUserId === userId) {
setIsOwner(true);
console.log('니가 작성한 체험임');
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

디버그용 console.log를 제거하세요.

프로덕션 코드에서 디버그 로그는 제거해야 합니다.

-      console.log('니가 작성한 체험임');
📝 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
console.log('니가 작성한 체험임');
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx at
line 40, remove the console.log statement used for debugging to ensure no debug
logs remain in the production code.

Comment on lines +46 to +72
const { data: schedulesData } = useQuery({
queryKey: ['available-schedule', id, year, month],
queryFn: async () => {
const prevMonth = month === 1 ? 12 : month - 1;
const prevYear = month === 1 ? year - 1 : year;
const nextMonth = month === 12 ? 1 : month + 1;
const nextYear = month === 12 ? year + 1 : year;

const results = await Promise.allSettled([
privateInstance.get(
`/activities/${id}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`,
),
privateInstance.get(
`/activities/${id}/available-schedule?year=${year}&month=${padMonth(month)}`,
),
privateInstance.get(
`/activities/${id}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`,
),
]);
// 성공한 것만 합치기
const data = results
.filter((r) => r.status === 'fulfilled')
.flatMap((r) => (r.status === 'fulfilled' ? r.value.data : []));
return data;
},
enabled: !!id && !!year && !!month,
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

복잡한 스케줄 조회 로직을 별도 함수로 분리하는 것을 고려해보세요.

3개월치 스케줄을 조회하는 로직이 복잡합니다. 가독성과 재사용성을 위해 유틸 함수로 분리하는 것을 권장합니다.

별도 유틸 함수 예시:

// utils/scheduleUtils.ts
export const fetchMultipleMonthSchedules = async (
  id: string, 
  year: number, 
  month: number
) => {
  const prevMonth = month === 1 ? 12 : month - 1;
  const prevYear = month === 1 ? year - 1 : year;
  const nextMonth = month === 12 ? 1 : month + 1;
  const nextYear = month === 12 ? year + 1 : year;

  const results = await Promise.allSettled([
    privateInstance.get(`/activities/${id}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`),
    privateInstance.get(`/activities/${id}/available-schedule?year=${year}&month=${padMonth(month)}`),
    privateInstance.get(`/activities/${id}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`),
  ]);

  return results
    .filter((r) => r.status === 'fulfilled')
    .flatMap((r) => (r.status === 'fulfilled' ? r.value.data : []));
};
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx
around lines 46 to 72, the logic for fetching schedules for three months is
complex and embedded directly in the useQuery hook. To improve readability and
reusability, extract this logic into a separate utility function, for example in
a utils/scheduleUtils.ts file. Move the calculation of previous, current, and
next month/year and the Promise.allSettled API calls into this function, then
call this utility function inside the queryFn of useQuery.

Comment on lines +2 to +4
export function Skeleton({ className = '' }: { className?: string }) {
return <div className={`animate-pulse bg-gray-200 ${className}`} />;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

기본적인 스켈레톤 컴포넌트가 잘 구현되었습니다.

로딩 상태를 위한 간단하고 효과적인 구현입니다.

더 나은 className 처리와 유연성을 위해 다음과 같은 개선을 고려해보세요:

import { cn } from '@/lib/utils'; // or use clsx

export function Skeleton({ 
  className = '',
  ...props 
}: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div 
      className={cn("animate-pulse bg-gray-200", className)} 
      {...props}
    />
  );
}

이렇게 하면 더 안전한 className 병합과 추가 HTML 속성 지원이 가능합니다.

🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/Skeleton.tsx lines 2 to 4,
improve the Skeleton component by replacing the manual className string
concatenation with a utility function like cn or clsx for safer class merging,
and extend the component props to accept all standard HTML div attributes by
typing with React.HTMLAttributes<HTMLDivElement> and spreading ...props onto the
div element to support additional HTML properties.

export default function TotalPriceDisplay({ price }: { price: number }) {
const participants = useBookingStore((state) => state.participants);
const pricePerPerson = 1000;

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 participants = useBookingStore((state) => state.participants);
-

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/FloatingBox/TotalPriceDisplay.tsx at line 5, there is an
unnecessary blank line that should be removed to maintain code consistency.
Delete the empty line to keep the code clean and uniform.

Comment on lines +46 to +62
export interface ActivityDetail {
id: number;
isOwner : boolean;
userId: number;
title: string;
description: string;
category: string;
price: number;
address: string | undefined;
bannerImageUrl: string;
rating: number;
reviewCount: number;
createdAt: string;
updatedAt: string;
subImages: ActivitySubImage[];
schedules: ActivitySchedule[];
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

ActivityDetail 인터페이스의 공백 문제

isOwner 속성 정의에 불필요한 공백이 있습니다.

-  isOwner : boolean;
+  isOwner: boolean;
📝 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
export interface ActivityDetail {
id: number;
isOwner : boolean;
userId: number;
title: string;
description: string;
category: string;
price: number;
address: string | undefined;
bannerImageUrl: string;
rating: number;
reviewCount: number;
createdAt: string;
updatedAt: string;
subImages: ActivitySubImage[];
schedules: ActivitySchedule[];
}
export interface ActivityDetail {
id: number;
isOwner: boolean;
userId: number;
title: string;
description: string;
category: string;
price: number;
address: string | undefined;
bannerImageUrl: string;
rating: number;
reviewCount: number;
createdAt: string;
updatedAt: string;
subImages: ActivitySubImage[];
schedules: ActivitySchedule[];
}
🤖 Prompt for AI Agents
In src/types/activityDetailType.ts between lines 46 and 62, remove the
unnecessary space before the colon in the isOwner property definition to ensure
consistent formatting and style in the ActivityDetail interface.

💡 Verification agent

🧩 Analysis chain

ActivityDetail 인터페이스의 address 타입 검토

addressstring | undefined로 정의되어 있는데, 이는 API 응답에서 주소가 선택적일 수 있음을 나타냅니다. 하지만 UI에서 주소를 표시할 때 적절한 처리가 필요합니다.

다음 스크립트로 address 필드 사용을 확인해보겠습니다:


🏁 Script executed:

#!/bin/bash
# address 필드가 undefined일 때의 처리 방법 확인
rg -A 5 -B 5 "address.*undefined|address\?" --type tsx --type ts

Length of output: 94


아직 address 사용처가 확인되지 않아, tsx 파일을 glob 패턴으로 검색해 다시 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
# ts, tsx 확장자 내 'address' 프로퍼티 사용 위치 탐색
rg -n "\.address\b" -g '*.ts' -g '*.tsx'

Length of output: 175


LocationMap 컴포넌트가 addressundefined일 때 어떻게 동작하는지 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
# ActivityDetailForm.tsx 내 LocationMap 사용 부분 확인
sed -n '110,130p' 'src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx'

# LocationMap 컴포넌트 정의 및 처리 로직 검색
rg -A5 -B5 "LocationMap" -g '*.tsx'

Length of output: 4108


LocationMap에 전달되는 address 타입 불일치 확인 및 처리 필요

ActivityDetail 인터페이스에는

address: string | undefined;

로 정의되어 있지만,
LocationMap 컴포넌트는

interface LocationMapProps {
  address: string;
}

string만 받도록 선언되어 있어, undefined가 전달될 경우 타입 불일치 또는 런타임 에러가 발생할 수 있습니다.

확인 지점:

  • src/types/activityDetailType.tsaddress: string | undefined;
  • src/components/LocationMap.tsxinterface LocationMapProps { address: string; }
  • src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx (~118행)
    <LocationMap address={activityData.address} />

제안

  • 렌더링 전에 activityData.address 존재 여부 검사 (address && <LocationMap .../>)
  • 또는 LocationMapPropsaddress?: string으로 변경하고 내부에서 undefined 처리 로직 추가
🤖 Prompt for AI Agents
In src/types/activityDetailType.ts lines 46-62, the address property is defined
as string | undefined, but in src/components/LocationMap.tsx the
LocationMapProps interface expects address as a string only. To fix this, either
update the LocationMapProps interface to make address optional (address?:
string) and handle undefined inside LocationMap, or in
src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx around
line 118, add a check to render LocationMap only if activityData.address exists
(e.g., address && <LocationMap address={address} />) to prevent passing
undefined.

Comment on lines +64 to +66

export interface BookinDateProps {
schedules: ActivitySchedule[];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

BookinDateProps 인터페이스명 오타

인터페이스명에 오타가 있습니다.

-export interface BookinDateProps {
+export interface BookingDateProps {

이 인터페이스를 사용하는 다른 파일에서도 함께 수정이 필요할 수 있습니다.

📝 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
export interface BookinDateProps {
schedules: ActivitySchedule[];
}
export interface BookingDateProps {
schedules: ActivitySchedule[];
}
🤖 Prompt for AI Agents
In src/types/activityDetailType.ts around lines 64 to 66, the interface name
BookinDateProps contains a typo. Rename the interface to BookingDateProps and
update all references to this interface in other files accordingly to maintain
consistency and avoid errors.

Comment on lines +1 to +3
export type ServerErrorResponse = {
error?: string;
};
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 전반에서 일관된 에러 응답 형식을 제공하는 좋은 접근입니다.

향후 확장성을 위해 더 포괄적인 에러 타입을 고려해보세요:

-export type ServerErrorResponse = {
-  error?: string;
-};
+export type ServerErrorResponse = {
+  error?: string;
+  code?: string;
+  details?: Record<string, any>;
+};
📝 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
export type ServerErrorResponse = {
error?: string;
};
export type ServerErrorResponse = {
error?: string;
code?: string;
details?: Record<string, any>;
};
🤖 Prompt for AI Agents
In src/types/apiErrorResponseType.ts at lines 1 to 3, the current
ServerErrorResponse type only includes an optional error string, which may limit
future extensibility. To fix this, expand the type to include additional fields
such as error codes, messages, or metadata that can accommodate more detailed
error information consistently across the API. This will ensure the error
response type is more comprehensive and scalable for future needs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🔭 Outside diff range comments (1)
src/components/FloatingBox/BookingInterface.tsx (1)

122-127: 모바일 뷰에서 하드코딩된 가격을 동적 가격으로 변경하세요

데스크톱과 태블릿 뷰에서는 price prop을 사용하지만, 모바일 뷰에서는 여전히 하드코딩된 "₩ 10,000"을 표시하고 있습니다.

            <div className='mb-1 text-xl font-bold text-gray-900'>
-              ₩ 10,000{' '}
+              ₩ {price.toLocaleString()}{' '}
              <span className='text-sm font-normal text-gray-600'>
                / 총 {participants}인
              </span>
            </div>
♻️ Duplicate comments (2)
src/app/api/activities/[id]/reservation/route.ts (1)

14-14: 요청 body 데이터 검증이 필요합니다.

selectedTimeIdparticipants에 대한 유효성 검사 없이 Number()로 변환하고 있어, 잘못된 데이터가 전달될 경우 NaN이 될 수 있습니다.

  const { selectedTimeId, participants } = await request.json();
+  
+  if (!selectedTimeId || !participants) {
+    return NextResponse.json(
+      { error: '필수 데이터가 누락되었습니다.' },
+      { status: 400 }
+    );
+  }
+  
+  const scheduleId = Number(selectedTimeId);
+  const headCount = Number(participants);
+  
+  if (isNaN(scheduleId) || isNaN(headCount) || headCount < 1) {
+    return NextResponse.json(
+      { error: '잘못된 데이터 형식입니다.' },
+      { status: 400 }
+    );
+  }
src/app/api/activities/[id]/available-schedule/route.ts (1)

16-17: 쿼리 파라미터 유효성 검사를 고려해보세요.

yearmonth 파라미터가 null일 수 있는데, URL에 직접 사용하고 있습니다. API 호출 전에 유효성 검사를 추가하는 것을 권장합니다.

  const year = searchParams.get('year');
  const month = searchParams.get('month');

+  if (!year || !month) {
+    return NextResponse.json(
+      { error: '년도와 월 정보가 필요합니다.' },
+      { status: 400 }
+    );
+  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d9cdc53 and 533b7cb.

📒 Files selected for processing (15)
  • src/app/(with-header)/activities/[id]/components/Title.tsx (2 hunks)
  • src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx (1 hunks)
  • src/app/api/activities/[id]/available-schedule/route.ts (1 hunks)
  • src/app/api/activities/[id]/reservation/route.ts (1 hunks)
  • src/components/ActivityDropdown/Dropdown.tsx (1 hunks)
  • src/components/ActivityDropdown/DropdownContext.tsx (1 hunks)
  • src/components/ActivityDropdown/Item.tsx (1 hunks)
  • src/components/ActivityDropdown/index.ts (1 hunks)
  • src/components/ActivityDropdown/menu.tsx (1 hunks)
  • src/components/ActivityDropdown/trigger.tsx (1 hunks)
  • src/components/DatePicker/CalendarBody.tsx (0 hunks)
  • src/components/FloatingBox/BookingInterface.tsx (3 hunks)
  • src/components/FloatingBox/TabletPopup.tsx (1 hunks)
  • src/ui/BookingModal.tsx (1 hunks)
  • src/ui/MobileBookingModal.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • src/components/DatePicker/CalendarBody.tsx
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/ui/MobileBookingModal.tsx (3)
src/types/activityDetailType.ts (1)
  • SchedulesProps (40-40)
src/components/DatePicker/DatePicker.tsx (1)
  • DatePicker (20-94)
src/components/FloatingBox/TotalPriceDisplay.tsx (1)
  • TotalPriceDisplay (3-12)
src/components/ActivityDropdown/Dropdown.tsx (2)
src/components/ActivityDropdown/DropdownContext.tsx (1)
  • DropdownContext (11-11)
src/components/Dropdown.tsx (1)
  • Dropdown (29-205)
src/components/ActivityDropdown/menu.tsx (1)
src/components/ActivityDropdown/DropdownContext.tsx (1)
  • useDropdownContext (13-19)
src/components/FloatingBox/TabletPopup.tsx (1)
src/ui/TabletBookingModal.tsx (1)
  • TabletModal (9-46)
src/components/ActivityDropdown/Item.tsx (1)
src/components/ActivityDropdown/DropdownContext.tsx (1)
  • useDropdownContext (13-19)
🪛 GitHub Check: Code Quality Check
src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx

[failure] 11-11:
Cannot find module '../../utils/uploadImage' or its corresponding type declarations.

🪛 GitHub Actions: CI
src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx

[error] 11-11: TypeScript error TS2307: Cannot find module '../../utils/uploadImage' or its corresponding type declarations.

🔇 Additional comments (25)
src/ui/MobileBookingModal.tsx (3)

11-19: Props 추가가 적절하게 구현되었습니다.

schedulesprice props가 올바르게 타입 정의되고 하위 컴포넌트들에게 전달되고 있습니다. 타입 임포트와 컴포넌트 간의 데이터 흐름이 일관성 있게 처리되었습니다.


54-54: 일관된 props 전달이 잘 구현되었습니다.

DatePickerschedules prop이 올바르게 전달되어 동적 스케줄 데이터를 지원합니다.


80-80: 가격 정보 전달이 적절합니다.

TotalPriceDisplayprice prop이 올바르게 전달되어 동적 가격 계산을 지원합니다.

src/ui/BookingModal.tsx (1)

8-19: Device별 props 전달이 올바르게 구현되었습니다.

schedulesprice props가 적절하게 타입 정의되고, 디바이스 유형에 따라 올바른 하위 컴포넌트에 전달되고 있습니다. MobileModal에는 두 props 모두, TabletModal에는 schedules만 전달하는 것이 적절합니다.

src/app/api/activities/[id]/reservation/route.ts (2)

27-40: API 호출 구현이 적절합니다.

인증 헤더 설정과 요청 데이터 구조가 올바르게 구현되어 있고, 응답 처리도 적절합니다.


41-51: 에러 처리가 체계적으로 구현되었습니다.

다양한 에러 응답 형태를 고려한 메시지 추출 로직과 상태 코드 처리가 잘 구현되어 있습니다.

src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx (3)

22-30: React Query를 이용한 데이터 페칭이 적절합니다.

액티비티 데이터를 가져오는 로직이 올바르게 구현되어 있고, 에러 핸들링과 로딩 상태 관리가 적절합니다.


62-88: 이미지 업로드 로직이 체계적으로 구현되었습니다.

메인 이미지와 서브 이미지 업로드 처리, 에러 핸들링, 용량 제한 로직이 잘 구현되어 있습니다. 업로드 실패 시 사용자에게 적절한 피드백을 제공합니다.


94-143: 폼 검증과 제출 로직이 포괄적으로 구현되었습니다.

필수 필드 검증, 에러 처리, 성공/실패 시 사용자 피드백이 체계적으로 처리되어 있습니다. Alert 사용에 대한 개선 계획이 주석으로 명시되어 있어 향후 리팩토링이 계획되어 있음을 확인할 수 있습니다.

src/app/api/activities/[id]/available-schedule/route.ts (2)

29-38: API 호출과 응답 처리가 적절합니다.

외부 API 호출을 위한 URL 구성, 인증 헤더 설정, 응답 데이터 반환이 올바르게 구현되어 있습니다.


39-51: 에러 처리 패턴이 일관성 있게 구현되었습니다.

다른 API 라우트와 동일한 에러 처리 패턴을 사용하여 일관성을 유지하고 있으며, 적절한 에러 메시지와 상태 코드를 반환합니다.

src/components/FloatingBox/BookingInterface.tsx (2)

59-64: 소유자 예약 방지 로직이 잘 구현되었습니다

isOwner prop을 활용하여 체험 등록자가 자신의 체험을 예약할 수 없도록 하는 로직이 적절히 구현되었습니다.

Also applies to: 77-79, 110-112


28-53: id 변수가 정의되기 전에 사용되고 있습니다

handleBooking 함수(30번 줄)에서 id를 사용하고 있지만, useParams에서 id를 추출하는 코드는 57번 줄에 있습니다. 이로 인해 참조 에러가 발생합니다.

export default function BookingInterface({
  schedules,
  onMonthChange,
  isOwner,
  price,
}: {
  schedules: SchedulesProps;
  onMonthChange?: (year: number, month: number) => void;
  isOwner: boolean;
  price: number;
}) {
+  const { id } = useParams();
+  const setIsOpen = useBookingStore((state) => state.setIsOpen);
+  const { selectedDate, selectedTime, participants, selectedTimeId } =
+    useBookingStore();
+
  const handleBooking = async () => {
    try {
      await privateInstance.post(`/activities/${id}/reservation`, {
        selectedTimeId,
        participants,
      });

      alert('예약이 완료되었습니다!');
      setIsOpen(false);
    } catch (err) {
      const error = err as AxiosError;

      const responseData = error.response?.data as
        | { error?: string; message?: string }
        | undefined;

      console.error('전체 에러:', error);

      alert(
        responseData?.error ||
          responseData?.message ||
          error.message ||
          '예약에 실패했습니다.',
      );
    }
  };
-  const setIsOpen = useBookingStore((state) => state.setIsOpen);
-  const { selectedDate, selectedTime, participants, selectedTimeId } =
-    useBookingStore();
-  const { id } = useParams();

Likely an incorrect or invalid review comment.

src/components/ActivityDropdown/trigger.tsx (2)

23-24: 컨텍스트 사용이 적절합니다

useDropdownContext 훅을 통해 드롭다운 상태에 올바르게 접근하고 있으며, 컴포넌트 간 상태 공유가 잘 구현되어 있습니다.


27-40: 키보드 접근성 향상을 위한 개선 제안

현재 구현은 마우스 클릭 이벤트만 처리하고 있습니다. 완전한 접근성을 위해 키보드 이벤트 처리를 추가하는 것을 고려해보세요.

다음과 같이 키보드 이벤트 핸들링을 추가할 수 있습니다:

     <button
       aria-expanded={isOpen}
       aria-haspopup='true'
       className='cursor-pointer'
       type='button'
       onClick={(e) => {
         e.preventDefault();
         e.stopPropagation();
         setIsOpen((prev) => !prev);
       }}
+      onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault();
+          e.stopPropagation();
+          setIsOpen((prev) => !prev);
+        }
+      }}
     >

Likely an incorrect or invalid review comment.

src/components/ActivityDropdown/index.ts (1)

27-31: 컴파운드 컴포넌트 패턴이 잘 구현되었습니다

Object.assign을 사용한 컴파운드 컴포넌트 패턴이 적절하게 구현되어 있습니다. 이 패턴은 사용자가 <ActivityDropdown.Trigger>, <ActivityDropdown.Menu> 등으로 직관적으로 사용할 수 있게 해줍니다.

더 현대적인 방식을 선호한다면 다음과 같이 구현할 수도 있습니다:

-const ActivityDropdown = Object.assign(Wrapper, {
-  Trigger: Trigger,
-  Menu: Menu,
-  Item: Item,
-});
+const ActivityDropdown = Object.assign(Wrapper, {
+  Trigger,
+  Menu,
+  Item,
+} as const);
src/components/ActivityDropdown/Dropdown.tsx (2)

10-21: 외부 클릭 감지 로직이 잘 구현되었습니다

useEffect를 사용한 외부 클릭 감지와 이벤트 리스너 정리가 적절하게 구현되어 있습니다. 메모리 누수 방지를 위한 cleanup 함수도 올바르게 설정되어 있습니다.


1-30: 기존 Dropdown과 ActivityDropdown은 중복되지 않습니다
두 컴포넌트는 목적과 구현 방식이 완전히 다릅니다.

  • src/components/Dropdown.tsx는 일반적인 옵션 선택용 제네릭 드롭다운으로, 옵션 리스트 렌더링, 애니메이션, 선택 관리(onChange 등)를 포함합니다.
  • src/components/ActivityDropdown/Dropdown.tsx는 드롭다운 상태(isOpen)와 외부 클릭 감지를 위한 Context 제공용 래퍼 역할만 합니다.

따라서 공통 로직을 추출하거나 통합할 필요가 없으며, 원래 제안된 중복성 검토는 해당되지 않습니다.

Likely an incorrect or invalid review comment.

src/components/ActivityDropdown/menu.tsx (1)

36-38: 조건부 렌더링이 적절하게 구현되었습니다

isOpen 상태에 따른 조건부 렌더링이 올바르게 구현되어 있어 성능상 이점을 제공합니다.

src/components/ActivityDropdown/DropdownContext.tsx (3)

5-9: TypeScript 인터페이스가 잘 정의되었습니다

DropdownContextType 인터페이스가 드롭다운에 필요한 모든 속성을 명확하게 정의하고 있습니다. 타입 안전성이 잘 보장되어 있습니다.


13-19: 커스텀 훅의 에러 처리가 적절합니다

useDropdownContext 훅에서 컨텍스트가 null인 경우 명확한 에러를 발생시키는 것이 좋습니다. 이는 개발자가 올바른 컴포넌트 구조를 사용하도록 도움을 줍니다.

더 구체적인 에러 메시지로 개선할 수 있습니다:

   if (!context) {
-    throw new Error('요소들은 <Dropdown> 내부에서만 사용되야합니다');
+    throw new Error('Dropdown 관련 컴포넌트들은 <ActivityDropdown> 내부에서만 사용할 수 있습니다');
   }

11-11: 컨텍스트 초기값 설정이 적절합니다

null을 기본값으로 설정하여 Provider 없이 사용하는 경우를 감지할 수 있도록 한 것이 좋은 패턴입니다.

src/components/ActivityDropdown/Item.tsx (3)

39-44: 이벤트 핸들링 로직 검증 완료

이벤트 처리 로직이 올바르게 구현되었습니다. preventDefault()stopPropagation()을 통해 의도하지 않은 동작을 방지하고, 드롭다운을 자동으로 닫는 로직도 적절합니다.


47-57: 버튼 요소 구현 우수

버튼 요소에 명시적인 type="button" 속성이 포함되어 있어 접근성 요구사항을 충족하며, 반응형 디자인과 호버 효과도 적절히 구현되었습니다.


9-27: JSDoc 문서화 우수

컴포넌트의 목적, 사용법, 매개변수에 대한 상세한 문서화가 잘 되어 있습니다. 예제 코드도 포함되어 있어 사용법을 쉽게 이해할 수 있습니다.

Comment on lines +4 to +8
import { ActivityDetail } from '@/types/activityDetailType';
import ActivityDropdown from '@/components/ActivityDropdown';
import Menu from '@/components/ActivityDropdown/menu';
import Item from '@/components/ActivityDropdown/Item';
import Trigger from '@/components/ActivityDropdown/trigger';
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Import 구조 개선 가능

모든 ActivityDropdown 관련 컴포넌트들을 개별적으로 import하는 대신, 더 깔끔한 구조로 정리할 수 있습니다.

-import { ActivityDetail } from '@/types/activityDetailType';
-import ActivityDropdown from '@/components/ActivityDropdown';
-import Menu from '@/components/ActivityDropdown/menu';
-import Item from '@/components/ActivityDropdown/Item';
-import Trigger from '@/components/ActivityDropdown/trigger';
+import { ActivityDetail } from '@/types/activityDetailType';
+import ActivityDropdown from '@/components/ActivityDropdown';

그리고 사용 시에는:

-        <ActivityDropdown>
-          <Trigger>
+        <ActivityDropdown>
+          <ActivityDropdown.Trigger>
             <IconDropdown />
-          </Trigger>
-          <Menu>
-            <Item onClick={() => alert('수정')}>수정하기</Item>
-            <Item onClick={() => alert('삭제')}>삭제하기</Item>
-          </Menu>
+          </ActivityDropdown.Trigger>
+          <ActivityDropdown.Menu>
+            <ActivityDropdown.Item onClick={() => alert('수정')}>수정하기</ActivityDropdown.Item>
+            <ActivityDropdown.Item onClick={() => alert('삭제')}>삭제하기</ActivityDropdown.Item>
+          </ActivityDropdown.Menu>
         </ActivityDropdown>
📝 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
import { ActivityDetail } from '@/types/activityDetailType';
import ActivityDropdown from '@/components/ActivityDropdown';
import Menu from '@/components/ActivityDropdown/menu';
import Item from '@/components/ActivityDropdown/Item';
import Trigger from '@/components/ActivityDropdown/trigger';
// Before, we had multiple imports for each sub-component:
-import { ActivityDetail } from '@/types/activityDetailType';
-import ActivityDropdown from '@/components/ActivityDropdown';
-import Menu from '@/components/ActivityDropdown/menu';
-import Item from '@/components/ActivityDropdown/Item';
-import Trigger from '@/components/ActivityDropdown/trigger';
// Apply these changes:
+import { ActivityDetail } from '@/types/activityDetailType';
+import ActivityDropdown from '@/components/ActivityDropdown';
// Later in the JSX, replace individual sub-component tags:
-<ActivityDropdown>
- <Trigger>
- <IconDropdown />
- </Trigger>
- <Menu>
- <Item onClick={() => alert('수정')}>수정하기</Item>
- <Item onClick={() => alert('삭제')}>삭제하기</Item>
- </Menu>
-</ActivityDropdown>
+<ActivityDropdown>
+ <ActivityDropdown.Trigger>
+ <IconDropdown />
+ </ActivityDropdown.Trigger>
+ <ActivityDropdown.Menu>
+ <ActivityDropdown.Item onClick={() => alert('수정')}>
+ 수정하기
+ </ActivityDropdown.Item>
+ <ActivityDropdown.Item onClick={() => alert('삭제')}>
+ 삭제하기
+ </ActivityDropdown.Item>
+ </ActivityDropdown.Menu>
+</ActivityDropdown>
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/Title.tsx around lines 4 to
8, the imports for ActivityDropdown and its subcomponents are done individually,
which can be simplified. Refactor the imports by creating an index file inside
the ActivityDropdown folder that exports all related components, then import
them collectively from that index file to improve code cleanliness and
maintainability.

Comment on lines +42 to +43
<Item onClick={() => alert('수정')}>수정하기</Item>
<Item onClick={() => alert('삭제')}>삭제하기</Item>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

임시 alert 핸들러를 실제 구현으로 교체 필요

현재 수정/삭제 기능이 alert로만 구현되어 있습니다. 실제 기능 구현이 필요합니다.

실제 수정/삭제 API 호출 로직을 구현하시겠습니까? 다음과 같은 구조를 제안드립니다:

const handleEdit = () => {
  // 수정 페이지로 라우팅 또는 모달 열기
  router.push(`/activities/${activityId}/edit`);
};

const handleDelete = async () => {
  if (confirm('정말 삭제하시겠습니까?')) {
    try {
      await deleteActivity(activityId);
      router.push('/activities');
    } catch (error) {
      // 에러 처리
    }
  }
};
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/Title.tsx at lines 42-43,
replace the temporary alert handlers for 수정하기 and 삭제하기 with actual
implementations. Implement a handleEdit function that routes to the edit page
for the current activity using router.push. Implement a handleDelete async
function that confirms deletion, calls the deleteActivity API with the
activityId, and on success routes back to the activities list; include error
handling for the API call. Update the onClick handlers of the Items to call
these new functions instead of alert.

import { ScheduleSelectForm } from '../../components/ScheduleSelectForm';
import { ImageSection } from '../../components/ImageSection';
import Button from '@/components/Button';
import { uploadImage } from '../../utils/uploadImage';
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

누락된 모듈 임포트로 인한 빌드 실패

uploadImage 모듈을 찾을 수 없어 TypeScript 컴파일 오류가 발생하고 있습니다. 해당 유틸리티 함수가 구현되어 있는지 확인하고 올바른 경로로 임포트해주세요.


🏁 Script executed:

#!/bin/bash
# uploadImage 모듈 존재 여부 확인
fd -t f "uploadImage" --exec echo "Found: {}"

# utils 디렉토리 구조 확인
fd -t d "utils" --exec ls -la {}

Length of output: 438


uploadImage 유틸리티 파일 부재로 인한 빌드 오류

컴파일 시 uploadImage 모듈을 찾을 수 없어 빌드가 실패하고 있습니다. 다음을 확인 및 조치해주세요.

  • 위치: src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx (11번 라인)
  • 현재 src/app/utils 디렉토리에 uploadImage.ts 파일이 없으며(validateInput.ts, MonthFormatChange.ts만 확인됨)

해결 방안:

  • src/app/utils/uploadImage.ts 파일을 생성하고 uploadImage 함수를 구현하거나
  • 올바른 경로에 있는 파일로 임포트 경로를 수정
🧰 Tools
🪛 GitHub Check: Code Quality Check

[failure] 11-11:
Cannot find module '../../utils/uploadImage' or its corresponding type declarations.

🪛 GitHub Actions: CI

[error] 11-11: TypeScript error TS2307: Cannot find module '../../utils/uploadImage' or its corresponding type declarations.

🤖 Prompt for AI Agents
In src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx at line
11, the import statement for uploadImage causes a build error because the file
src/app/utils/uploadImage.ts does not exist. To fix this, either create the
uploadImage.ts file in the src/app/utils directory with the appropriate
uploadImage function implementation or update the import path to point to the
correct existing file that exports uploadImage.

Comment on lines +10 to +21
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

키보드 접근성을 위한 Escape 키 처리 추가 제안

현재 외부 클릭으로만 드롭다운을 닫을 수 있습니다. 접근성 향상을 위해 Escape 키 처리를 추가하는 것을 고려해보세요.

다음과 같이 키보드 이벤트 핸들러를 추가할 수 있습니다:

   useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') {
+        setIsOpen(false);
+      }
+    };
+
     const handleClickOutside = (e: MouseEvent) => {
       if (
         dropdownRef.current &&
         !dropdownRef.current.contains(e.target as Node)
       ) {
         setIsOpen(false);
       }
     };
     document.addEventListener('mousedown', handleClickOutside);
+    document.addEventListener('keydown', handleKeyDown);
-    return () => document.removeEventListener('mousedown', handleClickOutside);
+    return () => {
+      document.removeEventListener('mousedown', handleClickOutside);
+      document.removeEventListener('keydown', handleKeyDown);
+    };
   }, []);
🤖 Prompt for AI Agents
In src/components/ActivityDropdown/Dropdown.tsx around lines 10 to 21, the
dropdown only closes on outside mouse clicks, lacking keyboard accessibility.
Add a keydown event listener to detect the Escape key press and close the
dropdown by setting isOpen to false. Ensure to clean up this event listener in
the useEffect cleanup function along with the existing mouse event listener.

Comment on lines +32 to +36
}: {
children: string | ReactNode;
onClick: () => void;
itemClassName?: string;
}) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

props 타입 정의를 별도 인터페이스로 분리 권장

현재 inline으로 정의된 props 타입을 별도 인터페이스로 분리하면 재사용성과 가독성이 향상됩니다.

+interface ItemProps {
+  children: string | ReactNode;
+  onClick: () => void;
+  itemClassName?: string;
+}
+
 export default function Item({
   children,
   onClick,
   itemClassName,
-}: {
-  children: string | ReactNode;
-  onClick: () => void;
-  itemClassName?: string;
-}) {
+}: ItemProps) {
🤖 Prompt for AI Agents
In src/components/ActivityDropdown/Item.tsx around lines 32 to 36, the props
type is currently defined inline. To improve reusability and readability,
extract the props definition into a separate interface. Define an interface with
the props structure and use it to type the component's props instead of inline
typing.

Comment on lines +40 to +49
return (
<div
className={cn(
'absolute top-37 right-0 z-9999 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
menuClassName,
)}
>
{children}
</div>
);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

z-index 값이 과도하게 높습니다

현재 z-9999로 설정된 z-index 값이 매우 높습니다. 이는 다른 UI 요소들과의 레이어링 충돌을 야기할 수 있습니다. 더 적절한 z-index 값을 사용하는 것을 고려해보세요.

       className={cn(
-        'absolute top-37 right-0 z-9999 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
+        'absolute top-37 right-0 z-50 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
         menuClassName,
       )}

또는 CSS 변수나 테일윈드 설정에서 드롭다운 전용 z-index 값을 정의하여 일관성을 유지할 수 있습니다.

📝 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
return (
<div
className={cn(
'absolute top-37 right-0 z-9999 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
menuClassName,
)}
>
{children}
</div>
);
return (
<div
className={cn(
'absolute top-37 right-0 z-50 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
menuClassName,
)}
>
{children}
</div>
);
🤖 Prompt for AI Agents
In src/components/ActivityDropdown/menu.tsx around lines 40 to 49, the z-index
value is set excessively high at z-9999, which may cause layering conflicts with
other UI elements. Reduce the z-index to a more reasonable value that still
ensures the dropdown appears above other components, such as a value within the
typical Tailwind z-index scale (e.g., z-50 or z-40). Alternatively, define a
dedicated CSS variable or Tailwind configuration for dropdown z-index to
maintain consistency across the app.

🧹 Nitpick (assertive)

UX 개선을 위한 애니메이션 추가 제안

현재 메뉴가 즉시 나타나고 사라집니다. 더 나은 사용자 경험을 위해 페이드 인/아웃 애니메이션을 추가하는 것을 고려해보세요.

Framer Motion을 사용한 애니메이션 예시:

+'use client';
+
+import { motion, AnimatePresence } from 'framer-motion';
 import { ReactNode } from 'react';

 export default function Menu({
   children,
   menuClassName,
 }: {
   children: ReactNode;
   menuClassName?: string;
 }) {
   const { isOpen } = useDropdownContext();

-  if (!isOpen) return null;
-
   return (
-    <div
-      className={cn(
-        'absolute top-37 right-0 z-50 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
-        menuClassName,
-      )}
-    >
-      {children}
-    </div>
+    <AnimatePresence>
+      {isOpen && (
+        <motion.div
+          initial={{ opacity: 0, y: -10 }}
+          animate={{ opacity: 1, y: 0 }}
+          exit={{ opacity: 0, y: -10 }}
+          transition={{ duration: 0.2 }}
+          className={cn(
+            'absolute top-37 right-0 z-50 flex flex-col content-center justify-around rounded-md border border-gray-300 bg-white p-2 shadow-lg',
+            menuClassName,
+          )}
+        >
+          {children}
+        </motion.div>
+      )}
+    </AnimatePresence>
   );
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/ActivityDropdown/menu.tsx around lines 40 to 49, the dropdown
menu currently appears and disappears instantly, which can be jarring for users.
To improve UX, wrap the div containing the menu with a Framer Motion component
like motion.div and add fade in/out animations using initial, animate, and exit
props. This will create smooth fade transitions when the menu mounts and
unmounts.

Comment on lines +37 to +52
} catch (err) {
const error = err as AxiosError;

const responseData = error.response?.data as
| { error?: string; message?: string }
| undefined;

console.error('전체 에러:', error);

alert(
responseData?.error ||
responseData?.message ||
error.message ||
'예약에 실패했습니다.',
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

프로덕션 환경에서의 에러 로깅 개선 필요

현재 console.error로 전체 에러를 로깅하고 있습니다. 프로덕션 환경에서는 적절한 에러 로깅 서비스(예: Sentry)를 사용하는 것이 좋습니다.

추가로, PR 목표에 언급된 대로 추후 toast 알림으로 전환할 계획이라면, TODO 주석을 추가하여 명시하는 것이 좋습니다:

      console.error('전체 에러:', error);

+     // TODO: toast 알림으로 전환 예정
      alert(
        responseData?.error ||
          responseData?.message ||
          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.

Suggested change
} catch (err) {
const error = err as AxiosError;
const responseData = error.response?.data as
| { error?: string; message?: string }
| undefined;
console.error('전체 에러:', error);
alert(
responseData?.error ||
responseData?.message ||
error.message ||
'예약에 실패했습니다.',
);
}
} catch (err) {
const error = err as AxiosError;
const responseData = error.response?.data as
| { error?: string; message?: string }
| undefined;
console.error('전체 에러:', error);
// TODO: toast 알림으로 전환 예정
alert(
responseData?.error ||
responseData?.message ||
error.message ||
'예약에 실패했습니다.',
);
}
🤖 Prompt for AI Agents
In src/components/FloatingBox/BookingInterface.tsx around lines 37 to 52,
replace the console.error call with a call to the configured production error
logging service such as Sentry to properly capture errors in production. Also,
add a TODO comment near the alert call indicating that this alert should be
replaced with a toast notification in the future as per the PR goals.

Comment on lines +1 to +29
// 'use client';

import useBookingStore from '@/stores/Booking/useBookingStore';
import IconClose from '@assets/svg/close';
import DatePicker from '../DatePicker/DatePicker';
import TimeSelector from './TimeSelector';
// import useBookingStore from '@/stores/Booking/useBookingStore';
// import IconClose from '@assets/svg/close';
// import DatePicker from '../DatePicker/DatePicker';
// import TimeSelector from './TimeSelector';
// import { SchedulesProps } from '@/types/activityDetailType';

export default function TabletPopup({}) {
const isOpen = useBookingStore((state) => state.isOpen);
const setIsOpen = useBookingStore((state) => state.setIsOpen);
// export default function TabletPopup({schedules}:SchedulesProps) {
// const isOpen = useBookingStore((state) => state.isOpen);
// const setIsOpen = useBookingStore((state) => state.setIsOpen);

if (!isOpen) return null;
// if (!isOpen) return null;

return (
<div className='absolute z-50 flex flex-col items-center justify-center'>
<div className='flex justify-between'>
<h2>날짜</h2>
<button onClick={() => setIsOpen(false)}>
<IconClose />
</button>
</div>
// return (
// <div className='absolute z-50 flex flex-col items-center justify-center'>
// <div className='flex justify-between'>
// <h2>날짜</h2>
// <button onClick={() => setIsOpen(false)}>
// <IconClose />
// </button>
// </div>

<DatePicker />
// <DatePicker schedules={schedules}/>

<TimeSelector />
</div>
);
}
// <TimeSelector />
// </div>
// );
// }
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

주석 처리된 코드를 제거하세요

전체 파일이 주석 처리되어 있습니다. TabletBookingModal.tsx가 이 컴포넌트를 대체하는 것으로 보입니다. 사용하지 않는 코드는 주석으로 남겨두기보다 완전히 제거하는 것이 좋습니다.

이 파일을 삭제하거나, 향후 사용 계획이 있다면 그 이유를 명시하는 주석을 추가하세요.

🤖 Prompt for AI Agents
In src/components/FloatingBox/TabletPopup.tsx lines 1 to 29, the entire code is
commented out and appears unused since TabletBookingModal.tsx replaces this
component. Remove all the commented-out code entirely to clean up the file. If
you plan to keep this file for future use, add a clear comment at the top
explaining the reason for retaining it; otherwise, consider deleting the file
altogether.

Copy link
Contributor

@LeeCh0129 LeeCh0129 left a comment

Choose a reason for hiding this comment

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

체험 상세페이지 API 연결 작업을 완성도 높게 해주셨네요! 라우트 구조부터, useQuery 활용이나 조건부 쿼리 등을 이용해서 체계적으로 잘 구현해주셨네요! 덕분에 많이 많이 배워갑니다 🙏

Copy link
Contributor

@minimo-9 minimo-9 left a comment

Choose a reason for hiding this comment

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

고생많으셨습니다! 구현 너무 잘해주셨네요!

Copy link
Contributor

@BokyungCodes BokyungCodes left a comment

Choose a reason for hiding this comment

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

Booking 인터페이스의 디바이스별 UI가 명확하게 분리되어 있어 유지보수하기 좋아 보입니다!

console 로그들은 나중에 삭제하는게 성능에 더 좋을 것 같습니다. 👍

@evaain706 evaain706 merged commit 817b379 into develop Jul 28, 2025
2 checks passed
@evaain706 evaain706 deleted the feat/74 branch July 28, 2025 09:42
@coderabbitai coderabbitai bot mentioned this pull request Aug 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants