Skip to content

Commit b51a036

Browse files
authored
Merge pull request #127 from codeit-2team/fix/117
Fix/117 QA사항수정 및 리팩토링
2 parents 1c853b3 + 7af0edf commit b51a036

File tree

14 files changed

+309
-166
lines changed

14 files changed

+309
-166
lines changed

src/app/(with-header)/myactivity/[id]/components/EditActivityForm.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default function EditActivityForm() {
2424
dates,
2525
isLoading,
2626
isError,
27+
editLoading,
2728
setTitle,
2829
setCategory,
2930
setPrice,
@@ -58,6 +59,7 @@ export default function EditActivityForm() {
5859
<Button
5960
variant='primary'
6061
type='submit'
62+
isLoading={editLoading}
6163
className='bg-nomad w-full rounded-[4px] px-32 py-11 text-lg'
6264
>
6365
수정하기
@@ -73,7 +75,7 @@ export default function EditActivityForm() {
7375
address={address}
7476
onTitleChange={setTitle}
7577
onCategoryChange={setCategory}
76-
onPriceChange={(price) => setPrice(Number(price))}
78+
onPriceChange={setPrice}
7779
onDescriptionChange={setDescription}
7880
onAddressChange={setAddress}
7981
/>

src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

2222
const [title, setTitle] = useState('');
2323
const [category, setCategory] = useState('');
24-
const [price, setPrice] = useState(0);
24+
const [price, setPrice] = useState('');
2525
const [description, setDescription] = useState('');
2626
const [address, setAddress] = useState('');
2727
const [mainImage, setMainImage] = useState<File | string | null>(null);
@@ -45,7 +45,7 @@ export const useEditActivityForm = () => {
4545
if (data) {
4646
setTitle(data.title);
4747
setCategory(data.category);
48-
setPrice(data.price);
48+
setPrice(data.price.toString());
4949
setDescription(data.description);
5050
setAddress(data.address);
5151
setMainImage(data.bannerImageUrl);
@@ -101,10 +101,8 @@ export const useEditActivityForm = () => {
101101
setMainImage(null);
102102
};
103103

104-
const handleSubmit = async (e: React.FormEvent) => {
105-
e.preventDefault();
106-
107-
try {
104+
const mutation = useMutation({
105+
mutationFn: async () => {
108106
let bannerImageUrl = '';
109107
if (typeof mainImage === 'string') {
110108
bannerImageUrl = mainImage;
@@ -118,7 +116,6 @@ export const useEditActivityForm = () => {
118116
.filter((id): id is number => id !== undefined);
119117

120118
const subImageUrlsToAdd: string[] = [];
121-
122119
for (const img of subImages) {
123120
if (!img.id) {
124121
if (img.url instanceof File) {
@@ -131,44 +128,56 @@ export const useEditActivityForm = () => {
131128
}
132129

133130
const newSchedules = dates.filter((d) => !d.id);
134-
135131
const scheduleIdsToRemove = originalSchedules
136132
.filter((orig) => !dates.some((d) => d.id === orig.id))
137133
.map((d) => d.id)
138134
.filter((id): id is number => id !== undefined);
139135

136+
const parsedPrice = parseInt(price, 10);
137+
if (isNaN(parsedPrice) || parsedPrice <= 0) {
138+
throw new Error('유효한 가격을 입력해주세요.');
139+
}
140+
140141
const payload = {
141142
title,
142143
category,
143144
description,
144145
address,
145-
price,
146+
price: parsedPrice,
146147
bannerImageUrl,
147148
subImageIdsToRemove,
148149
subImageUrlsToAdd,
149150
schedulesToAdd: newSchedules,
150151
scheduleIdsToRemove,
151152
};
152153

153-
await privateInstance.patch(`/editActivity/${id}`, payload);
154-
155-
toast.success('수정되었습니다!'); //토스트로 대체
154+
return await privateInstance.patch(`/editActivity/${id}`, payload);
155+
},
156+
onSuccess: () => {
157+
toast.success('수정되었습니다!');
156158
queryClient.invalidateQueries({ queryKey: ['activity', id] });
157159
router.push(`/activities/${id}`);
158-
} catch (err) {
160+
},
161+
onError: (err: unknown) => {
159162
const error = err as AxiosError;
160163
const responseData = error.response?.data as
161164
| { error?: string; message?: string }
162165
| undefined;
163-
164-
console.error('전체 에러:', error);
165-
166166
toast.error(
167167
responseData?.error ||
168168
responseData?.message ||
169169
error.message ||
170170
'수정에 실패했습니다.',
171171
);
172+
},
173+
});
174+
175+
const handleSubmit = async (e: React.FormEvent) => {
176+
e.preventDefault();
177+
try {
178+
await mutation.mutateAsync();
179+
} catch (error) {
180+
console.log('에러발생', error);
172181
}
173182
};
174183

@@ -183,6 +192,7 @@ export const useEditActivityForm = () => {
183192
dates,
184193
isLoading,
185194
isError,
195+
editLoading: mutation.isPending,
186196
setTitle,
187197
setCategory,
188198
setPrice,

src/app/(with-header)/myactivity/components/InfoSection.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Textarea from '@/components/Textarea';
88
interface InfoSectionProps {
99
title?: string;
1010
category?: string;
11-
price?: number;
11+
price?: string;
1212
description?: string;
1313
address?: string;
1414
onTitleChange: (value: string) => void;
@@ -21,7 +21,7 @@ interface InfoSectionProps {
2121
export function InfoSection({
2222
title = '',
2323
category = '',
24-
price = 0,
24+
price = '',
2525
description = '',
2626
address = '',
2727
onTitleChange,
@@ -41,9 +41,8 @@ export function InfoSection({
4141
value={title}
4242
onChange={(e) => onTitleChange(e.target.value)}
4343
/>
44-
4544
</div>
46-
45+
4746
<div>
4847
<CategoryInput
4948
category={category}
@@ -61,7 +60,7 @@ export function InfoSection({
6160
/>
6261
</div>
6362

64-
<div className='relative flex flex-col gap-12 text-xl text-black font-bold'>
63+
<div className='relative flex flex-col gap-12 text-xl font-bold text-black'>
6564
<p>가격</p>
6665
<Input
6766
type='number'
@@ -72,12 +71,9 @@ export function InfoSection({
7271
/>
7372
</div>
7473

75-
<div className='relative flex flex-col gap-12 text-xl text-black font-bold'>
74+
<div className='relative flex flex-col gap-12 text-xl font-bold text-black'>
7675
<p>주소</p>
77-
<AddressInput
78-
onAddressChange={onAddressChange}
79-
address={address}
80-
/>
76+
<AddressInput onAddressChange={onAddressChange} address={address} />
8177
</div>
8278
</section>
8379
);

src/app/(with-header)/myactivity/components/ReservationForm.tsx

Lines changed: 28 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,37 @@
11
'use client';
22

3-
import { useState } from 'react';
4-
import axios from 'axios';
5-
6-
import type React from 'react';
3+
import { useCreateActivityForm } from '../hooks/useCreateActivityForm';
74
import { InfoSection } from './InfoSection';
85
import { ScheduleSelectForm } from './ScheduleSelectForm';
96
import { ImageSection } from './ImageSection';
107
import Button from '@/components/Button';
11-
import { uploadImage } from '../utils/uploadImage';
12-
import { privateInstance } from '@/apis/privateInstance';
13-
import { toast } from 'sonner';
14-
15-
interface DateSlot {
16-
date: string;
17-
startTime: string;
18-
endTime: string;
19-
}
208

219
export default function ReservationForm() {
22-
const [dates, setDates] = useState<DateSlot[]>([
23-
{ date: '', startTime: '', endTime: '' },
24-
]);
25-
const [mainImage, setMainImage] = useState<File | string | null>(null);
26-
const [subImage, setSubImage] = useState<(File | string)[]>([]);
27-
const [title, setTitle] = useState('');
28-
const [category, setCategory] = useState('');
29-
const [price, setPrice] = useState(0);
30-
const [description, setDescription] = useState('');
31-
const [address, setAddress] = useState('');
32-
33-
const handleAddDate = () => {
34-
setDates([...dates, { date: '', startTime: '', endTime: '' }]);
35-
};
36-
37-
const handleRemoveDate = (index: number) => {
38-
setDates(dates.filter((_, i) => i !== index));
39-
};
40-
41-
const handleDateChange = (
42-
index: number,
43-
field: keyof DateSlot,
44-
value: string,
45-
) => {
46-
const updatedDates = dates.map((date, i) =>
47-
i === index ? { ...date, [field]: value } : date,
48-
);
49-
setDates(updatedDates);
50-
};
51-
52-
const handleMainImageSelect = async (file: File) => {
53-
try {
54-
const url = await uploadImage(file);
55-
setMainImage(url);
56-
} catch (err) {
57-
console.error(err);
58-
toast.error('메인 이미지 업로드에 실패했습니다.');
59-
}
60-
};
61-
62-
const handleMainImageRemove = () => {
63-
setMainImage(null);
64-
};
65-
66-
const handleSubImagesAdd = async (newFiles: File[]) => {
67-
const remainingSlots = 4 - subImage.length;
68-
const filesToAdd = newFiles.slice(0, remainingSlots);
10+
const {
11+
title,
12+
category,
13+
price,
14+
description,
15+
address,
16+
dates,
17+
mainImage,
18+
subImage,
19+
setTitle,
20+
setCategory,
21+
setPrice,
22+
setDescription,
23+
setAddress,
24+
handleAddDate,
25+
handleRemoveDate,
26+
handleDateChange,
27+
handleMainImageSelect,
28+
handleMainImageRemove,
29+
handleSubImagesAdd,
30+
handleSubImageRemove,
31+
handleSubmit,
32+
isLoading,
33+
} = useCreateActivityForm();
6934

70-
try {
71-
const uploadPromises = filesToAdd.map((file) => uploadImage(file));
72-
const uploadedUrls = await Promise.all(uploadPromises);
73-
setSubImage([...subImage, ...uploadedUrls]);
74-
} catch (err) {
75-
console.error('서브 이미지 업로드 실패', err);
76-
toast.error('서브 이미지 업로드 중 문제가 발생.');
77-
}
78-
};
79-
80-
const handleSubImageRemove = (index: number) => {
81-
setSubImage(subImage.filter((_, i) => i !== index));
82-
};
83-
84-
const handleSubmit = async (e: React.FormEvent) => {
85-
e.preventDefault();
86-
87-
if (!mainImage) {
88-
toast.error('메인 이미지를 업로드해주세요.'); //추후 토스트나 팝업으로 대체
89-
return;
90-
}
91-
92-
if (
93-
!title ||
94-
!category ||
95-
!description ||
96-
!address ||
97-
!price ||
98-
dates.length === 0
99-
) {
100-
toast.error('모든 필드를 입력해주세요.'); //추후 토스트나 팝업으로 대체
101-
return;
102-
}
103-
104-
const payload = {
105-
title,
106-
category,
107-
description,
108-
address,
109-
price,
110-
schedules: dates,
111-
bannerImageUrl: mainImage,
112-
subImageUrls: subImage,
113-
};
114-
115-
try {
116-
const response = await privateInstance.post('/addActivity', payload);
117-
console.log('등록 성공:', response.data);
118-
toast.success('체험이 성공적으로 등록되었습니다!'); //추후 토스트나 팝업으로 대체
119-
} catch (err) {
120-
console.error('체험 등록 실패:', err);
121-
122-
if (axios.isAxiosError(err)) {
123-
const detailMessage =
124-
err.response?.data?.detail?.message ||
125-
err.response?.data?.message ||
126-
'체험 등록 중 오류가 발생했습니다.';
127-
128-
toast.error(detailMessage); //추후 토스트나 팝업으로 대체
129-
} else {
130-
toast.error('알 수 없는 오류가 발생했습니다.'); //추후 토스트나 팝업으로 대체
131-
}
132-
}
133-
};
13435
return (
13536
<div className='bg-gray-white min-h-screen px-16 py-24 sm:px-6 md:py-0 lg:px-8'>
13637
<div className='mx-auto max-w-1200 p-4 sm:px-20 lg:p-8'>
@@ -141,12 +42,14 @@ export default function ReservationForm() {
14142
<Button
14243
variant='primary'
14344
type='submit'
45+
isLoading={isLoading}
14446
className='bg-nomad w-full rounded-[4px] px-32 py-11 text-lg'
14547
>
14648
등록하기
14749
</Button>
14850
</div>
14951
</div>
52+
15053
<InfoSection
15154
title={title}
15255
category={category}
@@ -155,7 +58,7 @@ export default function ReservationForm() {
15558
address={address}
15659
onTitleChange={setTitle}
15760
onCategoryChange={setCategory}
158-
onPriceChange={(value) => setPrice(Number(value))}
61+
onPriceChange={setPrice}
15962
onDescriptionChange={setDescription}
16063
onAddressChange={setAddress}
16164
/>

0 commit comments

Comments
 (0)