Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function EditActivityForm() {
dates,
isLoading,
isError,
editLoading,
setTitle,
setCategory,
setPrice,
Expand Down Expand Up @@ -58,6 +59,7 @@ export default function EditActivityForm() {
<Button
variant='primary'
type='submit'
isLoading={editLoading}
className='bg-nomad w-full rounded-[4px] px-32 py-11 text-lg'
>
수정하기
Expand All @@ -73,7 +75,7 @@ export default function EditActivityForm() {
address={address}
onTitleChange={setTitle}
onCategoryChange={setCategory}
onPriceChange={(price) => setPrice(Number(price))}
onPriceChange={setPrice}
onDescriptionChange={setDescription}
onAddressChange={setAddress}
/>
Expand Down
44 changes: 27 additions & 17 deletions src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
import { privateInstance } from '@/apis/privateInstance';
import { uploadImage } from '../../utils/uploadImage';
import { ActivityDetailEdit, Schedule } from '@/types/activityDetailType';
Expand All @@ -21,7 +21,7 @@ export const useEditActivityForm = () => {

const [title, setTitle] = useState('');
const [category, setCategory] = useState('');
const [price, setPrice] = useState(0);
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const [address, setAddress] = useState('');
const [mainImage, setMainImage] = useState<File | string | null>(null);
Expand All @@ -45,7 +45,7 @@ export const useEditActivityForm = () => {
if (data) {
setTitle(data.title);
setCategory(data.category);
setPrice(data.price);
setPrice(data.price.toString());
setDescription(data.description);
setAddress(data.address);
setMainImage(data.bannerImageUrl);
Expand Down Expand Up @@ -101,10 +101,8 @@ export const useEditActivityForm = () => {
setMainImage(null);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

try {
const mutation = useMutation({
mutationFn: async () => {
let bannerImageUrl = '';
if (typeof mainImage === 'string') {
bannerImageUrl = mainImage;
Expand All @@ -118,7 +116,6 @@ export const useEditActivityForm = () => {
.filter((id): id is number => id !== undefined);

const subImageUrlsToAdd: string[] = [];

for (const img of subImages) {
if (!img.id) {
if (img.url instanceof File) {
Expand All @@ -131,44 +128,56 @@ export const useEditActivityForm = () => {
}

const newSchedules = dates.filter((d) => !d.id);

const scheduleIdsToRemove = originalSchedules
.filter((orig) => !dates.some((d) => d.id === orig.id))
.map((d) => d.id)
.filter((id): id is number => id !== undefined);

const parsedPrice = parseInt(price, 10);
if (isNaN(parsedPrice) || parsedPrice <= 0) {
throw new Error('유효한 가격을 입력해주세요.');
}

const payload = {
title,
category,
description,
address,
price,
price: parsedPrice,
bannerImageUrl,
subImageIdsToRemove,
subImageUrlsToAdd,
schedulesToAdd: newSchedules,
scheduleIdsToRemove,
};

await privateInstance.patch(`/editActivity/${id}`, payload);

toast.success('수정되었습니다!'); //토스트로 대체
return await privateInstance.patch(`/editActivity/${id}`, payload);
},
onSuccess: () => {
toast.success('수정되었습니다!');
queryClient.invalidateQueries({ queryKey: ['activity', id] });
router.push(`/activities/${id}`);
} catch (err) {
},
onError: (err: unknown) => {
const error = err as AxiosError;
const responseData = error.response?.data as
| { error?: string; message?: string }
| undefined;

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

toast.error(
responseData?.error ||
responseData?.message ||
error.message ||
'수정에 실패했습니다.',
);
},
});

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await mutation.mutateAsync();
} catch (error) {
console.log('에러발생', error);
}
};

Expand All @@ -183,6 +192,7 @@ export const useEditActivityForm = () => {
dates,
isLoading,
isError,
editLoading: mutation.isPending,
setTitle,
setCategory,
setPrice,
Expand Down
16 changes: 6 additions & 10 deletions src/app/(with-header)/myactivity/components/InfoSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Textarea from '@/components/Textarea';
interface InfoSectionProps {
title?: string;
category?: string;
price?: number;
price?: string;
description?: string;
address?: string;
onTitleChange: (value: string) => void;
Expand All @@ -21,7 +21,7 @@ interface InfoSectionProps {
export function InfoSection({
title = '',
category = '',
price = 0,
price = '',
description = '',
address = '',
onTitleChange,
Expand All @@ -41,9 +41,8 @@ export function InfoSection({
value={title}
onChange={(e) => onTitleChange(e.target.value)}
/>

</div>

<div>
<CategoryInput
category={category}
Expand All @@ -61,7 +60,7 @@ export function InfoSection({
/>
</div>

<div className='relative flex flex-col gap-12 text-xl text-black font-bold'>
<div className='relative flex flex-col gap-12 text-xl font-bold text-black'>
<p>가격</p>
<Input
type='number'
Expand All @@ -72,12 +71,9 @@ export function InfoSection({
/>
</div>

<div className='relative flex flex-col gap-12 text-xl text-black font-bold'>
<div className='relative flex flex-col gap-12 text-xl font-bold text-black'>
<p>주소</p>
<AddressInput
onAddressChange={onAddressChange}
address={address}
/>
<AddressInput onAddressChange={onAddressChange} address={address} />
</div>
</section>
);
Expand Down
153 changes: 28 additions & 125 deletions src/app/(with-header)/myactivity/components/ReservationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,37 @@
'use client';

import { useState } from 'react';
import axios from 'axios';

import type React from 'react';
import { useCreateActivityForm } from '../hooks/useCreateActivityForm';
import { InfoSection } from './InfoSection';
import { ScheduleSelectForm } from './ScheduleSelectForm';
import { ImageSection } from './ImageSection';
import Button from '@/components/Button';
import { uploadImage } from '../utils/uploadImage';
import { privateInstance } from '@/apis/privateInstance';
import { toast } from 'sonner';

interface DateSlot {
date: string;
startTime: string;
endTime: string;
}

export default function ReservationForm() {
const [dates, setDates] = useState<DateSlot[]>([
{ date: '', startTime: '', endTime: '' },
]);
const [mainImage, setMainImage] = useState<File | string | null>(null);
const [subImage, setSubImage] = useState<(File | string)[]>([]);
const [title, setTitle] = useState('');
const [category, setCategory] = useState('');
const [price, setPrice] = useState(0);
const [description, setDescription] = useState('');
const [address, setAddress] = useState('');

const handleAddDate = () => {
setDates([...dates, { date: '', startTime: '', endTime: '' }]);
};

const handleRemoveDate = (index: number) => {
setDates(dates.filter((_, i) => i !== index));
};

const handleDateChange = (
index: number,
field: keyof DateSlot,
value: string,
) => {
const updatedDates = dates.map((date, i) =>
i === index ? { ...date, [field]: value } : date,
);
setDates(updatedDates);
};

const handleMainImageSelect = async (file: File) => {
try {
const url = await uploadImage(file);
setMainImage(url);
} catch (err) {
console.error(err);
toast.error('메인 이미지 업로드에 실패했습니다.');
}
};

const handleMainImageRemove = () => {
setMainImage(null);
};

const handleSubImagesAdd = async (newFiles: File[]) => {
const remainingSlots = 4 - subImage.length;
const filesToAdd = newFiles.slice(0, remainingSlots);
const {
title,
category,
price,
description,
address,
dates,
mainImage,
subImage,
setTitle,
setCategory,
setPrice,
setDescription,
setAddress,
handleAddDate,
handleRemoveDate,
handleDateChange,
handleMainImageSelect,
handleMainImageRemove,
handleSubImagesAdd,
handleSubImageRemove,
handleSubmit,
isLoading,
} = useCreateActivityForm();

try {
const uploadPromises = filesToAdd.map((file) => uploadImage(file));
const uploadedUrls = await Promise.all(uploadPromises);
setSubImage([...subImage, ...uploadedUrls]);
} catch (err) {
console.error('서브 이미지 업로드 실패', err);
toast.error('서브 이미지 업로드 중 문제가 발생.');
}
};

const handleSubImageRemove = (index: number) => {
setSubImage(subImage.filter((_, i) => i !== index));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!mainImage) {
toast.error('메인 이미지를 업로드해주세요.'); //추후 토스트나 팝업으로 대체
return;
}

if (
!title ||
!category ||
!description ||
!address ||
!price ||
dates.length === 0
) {
toast.error('모든 필드를 입력해주세요.'); //추후 토스트나 팝업으로 대체
return;
}

const payload = {
title,
category,
description,
address,
price,
schedules: dates,
bannerImageUrl: mainImage,
subImageUrls: subImage,
};

try {
const response = await privateInstance.post('/addActivity', payload);
console.log('등록 성공:', response.data);
toast.success('체험이 성공적으로 등록되었습니다!'); //추후 토스트나 팝업으로 대체
} catch (err) {
console.error('체험 등록 실패:', err);

if (axios.isAxiosError(err)) {
const detailMessage =
err.response?.data?.detail?.message ||
err.response?.data?.message ||
'체험 등록 중 오류가 발생했습니다.';

toast.error(detailMessage); //추후 토스트나 팝업으로 대체
} else {
toast.error('알 수 없는 오류가 발생했습니다.'); //추후 토스트나 팝업으로 대체
}
}
};
return (
<div className='bg-gray-white min-h-screen px-16 py-24 sm:px-6 md:py-0 lg:px-8'>
<div className='mx-auto max-w-1200 p-4 sm:px-20 lg:p-8'>
Expand All @@ -141,12 +42,14 @@ export default function ReservationForm() {
<Button
variant='primary'
type='submit'
isLoading={isLoading}
className='bg-nomad w-full rounded-[4px] px-32 py-11 text-lg'
>
등록하기
</Button>
</div>
</div>

<InfoSection
title={title}
category={category}
Expand All @@ -155,7 +58,7 @@ export default function ReservationForm() {
address={address}
onTitleChange={setTitle}
onCategoryChange={setCategory}
onPriceChange={(value) => setPrice(Number(value))}
onPriceChange={setPrice}
onDescriptionChange={setDescription}
onAddressChange={setAddress}
/>
Expand Down
Loading