Skip to content

Commit

Permalink
Merge pull request #274 from ODOICHON/feat/#273
Browse files Browse the repository at this point in the history
게시글 작성 시 S3 이미지 관련 기능 추가
  • Loading branch information
JunJongHun authored Jun 14, 2024
2 parents 808c61f + 0169aa2 commit 22c608a
Show file tree
Hide file tree
Showing 15 changed files with 195 additions and 40 deletions.
22 changes: 18 additions & 4 deletions src/apis/uploadS3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
VITE_S3_SECRET_ACCESS_KEY,
VITE_S3_BUCKET_NAME,
VITE_S3_REGION,
VITE_S3_DOMAIN,
} = import.meta.env;

AWS.config.update({
Expand All @@ -19,12 +20,10 @@ const s3 = new AWS.S3();
const bucketName = VITE_S3_BUCKET_NAME;

export const uploadFile = async (file: File) => {
const { type, name } = file;
const { type } = file;
const params = {
Bucket: bucketName,
Key: `${name}/${v4().toString().replace('-', '')}.${
file.type.split('/')[1]
}`,
Key: `${v4().toString().replaceAll('-', '')}.${type.split('/')[1]}`,
Body: file,
ContentType: type,
ACL: 'public-read',
Expand All @@ -36,3 +35,18 @@ export const uploadFile = async (file: File) => {
alert((err as AxiosError<ApiResponseType>).response?.data.message);
}
};

export const deleteFile = (urls: string[]) => {
urls.map(async (url) => {
try {
await s3
.deleteObject({
Bucket: bucketName,
Key: url.split(VITE_S3_DOMAIN)[1].slice(1),
})
.promise();
} catch (err) {
console.error('이미지를 삭제하는데 실패했습니다.');
}
});
};
4 changes: 2 additions & 2 deletions src/components/Community/Board/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import { CommunityBoardType } from '@/types/Board/communityType';
import { getCategoryName, getPrefixCategoryName } from '@/utils/utils';
import { THUMBNAIL_SIZE_OPTION } from '@/constants/image';
import styles from './styles.module.scss';

type CommunityBoardProps = Omit<CommunityBoardType, 'code'>;
Expand Down Expand Up @@ -52,7 +51,8 @@ export default function CommunityBoard({
{imageUrl && (
<img
className={styles.thumbnailImage}
src={imageUrl + THUMBNAIL_SIZE_OPTION}
// src={imageUrl + THUMBNAIL_SIZE_OPTION}
src={imageUrl}
alt="thumbnail"
/>
)}
Expand Down
54 changes: 47 additions & 7 deletions src/components/Community/Quill/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { QueryKeys, restFetcher } from '@/queryClient';
import { BoardFormType } from '@/types/Board/boardType';
import { CommunityBoardDetailType } from '@/types/Board/communityType';
import getImageUrls from '@/utils/Quill/getImageUrls';
import getNotUsedImageUrl from '@/utils/Quill/getNotUsedImageUrl';
import { PostBoardAPI } from '@/apis/boards';
import { deleteFile } from '@/apis/uploadS3';
import { imageStore } from '@/store/imageStore';
import useModalState from '@/hooks/useModalState';
import useQuillModules from '@/hooks/useQuillModules';
import useToastMessageType from '@/hooks/useToastMessageType';
Expand All @@ -25,19 +28,22 @@ type CommunityQuillProps = {
// TODO: 이미지 10개 이상 등록 불가

export default function CommunityQuill({ queryParam }: CommunityQuillProps) {
const { images, setImages, resetImages } = imageStore();
const navigate = useNavigate();
const location = useLocation();
const boardData: CommunityBoardDetailType | null = location.state;
const { modalState, handleModalOpen, handleModalClose } = useModalState();
const { toastMessageProps, handleToastMessageProps } = useToastMessageType();

const QuillRef = useRef<ReactQuill>();

const [title, setTitle] = useState(boardData ? boardData.title : '');
const [contents, setContents] = useState('');
const [category, setCategory] = useState(boardData ? boardData.category : '');
const [isProcessing, setIsProcessing] = useState(false);

const QuillRef = useRef<ReactQuill>();
const imagesRef = useRef(images);
const isProcessingRef = useRef(isProcessing);

const prefixCategory =
queryParam === 'free_board' ? 'DEFAULT' : 'ADVERTISEMENT';
const categoryList =
Expand Down Expand Up @@ -75,15 +81,20 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) {
);

// 이미지를 업로드 하기 위한 함수
const modules = useQuillModules(QuillRef);
const modules = useQuillModules(QuillRef, setImages);
const onChange = (content: string) => {
setContents(content);
};

const onPost = async () => {
setIsProcessing(true);
if (!checkBeforePost(title, contents)) {
setIsProcessing(false);
return;
}

const imageUrls = [...getImageUrls(contents)];
if (!checkBeforePost(title, contents)) return;
const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls);

const BoardForm: BoardFormType = {
title,
Expand All @@ -95,9 +106,12 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) {
prefixCategory,
fixed: false,
};

try {
const response = await PostBoardAPI(BoardForm);
if (response?.code === 'SUCCESS') {
deleteFile(notUsedImageUrls);
resetImages();
handleToastMessageProps('POST_CREATE_SUCCESS', () => {
handleModalClose();
navigate(`/community/${queryParam}`);
Expand All @@ -109,30 +123,56 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) {
}
} catch (error) {
console.error(error);
} finally {
setIsProcessing(false);
}
};

const onUpdate = () => {
setIsProcessing(true);
if (!checkBeforePost(title, contents)) {
setIsProcessing(false);
return;
}

const imageUrls = [...getImageUrls(contents)];
const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls);

const BoardForm: BoardFormType = {
title,
code: contents,
category,
imageUrls: [...getImageUrls(contents)],
imageUrls,
prefixCategory,
fixed: false,
};
mutate(BoardForm);
try {
mutate(BoardForm);
deleteFile(notUsedImageUrls);
resetImages();
} catch (error) {
console.error(error);
setIsProcessing(false);
}
};

useEffect(() => {
imagesRef.current = images;
isProcessingRef.current = isProcessing;
}, [images, isProcessing]);

useEffect(() => {
// 개발모드에선 StricMode 때문에 같은글이 두번 넣어짐. StrictMode를 해제하고 테스트하자
if (boardData) {
QuillRef.current
?.getEditor()
.clipboard.dangerouslyPasteHTML(0, boardData.code);
}
return () => {
if (!isProcessingRef.current) {
deleteFile(imagesRef.current);
resetImages();
}
};
}, []);

return (
Expand Down
59 changes: 49 additions & 10 deletions src/components/Introduce/Quill/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { QueryKeys, restFetcher } from '@/queryClient';
import { BoardFormType } from '@/types/Board/boardType';
import { IntroBoardDetailType } from '@/types/Board/introType';
import getImageUrls from '@/utils/Quill/getImageUrls';
import getNotUsedImageUrl from '@/utils/Quill/getNotUsedImageUrl';
import { PostBoardAPI } from '@/apis/boards';
import { uploadFile } from '@/apis/uploadS3';
import { deleteFile, uploadFile } from '@/apis/uploadS3';
import { imageStore } from '@/store/imageStore';
import useModalState from '@/hooks/useModalState';
import useQuillModules from '@/hooks/useQuillModules';
import useToastMessageType from '@/hooks/useToastMessageType';
Expand All @@ -25,15 +27,13 @@ import styles from './styles.module.scss';
const { VITE_S3_DOMAIN } = import.meta.env;

export default function IntroduceQuill() {
const { images, setImages, resetImages } = imageStore();
const navigate = useNavigate();
const location = useLocation();
const boardData: IntroBoardDetailType | null = location.state;
const { modalState, handleModalOpen, handleModalClose } = useModalState();
const { toastMessageProps, handleToastMessageProps } = useToastMessageType();

const QuillRef = useRef<ReactQuill>();
const thumbnailRef = useRef<HTMLInputElement>(null);

const [title, setTitle] = useState(boardData ? boardData.title : '');
const [contents, setContents] = useState('');
const [category, setCategory] = useState(boardData ? boardData.category : '');
Expand All @@ -45,6 +45,11 @@ export default function IntroduceQuill() {
);
const [isProcessing, setIsProcessing] = useState(false);

const QuillRef = useRef<ReactQuill>();
const thumbnailRef = useRef<HTMLInputElement>(null);
const imagesRef = useRef(images);
const isProcessingRef = useRef(isProcessing);

const queryClient = useQueryClient();

const { mutate, isLoading: isUpdateLoading } = useMutation(
Expand Down Expand Up @@ -86,6 +91,7 @@ export default function IntroduceQuill() {
// const imageUrl = VITE_CLOUD_FRONT_DOMAIN + imageName + DEFAULT_OPTIONS;
const imageUrl = VITE_S3_DOMAIN + imageName;
setThumbnail(imageUrl);
setImages(thumbnail);
} catch (error) {
const err = error as AxiosError;
return { ...err.response, success: false };
Expand All @@ -94,15 +100,20 @@ export default function IntroduceQuill() {
};

// 이미지를 업로드 하기 위한 함수
const modules = useQuillModules(QuillRef);
const modules = useQuillModules(QuillRef, setImages);
const onChange = (content: string) => {
setContents(content);
};

const onPost = async () => {
setIsProcessing(true);
const imageUrls = [thumbnail, ...getImageUrls(contents)];
if (!checkBeforePost(title, contents, imageUrls)) return;
if (!checkBeforePost(title, contents, thumbnail)) {
setIsProcessing(false);
return;
}

const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls);

const BoardForm: BoardFormType = {
title,
Expand All @@ -115,6 +126,8 @@ export default function IntroduceQuill() {
try {
const response = await PostBoardAPI(BoardForm);
if (response?.code === 'SUCCESS') {
deleteFile(notUsedImageUrls);
resetImages();
handleToastMessageProps('POST_CREATE_SUCCESS', () => {
handleModalClose();
navigate(`/introduce`);
Expand All @@ -126,30 +139,56 @@ export default function IntroduceQuill() {
}
} catch (error) {
console.error(error);
} finally {
setIsProcessing(false);
}
};

const onUpdate = () => {
setIsProcessing(true);
const imageUrls = [thumbnail, ...getImageUrls(contents)];

if (!checkBeforePost(title, contents, thumbnail)) {
setIsProcessing(false);
return;
}

const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls);

const BoardForm: BoardFormType = {
title,
code: contents,
category,
imageUrls: [thumbnail, ...getImageUrls(contents)],
imageUrls,
prefixCategory: 'INTRO',
fixed: false,
};
mutate(BoardForm);
try {
mutate(BoardForm);
deleteFile(notUsedImageUrls);
resetImages();
} catch (error) {
console.error(error);
setIsProcessing(false);
}
};

useEffect(() => {
imagesRef.current = images;
isProcessingRef.current = isProcessing;
}, [images, isProcessing]);

useEffect(() => {
// 개발모드에선 StricMode 때문에 같은글이 두번 넣어짐. StrictMode를 해제하고 테스트하자
if (boardData) {
QuillRef.current
?.getEditor()
.clipboard.dangerouslyPasteHTML(0, boardData.code);
}
return () => {
if (!isProcessingRef.current) {
deleteFile(imagesRef.current);
resetImages();
}
};
}, []);

return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/Introduce/ReviewBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useNavigate } from 'react-router-dom';
import { IntroBoardType } from '@/types/Board/introType';
import { THUMBNAIL_SIZE_OPTION } from '@/constants/image';
import styles from './styles.module.scss';

type ReviewBoardProps = IntroBoardType;
Expand All @@ -13,7 +12,8 @@ export default function ReviewBoard(props: ReviewBoardProps) {
className={styles.wrapper}
onClick={() => navigate(`/intro_board/${props.boardId}`)}
>
<img src={props.imageUrl + THUMBNAIL_SIZE_OPTION} alt="backgroundImg" />
{/* <img src={props.imageUrl + THUMBNAIL_SIZE_OPTION} alt="backgroundImg" /> */}
<img src={props.imageUrl} alt="backgroundImg" />
<h3>{props.title}</h3>
<p>{props.oneLineContent}...</p>
</div>
Expand Down
12 changes: 9 additions & 3 deletions src/components/Introduce/TrendBoard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { IntroBoardType } from '@/types/Board/introType';
import { THUMBNAIL_SIZE_OPTION } from '@/constants/image';
import { updownVariants } from '@/constants/variants';
import styles from './styles.module.scss';

Expand All @@ -17,12 +16,19 @@ export default function TrendBoard(props: TrendBoardProps) {
onClick={() => navigate(`/intro_board/${props.boardId}`)}
onMouseOver={() => setIsMouseOver(true)}
onMouseLeave={() => setIsMouseOver(false)}
// style={{
// backgroundImage: isMouseOver
// ? `linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)),
// url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`
// : `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
// url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`,
// }}
style={{
backgroundImage: isMouseOver
? `linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)),
url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`
url('${props.imageUrl}')`
: `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`,
url('${props.imageUrl}')`,
}}
>
<h1>{props.title}</h1>
Expand Down
Loading

0 comments on commit 22c608a

Please sign in to comment.