From c2e63038298f18b90d4e6004c63b91e06988040a Mon Sep 17 00:00:00 2001 From: evaain706 Date: Wed, 18 Feb 2026 00:10:02 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=EA=B3=BC=EA=B1=B0=EC=9B=94=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EB=AA=BB=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DatePicker/CalendarHeader.tsx | 8 +++++++- src/components/DatePicker/DatePicker.tsx | 7 ++++++- src/types/datePickerTypes.ts | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/DatePicker/CalendarHeader.tsx b/src/components/DatePicker/CalendarHeader.tsx index d27a6b74..86dce1f5 100644 --- a/src/components/DatePicker/CalendarHeader.tsx +++ b/src/components/DatePicker/CalendarHeader.tsx @@ -5,12 +5,18 @@ import { CalendarHeaderProps } from '@/types/datePickerTypes'; export default function CalendarHeader({ viewDate, onMonthChange, + isPrevDisabled, }: CalendarHeaderProps) { return (
diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 82cd0283..0093113d 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -64,7 +64,12 @@ export default function DatePicker({ console.log('뷰데이트', viewDate.format('YYYY-MM-DD')); }, [availableDates, viewDate]); + const isPrevDisabled = + viewDate.year() === today.year() && viewDate.month() === today.month(); + const changeMonth = (direction: 'add' | 'subtract') => { + if (direction === 'subtract' && isPrevDisabled) return; + setViewDate((prev) => { const newDate = direction === 'add' ? prev.add(1, 'month') : prev.subtract(1, 'month'); @@ -83,7 +88,7 @@ export default function DatePicker({ return (
- + void; + isPrevDisabled?: boolean; } From 8cccb35b6011545b3149f42399ea1467fc8ab669 Mon Sep 17 00:00:00 2001 From: evaain706 Date: Wed, 18 Feb 2026 20:32:26 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:SSR=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[id]/components/ActivityDetailForm.tsx | 149 ------------------ .../[id]/components/BookingSection.tsx | 80 ++++++++++ .../activities/[id]/components/ImageGrid.tsx | 7 +- .../activities/[id]/components/Title.tsx | 8 +- .../(with-header)/activities/[id]/loading.tsx | 72 +++++++++ .../(with-header)/activities/[id]/page.tsx | 82 +++++++++- src/app/api/deleteActivity/[id]/route.ts | 2 + src/app/api/editActivity/[id]/route.ts | 2 + .../api/reservations/[id]/reviews/route.ts | 4 + src/types/activityDetailType.ts | 11 +- 10 files changed, 254 insertions(+), 163 deletions(-) delete mode 100644 src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx create mode 100644 src/app/(with-header)/activities/[id]/components/BookingSection.tsx create mode 100644 src/app/(with-header)/activities/[id]/loading.tsx diff --git a/src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx b/src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx deleted file mode 100644 index 3b5cd6c0..00000000 --- a/src/app/(with-header)/activities/[id]/components/ActivityDetailForm.tsx +++ /dev/null @@ -1,149 +0,0 @@ -'use client'; - -import { useParams } from 'next/navigation'; -import Title from './Title'; -import ImageGrid from './ImageGrid'; -import BookingInterface from '@/components/FloatingBox/BookingInterface'; -import LocationMap from '@/components/LocationMap'; -import { useQuery } from '@tanstack/react-query'; -import { privateInstance } from '@/apis/privateInstance'; -import { useState, useCallback } from 'react'; -import useUserStore from '@/stores/authStore'; -import { padMonth } from '../utils/MonthFormatChange'; -import ReviewSection from './ReviewSection'; -import { AxiosError } from 'axios'; -import { notFound } from 'next/navigation'; - -import ActivityDetailSkeleton from './Skeletons/ActivityDetailSkeleton'; - -export default function ActivityDetailForm() { - const [year, setYear] = useState(new Date().getFullYear()); - const [month, setMonth] = useState(new Date().getMonth() + 1); - - const { id } = useParams(); - - const { - data: activityData, - isLoading, - status, - error, - } = useQuery({ - queryKey: ['activity', id], - queryFn: async () => { - return privateInstance.get(`/activities/${id}`); - }, - select: (response) => response.data, - enabled: !!id, - }); - - if (status === 'error') { - const axiosError = error as AxiosError; - const httpStatus = axiosError.response?.status; - - if (httpStatus === 404) { - console.log('404 에러임'); - notFound(); - } - } - - const currentUserId = useUserStore((state) => - state.user ? state.user.id : null, - ); - const userId = activityData?.userId; - const isOwner = currentUserId && userId && currentUserId === userId; - - 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, - }); - - const handleMonthChange = useCallback((year: number, month: number) => { - setTimeout(() => { - setYear(year); - setMonth(month); - }); - }, []); - - if (isLoading || !activityData) { - return ; - } - - const subImageUrls = activityData.subImages.map( - (image: { imageUrl: string }) => image.imageUrl, - ); - - return ( -
- - <ImageGrid - mainImage={activityData.bannerImageUrl} - subImages={subImageUrls} - /> - - <div - className={`mt-86 grid gap-15 ${ - isOwner ? 'md:grid-cols-2' : 'md:grid-cols-3' - } grid-cols-1`} - > - <div className={`${isOwner ? 'md:col-span-2' : 'md:col-span-2'}`}> - <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 설명</h2> - <p className='leading-relaxed whitespace-pre-line'> - {activityData.description} - </p> - </div> - - {!isOwner && ( - <div className='md:row-span-2'> - <BookingInterface - schedules={schedulesData ?? []} - onMonthChange={handleMonthChange} - isOwner={isOwner} - price={activityData.price} - /> - </div> - )} - - <div className={`${isOwner ? 'md:col-span-4' : 'md:col-span-2'}`}> - <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 장소</h2> - <LocationMap address={activityData.address} /> - - <ReviewSection - activityId={Number(id)} - reviewCount={activityData.reviewCount} - rating={activityData.rating} - /> - </div> - </div> - </div> - ); -} diff --git a/src/app/(with-header)/activities/[id]/components/BookingSection.tsx b/src/app/(with-header)/activities/[id]/components/BookingSection.tsx new file mode 100644 index 00000000..ea6d41d3 --- /dev/null +++ b/src/app/(with-header)/activities/[id]/components/BookingSection.tsx @@ -0,0 +1,80 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import useUserStore from '@/stores/authStore'; +import BookingInterface from '@/components/FloatingBox/BookingInterface'; +import { privateInstance } from '@/apis/privateInstance'; +import { padMonth } from '../utils/MonthFormatChange'; + +export default function BookingSection({ + activityId, + userId, + price, +}: { + activityId: string; + userId: number; + price: number; +}) { + const [year, setYear] = useState(new Date().getFullYear()); + const [month, setMonth] = useState(new Date().getMonth() + 1); + + const currentUserId = useUserStore((state) => + state.user ? state.user.id : null, + ); + const isOwner = currentUserId != null && userId != null && currentUserId === userId; + + const { data: schedulesData } = useQuery({ + queryKey: ['available-schedule', activityId, 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 currentResponse = await privateInstance.get( + `/activities/${activityId}/available-schedule?year=${year}&month=${padMonth(month)}`, + ); + + + const sideResults = await Promise.allSettled([ + privateInstance.get( + `/activities/${activityId}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`, + ), + privateInstance.get( + `/activities/${activityId}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`, + ), + ]); + + const sideData = sideResults + .filter( + (r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled', + ) + .flatMap((r) => r.value.data); + + return [...sideData, ...currentResponse.data]; + }, + enabled: !!activityId && !!year && !!month && !isOwner, + }); + + const handleMonthChange = useCallback((year: number, month: number) => { + setTimeout(() => { + setYear(year); + setMonth(month); + }); + }, []); + + if (isOwner) return null; + + return ( + <div className='md:row-span-2'> + <BookingInterface + schedules={schedulesData ?? []} + onMonthChange={handleMonthChange} + isOwner={isOwner} + price={price} + /> + </div> + ); +} diff --git a/src/app/(with-header)/activities/[id]/components/ImageGrid.tsx b/src/app/(with-header)/activities/[id]/components/ImageGrid.tsx index a993a3d0..9adfc0ad 100644 --- a/src/app/(with-header)/activities/[id]/components/ImageGrid.tsx +++ b/src/app/(with-header)/activities/[id]/components/ImageGrid.tsx @@ -67,9 +67,9 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) { src={image[currentIndex]} alt={`${currentIndex + 1}`} fill + sizes='100vw' className='rounded-lg object-cover' priority - unoptimized onError={() => handleImageError(currentIndex)} /> </motion.div> @@ -113,6 +113,8 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) { src={image[0]} alt='메인이미지' fill + sizes='50vw' + priority className='rounded-lg object-cover' onError={() => handleImageError(0)} /> @@ -127,6 +129,7 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) { src={image} alt={`서브이미지 ${index + 1}`} fill + sizes='25vw' className='rounded-lg object-cover' onError={() => handleImageError(index + 1)} /> @@ -145,6 +148,8 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) { src={selectedImage} alt='확대 이미지' fill + sizes='(max-width: 1200px) 100vw, 1200px' + quality={85} className='rounded-lg object-cover p-18' /> )} diff --git a/src/app/(with-header)/activities/[id]/components/Title.tsx b/src/app/(with-header)/activities/[id]/components/Title.tsx index 4f618076..c2c5d5d4 100644 --- a/src/app/(with-header)/activities/[id]/components/Title.tsx +++ b/src/app/(with-header)/activities/[id]/components/Title.tsx @@ -12,6 +12,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useDeleteActivity } from '../hooks/useDeleteActivity'; import Popup from '@/components/Popup'; import { TitleProps } from '@/types/activityDetailType'; +import useUserStore from '@/stores/authStore'; function Title({ title, @@ -19,10 +20,15 @@ function Title({ rating, reviewCount, address, - isOwner, + userId, }: TitleProps) { const [isPopupOpen, setIsPopupOpen] = useState(false); + const currentUserId = useUserStore((state) => + state.user ? state.user.id : null, + ); + const isOwner = currentUserId != null && userId != null && currentUserId === userId; + const { id } = useParams(); const router = useRouter(); const queryClient = useQueryClient(); diff --git a/src/app/(with-header)/activities/[id]/loading.tsx b/src/app/(with-header)/activities/[id]/loading.tsx new file mode 100644 index 00000000..c908f291 --- /dev/null +++ b/src/app/(with-header)/activities/[id]/loading.tsx @@ -0,0 +1,72 @@ +import ReviewCardSkeleton from './components/Skeletons/ReviewCardSkeleton'; +import SkeletonBookingInterface from './components/Skeletons/BookingInterfaceSkeleton'; + +export default function Loading() { + return ( + <div className='mx-auto max-w-1200 animate-pulse p-4 sm:px-20 lg:p-8'> + {/* 타이틀 */} + <div className='mb-6 flex items-start justify-between'> + <div className='flex w-full flex-col gap-10'> + <div className='h-16 w-24 rounded bg-gray-300' /> + <div className='h-42 w-3/4 rounded bg-gray-300' /> + <div className='flex gap-10'> + <div className='h-20 w-50 rounded bg-gray-300' /> + <div className='h-20 w-170 rounded bg-gray-300' /> + </div> + </div> + </div> + + {/* 이미지그리드 */} + <div className='relative block aspect-square h-[300px] w-full overflow-hidden rounded-lg bg-gray-300 md:hidden' /> + <div className='hidden h-[500px] grid-cols-4 grid-rows-4 gap-6 md:grid'> + <div className='col-span-2 row-span-4 rounded-lg bg-gray-300' /> + {[...Array(4)].map((_, i) => ( + <div + key={i} + className='col-span-1 row-span-2 rounded-lg bg-gray-300' + /> + ))} + </div> + + {/* 설명/예약인터페이스/장소 */} + <div className='mt-86 grid gap-10 grid-cols-1 md:grid-cols-3'> + {/* 설명 */} + <div className='md:col-span-2'> + <div className='mb-10 h-34 w-90 rounded bg-gray-300' /> + <div className='mb-4 h-180 w-full rounded bg-gray-300' /> + </div> + + {/* 예약인터페이스 */} + <div className='md:row-span-2'> + <SkeletonBookingInterface /> + </div> + + {/* 체험 장소/리뷰 */} + <div className='md:col-span-2 space-y-8'> + {/* 장소 */} + <div className='mb-40'> + <div className='mb-10 h-34 w-90 rounded bg-gray-300' /> + <div className='h-[480px] w-full rounded-lg bg-gray-400 shadow-md' /> + <div className='mt-8 flex items-center space-x-3'> + <div className='h-6 w-6 rounded-full bg-gray-300' /> + <div className='h-20 w-1/2 rounded bg-gray-300' /> + </div> + </div> + + {/* 리뷰 */} + <div> + <div className='mt-10 flex flex-col space-y-8'> + <div className='mb-10 h-34 w-50 rounded bg-gray-300' /> + <div className='mb-5 h-50 w-120 rounded bg-gray-300' /> + <div className='relative min-h-450 flex-col gap-30'> + {[...Array(3)].map((_, index) => ( + <ReviewCardSkeleton key={index} /> + ))} + </div> + </div> + </div> + </div> + </div> + </div> + ); +} diff --git a/src/app/(with-header)/activities/[id]/page.tsx b/src/app/(with-header)/activities/[id]/page.tsx index f62da97b..9f45fd6c 100644 --- a/src/app/(with-header)/activities/[id]/page.tsx +++ b/src/app/(with-header)/activities/[id]/page.tsx @@ -1,5 +1,81 @@ -import ActivityDetailForm from './components/ActivityDetailForm'; +import { notFound } from 'next/navigation'; +import Title from './components/Title'; +import ImageGrid from './components/ImageGrid'; +import BookingSection from './components/BookingSection'; +import LocationMap from '@/components/LocationMap'; +import ReviewSection from './components/ReviewSection'; +import { ActivityDetail } from '@/types/activityDetailType'; -export default function ActivityDetailPage() { - return <ActivityDetailForm />; +export default async function ActivityDetailPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + + let activityData: ActivityDetail; + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_SERVER_URL}/activities/${id}`, + { next: { tags: [`activity-${id}`] } }, + ); + + if (!res.ok) { + if (res.status === 404) notFound(); + throw new Error('활동 상세 데이터 조회 실패'); + } + + activityData = await res.json(); + } catch (error: any) { + if (error?.digest === 'NEXT_NOT_FOUND') throw error; + throw new Error('활동 상세 데이터 조회 실패'); + } + + const subImageUrls = activityData.subImages.map( + (image) => image.imageUrl, + ); + + return ( + <div className='mx-auto mt-30 max-w-1200 p-4 px-20 sm:px-20 lg:p-8'> + <Title + title={activityData.title} + category={activityData.category} + rating={activityData.rating} + reviewCount={activityData.reviewCount} + address={activityData.address ?? ''} + userId={activityData.userId} + /> + <ImageGrid + mainImage={activityData.bannerImageUrl} + subImages={subImageUrls} + /> + + <div className='mt-86 grid gap-15 grid-cols-1 md:grid-cols-3'> + <div className='md:col-span-2'> + <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 설명</h2> + <p className='leading-relaxed whitespace-pre-line'> + {activityData.description} + </p> + </div> + + <BookingSection + activityId={id} + userId={activityData.userId} + price={activityData.price} + /> + + <div className='md:col-span-2'> + <h2 className='mb-4 pb-2 text-2xl font-bold'>체험 장소</h2> + <LocationMap address={activityData.address ?? ''} /> + + <ReviewSection + activityId={Number(id)} + reviewCount={activityData.reviewCount} + rating={activityData.rating} + /> + </div> + </div> + </div> + ); } diff --git a/src/app/api/deleteActivity/[id]/route.ts b/src/app/api/deleteActivity/[id]/route.ts index d70aafa7..73ca4c15 100644 --- a/src/app/api/deleteActivity/[id]/route.ts +++ b/src/app/api/deleteActivity/[id]/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { cookies } from 'next/headers'; +import { revalidateTag } from 'next/cache'; import axios, { AxiosError } from 'axios'; const BACKEND_BASE_URL = process.env.NEXT_PUBLIC_API_SERVER_URL; @@ -38,6 +39,7 @@ export async function DELETE( }, ); + revalidateTag(`activity-${id}`); return NextResponse.json(response.data, { status: 200 }); } catch (error: unknown) { console.error('체험 삭제 에러:', error); diff --git a/src/app/api/editActivity/[id]/route.ts b/src/app/api/editActivity/[id]/route.ts index 021a2479..2960c1be 100644 --- a/src/app/api/editActivity/[id]/route.ts +++ b/src/app/api/editActivity/[id]/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { cookies } from 'next/headers'; +import { revalidateTag } from 'next/cache'; import axios, { AxiosError } from 'axios'; const BACKEND_BASE_URL = process.env.NEXT_PUBLIC_API_SERVER_URL; @@ -36,6 +37,7 @@ export async function PATCH( }, ); + revalidateTag(`activity-${id}`); return NextResponse.json(response.data, { status: 200 }); } catch (error: unknown) { console.error('체험 수정 에러:', error); diff --git a/src/app/api/reservations/[id]/reviews/route.ts b/src/app/api/reservations/[id]/reviews/route.ts index a10625be..95f38d8d 100644 --- a/src/app/api/reservations/[id]/reviews/route.ts +++ b/src/app/api/reservations/[id]/reviews/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { cookies } from 'next/headers'; +import { revalidateTag } from 'next/cache'; import axios from 'axios'; const BACKEND_BASE_URL = process.env.NEXT_PUBLIC_API_SERVER_URL; @@ -38,6 +39,9 @@ export async function POST( }, ); + if (response.data?.activityId) { + revalidateTag(`activity-${response.data.activityId}`); + } return NextResponse.json(response.data); } catch (error) { if (axios.isAxiosError(error)) { diff --git a/src/types/activityDetailType.ts b/src/types/activityDetailType.ts index 8fbe42f0..5a32ec5f 100644 --- a/src/types/activityDetailType.ts +++ b/src/types/activityDetailType.ts @@ -11,14 +11,7 @@ export interface ReviewCardProps { isBlured?: boolean; } -export interface TitleProps { - title: string; - category: string; - rating: number; - reviewCount: number; - address: string; - isDropDown?: boolean; -} + export interface ActivitySchedule { id: number; @@ -93,7 +86,7 @@ export interface TitleProps { rating: number; reviewCount: number; address: string; - isOwner: boolean; + userId: number; } export interface ReviewTitleProps { From e9aad15f899dbf8a3d23e81da3a63e05551deefa Mon Sep 17 00:00:00 2001 From: evaain706 <evaain706@gmail.com> Date: Wed, 18 Feb 2026 20:49:17 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20api=ED=98=B8=EC=B6=9C=20=EC=B5=9C=EC=A2=85?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=EC=8B=9C=EC=97=90=20=ED=95=9C=EB=B2=88?= =?UTF-8?q?=EB=A7=8C=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myactivity/hooks/useCreateActivityForm.ts | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts b/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts index e21806d6..7a997877 100644 --- a/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts +++ b/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts @@ -36,6 +36,23 @@ export const useCreateActivityForm = () => { throw new Error('유효한 가격을 입력해주세요.'); } + let bannerImageUrl = ''; + if (typeof mainImage === 'string') { + bannerImageUrl = mainImage; + } else if (mainImage instanceof File) { + bannerImageUrl = await uploadImage(mainImage); + } + + const subImageUrls: string[] = []; + for (const img of subImage) { + if (img instanceof File) { + const url = await uploadImage(img); + subImageUrls.push(url); + } else if (typeof img === 'string') { + subImageUrls.push(img); + } + } + const payload = { title, category, @@ -43,8 +60,8 @@ export const useCreateActivityForm = () => { address, price: parsedPrice, schedules: dates, - bannerImageUrl: mainImage, - subImageUrls: subImage, + bannerImageUrl, + subImageUrls, }; const res = await privateInstance.post('/addActivity', payload); @@ -89,30 +106,18 @@ export const useCreateActivityForm = () => { ); }; - const handleMainImageSelect = async (file: File) => { - try { - const url = await uploadImage(file); - setMainImage(url); - } catch { - toast.error('메인 이미지 업로드에 실패했습니다.'); - } + const handleMainImageSelect = (file: File) => { + setMainImage(file); }; const handleMainImageRemove = () => { setMainImage(null); }; - const handleSubImagesAdd = async (newFiles: File[]) => { + const handleSubImagesAdd = (newFiles: File[]) => { const remaining = 4 - subImage.length; - const filesToUpload = newFiles.slice(0, remaining); - try { - const uploadedUrls = await Promise.all( - filesToUpload.map((file) => uploadImage(file)), - ); - setSubImage((prev) => [...prev, ...uploadedUrls]); - } catch { - toast.error('서브 이미지 업로드 중 문제가 발생했습니다.'); - } + const filesToAdd = newFiles.slice(0, remaining); + setSubImage((prev) => [...prev, ...filesToAdd]); }; const handleSubImageRemove = (index: number) => { From e8d80d4b3b15393c88987bcd547bc66787b110c7 Mon Sep 17 00:00:00 2001 From: evaain706 <evaain706@gmail.com> Date: Sun, 22 Feb 2026 00:02:38 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix:=EC=88=98=EC=A0=95=ED=8F=BC=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=AC=B4=ED=9A=A8=ED=99=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts | 3 +++ src/hooks/useBookingMutation.ts | 0 2 files changed, 3 insertions(+) delete mode 100644 src/hooks/useBookingMutation.ts diff --git a/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts b/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts index 9a79d610..03a69fe5 100644 --- a/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts +++ b/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts @@ -184,6 +184,9 @@ export const useEditActivityForm = () => { onSuccess: () => { toast.success('수정되었습니다!'); queryClient.invalidateQueries({ queryKey: ['activity', id] }); + queryClient.invalidateQueries({ + queryKey: ['available-schedule', id], + }); queryClient.invalidateQueries({ queryKey: ['experiences'] }); queryClient.invalidateQueries({ queryKey: ['popularExperiences'] }); router.push(`/activities/${id}`); diff --git a/src/hooks/useBookingMutation.ts b/src/hooks/useBookingMutation.ts deleted file mode 100644 index e69de29b..00000000 From bb92a8839fff4216884a08d10c81ec00c966790d Mon Sep 17 00:00:00 2001 From: evaain706 <evaain706@gmail.com> Date: Sun, 22 Feb 2026 00:13:45 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=ED=83=80=EC=9E=85=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activities/[id]/components/BookingSection.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/(with-header)/activities/[id]/components/BookingSection.tsx b/src/app/(with-header)/activities/[id]/components/BookingSection.tsx index ea6d41d3..2a2a94a7 100644 --- a/src/app/(with-header)/activities/[id]/components/BookingSection.tsx +++ b/src/app/(with-header)/activities/[id]/components/BookingSection.tsx @@ -2,9 +2,11 @@ import { useState, useCallback } from 'react'; import { useQuery } from '@tanstack/react-query'; +import { AxiosResponse } from 'axios'; import useUserStore from '@/stores/authStore'; import BookingInterface from '@/components/FloatingBox/BookingInterface'; import { privateInstance } from '@/apis/privateInstance'; +import { GroupedSchedule } from '@/types/activityDetailType'; import { padMonth } from '../utils/MonthFormatChange'; export default function BookingSection({ @@ -33,23 +35,23 @@ export default function BookingSection({ const nextYear = month === 12 ? year + 1 : year; - const currentResponse = await privateInstance.get( + const currentResponse = await privateInstance.get<GroupedSchedule[]>( `/activities/${activityId}/available-schedule?year=${year}&month=${padMonth(month)}`, ); const sideResults = await Promise.allSettled([ - privateInstance.get( + privateInstance.get<GroupedSchedule[]>( `/activities/${activityId}/available-schedule?year=${prevYear}&month=${padMonth(prevMonth)}`, ), - privateInstance.get( + privateInstance.get<GroupedSchedule[]>( `/activities/${activityId}/available-schedule?year=${nextYear}&month=${padMonth(nextMonth)}`, ), ]); const sideData = sideResults .filter( - (r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled', + (r): r is PromiseFulfilledResult<AxiosResponse<GroupedSchedule[]>> => r.status === 'fulfilled', ) .flatMap((r) => r.value.data); From c9916f81bf15b17404f2d1e3cfa7bc54d13867af Mon Sep 17 00:00:00 2001 From: evaain706 <evaain706@gmail.com> Date: Sun, 22 Feb 2026 00:18:46 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=EC=97=90=EB=9F=AC=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(with-header)/activities/[id]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(with-header)/activities/[id]/page.tsx b/src/app/(with-header)/activities/[id]/page.tsx index 9f45fd6c..02732bb4 100644 --- a/src/app/(with-header)/activities/[id]/page.tsx +++ b/src/app/(with-header)/activities/[id]/page.tsx @@ -27,8 +27,8 @@ export default async function ActivityDetailPage({ } activityData = await res.json(); - } catch (error: any) { - if (error?.digest === 'NEXT_NOT_FOUND') throw error; + } catch (error) { + if (error instanceof Error && 'digest' in error) throw error; throw new Error('활동 상세 데이터 조회 실패'); }