Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@ import type { TabletReservationSheetProps } from './types';

type MobileStep = 'dateTime' | 'headCount';

interface MobileReservationSheetProps extends Omit<TabletReservationSheetProps, 'onConfirm'> {
activityId: number;
onReservationSuccess?: () => void;
onReservationError?: (error: Error) => void;
}

export default function MobileReservationSheet({
schedules,
price,
isOpen,
onClose,
onConfirm,
activityId,
onReservationSuccess,
onReservationError,
isAuthor = false,
isLoggedIn = true,
}: TabletReservationSheetProps) {
}: MobileReservationSheetProps) {
const [currentStep, setCurrentStep] = useState<MobileStep>('dateTime');

const {
Expand All @@ -33,7 +41,18 @@ export default function MobileReservationSheet({
availableTimes,
totalPrice,
reservableDates,
} = useReservation(schedules, price);
submitReservation,
isSubmitting,
} = useReservation(schedules, price, {
onSuccess: () => {
setCurrentStep('dateTime'); // 첫 번째 단계로 리셋
onClose(); // 시트 닫기
onReservationSuccess?.(); // 성공 콜백 호출
},
onError: (error) => {
onReservationError?.(error); // 에러 콜백 호출
},
});

const handleClose = () => {
setCurrentStep('dateTime');
Expand All @@ -50,27 +69,19 @@ export default function MobileReservationSheet({
setCurrentStep('dateTime');
};

const handleConfirm = () => {
if (selectedScheduleId && selectedDate) {
const selectedSchedule = schedules.find((s) => s.id === selectedScheduleId);
if (selectedSchedule) {
onConfirm({
date: selectedSchedule.date,
startTime: selectedSchedule.startTime,
endTime: selectedSchedule.endTime,
headCount,
scheduleId: selectedScheduleId,
});
setCurrentStep('dateTime'); // 완료 후 초기 단계로 리셋
}
const handleConfirm = async () => {
if (selectedScheduleId) {
await submitReservation(activityId);
}
};

// 버튼 텍스트 결정 (1단계, 2단계 공통)
let buttonText = '';
if (!isLoggedIn) buttonText = '로그인 필요';
if (isSubmitting) buttonText = '예약 중...';
else if (!isLoggedIn) buttonText = '로그인 필요';
else if (isAuthor) buttonText = '예약 불가';
else buttonText = '확인';
else if (currentStep === 'dateTime') buttonText = '다음';
else buttonText = '예약하기';

return (
<BottomSheet.Root isOpen={isOpen} onClose={handleClose}>
Expand Down Expand Up @@ -110,7 +121,7 @@ export default function MobileReservationSheet({
<div className='pt-8'>
<Button
className='w-full'
disabled={!selectedScheduleId || isAuthor || !isLoggedIn}
disabled={!selectedScheduleId || isAuthor || !isLoggedIn || isSubmitting}
size='lg'
variant='fill'
onClick={handleNextStep}
Expand Down Expand Up @@ -164,7 +175,7 @@ export default function MobileReservationSheet({
<div className='pt-8'>
<Button
className='w-full'
disabled={!isReadyToReserve || isAuthor || !isLoggedIn}
disabled={!isReadyToReserve || isAuthor || !isLoggedIn || isSubmitting}
size='lg'
variant='fill'
onClick={handleConfirm}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button } from '@what-today/design-system';
import { useQueryClient } from '@tanstack/react-query';
import { Button, useToast } from '@what-today/design-system';

import CalendarSelector from './CalendarSelector';
import HeadCountSelector from './HeadCountSelector';
Expand All @@ -7,17 +8,40 @@ import TimeSelector from './TimeSelector';
import type { ReservationFormProps } from './types';

export default function ReservationForm({
activityId,
schedules,
price,
onReservationChange,
onSubmit,
showSubmitButton = false,
showSubmitButton = true,
isSubmitting: externalIsSubmitting,
isAuthor = false,
isLoggedIn = true,
}: ReservationFormProps) {
const { toast } = useToast();
const queryClient = useQueryClient();

const reservation = useReservation(schedules, price, {
onReservationChange,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['reservations'],
});

toast({
title: '예약 완료',
description: '마이페이지에서 예약을 확인해보세요!',
type: 'success',
});
},
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : '예약 중 오류가 발생했습니다.';
toast({
title: '예약 실패',
description: errorMessage,
type: 'error',
});
},
});

const {
Expand All @@ -33,18 +57,25 @@ export default function ReservationForm({
totalPrice,
isReadyToReserve,
isSubmitting: internalIsSubmitting,
submitReservation,
} = reservation;

// 외부에서 전달받은 isSubmitting을 우선시
const isSubmitting = externalIsSubmitting ?? internalIsSubmitting;

const handleSubmit = async () => {
if (!onSubmit || !selectedScheduleId) return;
if (!selectedScheduleId) return;

await onSubmit({
scheduleId: selectedScheduleId,
headCount,
});
// onSubmit이 있으면 기존 방식 사용 (하위 호환성)
if (onSubmit) {
await onSubmit({
scheduleId: selectedScheduleId,
headCount,
});
} else {
// 기본 방식: submitReservation 사용 (자동 초기화)
await submitReservation(activityId);
}
};

// 버튼 텍스트 결정
Expand All @@ -54,7 +85,7 @@ export default function ReservationForm({
else if (isAuthor) buttonText = '예약 불가';
else buttonText = '예약하기';

return (
const content = (
<div className='flex flex-col gap-24'>
{/* 가격 표시 */}
<p className='text-xl text-[#79747E]'>
Expand Down Expand Up @@ -98,4 +129,10 @@ export default function ReservationForm({
</div>
</div>
);

return (
<section className='flex flex-col justify-between rounded-3xl border border-[#DDDDDD] p-30 shadow-sm'>
{content}
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ import { useReservation } from '@/components/activities/reservation/hooks/useRes
import TimeSelector from '@/components/activities/reservation/TimeSelector';
import type { TabletReservationSheetProps } from '@/components/activities/reservation/types';

interface ExtendedTabletReservationSheetProps extends Omit<TabletReservationSheetProps, 'onConfirm'> {
activityId: number;
onReservationSuccess?: () => void;
onReservationError?: (error: Error) => void;
}

export default function TabletReservationSheet({
schedules,
price,
isOpen,
onClose,
onConfirm,
activityId,
onReservationSuccess,
onReservationError,
isAuthor = false,
isLoggedIn = true,
}: TabletReservationSheetProps) {
}: ExtendedTabletReservationSheetProps) {
const {
selectedDate,
setSelectedDate,
Expand All @@ -27,13 +35,24 @@ export default function TabletReservationSheet({
availableTimes,
totalPrice,
reservableDates,
} = useReservation(schedules, price);
submitReservation,
isSubmitting,
} = useReservation(schedules, price, {
onSuccess: () => {
onClose(); // 시트 닫기
onReservationSuccess?.(); // 성공 콜백 호출
},
onError: (error) => {
onReservationError?.(error); // 에러 콜백 호출
},
});

// 버튼 텍스트 결정
let buttonText = '';
if (!isLoggedIn) buttonText = '로그인 필요';
if (isSubmitting) buttonText = '예약 중...';
else if (!isLoggedIn) buttonText = '로그인 필요';
else if (isAuthor) buttonText = '예약 불가';
else buttonText = '확인';
else buttonText = '예약하기';

return (
<BottomSheet.Root isOpen={isOpen} onClose={onClose}>
Expand Down Expand Up @@ -75,21 +94,12 @@ export default function TabletReservationSheet({
<div className='px-20 pt-8 pb-24'>
<Button
className='w-full'
disabled={!isReadyToReserve || isAuthor || !isLoggedIn}
disabled={!isReadyToReserve || isAuthor || !isLoggedIn || isSubmitting}
size='lg'
variant='fill'
onClick={() => {
if (selectedScheduleId && selectedDate) {
const selectedSchedule = schedules.find((s) => s.id === selectedScheduleId);
if (selectedSchedule) {
onConfirm({
date: selectedSchedule.date,
startTime: selectedSchedule.startTime,
endTime: selectedSchedule.endTime,
headCount,
scheduleId: selectedScheduleId,
});
}
onClick={async () => {
if (selectedScheduleId) {
await submitReservation(activityId);
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface UseReservationReturn {
}

export interface ReservationFormProps {
activityId: number;
schedules: Schedule[];
price: number;
onReservationChange?: (summary: ReservationSummary | null) => void;
Expand All @@ -60,14 +61,6 @@ export interface ReservationFormProps {
isLoggedIn?: boolean;
}

export interface DesktopReservationProps {
activityId: number;
price: number;
schedules: Schedule[];
isAuthor?: boolean;
isLoggedIn?: boolean;
}

export interface TabletReservationSheetProps {
schedules: Schedule[];
price: number;
Expand All @@ -82,7 +75,7 @@ export interface ReservationBottomBarProps {
price: number;
reservation: Pick<ReservationSummary, 'date' | 'startTime' | 'endTime' | 'headCount'> | null;
onSelectDate: () => void;
onReserve: () => void;
onReserve?: () => void;
isSubmitting?: boolean;
isAuthor?: boolean;
isLoggedIn?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion apps/what-today/src/layouts/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function DefaultLayout() {
const footerMarginBottom = isActivityDetailPage && !isDesktop ? 'w-full mb-125' : 'w-full';

// FloatingTranslateButton의 bottom 위치 조건부 설정
const floatingButtonClass = !isDesktop && isActivityDetailPage ? 'bottom-140' : undefined;
const floatingButtonClass = !isDesktop && isActivityDetailPage ? 'bottom-160' : undefined;

return (
<div className='flex w-full flex-col items-center gap-8 overflow-x-hidden'>
Expand Down
Loading