Skip to content

Commit 01659dd

Browse files
authored
Merge pull request #137 from codeit-2team/fix/128
Fix/128 QA(1차, 2차) 피드백 및 이슈 해결
2 parents 5a01586 + a0ccff2 commit 01659dd

20 files changed

Lines changed: 507 additions & 163 deletions

File tree

src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { privateInstance } from '@/apis/privateInstance';
22
import { useMutation, useQueryClient } from '@tanstack/react-query';
33
import { AxiosError } from 'axios';
44
import { useRouter } from 'next/navigation';
5+
import { toast } from 'sonner';
56

67
const deleteActivity = async (id: string) => {
78
const response = await privateInstance.delete(`/deleteActivity/${id}`);
@@ -15,9 +16,12 @@ export const useDeleteActivity = () => {
1516
return useMutation({
1617
mutationFn: deleteActivity,
1718
onSuccess: (_data) => {
18-
queryClient.invalidateQueries({ queryKey: ['activity'] }); // 내 체험 관리
19-
queryClient.invalidateQueries({ queryKey: ['experiences'], exact: false }); // 모든 체험 리스트
20-
queryClient.invalidateQueries({ queryKey: ['popularExperiences'] }); // 인기 체험
19+
queryClient.invalidateQueries({ queryKey: ['activity'] });
20+
queryClient.invalidateQueries({
21+
queryKey: ['experiences'],
22+
exact: false,
23+
});
24+
queryClient.invalidateQueries({ queryKey: ['popularExperiences'] });
2125
router.push(`/`);
2226
},
2327
onError: (error: AxiosError) => {
@@ -27,8 +31,7 @@ export const useDeleteActivity = () => {
2731

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

30-
alert(
31-
//토스트로 대체
34+
toast.error(
3235
responseData?.error ||
3336
responseData?.message ||
3437
error.message ||

src/app/(with-header)/mypage/activities/components/ActivityCard.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export default function ActivityCard({
2121

2222
const { id, title, price, bannerImageUrl, rating, reviewCount } = activity;
2323

24+
const handleCardClick = () => {
25+
router.push(`/activities/${id}`);
26+
};
27+
2428
const handleEdit = () => {
2529
router.push(`/myactivity/${id}`);
2630
};
@@ -31,7 +35,10 @@ export default function ActivityCard({
3135
};
3236

3337
return (
34-
<div className='flex h-128 w-full max-w-792 flex-row rounded-3xl border border-gray-300 bg-white sm:h-156 lg:h-204'>
38+
<div
39+
className='flex h-128 w-full max-w-792 cursor-pointer flex-row rounded-3xl border border-gray-300 bg-white transition-shadow hover:shadow-md sm:h-156 lg:h-204'
40+
onClick={handleCardClick}
41+
>
3542
{/* 이미지 영역 */}
3643
<div className='relative h-full w-128 flex-shrink-0 overflow-hidden rounded-l-3xl sm:w-156 lg:w-204'>
3744
<Image src={bannerImageUrl} alt={title} fill className='object-cover' />
@@ -68,7 +75,10 @@ export default function ActivityCard({
6875
{/* 더보기 옵션 */}
6976
<div className='relative'>
7077
<button
71-
onClick={() => setIsMenuOpen(!isMenuOpen)}
78+
onClick={(e) => {
79+
e.stopPropagation();
80+
setIsMenuOpen(!isMenuOpen);
81+
}}
7282
className='flex h-40 w-40 items-center justify-center rounded-full hover:bg-gray-100'
7383
>
7484
<MoreOptionsIcon size={40} />
@@ -78,18 +88,27 @@ export default function ActivityCard({
7888
<>
7989
<div
8090
className='fixed inset-0 z-40'
81-
onClick={() => setIsMenuOpen(false)}
91+
onClick={(e) => {
92+
e.stopPropagation();
93+
setIsMenuOpen(false);
94+
}}
8295
/>
8396
{/* 드롭다운 메뉴 */}
8497
<div className='absolute top-full right-0 z-50 w-120 rounded-md border border-gray-300 bg-white shadow-lg sm:w-160'>
8598
<button
86-
onClick={handleEdit}
99+
onClick={(e) => {
100+
e.stopPropagation();
101+
handleEdit();
102+
}}
87103
className='flex h-50 w-full items-center justify-center border-b border-gray-300 px-4 py-3 text-center text-base font-medium text-gray-900 hover:bg-gray-50 sm:h-62 sm:px-46 sm:py-18 sm:text-lg'
88104
>
89105
수정하기
90106
</button>
91107
<button
92-
onClick={handleDelete}
108+
onClick={(e) => {
109+
e.stopPropagation();
110+
handleDelete();
111+
}}
93112
className='flex h-50 w-full items-center justify-center px-4 py-3 text-center text-base font-medium text-gray-900 hover:bg-gray-50 sm:h-62 sm:px-46 sm:py-18 sm:text-lg'
94113
>
95114
삭제하기

src/app/(with-header)/mypage/activities/page.tsx

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,35 @@ export default function MyActivitiesPage() {
8989
{[1, 2, 3].map((i) => (
9090
<div
9191
key={i}
92-
className='rounded-24 h-204 w-792 animate-pulse bg-gray-200'
93-
/>
92+
className='flex h-128 w-full max-w-792 flex-row rounded-3xl border border-gray-300 bg-white sm:h-156 lg:h-204'
93+
>
94+
{/* 이미지 영역 스켈레톤 */}
95+
<div className='h-full w-128 flex-shrink-0 animate-pulse rounded-l-3xl bg-gray-200 sm:w-156 lg:w-204'></div>
96+
97+
{/* 콘텐츠 영역 스켈레톤 */}
98+
<div className='flex w-0 flex-grow flex-col justify-start px-12 py-10 sm:px-16 sm:py-12 lg:px-24 lg:py-14'>
99+
{/* 별점 및 리뷰 스켈레톤 */}
100+
<div className='flex items-center gap-6'>
101+
<div className='flex items-center gap-2'>
102+
<div className='h-4 w-4 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-5'></div>
103+
<div className='h-4 w-8 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-10'></div>
104+
<div className='h-4 w-12 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-16'></div>
105+
</div>
106+
</div>
107+
108+
{/* 제목 스켈레톤 */}
109+
<div className='mt-2 h-4 w-40 animate-pulse rounded bg-gray-200 sm:mt-4 sm:h-5 sm:w-48 lg:mt-6 lg:h-6 lg:w-56'></div>
110+
111+
{/* 가격 + 더보기 버튼 영역 스켈레톤 */}
112+
<div className='mt-auto flex items-center justify-between'>
113+
{/* 가격 스켈레톤 */}
114+
<div className='h-6 w-32 animate-pulse rounded bg-gray-200 sm:h-6 sm:w-36 lg:h-7 lg:w-40'></div>
115+
116+
{/* 더보기 버튼 스켈레톤 */}
117+
<div className='h-40 w-40 animate-pulse rounded-full bg-gray-200'></div>
118+
</div>
119+
</div>
120+
</div>
94121
))}
95122
</div>
96123
</div>
@@ -157,7 +184,34 @@ export default function MyActivitiesPage() {
157184

158185
{/* 무한 스크롤 로딩 */}
159186
{isFetchingNextPage && (
160-
<div className='rounded-24 h-204 w-792 animate-pulse bg-gray-200' />
187+
<div className='flex h-128 w-full max-w-792 flex-row rounded-3xl border border-gray-300 bg-white sm:h-156 lg:h-204'>
188+
{/* 이미지 영역 스켈레톤 */}
189+
<div className='h-full w-128 flex-shrink-0 animate-pulse rounded-l-3xl bg-gray-200 sm:w-156 lg:w-204'></div>
190+
191+
{/* 콘텐츠 영역 스켈레톤 */}
192+
<div className='flex w-0 flex-grow flex-col justify-start px-12 py-10 sm:px-16 sm:py-12 lg:px-24 lg:py-14'>
193+
{/* 별점 및 리뷰 스켈레톤 */}
194+
<div className='flex items-center gap-6'>
195+
<div className='flex items-center gap-2'>
196+
<div className='h-4 w-4 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-5'></div>
197+
<div className='h-4 w-8 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-10'></div>
198+
<div className='h-4 w-12 animate-pulse rounded bg-gray-200 sm:h-5 sm:w-16'></div>
199+
</div>
200+
</div>
201+
202+
{/* 제목 스켈레톤 */}
203+
<div className='mt-2 h-4 w-40 animate-pulse rounded bg-gray-200 sm:mt-4 sm:h-5 sm:w-48 lg:mt-6 lg:h-6 lg:w-56'></div>
204+
205+
{/* 가격 + 더보기 버튼 영역 스켈레톤 */}
206+
<div className='mt-auto flex items-center justify-between'>
207+
{/* 가격 스켈레톤 */}
208+
<div className='h-6 w-32 animate-pulse rounded bg-gray-200 sm:h-6 sm:w-36 lg:h-7 lg:w-40'></div>
209+
210+
{/* 더보기 버튼 스켈레톤 */}
211+
<div className='h-40 w-40 animate-pulse rounded-full bg-gray-200'></div>
212+
</div>
213+
</div>
214+
</div>
161215
)}
162216
</div>
163217
)}

src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useState } from 'react';
3+
import { useState, useEffect } from 'react';
44
import {
55
useReservedSchedules,
66
useActivityReservations,
@@ -9,14 +9,15 @@ import {
99
import { DashboardFilterOption } from '@/types/dashboardTypes';
1010
import { DASHBOARD_TAB_OPTIONS } from '@/constants/dashboardConstants';
1111
import {
12-
createTimeSlotOptions,
12+
createFilteredTimeSlotOptions,
1313
getScheduleIdFromTimeSlot,
1414
getSelectedTimeSlotValue,
1515
} from '@/utils/timeSlotUtils';
1616
import Dropdown from '@/components/Dropdown';
1717
import ReservationActionButtons from './ReservationActionButtons';
1818
import dayjs from 'dayjs';
1919
import CloseIcon from '@assets/svg/close';
20+
import { toast } from 'sonner';
2021

2122
interface Props {
2223
isOpen: boolean;
@@ -51,12 +52,26 @@ export default function ReservationInfoModal({
5152
// 예약 상태 업데이트
5253
const updateReservationMutation = useUpdateActivityReservationStatus();
5354

54-
const timeSlotOptions = createTimeSlotOptions(schedules);
55+
const timeSlotOptions = createFilteredTimeSlotOptions(schedules, activeTab);
5556
const selectedTimeSlotValue = getSelectedTimeSlotValue(
5657
schedules,
5758
selectedScheduleId,
5859
);
5960

61+
// 탭 변경 시 현재 선택된 시간대가 유효한지 확인하고 초기화
62+
useEffect(() => {
63+
if (selectedScheduleId && schedules) {
64+
const currentSchedule = schedules.find(
65+
(schedule) => schedule.scheduleId === selectedScheduleId,
66+
);
67+
68+
// 현재 선택된 시간대가 새 탭에서 유효하지 않으면 초기화
69+
if (!currentSchedule || currentSchedule.count[activeTab] === 0) {
70+
setSelectedScheduleId(null);
71+
}
72+
}
73+
}, [activeTab, schedules, selectedScheduleId]);
74+
6075
// 예약 승인/거절 처리
6176
const handleReservationAction = async (
6277
reservationId: number,
@@ -69,7 +84,7 @@ export default function ReservationInfoModal({
6984
data: { status },
7085
});
7186
} catch {
72-
alert('예약 처리에 실패했습니다.');
87+
toast.error('예약 처리에 실패했습니다.');
7388
}
7489
};
7590

src/app/(with-header)/mypage/dashboard/page.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useActivityOptions } from '@/hooks/useActivityOptions';
99
import EmptyDashboard from './components/EmptyDashboard';
1010
import ReservationDashboardCalendar from './components/ReservationDashboardCalendar';
1111
import ReservationInfoModal from './components/ReservationInfoModal';
12+
import CalendarSkeleton from './components/CalendarSkeleton';
1213

1314
export default function MyDashboardPage() {
1415
const [selectedActivityId, setSelectedActivityId] = useState<number | null>(
@@ -19,7 +20,11 @@ export default function MyDashboardPage() {
1920

2021
// 내 체험 리스트 조회
2122
const { data: activitiesData, isLoading, error } = useMyActivities();
22-
const { activityOptions, uniqueTitles } = useActivityOptions(activitiesData);
23+
const { activityOptions, uniqueTitles, handleActivityChange } =
24+
useActivityOptions(activitiesData, (activityId) => {
25+
setSelectedActivityId(activityId);
26+
setSelectedDate('');
27+
});
2328

2429
// 페이지 로드 시 첫 번째 체험 자동 선택
2530
useEffect(() => {
@@ -33,18 +38,6 @@ export default function MyDashboardPage() {
3338
}
3439
}, [activitiesData, selectedActivityId]);
3540

36-
// 체험 선택 -> 제목으로 ID 찾기
37-
const handleActivityChange = (selectedTitle: string) => {
38-
const selectedOption = activityOptions.find(
39-
(option) => option.label === selectedTitle,
40-
);
41-
42-
if (selectedOption) {
43-
setSelectedActivityId(parseInt(selectedOption.value));
44-
setSelectedDate('');
45-
}
46-
};
47-
4841
// 현재 선택된 체험의 제목 찾기
4942
const selectedActivityTitle =
5043
activityOptions.find(
@@ -76,7 +69,7 @@ export default function MyDashboardPage() {
7669
<div className='mb-48 h-56 w-full max-w-792 animate-pulse rounded-md bg-gray-200' />
7770

7871
{/* 달력 스켈레톤 */}
79-
<div className='rounded-24 h-600 w-full animate-pulse bg-gray-200' />
72+
<CalendarSkeleton calendarWeeksLength={6} />
8073
</div>
8174
);
8275
}
@@ -129,7 +122,8 @@ export default function MyDashboardPage() {
129122
value={selectedActivityTitle}
130123
onChange={handleActivityChange}
131124
placeholder='체험을 선택하세요'
132-
className='h-56'
125+
className='h-56 min-w-0'
126+
truncateText={true}
133127
/>
134128
</div>
135129

src/app/(with-header)/mypage/layout.tsx

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22

33
import { ProfileNavigation } from './components';
44
import useResponsiveRouting from '@/hooks/useResponsiveRouting';
5-
import { useMyProfile } from '@/hooks/useMyPageQueries';
65

76
export default function MyPageLayout({
87
children,
98
}: {
109
children: React.ReactNode;
1110
}) {
1211
const { mounted } = useResponsiveRouting();
13-
const { isLoading, error } = useMyProfile();
1412

15-
// mounted + API 로딩 상태 모두 체크
16-
if (!mounted || isLoading) {
13+
// mounted 상태만 체크
14+
if (!mounted) {
1715
return (
1816
<div className='min-h-screen bg-gray-100'>
1917
<div className='mx-auto max-w-1200 px-20 py-24 lg:py-72'>
@@ -36,28 +34,15 @@ export default function MyPageLayout({
3634
</div>
3735
</div>
3836
</div>
39-
{/* 메인 스켈레톤 */}
40-
<div className='flex-grow animate-pulse rounded bg-gray-200'></div>
37+
{/* 메인 영역 */}
38+
<div className='min-w-0 flex-grow'></div>
4139
</div>
4240
</div>
4341
</div>
4442
);
4543
}
4644

47-
if (error) {
48-
return (
49-
<div className='flex min-h-screen items-center justify-center bg-gray-100'>
50-
<div className='text-center'>
51-
<h2 className='mb-2 text-xl font-bold text-red-500'>
52-
로그인이 필요합니다
53-
</h2>
54-
<p className='text-gray-600'>다시 로그인해주세요.</p>
55-
</div>
56-
</div>
57-
);
58-
}
59-
60-
// API 로딩 완료 + mounted 상태일 때만 실행
45+
// mounted 상태일 때만 실행
6146
return (
6247
<div className='min-h-screen bg-gray-100'>
6348
<div className='mx-auto max-w-1200 px-20 py-24 lg:py-72'>
@@ -66,7 +51,7 @@ export default function MyPageLayout({
6651
<ProfileNavigation />
6752

6853
{/* 우측 메인 콘텐츠 섹션 */}
69-
<div className='flex-grow'>{children}</div>
54+
<div className='min-w-0 flex-grow'>{children}</div>
7055
</div>
7156
</div>
7257
</div>

0 commit comments

Comments
 (0)