diff --git a/src/api/service/group-service/index.ts b/src/api/service/group-service/index.ts index 1c2d5ec0..18a7bc3d 100644 --- a/src/api/service/group-service/index.ts +++ b/src/api/service/group-service/index.ts @@ -8,7 +8,6 @@ import { GetMyGroupsPayload, GetMyGroupsResponse, GroupIdPayload, - PreUploadGroupImagePayload, PreUploadGroupImageResponse, } from '@/types/service/group'; @@ -39,10 +38,8 @@ export const groupServiceRemote = () => ({ }, // 모임 이미지 사전 업로드 (POST /groups/images/upload) - multipart/form-data - uploadGroupImages: (payload: PreUploadGroupImagePayload) => { - return api.post('/groups/images/upload', payload, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); + uploadGroupImages: (payload: FormData) => { + return api.post('/groups/images/upload', payload); }, createGroup: (payload: CreateGroupPayload) => { diff --git a/src/app/post-meetup/page.tsx b/src/app/post-meetup/page.tsx index fa9f3219..216e061c 100644 --- a/src/app/post-meetup/page.tsx +++ b/src/app/post-meetup/page.tsx @@ -2,7 +2,7 @@ import { useRouter } from 'next/navigation'; -import { useForm } from '@tanstack/react-form'; +import { useForm, useStore } from '@tanstack/react-form'; import { MeetupCapField, @@ -40,6 +40,10 @@ const PostMeetupPage = () => { }, }); + const values = useStore(form.store, (state) => state.values); + + console.log(values); + return (
diff --git a/src/components/pages/meetup/meetup-banner-images/index.tsx b/src/components/pages/meetup/meetup-banner-images/index.tsx index 289443ea..c5d7b1fe 100644 --- a/src/components/pages/meetup/meetup-banner-images/index.tsx +++ b/src/components/pages/meetup/meetup-banner-images/index.tsx @@ -7,6 +7,7 @@ import './index.css'; import Image from 'next/image'; +import { DEFAULT_GROUP_IMAGE } from 'constants/default-images'; import { Pagination } from 'swiper/modules'; import { Swiper, SwiperSlide } from 'swiper/react'; @@ -19,9 +20,6 @@ interface Props { export const MeetupBannerImages = ({ images }: Props) => { const hasImages = Boolean(images.length); - const defaultImageUrl = - 'https://images.unsplash.com/photo-1705599359461-f99dc9e80efa?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; - return (
{hasImages ? ( @@ -34,7 +32,7 @@ export const MeetupBannerImages = ({ images }: Props) => { ) : (
- image + image
)}
diff --git a/src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx b/src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx index a1d46623..b512814d 100644 --- a/src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx +++ b/src/components/pages/meetup/meetup-descriptions/description-sections/description-profile/index.tsx @@ -1,8 +1,6 @@ -import Image from 'next/image'; import Link from 'next/link'; -import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; - +import { ImageWithFallback } from '@/components/ui'; import { GetGroupDetailsResponse } from '@/types/service/group'; interface Props { @@ -13,14 +11,13 @@ export const DescriptionProfile = ({ hostInfo: { nickName, profileImage, userId return (
- 프로필 사진
diff --git a/src/components/pages/meetup/meetup-members/index.tsx b/src/components/pages/meetup/meetup-members/index.tsx index 93f5e19a..7ce056b7 100644 --- a/src/components/pages/meetup/meetup-members/index.tsx +++ b/src/components/pages/meetup/meetup-members/index.tsx @@ -1,6 +1,5 @@ 'use client'; -import Image from 'next/image'; import Link from 'next/link'; import { useState } from 'react'; @@ -9,7 +8,7 @@ import clsx from 'clsx'; import { Icon } from '@/components/icon'; import { AnimateDynamicHeight } from '@/components/shared'; -import { Button } from '@/components/ui'; +import { Button, ImageWithFallback } from '@/components/ui'; import { GetGroupDetailsResponse } from '@/types/service/group'; interface Props { @@ -22,9 +21,6 @@ export const MeetupMembers = ({ members }: Props) => { const hasMoreMember = 2 < Math.ceil(members.length / 3); - const defaultProfileImageUrl = - 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?q=80&w=717&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; - const onExpandClick = () => { setExpand((prev) => !prev); setCoverMember((prev) => !prev); @@ -46,14 +42,13 @@ export const MeetupMembers = ({ members }: Props) => { >
- 프로필 사진

{ className='bg-mono-white focus:border-mint-500 text-text-md-medium h-40 w-full resize-none rounded-2xl border border-gray-300 px-5 py-4 text-gray-800 focus:outline-none' maxLength={300} placeholder='모임에 대해 설명해주세요' + required value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} /> 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 a6f7c076..05cddd27 100644 --- a/src/components/pages/post-meetup/fields/images-field/index.tsx +++ b/src/components/pages/post-meetup/fields/images-field/index.tsx @@ -2,76 +2,110 @@ import Image from 'next/image'; +import { useRef } from 'react'; + import { AnyFieldApi } from '@tanstack/react-form'; import { Icon } from '@/components/icon'; -import { ImageInput, ImageInputProps } from '@/components/ui'; +import { useUploadGroupImages } from '@/hooks/use-group/use-group-upload-images'; import { cn } from '@/lib/utils'; +import { ALLOWED_IMAGE_TYPES } from '@/types/service/common'; +import { PreUploadGroupImageResponse } from '@/types/service/group'; interface Props { field: AnyFieldApi; - initialImages?: ImageInputProps['initialImages']; } -export const MeetupImagesField = ({ field, initialImages }: Props) => { +export const MeetupImagesField = ({ field }: Props) => { + const { mutateAsync } = useUploadGroupImages(); + + const onUploadImage = async (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const fileArray = Array.from(files); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const invalidFile = fileArray.find((file) => !ALLOWED_IMAGE_TYPES.includes(file.type as any)); + + if (invalidFile) { + alert('jpg 또는 png 파일만 업로드 가능합니다.'); + e.target.value = ''; + return; + } + + const response = await mutateAsync({ + images: fileArray, + }); + + field.handleChange([...response.images]); + }; + + const onUploadImageButtonClick = () => { + if (!inputRef.current) { + return; + } + + inputRef.current.click(); + }; + + const inputRef = useRef(null); + return ( - - {(images, onRemoveImageClick, onFileSelectClick) => ( -

-
- + {field.state.value.map(({ imageUrl100x100 }: PreUploadGroupImageResponse['images'][0]) => ( +
+ 썸네일 이미지 + + {/* - {Object.entries(images).map(([url, _file]) => ( -
- 팀 이미지 - -
- ))} + + */}
-

최대 3개까지 업로드할 수 있어요.

-
- )} - + ))} +
+

최대 3개까지 업로드할 수 있어요.

+
); }; diff --git a/src/components/ui/image-with-fallback/index.tsx b/src/components/ui/image-with-fallback/index.tsx index e1cf3021..88ef61ae 100644 --- a/src/components/ui/image-with-fallback/index.tsx +++ b/src/components/ui/image-with-fallback/index.tsx @@ -9,9 +9,11 @@ interface ImageWithFallbackProps extends Omit { fallbackSrc?: string; } +import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; + export const ImageWithFallback = ({ src, - fallbackSrc = 'https://plus.unsplash.com/premium_photo-1738592736106-a17b897c0ab1?q=80&w=1934&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + fallbackSrc = DEFAULT_PROFILE_IMAGE, ...rest }: ImageWithFallbackProps) => { const [error, setError] = useState(false); diff --git a/src/hooks/use-group/use-group-upload-images/index.ts b/src/hooks/use-group/use-group-upload-images/index.ts index 7851ff74..22abc497 100644 --- a/src/hooks/use-group/use-group-upload-images/index.ts +++ b/src/hooks/use-group/use-group-upload-images/index.ts @@ -6,7 +6,13 @@ import { PreUploadGroupImagePayload } from '@/types/service/group'; export const useUploadGroupImages = () => { const query = useMutation({ mutationFn: (payload: PreUploadGroupImagePayload) => { - return API.groupService.uploadGroupImages(payload); + const formData = new FormData(); + + payload.images.forEach((file) => { + formData.append('images', file); + }); + + return API.groupService.uploadGroupImages(formData); }, onSuccess: () => { console.log('이미지 등록 성공'); diff --git a/src/types/service/common.ts b/src/types/service/common.ts index 698e7dd7..1756185a 100644 --- a/src/types/service/common.ts +++ b/src/types/service/common.ts @@ -12,3 +12,5 @@ export interface CommonSuccessResponse { success: boolean; data: T; } + +export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png'] as const;