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
7 changes: 4 additions & 3 deletions src/api/service/group-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ export const groupServiceRemote = () => ({
},

// 모임 이미지 사전 업로드 (POST /groups/images/upload) - multipart/form-data
uploadGroupImages: (payload: FormData) => {
return api.post<PreUploadGroupImageResponse>('/groups/images/upload', payload);
},

createGroup: (payload: CreateGroupPayload) => {
return api.post<CreateGroupResponse>('/groups/create', payload);
Expand All @@ -61,4 +58,8 @@ export const groupServiceRemote = () => ({
deleteGroup: (payload: GroupIdPayload) => {
return api.delete(`/groups/${payload.groupId}`);
},

uploadGroupImages: (payload: FormData) => {
return api.post<PreUploadGroupImageResponse>('/groups/images/upload', payload);
},
});
15 changes: 13 additions & 2 deletions src/app/post-meetup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
MeetupTitleField,
} from '@/components/pages/post-meetup';
import { useCreateGroup } from '@/hooks/use-group/use-group-create';
import { CreateGroupPayload } from '@/types/service/group';
import { CreateGroupPayload, PreUploadGroupImageResponse } from '@/types/service/group';

const PostMeetupPage = () => {
const { replace } = useRouter();
Expand All @@ -35,7 +35,18 @@ const PostMeetupPage = () => {
images: [],
} as CreateGroupPayload,
onSubmit: async ({ value }) => {
const res = await createGroup(value);
const images = [] as PreUploadGroupImageResponse['images'];

if (value.images) {
value.images.forEach((image, idx) => {
images.push({
...image,
sortOrder: idx,
});
});
}

const res = await createGroup({ ...value, images: images });
replace(`/meetup/${res.id}`);
},
});
Expand Down
65 changes: 36 additions & 29 deletions src/components/pages/post-meetup/fields/images-field/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ export const MeetupImagesField = ({ field }: Props) => {
const { mutateAsync } = useUploadGroupImages();

const onUploadImage = async (e: React.ChangeEvent<HTMLInputElement>) => {
const maxAllowed = 3 - field.state.value.length;
const files = e.target.files;

if (!files || files.length === 0) return;
if (files.length > maxAllowed) 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 파일만 업로드 가능합니다.');
alert('jpg, png, webp 파일만 업로드 가능합니다.');
e.target.value = '';
return;
}
Expand All @@ -38,17 +41,19 @@ export const MeetupImagesField = ({ field }: Props) => {
images: fileArray,
});

field.handleChange([...response.images]);
field.handleChange([...field.state.value, ...response.images]);
};

const onUploadImageButtonClick = () => {
if (!inputRef.current) {
return;
}

if (!inputRef.current) return;
inputRef.current.click();
};

const onRemoveImageClick = (removeIdx: number) => {
const removedArray = field.state.value.filter(({}, idx: number) => idx !== removeIdx);
field.handleChange(removedArray);
};

const inputRef = useRef<HTMLInputElement | null>(null);

return (
Expand Down Expand Up @@ -81,29 +86,31 @@ export const MeetupImagesField = ({ field }: Props) => {
onChange={(e) => onUploadImage(e)}
/>
</button>
{field.state.value.map(({ imageUrl100x100 }: PreUploadGroupImageResponse['images'][0]) => (
<div key={imageUrl100x100} className='relative aspect-square w-full max-w-20'>
<Image
className='border-mono-black/5 h-full w-full rounded-2xl border-1 object-cover'
alt='썸네일 이미지'
fill
src={imageUrl100x100}
/>

{/* <button
className={cn(
'flex-center bg-mono-white/80 group absolute top-1.5 right-2 size-4 cursor-pointer rounded-full', // 기본 스타일
'hover:bg-mono-white hover:scale-110', // hover 스타일
'transition-all duration-300', // animation 스타일
)}
aria-label='이미지 삭제 버튼'
type='button'
onClick={() => onRemoveImageClick(url)}
>
<Icon id='small-x-1' className='size-1.5 text-gray-700' />
</button> */}
</div>
))}
{field.state.value.map(
({ imageUrl100x100 }: PreUploadGroupImageResponse['images'][0], idx: number) => (
<div key={imageUrl100x100} className='relative aspect-square w-full max-w-20'>
<Image
className='border-mono-black/5 h-full w-full rounded-2xl border-1 object-cover'
alt='썸네일 이미지'
fill
src={imageUrl100x100}
/>

<button
className={cn(
'flex-center bg-mono-white/80 group absolute top-1.5 right-2 size-4 cursor-pointer rounded-full', // 기본 스타일
'hover:bg-mono-white hover:scale-110', // hover 스타일
'transition-all duration-300', // animation 스타일
)}
aria-label='이미지 삭제 버튼'
type='button'
onClick={() => onRemoveImageClick(idx)}
>
<Icon id='small-x-1' className='size-1.5 text-gray-700' />
</button>
</div>
),
)}
</div>
<p className='text-text-sm-medium px-2 text-gray-500'>최대 3개까지 업로드할 수 있어요.</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/types/service/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ export interface CommonSuccessResponse<T> {
data: T;
}

export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png'] as const;
export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;