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
32 changes: 31 additions & 1 deletion src/api/service/group-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { api } from '@/api/core';
import { api, baseAPI } from '@/api/core';
import { CommonSuccessResponse } from '@/types/service/common';
import {
CreateGroupPayload,
CreateGroupResponse,
Expand All @@ -8,6 +9,8 @@ import {
GetGroupsResponse,
GetMyGroupsPayload,
GetMyGroupsResponse,
PreUploadGroupImagePayload,
PreUploadGroupImageResponse,
} from '@/types/service/group';

export const groupServiceRemote = () => ({
Expand Down Expand Up @@ -36,6 +39,33 @@ export const groupServiceRemote = () => ({
return api.get<GetMyGroupsResponse>(`/groups/me?${params.toString()}`);
},

// 모임 이미지 사전 업로드 (POST /groups/images/upload) - multipart/form-data
uploadGroupImages: async (
payload: PreUploadGroupImagePayload,
): Promise<PreUploadGroupImageResponse> => {
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<CommonSuccessResponse<PreUploadGroupImageResponse>>(
'/groups/images/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);

return response.data.data;
},

createGroup: (payload: CreateGroupPayload) => {
return api.post<CreateGroupResponse>('/groups/groups/create', payload);
},
Expand Down
48 changes: 41 additions & 7 deletions src/app/post-meetup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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);
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const MeetupImagesField = ({ field, initialImages }: Props) => {
alt='팀 이미지'
fill
src={url}
unoptimized={url.startsWith('blob:')}
/>
<button
className={cn(
Expand Down
12 changes: 12 additions & 0 deletions src/hooks/use-group/use-upload-group-images/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMutation } from '@tanstack/react-query';

import { API } from '@/api';
import { PreUploadGroupImagePayload } from '@/types/service/group';

export const useUploadGroupImages = () => {
const query = useMutation({
mutationFn: (payload: PreUploadGroupImagePayload) =>
API.groupService.uploadGroupImages(payload),
});
return query;
};