From bf647aebda7c1177de7a2cba9ca9b3c4af1b48e6 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:14:48 +0900 Subject: [PATCH 01/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=EB=93=9C=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=98=A4=EB=B2=84=ED=94=8C=EB=A1=9C?= =?UTF-8?q?=EC=9A=B0=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(with-header)/mypage/dashboard/page.tsx | 3 ++- src/components/Dropdown.tsx | 8 +++++++- src/types/dropdownTypes.ts | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/(with-header)/mypage/dashboard/page.tsx b/src/app/(with-header)/mypage/dashboard/page.tsx index f9c15b6f..ac387aff 100644 --- a/src/app/(with-header)/mypage/dashboard/page.tsx +++ b/src/app/(with-header)/mypage/dashboard/page.tsx @@ -129,7 +129,8 @@ export default function MyDashboardPage() { value={selectedActivityTitle} onChange={handleActivityChange} placeholder='체험을 선택하세요' - className='h-56' + className='h-56 min-w-0' + truncateText={true} /> diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 38e78fb5..3544bf2c 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -34,6 +34,7 @@ export default function Dropdown({ className, disabled = false, disableScroll = false, + truncateText = false, }: DropdownProps) { // 내부 상태 관리 const [internalValue, setInternalValue] = useState(''); @@ -129,6 +130,7 @@ export default function Dropdown({ 'bg-white text-lg font-normal', 'transition-all duration-200', 'focus:border-green-300 focus:outline-none', + 'overflow-hidden', disabled && 'cursor-not-allowed bg-gray-100 opacity-50', isOpen && !disabled && 'border-green-300', )} @@ -137,7 +139,11 @@ export default function Dropdown({ aria-label={placeholder} > {selectedValue || placeholder} diff --git a/src/types/dropdownTypes.ts b/src/types/dropdownTypes.ts index 4d71dc80..87de7c5e 100644 --- a/src/types/dropdownTypes.ts +++ b/src/types/dropdownTypes.ts @@ -22,4 +22,5 @@ export interface DropdownProps { className?: ClassValue; disabled?: boolean; disableScroll?: boolean; + truncateText?: boolean; } From 4025fd5ae7ca161c5d3ad8366f3685866953b3ed Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:15:18 +0900 Subject: [PATCH 02/17] =?UTF-8?q?refactor:=20Mypage=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=95=88=EC=A0=95=EC=84=B1=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(with-header)/mypage/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(with-header)/mypage/layout.tsx b/src/app/(with-header)/mypage/layout.tsx index 22a07dc6..6c44bfd5 100644 --- a/src/app/(with-header)/mypage/layout.tsx +++ b/src/app/(with-header)/mypage/layout.tsx @@ -37,7 +37,7 @@ export default function MyPageLayout({ {/* 메인 스켈레톤 */} -
+
@@ -66,7 +66,7 @@ export default function MyPageLayout({ {/* 우측 메인 콘텐츠 섹션 */} -
{children}
+
{children}
From f2e5d7da6603f12f89ca6b0f74fb79fc0fe35ad0 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:17:10 +0900 Subject: [PATCH 03/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=83=81=ED=83=9C=EB=B3=84=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A7=8C=EB=A3=8C=EC=83=81=ED=83=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservations/components/ReservationCard.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx b/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx index a5482e91..85d3976a 100644 --- a/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx +++ b/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx @@ -36,9 +36,11 @@ export default function ReservationCard({ } = reservation; const isCompleted = isExperienceCompleted(date, endTime); - const showCancelButton = status === 'pending'; - const showReviewButton = isCompleted && !reviewSubmitted; - const showReviewCompleted = isCompleted && reviewSubmitted; + + const showCancelButton = status === 'pending' && !isCompleted; + const showReviewButton = status === 'completed' && !reviewSubmitted; + const showReviewCompleted = status === 'completed' && reviewSubmitted; + const showExpiredStatus = status === 'pending' && isCompleted; return (
@@ -112,6 +114,11 @@ export default function ReservationCard({ 후기 완료
)} + {showExpiredStatus && ( +
+ 예약 만료 +
+ )} From 7b0223f45ea997e080963a8260e872fa4b3f00d3 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:17:51 +0900 Subject: [PATCH 04/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EC=9E=91=EC=84=B1=20=EB=AA=A8=EB=8B=AC=20=EB=B0=98?= =?UTF-8?q?=EC=9D=91=ED=98=95=20=EB=B0=8F=20=EC=98=A4=EB=B2=84=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/reservations/components/ReviewModal.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx b/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx index 0dd352e0..e3380f3b 100644 --- a/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx +++ b/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx @@ -55,8 +55,12 @@ export default function ReviewModal({ return ( !open && handleClose()}> - -
+ +
{/* 헤더 */}

후기 작성

@@ -89,13 +93,13 @@ export default function ReviewModal({ {/* 체험 정보 */}
-

+

{activityTitle}

-
+
{activityDate} · {activityTime} · {headCount}명
-
+
₩{totalPrice?.toLocaleString()}
From 094a47c829e97be2de73d2e679c4f48ed48a626b Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:52:51 +0900 Subject: [PATCH 05/17] =?UTF-8?q?fix:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=ED=8E=98=E3=85=90=EC=9D=B4=EC=A7=80=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EB=B0=8F=20UX=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(with-header)/mypage/profile/page.tsx | 80 ++++++++++++++++--- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/src/app/(with-header)/mypage/profile/page.tsx b/src/app/(with-header)/mypage/profile/page.tsx index 3f1bab5a..09ebcf9b 100644 --- a/src/app/(with-header)/mypage/profile/page.tsx +++ b/src/app/(with-header)/mypage/profile/page.tsx @@ -5,6 +5,7 @@ import Input from '@/components/Input'; import Button from '@/components/Button'; import useMyPageStore from '@/stores/MyPage/useMyPageStore'; import { + validateNickname, validatePassword, validatePasswordConfirmation, } from '@/utils/validateInput'; @@ -26,10 +27,27 @@ export default function ProfilePage() { // 에러 상태 추가 const [errors, setErrors] = useState({ + nickname: '', newPassword: '', confirmPassword: '', }); + // API 요청 완료 후 비밀번호 필드 초기화 + useEffect(() => { + if (updateProfileMutation.isSuccess || updateProfileMutation.isError) { + setFormData((prev) => ({ + ...prev, + newPassword: '', + confirmPassword: '', + })); + setErrors((prev) => ({ + ...prev, + newPassword: '', + confirmPassword: '', + })); + } + }, [updateProfileMutation.isSuccess, updateProfileMutation.isError]); + // user 데이터가 로드되면 폼 업데이트 useEffect(() => { if (user) { @@ -51,28 +69,60 @@ export default function ProfilePage() { })); }; - // 비밀번호 유효성 검사 - const handlePasswordBlur = () => { + // 닉네임 유효성 검사 + const handleNicknameBlur = () => { setErrors((prev) => ({ ...prev, - newPassword: validatePassword(formData.newPassword), + nickname: validateNickname(formData.nickname), })); }; + // 비밀번호 유효성 검사 + const handlePasswordBlur = () => { + // 비밀번호를 입력한 경우에만 검증 + if (formData.newPassword) { + setErrors((prev) => ({ + ...prev, + newPassword: validatePassword(formData.newPassword), + })); + } + }; + // 비밀번호 확인 유효성 검사 const handleConfirmPasswordBlur = () => { - setErrors((prev) => ({ - ...prev, - confirmPassword: validatePasswordConfirmation( - formData.confirmPassword, - formData.newPassword, - ), - })); + // 비밀번호나 비밀번호 확인을 입력한 경우에만 검증 + if (formData.newPassword || formData.confirmPassword) { + setErrors((prev) => ({ + ...prev, + confirmPassword: validatePasswordConfirmation( + formData.confirmPassword, + formData.newPassword, + ), + })); + } + }; + + // 저장 버튼 비활성화 + const isButtonDisabled = () => { + // API 요청 중이면 비활성화 + if (updateProfileMutation.isPending) return true; + + // 닉네임 에러가 있으면 비활성화 + if (errors.nickname) return true; + + // 비밀번호를 입력했는데 에러가 있으면 비활성화 + if (formData.newPassword && errors.newPassword) return true; + + // 비밀번호 확인 에러가 있으면 비활성화 + if (formData.newPassword && errors.confirmPassword) return true; + + return false; }; // 저장 핸들러 const handleSave = () => { // 저장 전 최종 유효성 검사 + const nicknameError = validateNickname(formData.nickname); const passwordError = formData.newPassword ? validatePassword(formData.newPassword) : ''; @@ -83,8 +133,9 @@ export default function ProfilePage() { ) : ''; - if (passwordError || confirmPasswordError) { + if (nicknameError || passwordError || confirmPasswordError) { setErrors({ + nickname: nicknameError, newPassword: passwordError, confirmPassword: confirmPasswordError, }); @@ -92,7 +143,7 @@ export default function ProfilePage() { } const updateData: UpdateProfileRequest = { - nickname: formData.nickname, + nickname: formData.nickname.trim(), }; // 비밀번호가 입력된 경우에만 포함 @@ -114,8 +165,9 @@ export default function ProfilePage() { variant='primary' className='text-md h-56 w-100 rounded md:w-120 md:text-lg' onClick={handleSave} + disabled={isButtonDisabled()} > - 저장하기 + {updateProfileMutation.isPending ? '저장 중...' : '저장하기'}
@@ -130,6 +182,8 @@ export default function ProfilePage() { type='text' value={formData.nickname} onChange={handleInputChange('nickname')} + onBlur={handleNicknameBlur} + error={errors.nickname} placeholder='닉네임을 입력해주세요' />
From 214a21d8ec71de38312692bc3feb561a8805d8b1 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 01:53:17 +0900 Subject: [PATCH 06/17] =?UTF-8?q?refactor:=20=EB=82=B4=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20alert=20->=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMyPageQueries.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hooks/useMyPageQueries.ts b/src/hooks/useMyPageQueries.ts index 45ce17a0..c66f99af 100644 --- a/src/hooks/useMyPageQueries.ts +++ b/src/hooks/useMyPageQueries.ts @@ -10,6 +10,7 @@ import { UpdateProfileRequest } from '@/types/mypageTypes'; import useMyPageStore from '@/stores/MyPage/useMyPageStore'; import { useEffect } from 'react'; import useUserStore from '@/stores/authStore'; +import { toast } from 'sonner'; export const QUERY_KEYS = { PROFILE: ['mypage', 'profile'] as const, @@ -65,13 +66,13 @@ export const useUpdateProfile = () => { setLoading(false); // 캐시 업데이트 queryClient.setQueryData(QUERY_KEYS.PROFILE, mutation.data); - alert('프로필이 성공적으로 업데이트되었습니다!'); + toast.success('프로필이 성공적으로 업데이트되었습니다!'); } if (mutation.isError) { setError(mutation.error?.message || '프로필 업데이트에 실패했습니다.'); setLoading(false); - alert(`프로필 업데이트 실패: ${mutation.error?.message}`); + toast.error(`프로필 업데이트 실패: ${mutation.error?.message}`); } }, [ mutation.isPending, @@ -132,13 +133,13 @@ export const useUploadProfileImage = () => { queryClient.setQueryData(QUERY_KEYS.PROFILE, updatedUser); setLoading(false); - alert('프로필 이미지가 성공적으로 업로드되었습니다!'); + toast.success('프로필 이미지가 성공적으로 업로드되었습니다!'); } if (mutation.isError) { setError(mutation.error?.message || '이미지 업로드에 실패했습니다.'); setLoading(false); - alert(`이미지 업로드 실패: ${mutation.error?.message}`); + toast.error(`이미지 업로드 실패: ${mutation.error?.message}`); } }, [ mutation.isPending, From 7807799720596b92ca8e42c405188ede2bb43e1d Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 03:08:58 +0900 Subject: [PATCH 07/17] =?UTF-8?q?refactor:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80(=EC=A0=95=EB=B3=B4,=20=EB=82=B4=EC=97=AD,=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC,=20=ED=98=84=ED=99=A9)=20=EC=8A=A4=EC=BC=88?= =?UTF-8?q?=EB=A0=88=ED=86=A4=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-header)/mypage/activities/page.tsx | 60 +++++++++++++- .../(with-header)/mypage/dashboard/page.tsx | 3 +- src/app/(with-header)/mypage/layout.tsx | 27 ++---- src/app/(with-header)/mypage/profile/page.tsx | 82 +++++++++++++++++-- .../mypage/reservations/page.tsx | 54 +++++++++++- 5 files changed, 189 insertions(+), 37 deletions(-) diff --git a/src/app/(with-header)/mypage/activities/page.tsx b/src/app/(with-header)/mypage/activities/page.tsx index c979ad3d..a3550990 100644 --- a/src/app/(with-header)/mypage/activities/page.tsx +++ b/src/app/(with-header)/mypage/activities/page.tsx @@ -89,8 +89,35 @@ export default function MyActivitiesPage() { {[1, 2, 3].map((i) => (
+ 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' + > + {/* 이미지 영역 스켈레톤 */} +
+ + {/* 콘텐츠 영역 스켈레톤 */} +
+ {/* 별점 및 리뷰 스켈레톤 */} +
+
+
+
+
+
+
+ + {/* 제목 스켈레톤 */} +
+ + {/* 가격 + 더보기 버튼 영역 스켈레톤 */} +
+ {/* 가격 스켈레톤 */} +
+ + {/* 더보기 버튼 스켈레톤 */} +
+
+
+
))}
@@ -157,7 +184,34 @@ export default function MyActivitiesPage() { {/* 무한 스크롤 로딩 */} {isFetchingNextPage && ( -
+
+ {/* 이미지 영역 스켈레톤 */} +
+ + {/* 콘텐츠 영역 스켈레톤 */} +
+ {/* 별점 및 리뷰 스켈레톤 */} +
+
+
+
+
+
+
+ + {/* 제목 스켈레톤 */} +
+ + {/* 가격 + 더보기 버튼 영역 스켈레톤 */} +
+ {/* 가격 스켈레톤 */} +
+ + {/* 더보기 버튼 스켈레톤 */} +
+
+
+
)}
)} diff --git a/src/app/(with-header)/mypage/dashboard/page.tsx b/src/app/(with-header)/mypage/dashboard/page.tsx index ac387aff..7ed040e6 100644 --- a/src/app/(with-header)/mypage/dashboard/page.tsx +++ b/src/app/(with-header)/mypage/dashboard/page.tsx @@ -9,6 +9,7 @@ import { useActivityOptions } from '@/hooks/useActivityOptions'; import EmptyDashboard from './components/EmptyDashboard'; import ReservationDashboardCalendar from './components/ReservationDashboardCalendar'; import ReservationInfoModal from './components/ReservationInfoModal'; +import CalendarSkeleton from './components/CalendarSkeleton'; export default function MyDashboardPage() { const [selectedActivityId, setSelectedActivityId] = useState( @@ -76,7 +77,7 @@ export default function MyDashboardPage() {
{/* 달력 스켈레톤 */} -
+
); } diff --git a/src/app/(with-header)/mypage/layout.tsx b/src/app/(with-header)/mypage/layout.tsx index 6c44bfd5..7013c353 100644 --- a/src/app/(with-header)/mypage/layout.tsx +++ b/src/app/(with-header)/mypage/layout.tsx @@ -2,7 +2,6 @@ import { ProfileNavigation } from './components'; import useResponsiveRouting from '@/hooks/useResponsiveRouting'; -import { useMyProfile } from '@/hooks/useMyPageQueries'; export default function MyPageLayout({ children, @@ -10,10 +9,9 @@ export default function MyPageLayout({ children: React.ReactNode; }) { const { mounted } = useResponsiveRouting(); - const { isLoading, error } = useMyProfile(); - // mounted + API 로딩 상태 모두 체크 - if (!mounted || isLoading) { + // mounted 상태만 체크 + if (!mounted) { return (
@@ -36,28 +34,15 @@ export default function MyPageLayout({
- {/* 메인 스켈레톤 */} -
+ {/* 메인 영역 */} +
); } - if (error) { - return ( -
-
-

- 로그인이 필요합니다 -

-

다시 로그인해주세요.

-
-
- ); - } - - // API 로딩 완료 + mounted 상태일 때만 실행 + // mounted 상태일 때만 실행 return (
@@ -66,7 +51,7 @@ export default function MyPageLayout({ {/* 우측 메인 콘텐츠 섹션 */} -
{children}
+
{children}
diff --git a/src/app/(with-header)/mypage/profile/page.tsx b/src/app/(with-header)/mypage/profile/page.tsx index 09ebcf9b..fb46eeec 100644 --- a/src/app/(with-header)/mypage/profile/page.tsx +++ b/src/app/(with-header)/mypage/profile/page.tsx @@ -9,11 +9,12 @@ import { validatePassword, validatePasswordConfirmation, } from '@/utils/validateInput'; -import { useUpdateProfile } from '@/hooks/useMyPageQueries'; +import { useUpdateProfile, useMyProfile } from '@/hooks/useMyPageQueries'; import { UpdateProfileRequest } from '@/types/mypageTypes'; export default function ProfilePage() { const { user } = useMyPageStore(); + const { isLoading, error } = useMyProfile(); const updateProfileMutation = useUpdateProfile(); @@ -154,13 +155,76 @@ export default function ProfilePage() { updateProfileMutation.mutate(updateData); }; + // 로딩 상태 처리 + if (isLoading) { + return ( +
+ {/* 제목과 저장하기 버튼 */} +
+
+
+
+ + {/* 폼 섹션 스켈레톤 */} +
+ {/* 닉네임 스켈레톤 */} +
+
+
+
+ + {/* 이메일 스켈레톤 */} +
+
+
+
+ + {/* 비밀번호 스켈레톤 */} +
+
+
+
+ + {/* 비밀번호 재입력 스켈레톤 */} +
+
+
+
+
+
+ ); + } + + // 에러 상태 + if (error) { + return ( + <> + {/* 제목과 저장하기 버튼 */} +
+

내 정보

+ +
+ + {/* 에러 메시지 */} +
+

사용자 정보를 불러오는데 실패했습니다.

+

{error.message}

+
+ + ); + } + return ( -
+
{/* 제목과 저장하기 버튼 */}
-

- 내 정보 -

+

내 정보

@@ -226,7 +250,31 @@ export default function MyReservationsPage() { {/* 무한 스크롤 로딩 */} {isFetchingNextPage && ( -
+
+ {/* 이미지 영역 스켈레톤 */} +
+ + {/* 콘텐츠 영역 스켈레톤 */} +
+ {/* 상태 라벨 스켈레톤 */} +
+ + {/* 제목 스켈레톤 */} +
+ + {/* 날짜 및 인원 정보 스켈레톤 */} +
+ + {/* 가격 + 버튼 영역 스켈레톤 */} +
+ {/* 가격 스켈레톤 */} +
+ + {/* 버튼 스켈레톤 */} +
+
+
+
)}
)} From 892b6e4dd3c87a5b95117290430ffaae5b0d2682 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 03:59:59 +0900 Subject: [PATCH 08/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=ED=83=AD=EB=B3=84=20=EC=8B=9C=EA=B0=84=EB=8C=80=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=20=EB=B0=8F=20=EC=8B=A4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8,=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ReservationInfoModal.tsx | 22 ++++++++++++++++--- src/hooks/useDashboardQueries.ts | 10 +++++---- src/utils/timeSlotUtils.ts | 20 ++++++++++++++++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx b/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx index 8d8465ce..9f913f04 100644 --- a/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx +++ b/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx @@ -1,6 +1,7 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import { useReservedSchedules, useActivityReservations, @@ -9,7 +10,7 @@ import { import { DashboardFilterOption } from '@/types/dashboardTypes'; import { DASHBOARD_TAB_OPTIONS } from '@/constants/dashboardConstants'; import { - createTimeSlotOptions, + createFilteredTimeSlotOptions, getScheduleIdFromTimeSlot, getSelectedTimeSlotValue, } from '@/utils/timeSlotUtils'; @@ -50,13 +51,28 @@ export default function ReservationInfoModal({ // 예약 상태 업데이트 const updateReservationMutation = useUpdateActivityReservationStatus(); + const queryClient = useQueryClient(); - const timeSlotOptions = createTimeSlotOptions(schedules); + const timeSlotOptions = createFilteredTimeSlotOptions(schedules, activeTab); const selectedTimeSlotValue = getSelectedTimeSlotValue( schedules, selectedScheduleId, ); + // 탭 변경 시 현재 선택된 시간대가 유효한지 확인하고 초기화 + useEffect(() => { + if (selectedScheduleId && schedules) { + const currentSchedule = schedules.find( + (schedule) => schedule.scheduleId === selectedScheduleId, + ); + + // 현재 선택된 시간대가 새 탭에서 유효하지 않으면 초기화 + if (!currentSchedule || currentSchedule.count[activeTab] === 0) { + setSelectedScheduleId(null); + } + } + }, [activeTab, schedules, selectedScheduleId]); + // 예약 승인/거절 처리 const handleReservationAction = async ( reservationId: number, diff --git a/src/hooks/useDashboardQueries.ts b/src/hooks/useDashboardQueries.ts index fcb3532d..943f54a8 100644 --- a/src/hooks/useDashboardQueries.ts +++ b/src/hooks/useDashboardQueries.ts @@ -6,6 +6,7 @@ import { useQueryClient, useInfiniteQuery, } from '@tanstack/react-query'; +import { toast } from 'sonner'; import { getMyActivities, getMonthlyReservationDashboard, @@ -127,17 +128,17 @@ export const useUpdateActivityReservationStatus = () => { ], }); - // 성공 메시지 + // 성공 토스트 메시지 const statusText = { confirmed: '승인', declined: '거절', pending: '대기', }; - alert(`예약이 ${statusText[variables.data.status]}되었습니다.`); + toast.success(`예약이 ${statusText[variables.data.status]}되었습니다.`); }, onError: (error) => { - alert(`예약 상태 변경 실패: ${error.message}`); + toast.error(`예약 상태 변경 실패: ${error.message}`); }, }); }; @@ -182,10 +183,11 @@ export const useDeclineMultipleReservations = () => { variables.activityId, ], }); + toast.success('선택한 예약이 거절되었습니다.'); }, onError: (error) => { - alert(`예약 거절 실패: ${error.message}`); + toast.error(`예약 거절 실패: ${error.message}`); }, }); }; diff --git a/src/utils/timeSlotUtils.ts b/src/utils/timeSlotUtils.ts index eb657d71..af90425f 100644 --- a/src/utils/timeSlotUtils.ts +++ b/src/utils/timeSlotUtils.ts @@ -1,4 +1,7 @@ -import { ReservedSchedule } from '@/types/dashboardTypes'; +import { + ReservedSchedule, + DashboardFilterOption, +} from '@/types/dashboardTypes'; export const createTimeSlotOptions = (schedules: ReservedSchedule[] = []) => { return schedules.map( @@ -6,6 +9,21 @@ export const createTimeSlotOptions = (schedules: ReservedSchedule[] = []) => { ); }; +// 탭별로 필터링된 시간대 옵션 생성 +export const createFilteredTimeSlotOptions = ( + schedules: ReservedSchedule[] = [], + activeTab: DashboardFilterOption, +) => { + const filteredSchedules = schedules.filter((schedule) => { + // 해당 탭의 상태에 예약이 있는 시간대만 필터링 + return schedule.count[activeTab] > 0; + }); + + return filteredSchedules.map( + (schedule) => `${schedule.startTime} - ${schedule.endTime}`, + ); +}; + export const getScheduleIdFromTimeSlot = ( timeSlot: string, schedules: ReservedSchedule[] = [], From 6ac046373619225a8b016349fcc33cb550c7def3 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 05:35:45 +0900 Subject: [PATCH 09/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=EC=BC=88?= =?UTF-8?q?=EB=A0=88=ED=86=A4UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/reservations/page.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/(with-header)/mypage/reservations/page.tsx b/src/app/(with-header)/mypage/reservations/page.tsx index f6ebff83..4bd4a364 100644 --- a/src/app/(with-header)/mypage/reservations/page.tsx +++ b/src/app/(with-header)/mypage/reservations/page.tsx @@ -167,29 +167,29 @@ export default function MyReservationsPage() { {[1, 2, 3].map((i) => (
{/* 이미지 영역 스켈레톤 */} -
+
{/* 콘텐츠 영역 스켈레톤 */}
{/* 상태 라벨 스켈레톤 */} -
+
{/* 제목 스켈레톤 */} -
+
{/* 날짜 및 인원 정보 스켈레톤 */} -
+
{/* 가격 + 버튼 영역 스켈레톤 */}
{/* 가격 스켈레톤 */} -
+
{/* 버튼 스켈레톤 */} -
+
@@ -250,28 +250,28 @@ export default function MyReservationsPage() { {/* 무한 스크롤 로딩 */} {isFetchingNextPage && ( -
+
{/* 이미지 영역 스켈레톤 */} -
+
{/* 콘텐츠 영역 스켈레톤 */}
{/* 상태 라벨 스켈레톤 */} -
+
{/* 제목 스켈레톤 */} -
+
{/* 날짜 및 인원 정보 스켈레톤 */} -
+
{/* 가격 + 버튼 영역 스켈레톤 */}
{/* 가격 스켈레톤 */} -
+
{/* 버튼 스켈레톤 */} -
+
From 46594eb1d6983532c5d10aad88902c77b2484619 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 05:52:47 +0900 Subject: [PATCH 10/17] =?UTF-8?q?fix:=20=EC=B2=B4=ED=97=98=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=20=EC=BF=BC=EB=A6=AC=20=EB=AC=B4=ED=9A=A8?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMyActivitiesQueries.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hooks/useMyActivitiesQueries.ts b/src/hooks/useMyActivitiesQueries.ts index 99037dda..59d7ae69 100644 --- a/src/hooks/useMyActivitiesQueries.ts +++ b/src/hooks/useMyActivitiesQueries.ts @@ -9,6 +9,7 @@ import { getMyActivitiesWithPagination, deleteMyActivity, } from '@/apis/myActivities'; +import { toast } from 'sonner'; export const MY_ACTIVITIES_QUERY_KEYS = { ALL: ['my-activities'] as const, @@ -40,10 +41,20 @@ export const useDeleteMyActivity = () => { queryClient.invalidateQueries({ queryKey: MY_ACTIVITIES_QUERY_KEYS.ALL, }); - alert('체험이 삭제되었습니다.'); + + // 홈페이지 체험 리스트 쿼리들 무효화 + queryClient.invalidateQueries({ + queryKey: ['popularExperiences'], + }); + queryClient.invalidateQueries({ + queryKey: ['experiences'], + exact: false, + }); + + toast.success('체험이 삭제되었습니다.'); }, onError: (error) => { - alert(`체험 삭제 실패: ${error.message}`); + toast.error(`체험 삭제 실패: ${error.message}`); }, }); }; From 3a20ffb0751a5a9ad9d055a7debea4a1b6261366 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 06:17:50 +0900 Subject: [PATCH 11/17] =?UTF-8?q?refactor:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B4=80=EB=A0=A8=20alert->=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[id]/hooks/useDeleteActivity.ts | 13 +++++---- .../components/ReservationInfoModal.tsx | 3 +- .../reservations/components/ReviewModal.tsx | 7 +++-- .../mypage/reservations/page.tsx | 5 +--- src/hooks/useProfileImageUpload.ts | 5 ++-- src/hooks/useReservationQueries.ts | 29 +++++++++++++++---- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts b/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts index 3c293585..7d6dfff4 100644 --- a/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts +++ b/src/app/(with-header)/activities/[id]/hooks/useDeleteActivity.ts @@ -2,6 +2,7 @@ import { privateInstance } from '@/apis/privateInstance'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; const deleteActivity = async (id: string) => { const response = await privateInstance.delete(`/deleteActivity/${id}`); @@ -15,9 +16,12 @@ export const useDeleteActivity = () => { return useMutation({ mutationFn: deleteActivity, onSuccess: (_data) => { - queryClient.invalidateQueries({ queryKey: ['activity'] }); // 내 체험 관리 - queryClient.invalidateQueries({ queryKey: ['experiences'], exact: false }); // 모든 체험 리스트 - queryClient.invalidateQueries({ queryKey: ['popularExperiences'] }); // 인기 체험 + queryClient.invalidateQueries({ queryKey: ['activity'] }); + queryClient.invalidateQueries({ + queryKey: ['experiences'], + exact: false, + }); + queryClient.invalidateQueries({ queryKey: ['popularExperiences'] }); router.push(`/`); }, onError: (error: AxiosError) => { @@ -27,8 +31,7 @@ export const useDeleteActivity = () => { console.error('전체 에러:', error); - alert( - //토스트로 대체 + toast.error( responseData?.error || responseData?.message || error.message || diff --git a/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx b/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx index 9f913f04..60f82ece 100644 --- a/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx +++ b/src/app/(with-header)/mypage/dashboard/components/ReservationInfoModal.tsx @@ -18,6 +18,7 @@ import Dropdown from '@/components/Dropdown'; import ReservationActionButtons from './ReservationActionButtons'; import dayjs from 'dayjs'; import CloseIcon from '@assets/svg/close'; +import { toast } from 'sonner'; interface Props { isOpen: boolean; @@ -85,7 +86,7 @@ export default function ReservationInfoModal({ data: { status }, }); } catch { - alert('예약 처리에 실패했습니다.'); + toast.error('예약 처리에 실패했습니다.'); } }; diff --git a/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx b/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx index e3380f3b..5765363d 100644 --- a/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx +++ b/src/app/(with-header)/mypage/reservations/components/ReviewModal.tsx @@ -6,6 +6,7 @@ import Modal from '@/components/Modal'; import Button from '@/components/Button'; import Rating from '@/components/Rating'; import Close from '@/../public/assets/svg/close'; +import { toast } from 'sonner'; interface ReviewModalProps { isOpen: boolean; @@ -37,11 +38,11 @@ export default function ReviewModal({ const handleSubmit = () => { if (rating === 0) { - alert('별점을 선택해주세요.'); + toast.error('별점을 선택해주세요.'); return; } if (content.trim() === '') { - alert('후기를 작성해주세요.'); + toast.error('후기를 작성해주세요.'); return; } onConfirm(rating, content); @@ -58,7 +59,7 @@ export default function ReviewModal({
{/* 헤더 */} diff --git a/src/app/(with-header)/mypage/reservations/page.tsx b/src/app/(with-header)/mypage/reservations/page.tsx index 4bd4a364..de8c1c24 100644 --- a/src/app/(with-header)/mypage/reservations/page.tsx +++ b/src/app/(with-header)/mypage/reservations/page.tsx @@ -115,7 +115,6 @@ export default function MyReservationsPage() { }, { onSuccess: () => { - alert('후기가 작성되었습니다.'); setReviewModal({ isOpen: false, reservationId: null, @@ -127,9 +126,7 @@ export default function MyReservationsPage() { totalPrice: null, }); }, - onError: (error) => { - alert(`후기 작성 실패: ${error.message}`); - }, + onError: (error) => {}, }, ); } diff --git a/src/hooks/useProfileImageUpload.ts b/src/hooks/useProfileImageUpload.ts index 44c083ee..d8ba610e 100644 --- a/src/hooks/useProfileImageUpload.ts +++ b/src/hooks/useProfileImageUpload.ts @@ -1,5 +1,6 @@ import { useRef } from 'react'; import { useUploadProfileImage } from './useMyPageQueries'; +import { toast } from 'sonner'; /** * 프로필 이미지 업로드를 위한 커스텀 훅 @@ -25,13 +26,13 @@ export const useProfileImageUpload = () => { if (file) { // 파일 타입 검증 if (!file.type.startsWith('image/')) { - alert('이미지 파일만 업로드 가능합니다.'); + toast.error('이미지 파일만 업로드 가능합니다.'); return; } // 파일 크기 검증 if (file.size > 5 * 1024 * 1024) { - alert('파일 크기는 5MB 이하여야 합니다.'); + toast.error('파일 크기는 5MB 이하여야 합니다.'); return; } diff --git a/src/hooks/useReservationQueries.ts b/src/hooks/useReservationQueries.ts index bbb758ed..55d4e49d 100644 --- a/src/hooks/useReservationQueries.ts +++ b/src/hooks/useReservationQueries.ts @@ -5,8 +5,16 @@ import { useQueryClient, useInfiniteQuery, } from '@tanstack/react-query'; -import { getMyReservations, updateMyReservation, createReview } from '@/apis/reservations'; -import { ReservationStatus, CreateReviewRequest } from '@/types/reservationTypes'; +import { + getMyReservations, + updateMyReservation, + createReview, +} from '@/apis/reservations'; +import { + ReservationStatus, + CreateReviewRequest, +} from '@/types/reservationTypes'; +import { toast } from 'sonner'; export const RESERVATION_QUERY_KEYS = { RESERVATIONS: ['reservations'] as const, @@ -45,10 +53,10 @@ export const useCancelReservation = () => { queryClient.invalidateQueries({ queryKey: RESERVATION_QUERY_KEYS.RESERVATIONS, }); - alert('예약이 취소되었습니다.'); + toast.success('예약이 취소되었습니다.'); }, onError: (error) => { - alert(`예약 취소 실패: ${error.message}`); + toast.error(`예약 취소 실패: ${error.message}`); }, }); }; @@ -58,13 +66,22 @@ export const useCreateReview = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ reservationId, data }: { reservationId: number; data: CreateReviewRequest }) => - createReview(reservationId, data), + mutationFn: ({ + reservationId, + data, + }: { + reservationId: number; + data: CreateReviewRequest; + }) => createReview(reservationId, data), onSuccess: () => { // 예약 리스트 캐시 무효화하여 reviewSubmitted 상태 업데이트 queryClient.invalidateQueries({ queryKey: RESERVATION_QUERY_KEYS.RESERVATIONS, }); + toast.success('후기가 작성되었습니다.'); + }, + onError: (error) => { + toast.error(`후기 작성 실패: ${error.message}`); }, }); }; From 806f4b218a171647f3996289bd759e57071dca70 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 06:48:37 +0900 Subject: [PATCH 12/17] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B2=B4=ED=97=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20=EC=BF=BC=EB=A6=AC=20=EB=AC=B4?= =?UTF-8?q?=ED=9A=A8=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(with-header)/mypage/dashboard/page.tsx | 18 ++--- src/hooks/useActivityOptions.ts | 79 ++++++++++++++----- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/app/(with-header)/mypage/dashboard/page.tsx b/src/app/(with-header)/mypage/dashboard/page.tsx index 7ed040e6..c3e3645b 100644 --- a/src/app/(with-header)/mypage/dashboard/page.tsx +++ b/src/app/(with-header)/mypage/dashboard/page.tsx @@ -20,7 +20,11 @@ export default function MyDashboardPage() { // 내 체험 리스트 조회 const { data: activitiesData, isLoading, error } = useMyActivities(); - const { activityOptions, uniqueTitles } = useActivityOptions(activitiesData); + const { activityOptions, uniqueTitles, handleActivityChange } = + useActivityOptions(activitiesData, (activityId) => { + setSelectedActivityId(activityId); + setSelectedDate(''); + }); // 페이지 로드 시 첫 번째 체험 자동 선택 useEffect(() => { @@ -34,18 +38,6 @@ export default function MyDashboardPage() { } }, [activitiesData, selectedActivityId]); - // 체험 선택 -> 제목으로 ID 찾기 - const handleActivityChange = (selectedTitle: string) => { - const selectedOption = activityOptions.find( - (option) => option.label === selectedTitle, - ); - - if (selectedOption) { - setSelectedActivityId(parseInt(selectedOption.value)); - setSelectedDate(''); - } - }; - // 현재 선택된 체험의 제목 찾기 const selectedActivityTitle = activityOptions.find( diff --git a/src/hooks/useActivityOptions.ts b/src/hooks/useActivityOptions.ts index ba17be5f..386e1f47 100644 --- a/src/hooks/useActivityOptions.ts +++ b/src/hooks/useActivityOptions.ts @@ -1,28 +1,71 @@ +'use client'; + +import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; import { MyActivitiesResponse } from '@/types/dashboardTypes'; -export function useActivityOptions(activitiesData?: MyActivitiesResponse) { - return useMemo(() => { - if (!activitiesData) return { activityOptions: [], uniqueTitles: [] }; - - const activityOptions = activitiesData.activities.map((activity) => { - const duplicateCount = activitiesData.activities.filter( - (a) => a.title === activity.title, - ).length; +interface ActivityOption { + value: string; + label: string; +} - const displayTitle = - duplicateCount > 1 - ? `${activity.title} (${activity.id})` - : activity.title; +export const useActivityOptions = ( + activitiesData: MyActivitiesResponse | undefined, + onActivityChange?: (activityId: number) => void, +) => { + const queryClient = useQueryClient(); + const { activityOptions, uniqueTitles } = useMemo(() => { + if (!activitiesData?.activities) { return { - value: activity.id.toString(), - label: displayTitle, + activityOptions: [] as ActivityOption[], + uniqueTitles: [] as string[], }; - }); + } + + const options: ActivityOption[] = activitiesData.activities.map( + (activity: { id: number; title: string }) => ({ + value: activity.id.toString(), + label: activity.title, + }), + ); - const uniqueTitles = activityOptions.map((option) => option.label); + const uniqueTitles = options.map((option) => option.label); - return { activityOptions, uniqueTitles }; + return { activityOptions: options, uniqueTitles }; }, [activitiesData]); -} + + // 체험 변경 시 쿼리 무효화 함수 + const handleActivityChange = (selectedTitle: string): void => { + const selectedOption = activityOptions.find( + (option: ActivityOption) => option.label === selectedTitle, + ); + + if (selectedOption) { + const activityId = parseInt(selectedOption.value); + + // 관련 쿼리들 무효화 + queryClient.invalidateQueries({ + queryKey: ['reservedSchedules'], + exact: false, + }); + queryClient.invalidateQueries({ + queryKey: ['activityReservations'], + exact: false, + }); + queryClient.invalidateQueries({ + queryKey: ['monthlyReservationDashboard'], + exact: false, + }); + + // 콜백 호출 + onActivityChange?.(activityId); + } + }; + + return { + activityOptions, + uniqueTitles, + handleActivityChange, + }; +}; From d55662c49b4dfb330f5b9c1fca34fef5d896edf2 Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 07:02:21 +0900 Subject: [PATCH 13/17] =?UTF-8?q?feat:=20=EB=82=B4=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=B2=B4?= =?UTF-8?q?=ED=97=98=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ReservationCard.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx b/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx index 85d3976a..09efa8f4 100644 --- a/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx +++ b/src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx @@ -1,6 +1,7 @@ 'use client'; import Image from 'next/image'; +import { useRouter } from 'next/navigation'; import Button from '@/components/Button'; import { Reservation } from '@/types/reservationTypes'; import { STATUS_LABELS, STATUS_COLORS } from '@/constants/reservationConstants'; @@ -23,6 +24,8 @@ export default function ReservationCard({ onCancel, onReview, }: ReservationCardProps) { + const router = useRouter(); + const { id, activity, @@ -42,8 +45,15 @@ export default function ReservationCard({ const showReviewCompleted = status === 'completed' && reviewSubmitted; const showExpiredStatus = status === 'pending' && isCompleted; + const handleCardClick = () => { + router.push(`/activities/${activity.id}`); + }; + return ( -
+
{/* 이미지 영역 */}
onCancel?.(id)} + onClick={(e) => { + e.stopPropagation(); + onCancel?.(id); + }} > 예약 취소 @@ -103,8 +116,11 @@ export default function ReservationCard({ {showReviewButton && ( From 9bfe31d51d2712a1e2d4237d25c88b2d101a8e9f Mon Sep 17 00:00:00 2001 From: LeeCh0129 Date: Mon, 4 Aug 2025 07:02:41 +0900 Subject: [PATCH 14/17] =?UTF-8?q?feat:=20=EB=82=B4=20=EC=B2=B4=ED=97=98=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=B9=B4=EB=93=9C=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=B2=B4=ED=97=98=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activities/components/ActivityCard.tsx | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/app/(with-header)/mypage/activities/components/ActivityCard.tsx b/src/app/(with-header)/mypage/activities/components/ActivityCard.tsx index 47b3e1af..aa64a42c 100644 --- a/src/app/(with-header)/mypage/activities/components/ActivityCard.tsx +++ b/src/app/(with-header)/mypage/activities/components/ActivityCard.tsx @@ -21,6 +21,10 @@ export default function ActivityCard({ const { id, title, price, bannerImageUrl, rating, reviewCount } = activity; + const handleCardClick = () => { + router.push(`/activities/${id}`); + }; + const handleEdit = () => { router.push(`/myactivity/${id}`); }; @@ -31,7 +35,10 @@ export default function ActivityCard({ }; return ( -
+
{/* 이미지 영역 */}
{title} @@ -68,7 +75,10 @@ export default function ActivityCard({ {/* 더보기 옵션 */}