Skip to content

Commit 75d8e33

Browse files
author
jyn
committed
Merge branch 'dev'
2 parents df92bc1 + 81d3626 commit 75d8e33

File tree

16 files changed

+296
-57
lines changed

16 files changed

+296
-57
lines changed

src/app/activities/[id]/page.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client';
22

3+
import { AxiosError } from 'axios';
34
import Image from 'next/image';
4-
import { notFound, useParams } from 'next/navigation';
5+
import { useParams, useRouter } from 'next/navigation';
56

67
import AddressWithMap from '@/features/activityId/components/map/address-with-map';
78
import OwnerDropdown from '@/features/activityId/components/owner-drop-down';
@@ -13,30 +14,29 @@ import { useActivityIdQuery } from '@/features/activityId/libs/hooks/useActivity
1314
import LoadingSpinner from '@/shared/components/loading-spinner/loading-spinner';
1415
import StarImage from '@/shared/components/star';
1516

16-
// TODO
17-
// 🐛 1. calendar-for-form에서 클릭 시 id 저장하는 코드 제거하기 O
18-
// 3. isDesktop활용하여, 캘린더를 모달로 감싸서 사용 O
19-
// * mockData pageID = 5192
20-
2117
const ActivityPage = () => {
2218
const { id } = useParams();
2319
const { data, isLoading, isError, error } = useActivityIdQuery(id);
24-
25-
const images = data?.subImages;
26-
20+
const router = useRouter();
2721
if (isError) {
28-
console.error('에러 발생:', error);
29-
if (error.message === 'Request failed with status code 404') {
30-
notFound();
22+
if (error instanceof AxiosError && error.response) {
23+
const status = error.response.status;
24+
25+
if (status === 404 || status === 500) {
26+
router.push('/not-found');
27+
} else {
28+
throw new Error(String(status));
29+
}
3130
}
3231
}
3332

34-
if (isLoading) return <LoadingSpinner />;
33+
if (isLoading || !data) return <LoadingSpinner />;
34+
3535
return (
3636
<div className="flex-center flex-col p-[2.4rem]">
3737
<div>
3838
<div className="flex flex-col gap-[2rem] md:gap-[2.4rem] lg:flex-row lg:gap-[4rem]">
39-
<SubImages images={images} />
39+
<SubImages images={data?.subImages} />
4040
<div className="relative">
4141
{/* 타이틀 헤더 */}
4242
<header className="order-2 lg:w-[41rem]">

src/features/activities/components/activity-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Star } from 'lucide-react';
21
import Image from 'next/image';
32
import { useRouter } from 'next/navigation';
43
import React from 'react';
54

5+
import StarImage from '@/shared/components/star';
66
import { formatPrice } from '@/shared/libs/utils/formatPrice';
77
import { Activity } from '@/shared/types/activity';
88

@@ -63,7 +63,7 @@ export const ActivityCard = ({
6363
</h3>
6464
{/* 별점과 리뷰 */}
6565
<div className="flex items-center gap-[0.2rem] md:gap-[0.3rem]">
66-
<Star className="text-yellow h-[1.25rem] w-[1.25rem] md:h-[2rem] md:w-[2rem]" />
66+
<StarImage extraClassName="size-[1.3rem] md:size-[2rem]" />
6767
<span className="text-[1.2rem] font-medium text-gray-950 md:text-[1.4rem]">
6868
{activity.rating}
6969
</span>

src/features/activity-registration/components/activity-registration-form.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ const ActivityRegistrationForm = () => {
7474
const router = useRouter();
7575

7676
const onSubmit = async (data: FormData) => {
77-
console.log('폼 제출 시작:', data); // 디버깅용
78-
7977
// 모든 스케줄의 시간 유효성 검증
8078
const hasInvalidSchedules = data.schedules.some((schedule) => {
8179
const startIndex = TIME_OPTIONS.findIndex(
@@ -106,12 +104,9 @@ const ActivityRegistrationForm = () => {
106104
subImageUrls: data.subImages,
107105
};
108106

109-
console.log('API 데이터:', apiData); // 디버깅용
110-
111107
// TanStack Query mutation 실행
112108
try {
113109
await registrationMutation.mutateAsync(apiData);
114-
console.log('등록 성공!'); // 디버깅용
115110
toast.success('체험이 등록되었습니다!');
116111
router.push('/activities');
117112
} catch (error) {

src/features/activity-registration/libs/hooks/useRegistrationMutation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const useRegistrationMutation = () => {
1212
onSuccess: () => {
1313
// 성공 시 관련 쿼리들을 무효화하여 새로고침
1414
queryClient.invalidateQueries({ queryKey: ['activities'] });
15-
queryClient.invalidateQueries({ queryKey: ['my-activities'] });
15+
queryClient.invalidateQueries({ queryKey: ['myActivities'] });
1616
// toast 메시지 제거 - 모달로 대체
1717
},
1818
onError: (error) => {

src/features/activityId/components/owner-drop-down.tsx

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
'use client';
2+
import { useQueryClient } from '@tanstack/react-query';
13
import Image from 'next/image';
4+
import { useRouter } from 'next/navigation';
5+
import { toast } from 'sonner';
26

37
import { useAuthStore } from '@/features/auth/stores/useAuthStore';
8+
import { deleteActivities } from '@/features/my/my-activities/lib/api/myActivities.api';
49
import Dropdown from '@/shared/components/dropdown';
10+
import Modal from '@/shared/components/modal/components';
11+
import SecondModal from '@/shared/components/modal/components/second-modal/second-modal';
12+
import { useModalStore } from '@/shared/libs/stores/useModalStore';
513

614
const OwnerDropdown = ({
715
ownerId,
@@ -11,24 +19,36 @@ const OwnerDropdown = ({
1119
activityId: number;
1220
}) => {
1321
const { user } = useAuthStore();
14-
const isOwner = user?.id === String(ownerId);
15-
console.log(user?.id, 'useid');
16-
console.log(ownerId, 'ownerId');
22+
const isOwner = user?.id === ownerId;
23+
const {
24+
openSecondModal,
25+
closeSecondModal,
1726

18-
const handleDelete = () => {
19-
console.log(
20-
'activityId:',
21-
activityId,
22-
' isOwner:',
23-
isOwner,
24-
'want to delete?',
25-
user?.id,
26-
'user.id',
27-
);
27+
activityId_secondModal,
28+
secondModalName,
29+
} = useModalStore();
30+
31+
// delete confirm
32+
const queryClient = useQueryClient();
33+
const router = useRouter();
34+
const handleConfirm = async () => {
35+
if (!activityId_secondModal) return;
36+
try {
37+
await deleteActivities(activityId_secondModal);
38+
toast.success('삭제가 완료되었습니다.');
39+
await queryClient.invalidateQueries({ queryKey: ['activities'] });
40+
await queryClient.invalidateQueries({ queryKey: ['myActivities'] });
41+
router.push('/activities');
42+
} catch {
43+
toast.error('삭제에 실패하였습니다.');
44+
} finally {
45+
closeSecondModal();
46+
}
2847
};
48+
2949
return (
3050
<>
31-
{!isOwner && (
51+
{isOwner && (
3252
<Dropdown
3353
dropdownClassName="absolute top-8 right-2"
3454
trigger={
@@ -43,19 +63,41 @@ const OwnerDropdown = ({
4363
}
4464
>
4565
<div className="border-sub-300 h-[10.8rem] overflow-hidden rounded-[0.8rem] border bg-white">
46-
<button className="hover:text-main hover:bg-sub h-[5.4rem] w-[9.3rem] px-[1.8rem] text-[1.6rem]">
66+
<button
67+
onClick={() => router.push('/my/my-activities/')}
68+
className="hover:text-main hover:bg-sub h-[5.4rem] w-[9.3rem] px-[1.8rem] text-[1.6rem]"
69+
>
4770
수정하기
4871
</button>
4972
<hr />
5073
<button
51-
onClick={handleDelete}
74+
onClick={() => {
75+
openSecondModal(activityId, 'delete');
76+
}}
5277
className="h-[5.4rem] w-[9.3rem] px-[1.8rem] text-[1.6rem] hover:bg-red-100 hover:text-red-500"
5378
>
5479
삭제하기
5580
</button>
5681
</div>
5782
</Dropdown>
5883
)}
84+
{secondModalName === 'delete' && (
85+
<SecondModal type="warning" extraClassName="md:pb-[1rem]">
86+
<Modal.Header>체험을 삭제하시겠습니까?</Modal.Header>
87+
<div className="mb-0 flex w-[23.4rem] gap-2 md:w-[28.2rem] md:gap-3">
88+
<Modal.Button
89+
color="white"
90+
ariaLabel="아니요"
91+
onClick={closeSecondModal}
92+
>
93+
아니요
94+
</Modal.Button>
95+
<Modal.Button color="blue" ariaLabel="네" onClick={handleConfirm}>
96+
97+
</Modal.Button>
98+
</div>
99+
</SecondModal>
100+
)}
59101
</>
60102
);
61103
};

src/features/activityId/components/reservation-form.tsx

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
import { useAuthStore } from '@/features/auth/stores/useAuthStore';
2222
import CalendarForForm from '@/shared/components/calendar/components/calendar-for-form';
2323
import { formatDateToYMD } from '@/shared/components/calendar/libs/utils/formatDateToYMD';
24+
import Modal from '@/shared/components/modal/components';
25+
import SecondModal from '@/shared/components/modal/components/second-modal/second-modal';
2426
import { cn } from '@/shared/libs/cn';
2527
import { useCalendarStore } from '@/shared/libs/stores/useCalendarStore';
2628
import { useModalStore } from '@/shared/libs/stores/useModalStore';
@@ -39,22 +41,37 @@ const ReservationForm = ({
3941
price: number | undefined;
4042
activityId: number;
4143
}) => {
42-
const { selectedDate, resetSelectedDate } = useCalendarStore();
44+
const {
45+
selectedDate,
46+
resetSelectedDate,
47+
year,
48+
month,
49+
setMonth,
50+
setYear,
51+
resetDate,
52+
} = useCalendarStore();
53+
const {
54+
appear,
55+
disappearModal,
56+
appearModal,
57+
isDesktop,
58+
secondModalName,
59+
closeSecondModal,
60+
openSecondModal,
61+
} = useModalStore();
4362
const [schedulesInDate, setSchedulesInDate] = useState<
44-
TimeSlot[] | undefined[] | undefined
63+
TimeSlot[] | undefined
4564
>([]);
65+
const [scheduledDate, setScheduledDate] = useState<AvailableScheduleList>();
4666
const [selectedTime, setSelectedTime] = useState('');
47-
const { appear, disappearModal, appearModal, isDesktop } = useModalStore();
4867
const [nextStep, setNextStep] = useState(false);
49-
const { mutate } = useReservationMutation(activityId);
5068
const isTablet = useIsTablet();
51-
const { year, month, setMonth, setYear } = useCalendarStore();
52-
const [scheduledDate, setScheduledDate] = useState<AvailableScheduleList>();
69+
const { isLoggedIn } = useAuthStore();
70+
const { mutate } = useReservationMutation(activityId);
5371
const { data, isLoading, error } = useSchedulesQuery(activityId, {
5472
year: String(year),
5573
month: String(month + 1).padStart(2, '0'),
5674
});
57-
const { isLoggedIn } = useAuthStore();
5875

5976
// 리액트훅폼
6077
const {
@@ -90,8 +107,9 @@ const ReservationForm = ({
90107
const today = new Date();
91108
setYear(today.getFullYear());
92109
setMonth(today.getMonth());
110+
resetDate();
93111
};
94-
}, [setMonth, setYear]);
112+
}, [setMonth, setYear, resetDate]);
95113

96114
return (
97115
<>
@@ -107,8 +125,10 @@ const ReservationForm = ({
107125
mutate(data, {
108126
onSuccess: (res) => {
109127
console.log('✅ 예약 성공:', res);
128+
openSecondModal(undefined, 'success');
110129
addReservation(data.scheduleId); //save id in localStorage
111-
resetSelectedDate(); //🐛이거 해도 제출후 다시 열어보면, 이전 선택 날짜가 칠해져있음...뭔가 리렌더링 기회가 없는건가
130+
resetSelectedDate(); //🐛이거 해도 제출후 다시 열어보면, 이전 선택 날짜가 칠해져있음...:스타일링은 date 담당이기 떄문이었다.
131+
resetDate(); //이거까지 해야함
112132
setSelectedTime('');
113133
reset(); // 제출 후 폼 초기화
114134
},
@@ -405,6 +425,20 @@ const ReservationForm = ({
405425
</button>
406426
</section>
407427
</form>
428+
{secondModalName === 'success' && (
429+
<SecondModal type="confirm" extraClassName="md:pb-[1rem]">
430+
<Modal.Header>예약이 완료되었습니다.</Modal.Header>
431+
<div className="w-[18rem] md:w-[20rem]">
432+
<Modal.Button
433+
color="blue"
434+
ariaLabel="확인"
435+
onClick={closeSecondModal}
436+
>
437+
확인
438+
</Modal.Button>
439+
</div>
440+
</SecondModal>
441+
)}
408442
</>
409443
);
410444
};

src/features/activityId/libs/hooks/useActivityIdQuery.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import { ParamValue } from 'next/dist/server/request/params';
33

44
import { getActivityId } from '@/features/activityId/libs/api/getActivityId';
55

6-
//staleTime - 예약현황 업데이트 빈도: e.g.숙소 앱은 1~3분 & 예약 직전 실시간 확인 refetch()
76
export const useActivityIdQuery = (id: ParamValue) => {
87
return useQuery({
98
queryKey: ['activityId', id],
109
queryFn: () => getActivityId(id),
11-
staleTime: 1000 * 60 * 5,
10+
staleTime: 1000 * 60 * 30,
1211
retry: 1,
1312
});
1413
};
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { useMutation } from '@tanstack/react-query';
1+
import { useMutation, useQueryClient } from '@tanstack/react-query';
22

33
import { postReservationForm } from '@/features/activityId/libs/api/postReservationForm';
44
import { ReservationRequestBody } from '@/features/activityId/libs/types/reservationType';
55

66
export const useReservationMutation = (activityId: number) => {
7+
const queryClient = useQueryClient();
8+
79
return useMutation({
810
mutationFn: (body: ReservationRequestBody) =>
911
postReservationForm(activityId, body),
12+
onSuccess: () => {
13+
queryClient.invalidateQueries({
14+
queryKey: ['booking'],
15+
});
16+
},
1017
});
1118
};

src/features/activityId/libs/utils/addReservation.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,19 @@ export const getMyResertvation = (): number[] => {
1111
console.log('geteMuRservation!and diffing');
1212
return JSON.parse(localStorage.getItem('myReservation') || '[]');
1313
};
14+
15+
export const removeReservation = (scheduleId: number | null) => {
16+
if (typeof window === 'undefined') return;
17+
if (scheduleId === null) {
18+
console.log('해당 스케줄은 "없는" 값으로, 로컬스토리지에서 삭제 못함.');
19+
return;
20+
}
21+
22+
const saved = JSON.parse(
23+
localStorage.getItem('myReservation') || '[]',
24+
) as number[];
25+
const filtered = saved.filter((id) => id !== scheduleId);
26+
localStorage.setItem('myReservation', JSON.stringify(filtered));
27+
console.log(`예약 ${scheduleId} 제거됨`, filtered, '🗑️');
28+
// 전역상태 스케줄아이디 값 널값으로 다시 리셋????
29+
};

src/features/booking-detail/components/booking-modal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const BookingModal = ({
3131
statusLabel,
3232
isOpen,
3333
}: BookingModalProps) => {
34-
const { closeModal } = useModalStore();
34+
const { closeModal, setScheduleId } = useModalStore();
3535

3636
// 예약 취소 mutation
3737
const { mutate: cancelBooking, isPending } = useCancelMutation();
@@ -61,6 +61,8 @@ const BookingModal = ({
6161
// 예약 취소 모달에서 확인 버튼 클릭 시
6262
const handleModalCancelBooking = () => {
6363
cancelBooking(reservation.id);
64+
// 스케줄아이디 저장
65+
setScheduleId(reservation.scheduleId);
6466
closeModal();
6567
};
6668

0 commit comments

Comments
 (0)