diff --git a/src/app/(global)/(mypage)/myInfo/_components/myExperiences.tsx b/src/app/(global)/(mypage)/myInfo/_components/myExperiences.tsx index a605012..d69cb3a 100644 --- a/src/app/(global)/(mypage)/myInfo/_components/myExperiences.tsx +++ b/src/app/(global)/(mypage)/myInfo/_components/myExperiences.tsx @@ -3,16 +3,21 @@ import MyExperienceCard from '@/src/components/pages/myExperiences/MyExperienceCard'; import Button from '@/src/components/primitives/Button'; import LoadingSpinner from '@/src/components/primitives/LoadingSpinner'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import Image from 'next/image'; import BackIcon from '@/public/images/icons/BackIcon.svg'; -import { useContext } from 'react'; +import { useContext, useState } from 'react'; import { TabContext } from '../pageContext'; import { queries } from '@/src/services/primitives/queries'; +import ConfirmModal from '@/src/components/primitives/modal/ConfirmModal'; +import Link from 'next/link'; export default function MyExperiencesPage() { + const [isModalVisible, setIsModalVisible] = useState(false); + const [selectedExperience, setSelectedExperience] = useState(0); const { setIsTabOpen } = useContext(TabContext); const { data, isPending } = useQuery(queries.myExperiencesOptions()); + const mutation = useMutation(queries.myExperiencesMutationOptions()); return (
@@ -31,14 +36,21 @@ export default function MyExperiencesPage() {

- + + + {!isPending ? ( <> {data && data.totalCount !== 0 ? ( <> {data.activities.map((activity) => ( - + ))} ) : ( @@ -59,6 +71,15 @@ export default function MyExperiencesPage() { ) : ( )} + { + mutation.mutate(selectedExperience); + setIsModalVisible(false); + }} + onCancel={() => setIsModalVisible(false)} + isOpen={isModalVisible} + message='정말 체험을 삭제하시겠습니까?' + />
); } diff --git a/src/app/(global)/(mypage)/myInfo/page.tsx b/src/app/(global)/(mypage)/myInfo/page.tsx index 488f462..e831d7f 100644 --- a/src/app/(global)/(mypage)/myInfo/page.tsx +++ b/src/app/(global)/(mypage)/myInfo/page.tsx @@ -55,7 +55,7 @@ export default function MypageLayout() {
(); const router = useRouter(); @@ -119,6 +127,12 @@ export default function MyUpdateExperiencesPage() { const [bannerUrls, setBannerUrls] = useState([]); const [subUrls, setSubUrls] = useState([]); + // 서버에서 받아온 카테고리 상태 + const [categoryDefaultValue, setCategoryDefaultValue] = useState<{ + id: number; + title: string; + } | null>(null); + // 1️⃣ 기존 데이터 불러오기 useEffect(() => { const fetchData = async () => { @@ -136,6 +150,11 @@ export default function MyUpdateExperiencesPage() { address: detail.address, }); + // 카테고리 상태 세팅 + setCategoryDefaultValue( + dropdownItem.filter((el) => el.title === detail.category)[0] + ); + // 이미지 상태 세팅 setBannerUrls([detail.bannerImageUrl]); setSubUrls( @@ -197,6 +216,11 @@ export default function MyUpdateExperiencesPage() { console.log('📌 새로 업로드한 이미지 URL:', subImageUrlsToAdd); } + // 드롭다운 이름 파싱 + data.category = dropdownItem.find( + (el) => el.id === parseInt(data.category) + )!.title; + // 4️⃣ payload 구성 (기존 이미지는 API에서 자동 유지됨) const payload: UpdateExperiencePayload = { ...data, @@ -247,16 +271,10 @@ export default function MyUpdateExperiencesPage() { render={({ field, fieldState }) => ( )} diff --git a/src/components/pages/detail/ActivityFooter.tsx b/src/components/pages/detail/ActivityFooter.tsx index 306c136..5c2bd51 100644 --- a/src/components/pages/detail/ActivityFooter.tsx +++ b/src/components/pages/detail/ActivityFooter.tsx @@ -2,6 +2,8 @@ import { IActivity } from '@/src/types/scheduleType'; import Button from '../../primitives/Button'; import { format } from 'date-fns'; import { useReservationStore } from '@/src/store/ReservationStore'; +import { createReservation } from '@/src/services/pages/detail/postReservation'; +import { useToastStore } from '@/src/store/useToastStore'; export default function ActivityFooter({ activity, @@ -16,12 +18,28 @@ export default function ActivityFooter({ const timeInfo = activity.schedules.find( (schedule) => schedule.id === timeSelector.timeId ); + const createToast = useToastStore((state) => state.createToast); const handleReserveClick = () => { - console.log({ + createReservation(activity.id, { scheduleId: timeSelector.timeId!, headCount: personSelector.person, - }); + }) + .then(() => { + createToast({ + message: '예약이 완료되었습니다!', + type: 'success', + }); + }) + .catch((err) => { + createToast({ + message: + err.response.status === 409 + ? '이미 예약된 스케줄입니다!' + : '예약이 실패했습니다!', + type: 'failed', + }); + }); }; return ( diff --git a/src/components/pages/detail/ReservationUI.tsx b/src/components/pages/detail/ReservationUI.tsx index a5207e6..dbe5dc7 100644 --- a/src/components/pages/detail/ReservationUI.tsx +++ b/src/components/pages/detail/ReservationUI.tsx @@ -7,15 +7,32 @@ import Calendar from './DateSelector/Calendar'; import { useReservationStore } from '@/src/store/ReservationStore'; import TimeSelectorButtons from './DateSelector/TimeSelector'; import { createReservation } from '@/src/services/pages/detail/postReservation'; +import { useToastStore } from '@/src/store/useToastStore'; export default function ReservationUI({ activity }: { activity: IActivity }) { const { personSelector, timeSelector } = useReservationStore(); + const createToast = useToastStore((state) => state.createToast); const handleReserveClick = async () => { - const data = await createReservation(activity.id, { + createReservation(activity.id, { scheduleId: timeSelector.timeId!, headCount: personSelector.person, - }); + }) + .then(() => { + createToast({ + message: '예약이 완료되었습니다!', + type: 'success', + }); + }) + .catch((err) => { + createToast({ + message: + err.response.status === 409 + ? '이미 예약된 스케줄입니다!' + : '예약이 실패했습니다!', + type: 'failed', + }); + }); }; return ( diff --git a/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx b/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx index 9258f40..e374f23 100644 --- a/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx +++ b/src/components/pages/myCreateExperiences/AvailableTimeSlots.tsx @@ -1,8 +1,8 @@ 'use client'; +import TimepickerDropdown from './Dropdown'; import { useBreakPoint } from '@/src/hooks/useBreakPoint'; import CalendarInput from '../../primitives/input/CalendarInput'; -import TimepickerDropdown from './TimePickerDropdown'; import { useTimeSlotStore } from '@/src/store/TimeSlotStore'; import Image from 'next/image'; diff --git a/src/components/pages/myCreateExperiences/TimePickerDropdown.tsx b/src/components/pages/myCreateExperiences/Dropdown.tsx similarity index 100% rename from src/components/pages/myCreateExperiences/TimePickerDropdown.tsx rename to src/components/pages/myCreateExperiences/Dropdown.tsx diff --git a/src/components/pages/myExperiences/MyExperienceCard.tsx b/src/components/pages/myExperiences/MyExperienceCard.tsx index b75e9ef..00d4cb2 100644 --- a/src/components/pages/myExperiences/MyExperienceCard.tsx +++ b/src/components/pages/myExperiences/MyExperienceCard.tsx @@ -3,10 +3,17 @@ import { IActivity } from '@/src/types/scheduleType'; import StarIcon from '@/public/images/icons/StarFilled.svg'; import ExperienceButton from './ExperienceButton'; +import Link from 'next/link'; -export default function MyExperienceCard({ data }: { data: IActivity }) { - console.log(data); - // TODO: 카드 클릭하면 체험 관리 페이지로 가게 +export default function MyExperienceCard({ + data, + callbackId, + setIsModalVisible, +}: { + data: IActivity; + callbackId: (id: number) => void; + setIsModalVisible: (state: boolean) => void; +}) { return (
@@ -24,10 +31,20 @@ export default function MyExperienceCard({ data }: { data: IActivity }) { ₩ {data.price.toLocaleString()} / 인
- - 수정하기 - - + + + 수정하기 + + + { + callbackId(data.id); + setIsModalVisible(true); + }} + > 삭제하기
diff --git a/src/components/primitives/Dropdown.tsx b/src/components/primitives/Dropdown.tsx index cb6f5b0..861d149 100644 --- a/src/components/primitives/Dropdown.tsx +++ b/src/components/primitives/Dropdown.tsx @@ -15,6 +15,7 @@ interface DropdownProps { value: string | null; onChange: (id: number) => void; error?: string; + defaultValue?: DropdownItem; } export default function Dropdown({ @@ -22,9 +23,12 @@ export default function Dropdown({ items, value, onChange, + defaultValue, }: DropdownProps) { const [isOpen, setIsOpen] = useState(false); - const [selectedItem, setSelectedItem] = useState(null); + const [selectedItem, setSelectedItem] = useState( + defaultValue ?? null + ); const [contentUp, setContentUp] = useState(false); const dropdownRef = useRef(null); const contentRef = useRef(null); @@ -50,6 +54,7 @@ export default function Dropdown({ }, [isOpen]); useEffect(() => { + if (defaultValue) setSelectedItem(defaultValue); function handleClickOutside(event: MouseEvent) { if ( dropdownRef.current && @@ -62,6 +67,10 @@ export default function Dropdown({ document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); + + useEffect(() => { + if (defaultValue) setSelectedItem(defaultValue); + }, [defaultValue]); return (
{label}