- {images.map((_, i) => (
+ {image.map((_, i) => (
-
+
handleImageClick(mainImage)}
+ className='relative col-span-2 row-span-4 hover:animate-pulse'
+ >
handleImageError(0)}
/>
- {subImages.slice(0, 4).map((image, index) => (
+ {image.slice(1, 5).map((image, index) => (
handleImageClick(image)}
className='relative col-span-1 row-span-2 h-full hover:animate-pulse'
>
handleImageError(index + 1)}
/>
))}
+
+
+
+
+
+
+
+ {selectedImage && (
+
+ )}
+
+
+
+
+
+
>
);
}
diff --git a/src/app/(with-header)/activities/[id]/components/ReviewSection.tsx b/src/app/(with-header)/activities/[id]/components/ReviewSection.tsx
index 51a889cb..c2d45cc1 100644
--- a/src/app/(with-header)/activities/[id]/components/ReviewSection.tsx
+++ b/src/app/(with-header)/activities/[id]/components/ReviewSection.tsx
@@ -9,24 +9,9 @@ import { privateInstance } from '@/apis/privateInstance';
import ReviewTitle from './ReviewTitle';
import useUserStore from '@/stores/authStore';
import cn from '@/lib/cn';
-
import ReviewCardSkeleton from './Skeletons/ReviewCardSkeleton';
-
-interface ReviewSectionProps {
- activityId: string;
- reviewCount: number;
- rating: number;
-}
-
-interface ReviewProps {
- id: string;
- user: {
- nickname: string;
- profileImageUrl: string;
- };
- createdAt: string;
- content: string;
-}
+import { ReviewSectionProps } from '@/types/activityDetailType';
+import { ReviewProps } from '@/types/activityDetailType';
function ReviewSection({
activityId,
diff --git a/src/app/(with-header)/activities/[id]/components/ReviewTitle.tsx b/src/app/(with-header)/activities/[id]/components/ReviewTitle.tsx
index 3f378639..7dadf062 100644
--- a/src/app/(with-header)/activities/[id]/components/ReviewTitle.tsx
+++ b/src/app/(with-header)/activities/[id]/components/ReviewTitle.tsx
@@ -2,11 +2,8 @@
import Star from '@assets/svg/star';
import { useState, useEffect } from 'react';
+import { ReviewTitleProps } from '@/types/activityDetailType';
-interface ReviewTitleProps {
- reviewCount: number;
- rating: number;
-}
export default function ReviewTitle({
reviewCount = 0,
rating = 0,
diff --git a/src/app/(with-header)/activities/[id]/components/Title.tsx b/src/app/(with-header)/activities/[id]/components/Title.tsx
index c8bf18da..4f618076 100644
--- a/src/app/(with-header)/activities/[id]/components/Title.tsx
+++ b/src/app/(with-header)/activities/[id]/components/Title.tsx
@@ -11,15 +11,7 @@ import { useParams, useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';
import { useDeleteActivity } from '../hooks/useDeleteActivity';
import Popup from '@/components/Popup';
-
-interface TitleProps {
- title: string;
- category: string;
- rating: number;
- reviewCount: number;
- address: string;
- isOwner: boolean;
-}
+import { TitleProps } from '@/types/activityDetailType';
function Title({
title,
@@ -43,10 +35,9 @@ function Title({
const handleDeleteConfirm = () => {
if (!id) return;
- mutate(id as string);
+ mutate(Number(id));
setIsPopupOpen(false);
};
-
return (
<>
diff --git a/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts b/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts
index 7d6dfff4..14470f20 100644
--- a/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts
+++ b/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
-const deleteActivity = async (id: string) => {
+const deleteActivity = async (id: number) => {
const response = await privateInstance.delete(`/deleteActivity/${id}`);
return response.data;
};
@@ -23,6 +23,7 @@ export const useDeleteActivity = () => {
});
queryClient.invalidateQueries({ queryKey: ['popularExperiences'] });
router.push(`/`);
+ toast.success('체험이 삭제되었습니다!');
},
onError: (error: AxiosError) => {
const responseData = error.response?.data as
diff --git a/src/app/(with-header)/activities/[id]/mock/mock.ts b/src/app/(with-header)/activities/[id]/mock/mock.ts
deleted file mode 100644
index fe2f95bb..00000000
--- a/src/app/(with-header)/activities/[id]/mock/mock.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-export const mockActivity = {
- id: 5088,
- userId: 2145,
- title: '함께 배우면 즐거운 스트릿댄스',
- description: '둠칫 둠칫 두둠칫',
- category: '투어',
- price: 10000,
- address: '서울특별시 강남구 테헤란로 427',
- bannerImageUrl: '/test/image1.png',
- rating: 0,
- reviewCount: 0,
- createdAt: '2025-07-16T16:49:19.971Z',
- updatedAt: '2025-07-16T16:49:19.971Z',
- subImages: [
- {
- id: 10643,
- imageUrl: '/test/image2.png',
- },
- {
- id: 10644,
- imageUrl: '/test/image3.png',
- },
- {
- id: 10645,
- imageUrl: '/test/image4.png',
- },
- {
- id: 10646,
- imageUrl: '/test/image5.png',
- },
- ],
- schedules: [
- {
- id: 21515,
- date: '2025-12-01',
- startTime: '12:00',
- endTime: '13:00',
- },
- {
- id: 21516,
- date: '2025-12-05',
- startTime: '12:00',
- endTime: '13:00',
- },
- {
- id: 21517,
- date: '2025-12-05',
- startTime: '13:00',
- endTime: '14:00',
- },
- {
- id: 21518,
- date: '2025-12-05',
- startTime: '14:00',
- endTime: '15:00',
- },
- ],
-};
diff --git a/src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx b/src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx
index 75595352..90328d5f 100644
--- a/src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx
+++ b/src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx
@@ -6,11 +6,7 @@ import { ScheduleSelectForm } from '../../components/ScheduleSelectForm';
import { ImageSection } from '../../components/ImageSection';
import { useEditActivityForm } from '../hooks/useEditActivityForm';
import EditActivityFormSkeleton from '../../loading';
-
-interface SubImageType {
- id?: number;
- url: string | File;
-}
+import { SubImageType } from '@/types/addEditExperienceType';
export default function EditActivityForm() {
const {
diff --git a/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts b/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts
index 6290a048..9a79d610 100644
--- a/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts
+++ b/src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts
@@ -9,15 +9,10 @@ import { ActivityDetailEdit, Schedule } from '@/types/activityDetailType';
import { AxiosError } from 'axios';
import { toast } from 'sonner';
import { notFound } from 'next/navigation';
-import { validateSchedules } from '../../utils/dateValidatoin';
-
-interface SubImageType {
- id?: number;
- url: string | File;
-}
+import { SubImageType } from '@/types/addEditExperienceType';
export const useEditActivityForm = () => {
- const { id } = useParams() as { id: string };
+ const { id } = useParams();
const router = useRouter();
const queryClient = useQueryClient();
@@ -209,9 +204,16 @@ export const useEditActivityForm = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- const validationMessage = validateSchedules(dates);
- if (validationMessage) {
- toast.error(validationMessage);
+ if (
+ !title ||
+ !category ||
+ !description ||
+ !address ||
+ !price ||
+ !mainImage ||
+ dates.length === 0
+ ) {
+ toast.error('소개이미지를 제외한 모든값은 필수값입니다!');
return;
}
try {
diff --git a/src/app/(with-header)/myactivity/components/AddressInput.tsx b/src/app/(with-header)/myactivity/components/AddressInput.tsx
index 86a52d93..49321eba 100644
--- a/src/app/(with-header)/myactivity/components/AddressInput.tsx
+++ b/src/app/(with-header)/myactivity/components/AddressInput.tsx
@@ -5,20 +5,8 @@ import DaumPostcode from 'react-daum-postcode';
import { useState } from 'react';
import Input from '@/components/Input';
import Button from '@/components/Button';
-
-interface AddressInputProps {
- onAddressChange: (address: string) => void;
- address: string;
-}
-
-interface PostcodeData {
- address: string;
- addressType: 'R' | 'J';
- bname: string;
- buildingName: string;
- zonecode: string;
- userSelectedType: string;
-}
+import { AddressInputProps } from '@/types/addEditExperienceType';
+import { PostcodeData } from '@/types/addEditExperienceType';
export default function AddressInput({
onAddressChange,
diff --git a/src/app/(with-header)/myactivity/components/CategoryInput.tsx b/src/app/(with-header)/myactivity/components/CategoryInput.tsx
index 2fecdacf..e9c059f0 100644
--- a/src/app/(with-header)/myactivity/components/CategoryInput.tsx
+++ b/src/app/(with-header)/myactivity/components/CategoryInput.tsx
@@ -1,9 +1,5 @@
import ChevronIcon from '@assets/svg/chevron'; // 아이콘 경로는 맞게 조정
-
-interface CategoryProps {
- category?: string;
- onCategoryChange: (value: string) => void;
-}
+import { CategoryProps } from '@/types/addEditExperienceType';
export default function CategoryInput({
category,
@@ -16,7 +12,7 @@ export default function CategoryInput({
*/}
{/* 커스텀 화살표 아이콘 */}
-
diff --git a/src/app/(with-header)/myactivity/components/FormSection.tsx b/src/app/(with-header)/myactivity/components/FormSection.tsx
index cb33e7cc..f3534de7 100644
--- a/src/app/(with-header)/myactivity/components/FormSection.tsx
+++ b/src/app/(with-header)/myactivity/components/FormSection.tsx
@@ -1,10 +1,5 @@
import type React from 'react';
-
-interface FormSectionProps {
- title: string;
- children: React.ReactNode;
- description?: string;
-}
+import { FormSectionProps } from '@/types/addEditExperienceType';
export function FormSection({
title,
diff --git a/src/app/(with-header)/myactivity/components/ImagePreview.tsx b/src/app/(with-header)/myactivity/components/ImagePreview.tsx
index 1948f6e5..31a0de6b 100644
--- a/src/app/(with-header)/myactivity/components/ImagePreview.tsx
+++ b/src/app/(with-header)/myactivity/components/ImagePreview.tsx
@@ -1,14 +1,8 @@
'use client';
import IconClose from '@assets/svg/close';
-import { useState,useEffect } from 'react';
-
-interface ImagePreviewProps {
- image: File | string;
- onRemove: () => void;
- alt: string;
- className?: string;
-}
+import { useState, useEffect } from 'react';
+import { ImagePreviewProps } from '@/types/addEditExperienceType';
export function ImagePreview({
image,
@@ -43,10 +37,14 @@ export function ImagePreview({
);
diff --git a/src/app/(with-header)/myactivity/components/ImageSection.tsx b/src/app/(with-header)/myactivity/components/ImageSection.tsx
index eddd9b32..24390caa 100644
--- a/src/app/(with-header)/myactivity/components/ImageSection.tsx
+++ b/src/app/(with-header)/myactivity/components/ImageSection.tsx
@@ -2,22 +2,14 @@
import { MainImageSelect } from './MainImageSelect';
import { SubImageSelect } from './SubImageSelect';
-
-interface ImagesSectionProps {
- mainImage: string | File | null;
- subImage: (string | File)[];
- onMainImageSelect: (file: File) => void;
- onMainImageRemove: () => void;
- onSubImageAdd: (files: File[]) => void;
- onSubImageRemove: (index: number) => void;
-}
+import { ImagesSectionProps } from '@/types/addEditExperienceType';
export function ImageSection({
mainImage,
subImage,
onMainImageSelect,
onMainImageRemove,
- onSubImageAdd,
+ onSubImageAdd,
onSubImageRemove,
}: ImagesSectionProps) {
return (
diff --git a/src/app/(with-header)/myactivity/components/ImageUpload.tsx b/src/app/(with-header)/myactivity/components/ImageUpload.tsx
index 12a9bb93..68465be4 100644
--- a/src/app/(with-header)/myactivity/components/ImageUpload.tsx
+++ b/src/app/(with-header)/myactivity/components/ImageUpload.tsx
@@ -1,13 +1,7 @@
'use client';
import type React from 'react';
-
-interface ImageUploadProps {
- onImageSelect: (file: File) => void;
- multiple?: boolean;
- className?: string;
- children?: React.ReactNode;
-}
+import { ImageUploadProps } from '@/types/addEditExperienceType';
export function ImageUpload({
onImageSelect,
diff --git a/src/app/(with-header)/myactivity/components/InfoSection.tsx b/src/app/(with-header)/myactivity/components/InfoSection.tsx
index c507ac6a..7f1631b5 100644
--- a/src/app/(with-header)/myactivity/components/InfoSection.tsx
+++ b/src/app/(with-header)/myactivity/components/InfoSection.tsx
@@ -4,19 +4,7 @@ import Input from '@/components/Input';
import AddressInput from './AddressInput';
import CategoryInput from './CategoryInput';
import Textarea from '@/components/Textarea';
-
-interface InfoSectionProps {
- title?: string;
- category?: string;
- price?: string;
- description?: string;
- address?: string;
- onTitleChange: (value: string) => void;
- onCategoryChange: (value: string) => void;
- onPriceChange: (value: string) => void;
- onDescriptionChange: (value: string) => void;
- onAddressChange: (value: string) => void;
-}
+import { InfoSectionProps } from '@/types/addEditExperienceType';
export function InfoSection({
title = '',
diff --git a/src/app/(with-header)/myactivity/components/MainImageSelect.tsx b/src/app/(with-header)/myactivity/components/MainImageSelect.tsx
index 354cd84d..22c461d0 100644
--- a/src/app/(with-header)/myactivity/components/MainImageSelect.tsx
+++ b/src/app/(with-header)/myactivity/components/MainImageSelect.tsx
@@ -2,12 +2,7 @@
import { ImageUpload } from './ImageUpload';
import { ImagePreview } from './ImagePreview';
-
-interface MainImageSelectProps {
- mainImage: File | string | null;
- onImageSelect: (file: File) => void;
- onImageRemove: () => void;
-}
+import { MainImageSelectProps } from '@/types/addEditExperienceType';
export function MainImageSelect({
mainImage,
diff --git a/src/app/(with-header)/myactivity/components/ScheduleSelect.tsx b/src/app/(with-header)/myactivity/components/ScheduleSelect.tsx
index c64fd3f5..50ee8531 100644
--- a/src/app/(with-header)/myactivity/components/ScheduleSelect.tsx
+++ b/src/app/(with-header)/myactivity/components/ScheduleSelect.tsx
@@ -2,19 +2,7 @@
import Input from '@/components/Input';
import IconClose from '@assets/svg/close';
-
-interface ScheduleSelectProps {
- index: number;
- isRemovable: boolean;
- onAddDate: () => void;
- onRemove: (index: number) => void;
- onDateChange: (index: number, value: string) => void;
- onStartTimeChange: (index: number, value: string) => void;
- onEndTimeChange: (index: number, value: string) => void;
- date: string;
- startTime: string;
- endTime: string;
-}
+import { ScheduleSelectProps } from '@/types/addEditExperienceType';
export function ScheduleSelect({
index,
diff --git a/src/app/(with-header)/myactivity/components/ScheduleSelectForm.tsx b/src/app/(with-header)/myactivity/components/ScheduleSelectForm.tsx
index 4e63ec11..0a0361a5 100644
--- a/src/app/(with-header)/myactivity/components/ScheduleSelectForm.tsx
+++ b/src/app/(with-header)/myactivity/components/ScheduleSelectForm.tsx
@@ -2,29 +2,9 @@
import { toast } from 'sonner';
import { ScheduleSelect } from './ScheduleSelect';
-import { Schedule } from '@/types/activityDetailType';
-
-interface ScheduleSelectFormProps {
- dates: Schedule[];
- onAddDate: () => void;
- onRemoveDate: (index: number) => void;
- onDateChange: (
- index: number,
- field: keyof Omit
,
- value: string,
- ) => void;
-}
-
-function isPastDate(dateStr: string) {
- const selected = new Date(dateStr);
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- return selected < today;
-}
-
-function isInvalidTimeRange(start: string, end: string) {
- return start >= end;
-}
+import { ScheduleSelectFormProps } from '@/types/addEditExperienceType';
+import { isPastDate } from '../utils/dateValidatoin';
+import { isInvalidTimeRange } from '../utils/dateValidatoin';
export function ScheduleSelectForm({
dates,
diff --git a/src/app/(with-header)/myactivity/components/SubImageSelect.tsx b/src/app/(with-header)/myactivity/components/SubImageSelect.tsx
index ff8ae544..1e0a30cc 100644
--- a/src/app/(with-header)/myactivity/components/SubImageSelect.tsx
+++ b/src/app/(with-header)/myactivity/components/SubImageSelect.tsx
@@ -1,11 +1,6 @@
import { ImagePreview } from './ImagePreview';
import { ImageUpload } from './ImageUpload';
-
-interface SubImageSelectProps {
- subImage: (string | File)[];
- onImagesAdd: (files: File[]) => void;
- onImageRemove: (index: number) => void;
-}
+import { SubImageSelectProps } from '@/types/addEditExperienceType';
export function SubImageSelect({
subImage,
diff --git a/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts b/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts
index e3ca6d53..e21806d6 100644
--- a/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts
+++ b/src/app/(with-header)/myactivity/hooks/useCreateActivityForm.ts
@@ -8,12 +8,7 @@ import axios from 'axios';
import { uploadImage } from '../utils/uploadImage';
import { privateInstance } from '@/apis/privateInstance';
import { useQueryClient } from '@tanstack/react-query';
-
-export interface DateSlot {
- date: string;
- startTime: string;
- endTime: string;
-}
+import { DateSlot } from '@/types/addEditExperienceType';
export const useCreateActivityForm = () => {
const [dates, setDates] = useState([
@@ -132,9 +127,10 @@ export const useCreateActivityForm = () => {
!description ||
!address ||
!price ||
+ !mainImage ||
dates.length === 0
) {
- toast.error('모든 필드를 입력해주세요.');
+ toast.error('소개이미지를 제외한 모든값은 필수값입니다!');
return;
}
mutation.mutate();
diff --git a/src/app/(with-header)/myactivity/utils/dateValidatoin.ts b/src/app/(with-header)/myactivity/utils/dateValidatoin.ts
index 6aa91ce6..c5a2283e 100644
--- a/src/app/(with-header)/myactivity/utils/dateValidatoin.ts
+++ b/src/app/(with-header)/myactivity/utils/dateValidatoin.ts
@@ -29,3 +29,14 @@ export function validateSchedules(schedules: Schedule[]) {
return null;
}
+
+export function isPastDate(dateStr: string) {
+ const selected = new Date(dateStr);
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ return selected < today;
+}
+
+export function isInvalidTimeRange(start: string, end: string) {
+ return start >= end;
+}
diff --git a/src/components/DatePicker/CalendarBody.tsx b/src/components/DatePicker/CalendarBody.tsx
index 195dcfd2..27c8f3bc 100644
--- a/src/components/DatePicker/CalendarBody.tsx
+++ b/src/components/DatePicker/CalendarBody.tsx
@@ -2,6 +2,7 @@
import type dayjs from 'dayjs';
import { CalendarBodyProps } from '@/types/datePickerTypes';
+import cn from '@/lib/cn';
export default function CalendarBody({
viewDate,
@@ -51,36 +52,43 @@ export default function CalendarBody({
);
diff --git a/src/components/FloatingBox/BookingButton.tsx b/src/components/FloatingBox/BookingButton.tsx
index 3ae771ea..93e66286 100644
--- a/src/components/FloatingBox/BookingButton.tsx
+++ b/src/components/FloatingBox/BookingButton.tsx
@@ -1,13 +1,6 @@
import React from 'react';
import { cn } from '@/lib/utils';
-
-interface BookingButtonProps {
- onClick: () => void;
- children: React.ReactNode;
- disabled?: boolean;
- onBooking?: boolean;
- className?: string;
-}
+import { BookingButtonProps } from '@/types/bookingInterfaceType';
export default function BookingButton({
onClick,
diff --git a/src/components/FloatingBox/BookingInterface.tsx b/src/components/FloatingBox/BookingInterface.tsx
index 5158b1a1..53046992 100644
--- a/src/components/FloatingBox/BookingInterface.tsx
+++ b/src/components/FloatingBox/BookingInterface.tsx
@@ -9,12 +9,7 @@ import TotalPriceDisplay from './TotalPriceDisplay';
import BookingModal from '@/ui/BookingModal';
import DatePicker from '../DatePicker/DatePicker';
import { SchedulesProps } from '@/types/activityDetailType';
-import { privateInstance } from '@/apis/privateInstance';
-import { useParams } from 'next/navigation';
-import { AxiosError } from 'axios';
-import useUserStore from '@/stores/authStore';
-import { toast } from 'sonner';
-import { useState } from 'react';
+import { useBooking } from '@/hooks/useBooking';
export default function BookingInterface({
schedules,
@@ -27,62 +22,16 @@ export default function BookingInterface({
isOwner: boolean;
price: number;
}) {
- const [onBooking, setOnBooking] = useState(false);
- const { user } = useUserStore();
const setIsOpen = useBookingStore((state) => state.setIsOpen);
+
const {
+ onBooking,
+ handleBooking,
+ isBookable,
+ buttonText,
selectedDate,
selectedTime,
- participants,
- selectedTimeId,
- setToInitial,
- } = useBookingStore();
-
- const { id } = useParams();
-
- const handleBooking = async () => {
- setOnBooking(true);
- try {
- await privateInstance.post(`/activities/${id}/reservation`, {
- selectedTimeId,
- participants,
- });
-
- toast.success('예약되었습니다!');
- setToInitial();
- } catch (err) {
- const error = err as AxiosError;
- const responseData = error.response?.data as
- | { error?: string; message?: string }
- | undefined;
-
- console.error('전체 에러:', error);
-
- toast.error(
- responseData?.error ||
- responseData?.message ||
- error.message ||
- '예약에 실패했습니다.',
- );
- } finally {
- setOnBooking(false);
- }
- };
-
- const isLoggedIn = !!user;
- const isBookable =
- !!selectedDate &&
- !!selectedTime &&
- !!selectedTimeId &&
- !!participants &&
- !isOwner &&
- isLoggedIn;
-
- const buttonText = !isLoggedIn
- ? '로그인이 필요한 기능입니다'
- : isOwner
- ? '본인이 등록한 체험입니다'
- : '예약하기';
+ } = useBooking(isOwner);
return (
diff --git a/src/components/FloatingBox/PriceDisplay.tsx b/src/components/FloatingBox/PriceDisplay.tsx
index 4fd270d9..60620f1a 100644
--- a/src/components/FloatingBox/PriceDisplay.tsx
+++ b/src/components/FloatingBox/PriceDisplay.tsx
@@ -1,9 +1,18 @@
export default function PriceDisplay({ price }: { price: number }) {
return (
-
-
- ₩{price.toLocaleString('ko-KR')}{' '}
-
/ 인
+
+
+
+ ₩{price.toLocaleString('ko-KR')}
+ /인
+
+
+
);
diff --git a/src/components/FloatingBox/TabletPopup.tsx b/src/components/FloatingBox/TabletPopup.tsx
deleted file mode 100644
index e3f01a3f..00000000
--- a/src/components/FloatingBox/TabletPopup.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-// 'use client';
-
-// 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({schedules}:SchedulesProps) {
-// const isOpen = useBookingStore((state) => state.isOpen);
-// const setIsOpen = useBookingStore((state) => state.setIsOpen);
-
-// if (!isOpen) return null;
-
-// return (
-//
-//
-//
날짜
-//
-//
-
-//
-
-//
-//
-// );
-// }
diff --git a/src/components/Modal/Example/TestModal.tsx b/src/components/Modal/Example/TestModal.tsx
deleted file mode 100644
index a630f878..00000000
--- a/src/components/Modal/Example/TestModal.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-'use client';
-
-import Modal from '@/components/Modal';
-
-export default function TestModal({
- isOpen,
- setIsOpen,
-}: {
- isOpen: boolean;
- setIsOpen: React.Dispatch
>;
-}) {
- //예시 동작
- function TestAction() {
- window.alert('리뷰 작성');
- setIsOpen(false);
- }
-
- return (
- // 외부에서 정의된 모달 제어 state를 받아서 사용
-
-
-
- 제목
-
-
- 제어모달예시
-
-
-
-
-
-
- );
-}
diff --git a/src/components/Modal/Example/TestModalButton.tsx b/src/components/Modal/Example/TestModalButton.tsx
deleted file mode 100644
index b9af0759..00000000
--- a/src/components/Modal/Example/TestModalButton.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import TestModal from './TestModal';
-
-export default function TestModalButton() {
- const [isOpen, setIsOpen] = useState(false); //외부에서 모달을 제어하기위한 state 정의
-
- return (
-
-
-
- {/* props로 state를 내려줌 */}
-
-
- );
-}
diff --git a/src/components/Modal/Example/UncontrolledModal.tsx b/src/components/Modal/Example/UncontrolledModal.tsx
deleted file mode 100644
index ffc909c8..00000000
--- a/src/components/Modal/Example/UncontrolledModal.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import Modal from '..';
-
-export default function UncontrolledModal() {
- return (
-
- 비제어 모달 열기
-
-
- 제목
-
-
- 비제어모달
-
-
-
-
- );
-}
diff --git a/src/components/Modal/Example/readme b/src/components/Modal/Example/readme
deleted file mode 100644
index 4331d421..00000000
--- a/src/components/Modal/Example/readme
+++ /dev/null
@@ -1,38 +0,0 @@
-모달 제어/비제어 사용 예시를 보여드리기위해 만들어둔 폴더입니다.
-
-제어모달예시 - TestModalButton.tsx , TestModal.tsx
-
-비제어모달예시 - UncontrolledModal.tsx
-
-
----
-
-# 제어(Controlled) 모달
-
-- 외부에서 모달의 열림/닫힘 상태를 직접 관리하는 방식입니다.
-- 보통 `useState`로 상태를 만들고, `isOpen`, `onOpenChange`(또는 setState)를 모달에 전달합니다.
-- 모달을 열거나 닫는 로직을 컴포넌트 외부에서 제어할 수 있어, 복잡한 조건이나 여러 곳에서 모달 상태를 변경해야 할 때 유용합니다.
-
-**예시 파일:**
-- `TestModalButton.tsx`
-- `TestModal.tsx`
-
-
----
-
-# 비제어(Uncontrolled) 모달
-
-- **모달 컴포넌트 내부에서 열림/닫힘 상태를 자체적으로 관리**하는 방식입니다.
-- 별도의 상태 관리가 필요 없고, 간단하게 모달을 띄우고 닫을 때 사용하기 좋습니다.
-- 외부에서 모달의 상태를 직접 제어할 수 없으므로, 단순한 용도에 적합합니다.
-
-**예시 파일:**
-- `UncontrolledModal.tsx`
-
----
-
-## 요약
-
-- **제어 모달**: 외부에서 상태를 관리하며, 복잡한 상황에 적합(어떤 상황발생시 트리거 되어 열리고 특정 액션(폼 작성후 제출등)이 트리거되어 닫혀야하는모달)
-- **비제어 모달**: 내부에서 상태를 관리하며, 간단한 상황에 적합(엮인 동작이 없는 단순 정보출력용 모달)
-
diff --git a/src/constants/AvatarConstants.ts b/src/constants/AvatarConstants.ts
index 0e5eeb80..b47adefe 100644
--- a/src/constants/AvatarConstants.ts
+++ b/src/constants/AvatarConstants.ts
@@ -5,3 +5,6 @@ export const AVATAR_SIZE = {
};
export const DEFAULT_IMG = '/assets/svg/profile-default.svg';
+
+
+export const DEFAULT_BG = '/assets/img/default-bg.png';
diff --git a/src/hooks/useBooking.ts b/src/hooks/useBooking.ts
new file mode 100644
index 00000000..d2abbad6
--- /dev/null
+++ b/src/hooks/useBooking.ts
@@ -0,0 +1,74 @@
+'use client';
+
+import { useParams } from 'next/navigation';
+import { privateInstance } from '@/apis/privateInstance';
+import { AxiosError } from 'axios';
+import { toast } from 'sonner';
+import useBookingStore from '@/stores/Booking/useBookingStore';
+import useUserStore from '@/stores/authStore';
+import { useMutation } from '@tanstack/react-query';
+
+export const useBooking = (isOwner: boolean) => {
+ const { id } = useParams();
+ const { user } = useUserStore();
+
+ const {
+ selectedDate,
+ selectedTime,
+ participants,
+ selectedTimeId,
+ setToInitial,
+ } = useBookingStore();
+
+ const bookingMutation = useMutation({
+ mutationFn: async () => {
+ return await privateInstance.post(`/activities/${id}/reservation`, {
+ selectedTimeId,
+ participants,
+ });
+ },
+ onSuccess: () => {
+ toast.success('예약되었습니다!');
+ setToInitial();
+ },
+ onError: (err) => {
+ const error = err as AxiosError;
+ const responseData = error.response?.data as
+ | { error?: string; message?: string }
+ | undefined;
+
+ console.error('전체 에러:', error);
+
+ toast.error(
+ responseData?.error ||
+ responseData?.message ||
+ error.message ||
+ '예약에 실패했습니다.',
+ );
+ },
+ });
+
+ const isLoggedIn = !!user;
+ const isBookable =
+ !!selectedDate &&
+ !!selectedTime &&
+ !!selectedTimeId &&
+ !!participants &&
+ !isOwner &&
+ isLoggedIn;
+
+ const buttonText = !isLoggedIn
+ ? '로그인이 필요한 기능입니다'
+ : isOwner
+ ? '본인이 등록한 체험입니다'
+ : '예약하기';
+
+ return {
+ onBooking: bookingMutation.isPending,
+ handleBooking: () => bookingMutation.mutate(),
+ isBookable,
+ buttonText,
+ selectedDate,
+ selectedTime,
+ };
+};
diff --git a/src/types/activityDetailType.ts b/src/types/activityDetailType.ts
index 0f254019..8fbe42f0 100644
--- a/src/types/activityDetailType.ts
+++ b/src/types/activityDetailType.ts
@@ -86,3 +86,33 @@ export interface ActivityDetailEdit {
}[];
schedules: Schedule[];
}
+
+export interface TitleProps {
+ title: string;
+ category: string;
+ rating: number;
+ reviewCount: number;
+ address: string;
+ isOwner: boolean;
+}
+
+export interface ReviewTitleProps {
+ reviewCount: number;
+ rating: number;
+}
+
+export interface ReviewSectionProps {
+ activityId: number;
+ reviewCount: number;
+ rating: number;
+}
+
+export interface ReviewProps {
+ id: string;
+ user: {
+ nickname: string;
+ profileImageUrl: string;
+ };
+ createdAt: string;
+ content: string;
+}
diff --git a/src/types/addEditExperienceType.ts b/src/types/addEditExperienceType.ts
new file mode 100644
index 00000000..ae53d765
--- /dev/null
+++ b/src/types/addEditExperienceType.ts
@@ -0,0 +1,111 @@
+import { Schedule } from '@/types/activityDetailType';
+
+export interface SubImageType {
+ id?: number;
+ url: string | File;
+}
+
+export interface AddressInputProps {
+ onAddressChange: (address: string) => void;
+ address: string;
+}
+
+export interface PostcodeData {
+ address: string;
+ addressType: 'R' | 'J';
+ bname: string;
+ buildingName: string;
+ zonecode: string;
+ userSelectedType: string;
+}
+
+export interface CategoryProps {
+ category?: string;
+ onCategoryChange: (value: string) => void;
+}
+
+export interface FormSectionProps {
+ title: string;
+ children: React.ReactNode;
+ description?: string;
+}
+
+export interface ImagePreviewProps {
+ image: File | string;
+ onRemove: () => void;
+ alt: string;
+ className?: string;
+}
+
+export interface ImagesSectionProps {
+ mainImage: string | File | null;
+ subImage: (string | File)[];
+ onMainImageSelect: (file: File) => void;
+ onMainImageRemove: () => void;
+ onSubImageAdd: (files: File[]) => void;
+ onSubImageRemove: (index: number) => void;
+}
+
+export interface ImageUploadProps {
+ onImageSelect: (file: File) => void;
+ multiple?: boolean;
+ className?: string;
+ children?: React.ReactNode;
+}
+
+export interface InfoSectionProps {
+ title?: string;
+ category?: string;
+ price?: string;
+ description?: string;
+ address?: string;
+ onTitleChange: (value: string) => void;
+ onCategoryChange: (value: string) => void;
+ onPriceChange: (value: string) => void;
+ onDescriptionChange: (value: string) => void;
+ onAddressChange: (value: string) => void;
+}
+
+export interface MainImageSelectProps {
+ mainImage: File | string | null;
+ onImageSelect: (file: File) => void;
+ onImageRemove: () => void;
+}
+
+
+export interface SubImageSelectProps {
+ subImage: (string | File)[];
+ onImagesAdd: (files: File[]) => void;
+ onImageRemove: (index: number) => void;
+}
+
+export interface ScheduleSelectProps {
+ index: number;
+ isRemovable: boolean;
+ onAddDate: () => void;
+ onRemove: (index: number) => void;
+ onDateChange: (index: number, value: string) => void;
+ onStartTimeChange: (index: number, value: string) => void;
+ onEndTimeChange: (index: number, value: string) => void;
+ date: string;
+ startTime: string;
+ endTime: string;
+}
+
+export interface ScheduleSelectFormProps {
+ dates: Schedule[];
+ onAddDate: () => void;
+ onRemoveDate: (index: number) => void;
+ onDateChange: (
+ index: number,
+ field: keyof Omit,
+ value: string,
+ ) => void;
+}
+
+
+export interface DateSlot {
+ date: string;
+ startTime: string;
+ endTime: string;
+}
diff --git a/src/types/bookingInterfaceType.ts b/src/types/bookingInterfaceType.ts
new file mode 100644
index 00000000..6720b0eb
--- /dev/null
+++ b/src/types/bookingInterfaceType.ts
@@ -0,0 +1,7 @@
+export interface BookingButtonProps {
+ onClick: () => void;
+ children: React.ReactNode;
+ disabled?: boolean;
+ onBooking?: boolean;
+ className?: string;
+}