Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] refactor: 이미지 압축 라이브러리 도입 #786

Merged
merged 7 commits into from
Oct 18, 2023
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
2 changes: 2 additions & 0 deletions frontend/.storybook/preview-body.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,6 @@
</symbol>
</svg>
</div>
<div id="dialog-container"></div>
<div id="toast-container"></div>

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
"test:coverage": "jest --watchAll --coverage"
},
"dependencies": {
"@fun-eat/design-system": "^0.3.15",
"@fun-eat/design-system": "^0.3.18",
"@tanstack/react-query": "^4.32.6",
"@tanstack/react-query-devtools": "^4.32.6",
"browser-image-compression": "^2.0.2",
"dayjs": "^1.11.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down Expand Up @@ -46,7 +47,6 @@
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"babel-plugin-styled-components": "^2.1.4",
"browser-image-compression": "^2.0.2",
"copy-webpack-plugin": "^11.0.0",
"dotenv-webpack": "^8.0.1",
"eslint": "^8.44.0",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</head>
<body>
<div id="root"></div>
<div id="dialog-container"></div>
<div id="toast-container"></div>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styled from 'styled-components';

import { IMAGE_MAX_SIZE } from '@/constants';
import { useEnterKeyDown } from '@/hooks/common';
import { useToastActionContext } from '@/hooks/context';

interface ReviewImageUploaderProps {
previewImage: string;
Expand All @@ -13,6 +14,7 @@ interface ReviewImageUploaderProps {

const ImageUploader = ({ previewImage, uploadImage, deleteImage }: ReviewImageUploaderProps) => {
const { inputRef, handleKeydown } = useEnterKeyDown();
const { toast } = useToastActionContext();

const handleImageUpload: ChangeEventHandler<HTMLInputElement> = (event) => {
if (!event.target.files) {
Expand All @@ -22,7 +24,7 @@ const ImageUploader = ({ previewImage, uploadImage, deleteImage }: ReviewImageUp
const imageFile = event.target.files[0];

if (imageFile.size > IMAGE_MAX_SIZE) {
alert('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.');
toast.error('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.');
event.target.value = '';
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => {
const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]);

useEffect(() => {
handleOpenBottomSheet();
}, []);

return (
<BottomSheet ref={ref} isClosing={isClosing} close={handleCloseBottomSheet}>
<BottomSheet isOpen={isOpen} isClosing={isClosing} close={handleCloseBottomSheet}>
<SortOptionList
options={PRODUCT_SORT_OPTIONS}
selectedOption={selectedOption}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ImageUploader, SvgIcon } from '@/components/Common';
import { ProductOverviewItem } from '@/components/Product';
import { MIN_DISPLAYED_TAGS_LENGTH } from '@/constants';
import { useFormData, useImageUploader, useScroll } from '@/hooks/common';
import { useReviewFormActionContext, useReviewFormValueContext } from '@/hooks/context';
import { useReviewFormActionContext, useReviewFormValueContext, useToastActionContext } from '@/hooks/context';
import { useProductDetailQuery } from '@/hooks/queries/product';
import { useReviewRegisterFormMutation } from '@/hooks/queries/review';
import type { ReviewRequest } from '@/types/review';
Expand All @@ -29,10 +29,11 @@ interface ReviewRegisterFormProps {

const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog, initTabMenu }: ReviewRegisterFormProps) => {
const { scrollToPosition } = useScroll();
const { previewImage, imageFile, uploadImage, deleteImage } = useImageUploader();
const { isImageUploading, previewImage, imageFile, uploadImage, deleteImage } = useImageUploader();

const reviewFormValue = useReviewFormValueContext();
const { resetReviewFormValue } = useReviewFormActionContext();
const { toast } = useToastActionContext();

const { data: productDetail } = useProductDetailQuery(productId);
const { mutate, isLoading } = useReviewRegisterFormMutation(productId);
Expand All @@ -41,7 +42,8 @@ const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog, initTabMe
reviewFormValue.rating > MIN_RATING_SCORE &&
reviewFormValue.tagIds.length >= MIN_SELECTED_TAGS_COUNT &&
reviewFormValue.tagIds.length <= MIN_DISPLAYED_TAGS_LENGTH &&
reviewFormValue.content.length > MIN_CONTENT_LENGTH;
reviewFormValue.content.length > MIN_CONTENT_LENGTH &&
!isImageUploading;

const formData = useFormData<ReviewRequest>({
imageKey: 'image',
Expand All @@ -64,15 +66,16 @@ const ReviewRegisterForm = ({ productId, targetRef, closeReviewDialog, initTabMe
resetAndCloseForm();
initTabMenu();
scrollToPosition(targetRef);
toast.success('📝 리뷰가 등록 됐어요');
},
onError: (error) => {
resetAndCloseForm();
if (error instanceof Error) {
alert(error.message);
toast.error(error.message);
return;
}

alert('리뷰 등록을 다시 시도해주세요');
toast.error('리뷰 등록을 다시 시도해주세요');
},
});
};
Expand Down
38 changes: 35 additions & 3 deletions frontend/src/hooks/common/useImageUploader.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import imageCompression from 'browser-image-compression';
import { useState } from 'react';

import { useToastActionContext } from '../context';

const isImageFile = (file: File) => file.type !== 'image/png' && file.type !== 'image/jpeg';

const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true,
};

const useImageUploader = () => {
const { toast } = useToastActionContext();

const [imageFile, setImageFile] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
const [previewImage, setPreviewImage] = useState('');

const uploadImage = (imageFile: File) => {
const uploadImage = async (imageFile: File) => {
if (isImageFile(imageFile)) {
alert('이미지 파일만 업로드 가능합니다.');
toast.error('이미지 파일만 업로드 가능합니다.');
return;
}

setPreviewImage(URL.createObjectURL(imageFile));
setImageFile(imageFile);

try {
setIsImageUploading(true);

const compressedFile = await imageCompression(imageFile, options);
const compressedImageFilePromise = imageCompression.getFilefromDataUrl(
await imageCompression.getDataUrlFromFile(compressedFile),
compressedFile.name
);
compressedImageFilePromise
.then((result) => {
setImageFile(result);
})
.then(() => {
setIsImageUploading(false);
toast.success('이미지가 성공적으로 등록 됐습니다');
});
} catch (error) {
console.log(error);
}
};

const deleteImage = () => {
Expand All @@ -23,6 +54,7 @@ const useImageUploader = () => {
};

return {
isImageUploading,
previewImage,
imageFile,
uploadImage,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/pages/MemberModifyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SectionTitle, SvgIcon } from '@/components/Common';
import { MemberModifyInput } from '@/components/Members';
import { IMAGE_MAX_SIZE } from '@/constants';
import { useFormData, useImageUploader } from '@/hooks/common';
import { useToastActionContext } from '@/hooks/context';
import { useMemberModifyMutation, useMemberQuery } from '@/hooks/queries/members';
import type { MemberRequest } from '@/types/member';

Expand All @@ -16,6 +17,7 @@ export const MemberModifyPage = () => {
const { mutate } = useMemberModifyMutation();

const { previewImage, imageFile, uploadImage } = useImageUploader();
const { toast } = useToastActionContext();

const [nickname, setNickname] = useState(member?.nickname ?? '');
const navigate = useNavigate();
Expand Down Expand Up @@ -43,7 +45,7 @@ export const MemberModifyPage = () => {
const imageFile = event.target.files[0];

if (imageFile.size > IMAGE_MAX_SIZE) {
alert('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.');
toast.error('이미지 크기가 너무 커요. 5MB 이하의 이미지를 골라주세요.');
event.target.value = '';
return;
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/ProductDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const ProductDetailPage = () => {
const tabRef = useRef<HTMLUListElement>(null);

const { selectedOption, selectSortOption } = useSortOption(REVIEW_SORT_OPTIONS[0]);
const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const [activeSheet, setActiveSheet] = useState<'registerReview' | 'sortOption'>('sortOption');
const { gaEvent } = useGA();

Expand Down Expand Up @@ -136,7 +136,7 @@ export const ProductDetailPage = () => {
/>
</ReviewRegisterButtonWrapper>
<ScrollButton targetRef={productDetailPageRef} />
<BottomSheet maxWidth="600px" ref={ref} isClosing={isClosing} close={handleCloseBottomSheet}>
<BottomSheet maxWidth="600px" isOpen={isOpen} isClosing={isClosing} close={handleCloseBottomSheet}>
{activeSheet === 'registerReview' ? (
<ReviewFormProvider>
<ReviewRegisterForm
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/ProductListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ProductListPage = () => {
const { category } = useParams();
const productListRef = useRef<HTMLDivElement>(null);

const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { selectedOption, selectSortOption } = useSortOption(PRODUCT_SORT_OPTIONS[0]);
const { reset } = useQueryErrorResetBoundary();
const { gaEvent } = useGA();
Expand Down Expand Up @@ -68,7 +68,7 @@ export const ProductListPage = () => {
</ProductListContainer>
</ProductListSection>
<ScrollButton targetRef={productListRef} />
<BottomSheet ref={ref} isClosing={isClosing} maxWidth="600px" close={handleCloseBottomSheet}>
<BottomSheet isOpen={isOpen} isClosing={isClosing} maxWidth="600px" close={handleCloseBottomSheet}>
<SortOptionList
options={PRODUCT_SORT_OPTIONS}
selectedOption={selectedOption}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/RecipePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const REGISTER_RECIPE_AFTER_LOGIN = '로그인 후 꿀조합을 작성할 수
export const RecipePage = () => {
const [activeSheet, setActiveSheet] = useState<'registerRecipe' | 'sortOption'>('sortOption');
const { selectedOption, selectSortOption } = useSortOption(RECIPE_SORT_OPTIONS[0]);
const { ref, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { isOpen, isClosing, handleOpenBottomSheet, handleCloseBottomSheet } = useBottomSheet();
const { reset } = useQueryErrorResetBoundary();
const { gaEvent } = useGA();

Expand Down Expand Up @@ -72,7 +72,7 @@ export const RecipePage = () => {
/>
</RecipeRegisterButtonWrapper>
<ScrollButton targetRef={recipeRef} isRecipePage />
<BottomSheet ref={ref} isClosing={isClosing} maxWidth="600px" close={handleCloseBottomSheet}>
<BottomSheet isOpen={isOpen} isClosing={isClosing} maxWidth="600px" close={handleCloseBottomSheet}>
{activeSheet === 'sortOption' ? (
<SortOptionList
options={RECIPE_SORT_OPTIONS}
Expand Down
8 changes: 4 additions & 4 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1375,10 +1375,10 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9"
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==

"@fun-eat/design-system@^0.3.15":
version "0.3.15"
resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.3.15.tgz#61a9a01a82f84fa5627c49bd646cb72ca9e648c8"
integrity sha512-uhn5UZWfvQhNz/2sOoMwDr7Hj7SSx94bN35jifuYpm7ju0A8LHfivmu0mAbrMojuQ6XKYf0ZUME8FMMHwpw9Fg==
"@fun-eat/design-system@^0.3.18":
version "0.3.18"
resolved "https://registry.yarnpkg.com/@fun-eat/design-system/-/design-system-0.3.18.tgz#0c930437cd47923a9daffbaec748ef5db3b4d0c1"
integrity sha512-d1yfTLJLKPakFzf/wiDcLkRi5cit16hDJClH4+Mj6nMtChxMeUu3VU+i4oCJNqaNjZHDw9wOa+7L4kmIcKQnRg==

"@humanwhocodes/config-array@^0.11.11":
version "0.11.11"
Expand Down
Loading