Skip to content

Commit

Permalink
Merge pull request #218 from BCSDLab/feature/#217
Browse files Browse the repository at this point in the history
[공통] 다중 이미지 업로드 방식으로 통일
  • Loading branch information
chaeseungyun authored Mar 29, 2024
2 parents ad6a140 + ebabfe9 commit a26f379
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 28 deletions.
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
name="description"
content="backoffice for koin's store owner"
/>
<meta name="google" content="notranslate">
<link rel="icon" type="image/png" sizes="32x32" href="https://static.koreatech.in/assets/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://static.koreatech.in/assets/favicons/favicon-16x16.png">
<link rel="apple-touch-icon" href="https://static.koreatech.in/assets/favicons/favicon-32x32.png" />
Expand Down
15 changes: 8 additions & 7 deletions src/page/AddMenu/components/AddMenuImgModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import React, { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { ReactComponent as CancelIcon } from 'assets/svg/addmenu/mobile-cancle-icon.svg';
import useAddMenuStore from 'store/addMenu';
import useImageUpload from 'utils/hooks/useImageUpload';
import ErrorMessage from 'page/Auth/Signup/component/ErrorMessage';
import { ERRORMESSAGE } from 'page/ShopRegistration/constant/errorMessage'; import styles from './AddMenuImgModal.module.scss';
import { ERRORMESSAGE } from 'page/ShopRegistration/constant/errorMessage'; import useImagesUpload from 'utils/hooks/useImagesUpload';
import styles from './AddMenuImgModal.module.scss';

interface AddMenuImgModalProps {
isOpen: boolean;
closeModal: (event?: React.MouseEvent | React.KeyboardEvent) => void;
}

export default function AddMenuImgModal({ isOpen, closeModal }: AddMenuImgModalProps) {
const { setImageUrl } = useAddMenuStore();
const { setImageUrls } = useAddMenuStore();

const {
imageFile, imgRef, saveImgFile, uploadError,
} = useImageUpload();
} = useImagesUpload();

const triggerFileInput = () => {
imgRef.current?.click();
Expand All @@ -28,10 +29,10 @@ export default function AddMenuImgModal({ isOpen, closeModal }: AddMenuImgModalP
};
useEffect(() => {
if (imageFile && !uploadError) {
setImageUrl(imageFile);
setImageUrls(imageFile);
closeModal();
}
}, [imageFile, uploadError, setImageUrl, closeModal]);
}, [imageFile, uploadError, setImageUrls, closeModal]);

if (!isOpen) return null;

Expand All @@ -44,7 +45,7 @@ export default function AddMenuImgModal({ isOpen, closeModal }: AddMenuImgModalP
<span className={styles['content__main-text']}>이미지 추가</span>
<span className={styles['content__sub-text']}>메뉴 사진을 추가할 수 있습니다.</span>
<div className={styles['content__button-container']}>
<input type="file" accept="image/*" style={{ display: 'none' }} onChange={handleImageChange} ref={imgRef} />
<input type="file" accept="image/*" style={{ display: 'none' }} onChange={handleImageChange} ref={imgRef} multiple />
<button type="button" className={styles['content__album-button']} onClick={triggerFileInput}>사진 앨범</button>
<button type="button" className={styles['content__camera-button']} onClick={triggerCameraInput}>카메라 촬영</button>
</div>
Expand Down
44 changes: 23 additions & 21 deletions src/page/AddMenu/components/MenuImage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import useMediaQuery from 'utils/hooks/useMediaQuery';
import useBooleanState from 'utils/hooks/useBooleanState';
import AddMenuImgModal from 'page/AddMenu/components/AddMenuImgModal';
import useAddMenuStore from 'store/addMenu';
import useImageUpload from 'utils/hooks/useImageUpload';
import ErrorMessage from 'page/Auth/Signup/component/ErrorMessage';
import { ERRORMESSAGE } from 'page/ShopRegistration/constant/errorMessage';
import useImagesUpload from 'utils/hooks/useImagesUpload';
import styles from './MenuImage.module.scss';

interface MenuImageProps {
Expand All @@ -16,15 +16,16 @@ interface MenuImageProps {

export default function MenuImage({ isComplete }: MenuImageProps) {
const { isMobile } = useMediaQuery();
const { imageUrl, setImageUrl, removeImageUrl } = useAddMenuStore();
const { imageUrl, setImageUrls, removeImageUrl } = useAddMenuStore();
const {
value: isAddMenuImgModal,
setTrue: openAddMenuImgModal,
setFalse: closeAddMenuImgModal,
} = useBooleanState(false);

const {
imageFile, imgRef, saveImgFile, uploadError,
} = useImageUpload();
} = useImagesUpload();
const handleAddImage = () => {
imgRef.current?.click();
};
Expand All @@ -36,9 +37,9 @@ export default function MenuImage({ isComplete }: MenuImageProps) {
};
useEffect(() => {
if (imageFile) {
setImageUrl(imageFile);
setImageUrls(imageFile);
}
}, [imageFile, setImageUrl]);
}, [imageFile, setImageUrls]);
return (
<div>
{isMobile ? (
Expand All @@ -52,14 +53,14 @@ export default function MenuImage({ isComplete }: MenuImageProps) {
<div key={image} className={styles['mobile__new-image__item']}>
<img src={image} alt={`Selected ${index + 1}`} className={styles['mobile__new-image__selected']} />
{!isComplete && (
<button
type="button"
onClick={() => handleDeleteImage(image)}
className={styles['mobile__delete-img-button']}
aria-label="Delete image"
>
<MobileDeleteImgIcon className={styles['mobile__delete-img-icon']} />
</button>
<button
type="button"
onClick={() => handleDeleteImage(image)}
className={styles['mobile__delete-img-button']}
aria-label="Delete image"
>
<MobileDeleteImgIcon className={styles['mobile__delete-img-icon']} />
</button>
)}
</div>
))}
Expand Down Expand Up @@ -88,14 +89,14 @@ export default function MenuImage({ isComplete }: MenuImageProps) {
<div key={image} className={styles['new-image__item']}>
<img src={image} alt={`Selected ${index + 1}`} className={styles['new-image__selected']} />
{!isComplete && (
<button
type="button"
onClick={() => handleDeleteImage(image)}
className={styles['new-image__delete-img-button']}
aria-label="Delete image"
>
<MobileDeleteImgIcon className={styles['new-image__delete-img-icon']} />
</button>
<button
type="button"
onClick={() => handleDeleteImage(image)}
className={styles['new-image__delete-img-button']}
aria-label="Delete image"
>
<MobileDeleteImgIcon className={styles['new-image__delete-img-icon']} />
</button>
)}
</div>
))}
Expand All @@ -114,6 +115,7 @@ export default function MenuImage({ isComplete }: MenuImageProps) {
style={{ display: 'none' }}
onChange={handleImageChange}
ref={imgRef}
multiple
/>
</div>
<div className={styles['image-error-message']}>
Expand Down
4 changes: 4 additions & 0 deletions src/store/addMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface AddMenuStore {
setCategoryIds: (categoryIds: number[]) => void;
setDescription: (description: string) => void;
setImageUrl: (newImageUrl: string) => void;
setImageUrls: (newImageUrls: string[]) => void,
removeImageUrl: (imageUrlToRemove: string) => void;
setIsSingle: (isSingle: boolean) => void;
setName: (name: string) => void;
Expand All @@ -45,6 +46,9 @@ const useAddMenuStore = create<AddMenuStore>((set) => ({
setImageUrl: (newImageUrl) => set((state) => ({
imageUrl: [...state.imageUrl, newImageUrl],
})),
setImageUrls: (newImageUrl) => set((state) => ({
imageUrl: [...state.imageUrl, ...newImageUrl],
})),
removeImageUrl: (imageUrlToRemove) => set((state) => ({
imageUrl: state.imageUrl.filter((img) => img !== imageUrlToRemove),
})),
Expand Down
75 changes: 75 additions & 0 deletions src/utils/hooks/useImagesUpload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { uploadFile } from 'api/uploadFile/Uploadfile';
import { useRef, useState } from 'react';
import showToast from 'utils/ts/showToast';

// 정의할 수 있는 에러 타입
type UploadError = '413' | '415' | '404' | '422' | 'networkError' | '401' | '';

const MAXSIZE = 1024 * 1024 * 10;

/* eslint-disable */
export default function useImagesUpload() {
const [imageFile, setImageFile] = useState<string[]>([]);
const [uploadError, setUploadError] = useState<UploadError>('');
const imgRef = useRef<HTMLInputElement>(null);

const saveImgFile = async () => {
const files = imgRef.current?.files;

if (files && (files.length > 3 || imageFile.length >= 3)) {
showToast('error', '파일은 3개까지 등록할 수 있습니다.')
return;
}

if (files && files.length) {
const uploadedFiles: string[] = [];
const correctForm = new RegExp('(.*?)\\.(jpg|jpeg|gif|bmp|png)$');

for (let i = 0; i < files.length; i += 1) {
const file = files[i];

if (file.size > MAXSIZE) {
setUploadError('413'); // 파일 사이즈가 너무 큰 경우
return;
}

if (!correctForm.test(file.name)) {
setUploadError('415'); // 지원하지 않는 타입 에러
return;
}

const formData = new FormData();
formData.append('multipartFile', file);

try {
const data = await uploadFile(formData);
if (data?.data?.file_url) {
uploadedFiles.push(data.data.file_url);
}
} catch (error: any) {
setImageFile([]);
const errorMessage = error.toString();
if (errorMessage.includes('415')) {
setUploadError('415');
} else if (errorMessage.includes('404')) {
setUploadError('404');
} else if (errorMessage.includes('422')) {
setUploadError('422');
} else if (errorMessage.includes('Network Error')) {
setUploadError('networkError');
} else {
setUploadError('401');
}
return;
}
}

setImageFile(uploadedFiles);
setUploadError('');
}
};

return {
imageFile, imgRef, saveImgFile, uploadError,
};
}

0 comments on commit a26f379

Please sign in to comment.