diff --git a/public/images/airballoon.png b/public/images/airballoon.png deleted file mode 100644 index 8f19d06..0000000 Binary files a/public/images/airballoon.png and /dev/null differ diff --git a/public/images/bridge.png b/public/images/bridge.png deleted file mode 100644 index 00c658b..0000000 Binary files a/public/images/bridge.png and /dev/null differ diff --git a/public/images/dance.png b/public/images/dance.png deleted file mode 100644 index 698e46b..0000000 Binary files a/public/images/dance.png and /dev/null differ diff --git a/public/images/default_profile_image.png b/public/images/default_profile_image.png deleted file mode 100644 index e95313c..0000000 Binary files a/public/images/default_profile_image.png and /dev/null differ diff --git a/public/images/fiord.png b/public/images/fiord.png deleted file mode 100644 index 6535366..0000000 Binary files a/public/images/fiord.png and /dev/null differ diff --git a/public/images/fish.png b/public/images/fish.png deleted file mode 100644 index c419681..0000000 Binary files a/public/images/fish.png and /dev/null differ diff --git a/public/images/forest.png b/public/images/forest.png deleted file mode 100644 index 2656c78..0000000 Binary files a/public/images/forest.png and /dev/null differ diff --git a/public/images/himalaya.png b/public/images/himalaya.png deleted file mode 100644 index c08e0f5..0000000 Binary files a/public/images/himalaya.png and /dev/null differ diff --git a/public/images/reed.png b/public/images/reed.png deleted file mode 100644 index ed5ac47..0000000 Binary files a/public/images/reed.png and /dev/null differ diff --git a/public/images/sunset.png b/public/images/sunset.png deleted file mode 100644 index ce2df42..0000000 Binary files a/public/images/sunset.png and /dev/null differ diff --git a/public/images/town.png b/public/images/town.png deleted file mode 100644 index 7904904..0000000 Binary files a/public/images/town.png and /dev/null differ diff --git a/public/images/vietnam.png b/public/images/vietnam.png deleted file mode 100644 index f316614..0000000 Binary files a/public/images/vietnam.png and /dev/null differ diff --git a/public/images/vr.png b/public/images/vr.png deleted file mode 100644 index e692dfb..0000000 Binary files a/public/images/vr.png and /dev/null differ diff --git a/src/app/experienceedit/[activityId]/page.css.ts b/src/app/edit-activity/[activityId]/page.css.ts similarity index 100% rename from src/app/experienceedit/[activityId]/page.css.ts rename to src/app/edit-activity/[activityId]/page.css.ts diff --git a/src/app/experienceedit/[activityId]/page.tsx b/src/app/edit-activity/[activityId]/page.tsx similarity index 100% rename from src/app/experienceedit/[activityId]/page.tsx rename to src/app/edit-activity/[activityId]/page.tsx diff --git a/src/app/experienceedit/components/categoryDropdown.css.ts b/src/app/edit-activity/components/categoryDropdown.css.ts similarity index 100% rename from src/app/experienceedit/components/categoryDropdown.css.ts rename to src/app/edit-activity/components/categoryDropdown.css.ts diff --git a/src/app/experienceedit/components/categoryDropdown.tsx b/src/app/edit-activity/components/categoryDropdown.tsx similarity index 100% rename from src/app/experienceedit/components/categoryDropdown.tsx rename to src/app/edit-activity/components/categoryDropdown.tsx diff --git a/src/app/experienceedit/page.css.ts b/src/app/experienceedit/page.css.ts deleted file mode 100644 index 163e910..0000000 --- a/src/app/experienceedit/page.css.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { theme } from '../global.css'; -import { mediaQueries } from '@/styles/media'; - -export const mainContainer = style({ - width: '1200px', - display: 'flex', - justifyContent: 'space-between', - margin: '30px auto', - backgroundColor: theme.colors.gray9, - - '@media': { - [mediaQueries.tablet]: { - width: '696px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const sideContainer = style({ - width: '792px', - height: '100%', - backgroundColor: theme.colors.gray9, - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const contentContainer = style({ - width: '100%', - height: '56px', - backgroundColor: theme.colors.white, - border: '1px solid #79747e', - borderRadius: '4px', - padding: '0px 10px', -}); - -export const descriptionContainer = style({ - width: '100%', - height: '346px', - backgroundColor: theme.colors.white, - border: '1px solid #79747e', - borderRadius: '4px', - padding: '20px 10px', - marginTop: '20px', -}); - -export const dateContainer = style({ - width: '379px', - height: '56px', - padding: '8px', - backgroundColor: theme.colors.white, - border: '1px solid #79747e', - borderRadius: '4px', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - position: 'relative', - - '@media': { - [mediaQueries.tablet]: { - width: '149px', - }, - [mediaQueries.mobile]: { - width: '130px', - }, - }, -}); - -export const reservationContainer = style({ - width: '792px', - height: '100%', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'flex-start', - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const addedDateWrapper = style({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginTop: '10px', -}); - -export const addedDateContainer = style({ - width: '379px', - height: '56px', - backgroundColor: theme.colors.white, - border: '1px solid #79747e', - borderRadius: '4px', - padding: '8px', - display: 'flex', - alignItems: 'center', - - '@media': { - [mediaQueries.tablet]: { - width: '149px', - }, - [mediaQueries.mobile]: { - width: '130px', - height: '44px', - }, - }, -}); - -export const calendarWrapper = style({ - border: `1px solid ${theme.colors.black}`, - padding: '10px', - position: 'absolute', - top: '56px', - backgroundColor: theme.colors.white, -}); - -export const line = style({ - marginTop: '15px', - width: '792px', - border: `1px solid ${theme.colors.gray7}`, - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const postSearchButton = style({ - position: 'absolute', - right: '20px', - top: '50%', - transform: 'translateY(-50%)', - cursor: 'pointer', - padding: '10px', - backgroundColor: '#ccc', -}); - -export const imageRegister = style({ - width: '180px', - height: '180px', - borderRadius: '12px', - border: `1px dashed ${theme.colors.gray1}`, - backgroundColor: theme.colors.gray9, - textAlign: 'center', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - cursor: 'pointer', - - '@media': { - [mediaQueries.tablet]: { - width: '206px', - height: '206px', - }, - [mediaQueries.mobile]: { - width: '167px', - height: '167px', - }, - }, -}); - -export const imagePreviewContainer = style({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', -}); - -export const bannerContainer = style({ - width: '792px', - display: 'flex', - alignItems: 'center', - gap: '24px', - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const introContainer = style({ - width: '792px', - display: 'flex', - alignItems: 'center', - gap: '24px', - flexWrap: 'wrap', - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - gap: '17px', - }, - [mediaQueries.mobile]: { - width: '343px', - gap: '8px', - }, - }, -}); - -export const images = style({ - width: '180px', - height: '180px', - borderRadius: '12px', - - '@media': { - [mediaQueries.tablet]: { - width: '206px', - height: '206px', - }, - [mediaQueries.mobile]: { - width: '167px', - height: '167px', - }, - }, -}); - -export const deleteButton = style({ - position: 'absolute', - top: '0px', - right: '0px', - width: '40px', - height: '40px', - cursor: 'pointer', - - '@media': { - [mediaQueries.tablet]: { - width: '32px', - height: '32px', - }, - [mediaQueries.mobile]: { - width: '24px', - height: '24px', - }, - }, -}); - -export const qqq = style({ - width: '792px', - height: '48px', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: '20px', - - '@media': { - [mediaQueries.tablet]: { - width: '429px', - }, - [mediaQueries.mobile]: { - width: '343px', - }, - }, -}); - -export const inputWithPlaceholder = style({ - fontSize: '14px', - color: '#000000', - resize: 'none', - - selectors: { - '&::placeholder': { - color: '#a1a1a1', - fontSize: '14px', - }, - }, -}); - -export const tildeSymbol = style({ - display: 'inline', - '@media': { - [mediaQueries.tablet]: { - display: 'none', - }, - [mediaQueries.mobile]: { - display: 'none', - }, - }, -}); - -export const addedStartTimeContainer = style({ - width: '140px', - height: '56px', - borderRadius: '4px', - border: `1px solid ${theme.colors.gray2}`, - backgroundColor: theme.colors.white, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '0 10px', - cursor: 'pointer', - - '@media': { - [mediaQueries.tablet]: { - width: '104px', - }, - [mediaQueries.mobile]: { - width: '79px', - }, - }, -}); - -export const addedEndTimeContainer = style({ - width: '140px', - height: '56px', - borderRadius: '4px', - border: `1px solid ${theme.colors.gray2}`, - backgroundColor: theme.colors.white, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: '0 10px', - cursor: 'pointer', - - '@media': { - [mediaQueries.tablet]: { - width: '104px', - }, - [mediaQueries.mobile]: { - width: '79px', - }, - }, -}); diff --git a/src/app/experienceedit/page.tsx b/src/app/experienceedit/page.tsx deleted file mode 100644 index ebaf508..0000000 --- a/src/app/experienceedit/page.tsx +++ /dev/null @@ -1,570 +0,0 @@ -'use client'; - -import React, { useEffect, useState } from 'react'; -import { useSearchParams } from 'next/navigation'; -import { instance } from '@/app/api/instance'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import Image from 'next/image'; -import SideNavigationMenu from '../../components/SideNavigationMenu'; -import CategoryDropDown from './components/categoryDropdown'; -import StartTimeDropDown from '../experienceregister/StartTimeDropDown'; -import EndTimeDropDown from '../experienceregister/EndTimeDropDown'; -import { DayPicker } from 'react-day-picker'; -import 'react-day-picker/dist/style.css'; -import DaumPostcode from 'react-daum-postcode'; -import CustomButton from '../../components/CustomButton'; -import * as S from './page.css'; -import Xbotton from '../../../public/icons/xbutton.svg'; - -/* ------------------ 타입 정의 ------------------ */ -interface Schedule { - date: string; - startTime: string; - endTime: string; -} - -interface ActivityResParams { - id: number; - userId: number; - title: string; - description: string; - category: string; - price: number; - address: string; - bannerImageUrl: string; - rating: number; - reviewCount: number; - createdAt: string; - updatedAt: string; - subImages: { - imageUrl: string; - id: number; - }[]; - schedules: { - startTime: string; - endTime: string; - date: string; - id: number; - }[]; -} - -interface UpdateMyActivityBodyData { - title: string; - category: string; - description: string; - price: number; - address: string; - bannerImageUrl: string; - subImageIdsToRemove: number[]; - subImageUrlsToAdd: string[]; - scheduleIdsToRemove: number[]; - schedulesToAdd: { - date: string; - startTime: string; - endTime: string; - }[]; -} - -// props에서 activityId를 받는 대신, 쿼리 파라미터를 직접 읽음 -export default function ExperienceEdit() { - // 1) searchParams 로부터 activityId를 읽어옴 - const searchParams = useSearchParams(); - const activityIdString = searchParams.get('activityId'); // "3612" 등 문자열 - const activityId = activityIdString - ? parseInt(activityIdString, 10) - : undefined; - - const queryClient = useQueryClient(); - - /* =============== 서버에서 기존 데이터 GET =============== */ - const { - data: activityDetail, - isLoading: isLoadingActivity, - isError: isActivityError, - } = useQuery({ - queryKey: ['activityDetail', activityId], - queryFn: async () => { - if (!activityId) { - throw new Error('activityId가 유효하지 않습니다.'); - } - const res = await instance.get(`/activities/${activityId}`); - return res.data as ActivityResParams; - }, - // activityId가 없으면 요청 X - enabled: !!activityId, - }); - - // 이하 기존 로직 동일 - // (dates, selectedDate, price, address, etc...) - - const [dates, setDates] = useState([]); - const [selectedDate, setSelectedDate] = useState(); - const [startTime, setStartTime] = useState('시간선택'); - const [endTime, setEndTime] = useState('시간선택'); - const [isCalendarVisible, setIsCalendarVisible] = useState(false); - - const [address, setAddress] = useState(''); - const [isPostcodeVisible, setIsPostcodeVisible] = useState(false); - const [price, setPrice] = useState(''); - const [priceError, setPriceError] = useState(null); - const [title, setTitle] = useState(''); - const [description, setDescription] = useState(''); - const [category, setCategory] = useState(''); - - const [bannerImageUrl, setBannerImageUrl] = useState(''); - const [introImageUrls, setIntroImageUrls] = useState([]); - - // 반응형 체크 - const [isMobile, setIsMobile] = useState(false); - useEffect(() => { - const handleResize = () => { - setIsMobile(window.innerWidth < 768); - }; - handleResize(); - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, []); - - /* =============== 서버 데이터 -> 로컬 상태 세팅 =============== */ - useEffect(() => { - if (activityDetail) { - setTitle(activityDetail.title); - setDescription(activityDetail.description); - setCategory(activityDetail.category); - setPrice(String(activityDetail.price)); - setAddress(activityDetail.address); - setBannerImageUrl(activityDetail.bannerImageUrl); - - const initialIntroUrls = activityDetail.subImages.map( - (img) => img.imageUrl - ); - setIntroImageUrls(initialIntroUrls); - - const initialSchedules: Schedule[] = activityDetail.schedules.map( - (s) => ({ - date: s.date, - startTime: s.startTime, - endTime: s.endTime, - }) - ); - setDates(initialSchedules); - } - }, [activityDetail]); - - // 주소 찾기 - interface DaumPostcodeCompleteData { - address: string; - zonecode: string; - } - const handlePostcodeClick = () => { - setIsPostcodeVisible(true); - }; - const handlePostcodeComplete = (data: DaumPostcodeCompleteData) => { - setAddress(data.address); - setIsPostcodeVisible(false); - }; - - // 일정 추가/삭제 - const handleAddDate = () => { - if (selectedDate && startTime !== '시간선택' && endTime !== '시간선택') { - const year = selectedDate.getFullYear(); - const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); - const day = String(selectedDate.getDate()).padStart(2, '0'); - const formattedDate = `${year}-${month}-${day}`; - - setDates((prev) => [ - ...prev, - { date: formattedDate, startTime, endTime }, - ]); - } - }; - const handleRemoveDate = (idxToRemove: number) => { - setDates((prev) => prev.filter((_, i) => i !== idxToRemove)); - }; - - // 가격 숫자만 - const handlePriceChange = (e: React.ChangeEvent) => { - const value = e.target.value; - if (/^\d*$/.test(value)) { - setPrice(value); - setPriceError(null); - } else { - setPriceError('가격은 숫자만 입력 가능합니다.'); - } - }; - - // 카테고리 선택 - const handleCategoryChange = (selectedCategory: string) => { - setCategory(selectedCategory); - }; - - // 제목, 설명 - const handleTitleChange = (e: React.ChangeEvent) => { - setTitle(e.target.value); - }; - const handleDescriptionChange = ( - e: React.ChangeEvent - ) => { - setDescription(e.target.value); - }; - - // 배너 이미지 업로드/삭제 - const handleBannerImageChange = async ( - e: React.ChangeEvent - ) => { - if (!e.target.files?.[0]) return; - - try { - // 1) 파일 준비 - const file = e.target.files[0]; - const formData = new FormData(); - formData.append('image', file); - - // 2) 서버에 업로드 (multipart/form-data) - const res = await instance.post('/activities/image', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - // 3) 업로드 성공 → 서버가 준 URL (activityImageUrl)을 배너에 반영 - const { activityImageUrl } = res.data; - setBannerImageUrl(activityImageUrl); - } catch (err) { - console.error(err); - alert('배너 이미지 업로드 실패'); - } - }; - - const handleDeleteBannerImage = () => { - setBannerImageUrl(''); - }; - - // 소개 이미지 - const handleIntroImageChange = async ( - e: React.ChangeEvent - ) => { - if (!e.target.files?.[0]) return; - - const file = e.target.files[0]; - const formData = new FormData(); - formData.append('image', file); - - try { - // 1) 업로드 - // /activities/image (multipart/form-data) - const res = await instance.post('/activities/image', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - // 2) 응답에서 실제 업로드된 이미지 URL 받기 - // 예: { "activityImageUrl": "https://.../some/path.jpg" } - const { activityImageUrl } = res.data; - - // 3) introImageUrls 배열에 추가 - setIntroImageUrls((prev) => [...prev, activityImageUrl]); - - // 필요하다면 업로드 성공 메시지 - // alert('이미지 업로드 성공'); - } catch (error) { - console.error(error); - alert('이미지 업로드 실패'); - } - }; - - const handleDeleteIntroImage = (idx: number) => { - setIntroImageUrls((prev) => prev.filter((_, i) => i !== idx)); - }; - - // PATCH 뮤테이션 - const patchMutation = useMutation({ - mutationFn: async (updateBody: UpdateMyActivityBodyData) => { - if (!activityId) throw new Error('activityId가 없음'); - await instance.patch(`/my-activities/${activityId}`, updateBody); - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['activityDetail', activityId], - }); - alert('수정이 완료되었습니다.'); - }, - onError: (err) => { - console.error(err); - alert('수정 실패. 다시 시도해주세요.'); - }, - }); - - // 업데이트 핸들러 - const handleUpdate = () => { - if (!activityDetail || !activityId) return; - - // 1) 원래 서버에 존재했던 subImages - // -> 지금 introImageUrls 배열에 없는 것 => 삭제 대상 - const subImageIdsToRemove = activityDetail.subImages - .filter((orig) => !introImageUrls.includes(orig.imageUrl)) - .map((orig) => orig.id); - - // 2) 새로 추가된 이미지 - // -> 기존 subImages 목록에는 없고, introImageUrls에는 있는 것 - const existingUrls = activityDetail.subImages.map((orig) => orig.imageUrl); - const subImageUrlsToAdd = introImageUrls.filter( - (url) => !existingUrls.includes(url) - ); - - // 3) 스케줄 변경도 동일한 로직 - const scheduleIdsToRemove = activityDetail.schedules.map((sch) => sch.id); - const schedulesToAdd = dates.map((d) => ({ - date: d.date, - startTime: d.startTime, - endTime: d.endTime, - })); - - const updateBody: UpdateMyActivityBodyData = { - title: title.trim(), - category: category.trim(), - description: description.trim(), - price: Number(price) || 0, - address: address.trim(), - bannerImageUrl, - subImageIdsToRemove, // 삭제할 ID 배열 - subImageUrlsToAdd, // 새로 추가할 이미지 URL 배열 - scheduleIdsToRemove, - schedulesToAdd, - }; - - patchMutation.mutate(updateBody); - }; - - // 로딩/에러 처리 - if (isLoadingActivity) return

로딩 중...

; - if (isActivityError) return

데이터를 불러오는 중 오류가 발생했습니다.

; - - // 렌더링 - return ( -
-
- {!isMobile && } -
-
-

내 체험 수정

- - 수정하기 - -
- - {/* 제목 입력 */} - - - {/* 카테고리 선택 */} - - - {/* 설명 입력 */} -