diff --git a/frontend/src/components/common/TripInformation/TripInformation.tsx b/frontend/src/components/common/TripInformation/TripInformation.tsx
index 32c05eb3f..e16133675 100644
--- a/frontend/src/components/common/TripInformation/TripInformation.tsx
+++ b/frontend/src/components/common/TripInformation/TripInformation.tsx
@@ -23,7 +23,7 @@ import { useTrip } from '@hooks/trip/useTrip';
import { mediaQueryMobileState } from '@store/mediaQuery';
-import convertImageName from '@utils/convertImageName';
+import { convertToImageUrl } from '@utils/convertImage';
import { formatDate } from '@utils/formatter';
import type { TripData, TripTypeData } from '@type/trip';
@@ -65,7 +65,7 @@ const TripInformation = ({
diff --git a/frontend/src/components/common/TripItem/TripItem.tsx b/frontend/src/components/common/TripItem/TripItem.tsx
index 6a9edffad..519a5451f 100644
--- a/frontend/src/components/common/TripItem/TripItem.tsx
+++ b/frontend/src/components/common/TripItem/TripItem.tsx
@@ -26,8 +26,7 @@ import useResizeImage from '@hooks/trip/useResizeImage';
import { mediaQueryMobileState } from '@store/mediaQuery';
-import convertImageName from '@utils/convertImageName';
-import convertImageNames from '@utils/convertImageNames';
+import { convertToImageUrl, convertToImageUrls } from '@utils/convertImage';
import { formatNumberToMoney } from '@utils/formatter';
import type { TripItemData } from '@type/tripItem';
@@ -101,7 +100,7 @@ const TripItem = ({
showNavigationOnHover={!isMobile}
showArrows={information.imageNames.length > 1}
showDots={information.imageNames.length > 1}
- images={convertImageNames(information.imageNames)}
+ images={convertToImageUrls(information.imageNames)}
/>
)}
@@ -147,11 +146,11 @@ const TripItem = ({
showNavigationOnHover={!isMobile}
showArrows={information.imageNames.length > 1}
showDots={information.imageNames.length > 1}
- images={convertImageNames(information.imageNames)}
+ images={convertToImageUrls(information.imageNames)}
>
{information.imageNames.map((imageName) => (
-
+
))}
diff --git a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx
index 21840badf..894d8a2e6 100644
--- a/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx
+++ b/frontend/src/components/trip/TripInfoEditModal/TripInfoEditModal.tsx
@@ -46,22 +46,19 @@ const TripInfoEditModal = ({ isOpen, onClose, ...information }: TripInfoEditModa
handleSubmit,
} = useTripEditForm(information, onClose);
- const handleImageNamesChange = useCallback(
+ const handleImageNameChange = useCallback(
(imageNames: string[]) => {
updateCoverImage(imageNames[0]);
},
[updateCoverImage]
);
- const {
- uploadedImageNames: uploadedImageName,
- isImageUploading,
- handleImageUpload,
- handleImageRemoval,
- } = useMultipleImageUpload({
- initialImageNames: information.imageName === null ? [] : [information.imageName],
- onSuccess: handleImageNamesChange,
- });
+ const { imageUrls, isImageUploading, handleImageUpload, handleImageRemoval } =
+ useMultipleImageUpload({
+ initialImageNames: information.imageName === null ? [] : [information.imageName],
+ updateFormImage: handleImageNameChange,
+ maxUploadCount: 1,
+ });
return (
{
css={clickableLikeStyling}
/>
diff --git a/frontend/src/components/trips/TripsItem/TripsItem.tsx b/frontend/src/components/trips/TripsItem/TripsItem.tsx
index 52f7bc5df..f3cd87ff3 100644
--- a/frontend/src/components/trips/TripsItem/TripsItem.tsx
+++ b/frontend/src/components/trips/TripsItem/TripsItem.tsx
@@ -10,7 +10,7 @@ import {
nameStyling,
} from '@components/trips/TripsItem/TripsItem.style';
-import convertImageName from '@utils/convertImageName';
+import { convertToImageUrl } from '@utils/convertImage';
import type { CityData } from '@type/city';
@@ -49,7 +49,7 @@ const TripsItem = ({
onClick={() => navigate(PATH.TRIP(String(id)))}
>
diff --git a/frontend/src/hooks/api/useImageMutation.ts b/frontend/src/hooks/api/useImageMutation.ts
index 3296075ba..ae76d0bc5 100644
--- a/frontend/src/hooks/api/useImageMutation.ts
+++ b/frontend/src/hooks/api/useImageMutation.ts
@@ -14,6 +14,9 @@ export const useImageMutation = () => {
const imageMutation = useMutation({
mutationFn: postImage,
+ onSuccess: () => {
+ createToast('이미지 업로드에 성공했습니다', 'success');
+ },
onError: (error: ErrorResponseData) => {
if (error.code && error.code > ERROR_CODE.TOKEN_ERROR_RANGE) {
handleTokenError();
diff --git a/frontend/src/hooks/common/useMultipleImageUpload.ts b/frontend/src/hooks/common/useMultipleImageUpload.ts
index a17e755d4..df6151e66 100644
--- a/frontend/src/hooks/common/useMultipleImageUpload.ts
+++ b/frontend/src/hooks/common/useMultipleImageUpload.ts
@@ -1,12 +1,11 @@
import type { ChangeEvent } from 'react';
-import { useCallback, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import imageCompression from 'browser-image-compression';
import { useImageMutation } from '@hooks/api/useImageMutation';
-import { useToast } from '@hooks/common/useToast';
-import convertImageNames from '@utils/convertImageNames';
+import { convertToImageNames, convertToImageUrls } from '@utils/convertImage';
import { IMAGE_COMPRESSION_OPTIONS } from '@constants/image';
import { TRIP_ITEM_ADD_MAX_IMAGE_UPLOAD_COUNT } from '@constants/ui';
@@ -14,117 +13,129 @@ import { TRIP_ITEM_ADD_MAX_IMAGE_UPLOAD_COUNT } from '@constants/ui';
interface UseMultipleImageUploadParams {
initialImageNames: string[];
maxUploadCount?: number;
- handleInitialImage?: (images: string[]) => void;
- onSuccess?: CallableFunction;
+ updateFormImage?: CallableFunction;
onError?: CallableFunction;
}
export const useMultipleImageUpload = ({
initialImageNames,
maxUploadCount = TRIP_ITEM_ADD_MAX_IMAGE_UPLOAD_COUNT,
- onSuccess,
+ updateFormImage,
onError,
}: UseMultipleImageUploadParams) => {
const imageMutation = useImageMutation();
const isImageUploading = imageMutation.isLoading;
- const convertedImageNames = convertImageNames(initialImageNames);
+ const initialImageUrls = convertToImageUrls([...initialImageNames]);
- const { createToast } = useToast();
- const [uploadedImageNames, setUploadedImageNames] = useState(convertedImageNames);
+ const [imageUrls, setImageUrls] = useState(initialImageUrls);
+ const [imageNames, setImageNames] = useState(initialImageNames);
- const handleImageUpload = useCallback(
- async (event: ChangeEvent) => {
- const originalImageFiles = event.target.files;
-
- if (!originalImageFiles) return;
+ useEffect(() => {
+ updateFormImage?.(imageNames);
+ }, [imageNames, updateFormImage]);
- if (originalImageFiles.length + uploadedImageNames.length > maxUploadCount) {
- onError?.();
+ const compressImages = useCallback(async (originalImageFiles: FileList): Promise => {
+ const imageFiles: File[] = [];
- return;
- }
+ try {
+ await Promise.all(
+ [...originalImageFiles].map(async (file) => {
+ const compressedImageFile = await imageCompression(file, IMAGE_COMPRESSION_OPTIONS);
- const prevImageNames = uploadedImageNames;
+ const fileName = file.name;
+ const fileType = compressedImageFile.type;
+ const convertedFile = new File([compressedImageFile], fileName, { type: fileType });
- setUploadedImageNames((prevImageNames) => {
- const newImageNames = [...originalImageFiles].map((file) => URL.createObjectURL(file));
-
- return [...prevImageNames, ...newImageNames];
- });
-
- const imageFiles: File[] = [];
-
- try {
- await Promise.all(
- [...originalImageFiles].map(async (file) => {
- const compressedImageFile = await imageCompression(file, IMAGE_COMPRESSION_OPTIONS);
-
- const fileName = file.name;
- const fileType = compressedImageFile.type;
- const convertedFile = new File([compressedImageFile], fileName, { type: fileType });
+ imageFiles.push(convertedFile);
+ })
+ );
+ } catch (e) {
+ imageFiles.push(...originalImageFiles);
+ }
- imageFiles.push(convertedFile);
- })
- );
- } catch (e) {
- imageFiles.push(...originalImageFiles);
- }
+ return imageFiles;
+ }, []);
- const imageUploadFormData = new FormData();
+ const convertToImageFormData = useCallback(
+ async (imageFiles: FileList) => {
+ const compressedImages = await compressImages(imageFiles);
+ const imageFormData = new FormData();
- [...imageFiles].forEach((file) => {
- imageUploadFormData.append('images', file);
+ compressedImages.forEach((file) => {
+ imageFormData.append('images', file);
});
+ return imageFormData;
+ },
+ [compressImages]
+ );
+
+ const postImageNames = useCallback(
+ async (images: FormData) => {
imageMutation.mutate(
- { images: imageUploadFormData },
+ { images },
{
onSuccess: ({ imageNames }) => {
if (maxUploadCount === 1) {
- onSuccess?.([...imageNames]);
- createToast('이미지 업로드에 성공했습니다', 'success');
+ setImageNames([...imageNames]);
return;
}
- onSuccess?.([...convertedImageNames, ...imageNames]);
- createToast('이미지 업로드에 성공했습니다', 'success');
+ setImageNames((prev) => [...prev, ...imageNames]);
},
onError: () => {
- setUploadedImageNames(prevImageNames);
+ setImageUrls(initialImageUrls);
},
}
);
+ },
+ [imageMutation, maxUploadCount, initialImageUrls]
+ );
+
+ const handleImageUpload = useCallback(
+ async (event: ChangeEvent) => {
+ const originalImageFiles = event.target.files;
+
+ if (!originalImageFiles) return;
+
+ if (originalImageFiles.length + imageUrls.length > maxUploadCount) {
+ onError?.();
+
+ return;
+ }
+
+ // 화면에 보여지는 이미지 url로 변경 + 업데이트
+ setImageUrls((prevImageUrls) => {
+ const newImageUrls = [...originalImageFiles].map((file) => URL.createObjectURL(file));
+
+ return [...prevImageUrls, ...newImageUrls];
+ });
+
+ const imageFormData = await convertToImageFormData(originalImageFiles);
+ postImageNames(imageFormData);
// eslint-disable-next-line no-param-reassign
event.target.value = '';
},
- [
- createToast,
- imageMutation,
- convertedImageNames,
- maxUploadCount,
- onError,
- onSuccess,
- uploadedImageNames,
- ]
+ [imageUrls, maxUploadCount, convertToImageFormData, postImageNames, onError]
);
const handleImageRemoval = useCallback(
- (selectedImageName: string) => () => {
- setUploadedImageNames((prevImageNames) => {
- const updatedImageNames = prevImageNames.filter(
- (imageName) => imageName !== selectedImageName
- );
-
- onSuccess?.(updatedImageNames);
-
- return updatedImageNames;
+ (selectedImageUrl: string) => () => {
+ setImageUrls((prevImageUrls) => {
+ const updatedImageUrls = prevImageUrls.filter((imageUrl) => imageUrl !== selectedImageUrl);
+ // form에 들어가는 imageName 변경
+ const imageNames = convertToImageNames(updatedImageUrls);
+ updateFormImage?.(imageNames);
+
+ // 화면에 보여지는 imageUrl 변경
+ return updatedImageUrls;
});
},
- [onSuccess]
+ [updateFormImage]
);
- return { isImageUploading, uploadedImageNames, handleImageUpload, handleImageRemoval };
+ return { isImageUploading, imageUrls, handleImageUpload, handleImageRemoval };
};
diff --git a/frontend/src/hooks/trip/useAddTripItemForm.ts b/frontend/src/hooks/trip/useAddTripItemForm.ts
index 091ef6cee..90c1c4cd6 100644
--- a/frontend/src/hooks/trip/useAddTripItemForm.ts
+++ b/frontend/src/hooks/trip/useAddTripItemForm.ts
@@ -107,7 +107,6 @@ export const useAddTripItemForm = ({
return;
}
-
if (!itemId) {
addTripItemMutation.mutate(
{
diff --git a/frontend/src/mocks/data/image.ts b/frontend/src/mocks/data/image.ts
index 4355c8ecc..1d3624a76 100644
--- a/frontend/src/mocks/data/image.ts
+++ b/frontend/src/mocks/data/image.ts
@@ -1,6 +1,5 @@
export const images = [
- 'https://upload.wikimedia.org/wikipedia/commons/f/f9/Open_Happiness_Piccadilly_Circus_Blue-Pink_Hour_120917-1126-jikatu.jpg',
- 'https://e3.365dm.com/17/10/2048x1152/skynews-piccadilly-piccadilly-circus_4131587.jpg',
- 'https://dynamic-media-cdn.tripadvisor.com/media/photo-o/15/9e/a5/6f/regent-str.jpg?w=1200&h=-1&s=1',
- 'https://flashbak.com/wp-content/uploads/2017/01/Piccadilly-by-Night-London-1960-by-Elmar-Ludwig.jpg',
+ 'Open_Happiness_Piccadilly_Circus_Blue-Pink_Hour_120917-1126-jikatu.jpg',
+ 'regent-str.jpg',
+ 'Piccadilly-by-Night-London-1960-by-Elmar-Ludwig.jpg',
];
diff --git a/frontend/src/mocks/handlers/tripItem.ts b/frontend/src/mocks/handlers/tripItem.ts
index 4a8a5369f..872cec452 100644
--- a/frontend/src/mocks/handlers/tripItem.ts
+++ b/frontend/src/mocks/handlers/tripItem.ts
@@ -39,7 +39,7 @@ export const tripItemHandlers = [
},
}
: null,
- imageNames: [],
+ imageNames: response.imageNames,
};
trip.dayLogs[0].items.push(newTripItem);
diff --git a/frontend/src/utils/convertImage.ts b/frontend/src/utils/convertImage.ts
new file mode 100644
index 000000000..c8b75b599
--- /dev/null
+++ b/frontend/src/utils/convertImage.ts
@@ -0,0 +1,13 @@
+export const convertToImageUrl = (imageName: string | null) => {
+ return `${process.env.IMAGE_BASEURL}${imageName}`;
+};
+
+export const convertToImageUrls = (imageNames: string[]) => {
+ return [...imageNames]?.map((imageName) => `${process.env.IMAGE_BASEURL}${imageName}`);
+};
+
+export const convertToImageNames = (imageUrls: string[]) => {
+ return [...imageUrls]?.map((imageUrl) =>
+ imageUrl.replace('blob:', '').replace(`${process.env.IMAGE_BASEURL}`, '')
+ );
+};
diff --git a/frontend/src/utils/convertImageName.ts b/frontend/src/utils/convertImageName.ts
deleted file mode 100644
index d299825c9..000000000
--- a/frontend/src/utils/convertImageName.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-const convertImageName = (imageName: string | null) => {
- return `${process.env.IMAGE_BASEURL}${imageName}`;
-};
-
-export default convertImageName;
diff --git a/frontend/src/utils/convertImageNames.ts b/frontend/src/utils/convertImageNames.ts
deleted file mode 100644
index 10ff859ba..000000000
--- a/frontend/src/utils/convertImageNames.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-const convertImageNames = (imageName: string[]) => {
- return imageName?.map((imageName) => `${process.env.IMAGE_BASEURL}${imageName}`);
-};
-
-export default convertImageNames;