diff --git a/src/api/service/group-service/index.ts b/src/api/service/group-service/index.ts index e08d1892..8f7c6bd1 100644 --- a/src/api/service/group-service/index.ts +++ b/src/api/service/group-service/index.ts @@ -1,4 +1,5 @@ -import { api } from '@/api/core'; +import { api, baseAPI } from '@/api/core'; +import { CommonSuccessResponse } from '@/types/service/common'; import { CreateGroupPayload, CreateGroupResponse, @@ -8,6 +9,8 @@ import { GetGroupsResponse, GetMyGroupsPayload, GetMyGroupsResponse, + PreUploadGroupImagePayload, + PreUploadGroupImageResponse, } from '@/types/service/group'; export const groupServiceRemote = () => ({ @@ -36,6 +39,33 @@ export const groupServiceRemote = () => ({ return api.get(`/groups/me?${params.toString()}`); }, + // 모임 이미지 사전 업로드 (POST /groups/images/upload) - multipart/form-data + uploadGroupImages: async ( + payload: PreUploadGroupImagePayload, + ): Promise => { + const formData = new FormData(); + payload.images.forEach((file) => { + if (file instanceof File) { + formData.append('images', file); + } else { + console.error('[이미지 업로드 오류] File 객체가 아닌 값이 포함됨:', file); + throw new Error('이미지 파일은 File 객체여야 합니다.'); + } + }); + + const response = await baseAPI.post>( + '/groups/images/upload', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); + + return response.data.data; + }, + createGroup: (payload: CreateGroupPayload) => { return api.post('/groups/groups/create', payload); }, diff --git a/src/app/post-meetup/page.tsx b/src/app/post-meetup/page.tsx index 97e03024..424759e8 100644 --- a/src/app/post-meetup/page.tsx +++ b/src/app/post-meetup/page.tsx @@ -12,11 +12,14 @@ import { MeetupTagsField, MeetupTitleField, } from '@/components/pages/post-meetup'; +import { ImageRecord } from '@/components/ui'; import { useCreateGroup } from '@/hooks/use-group/use-group-create'; +import { useUploadGroupImages } from '@/hooks/use-group/use-upload-group-images'; import { CreateGroupPayload } from '@/types/service/group'; const PostMeetupPage = () => { - const { mutate } = useCreateGroup(); + const { mutateAsync: createGroup } = useCreateGroup(); + const { mutateAsync: uploadImages } = useUploadGroupImages(); const form = useForm({ defaultValues: { @@ -28,12 +31,43 @@ const PostMeetupPage = () => { tags: [] as string[], description: '', maxParticipants: 0, - images: [], - } as CreateGroupPayload, - onSubmit: ({ value }) => { - console.log(value); - const res = mutate(value); - console.log(res); + images: {} as ImageRecord, + }, + onSubmit: async ({ value }) => { + const imageRecords = value.images as ImageRecord; + const imageFiles = Object.values(imageRecords).filter( + (file): file is File => file !== null && file instanceof File, + ); + + // 이미지가 있으면 먼저 업로드하여 URL 배열 수확 + let uploadedImages = null; + if (imageFiles.length > 0) { + const uploadResponse = await uploadImages({ images: imageFiles }); + uploadedImages = uploadResponse.images; + } + + if (!value.startTime) { + throw new Error('모임 시작 시간을 입력해주세요.'); + } + + const maxParticipantsNumber = Number(value.maxParticipants); + if (isNaN(maxParticipantsNumber) || maxParticipantsNumber < 1) { + throw new Error('모임 최대 인원은 1명 이상이어야 합니다.'); + } + + const createPayload: CreateGroupPayload = { + title: value.title.trim(), + location: value.location.trim(), + locationDetail: value.locationDetail?.trim() || null, + startTime: value.startTime, + ...(value.endTime && { endTime: value.endTime }), + tags: value.tags && value.tags.length > 0 ? value.tags : null, + description: value.description.trim(), + maxParticipants: maxParticipantsNumber, + images: uploadedImages && uploadedImages.length > 0 ? uploadedImages : null, + }; + + await createGroup(createPayload); }, }); diff --git a/src/components/pages/post-meetup/fields/images-field/index.tsx b/src/components/pages/post-meetup/fields/images-field/index.tsx index 806fe3a4..a8dda984 100644 --- a/src/components/pages/post-meetup/fields/images-field/index.tsx +++ b/src/components/pages/post-meetup/fields/images-field/index.tsx @@ -53,6 +53,7 @@ export const MeetupImagesField = ({ field, initialImages }: Props) => { alt='팀 이미지' fill src={url} + unoptimized={url.startsWith('blob:')} />