diff --git a/src/api/s3.ts b/src/api/s3.ts index e10718d7..12c80a20 100644 --- a/src/api/s3.ts +++ b/src/api/s3.ts @@ -24,12 +24,12 @@ export const s3Request = { } }, - uploadImages: async (images: FileList[]) => { + uploadImages: async (images: File[]) => { try { // 이미지 당 presigned url 발급 const res = await axios.post( '/api/s3/presigned', - images.map((image) => image[0].name), + images.map((image) => image.name), ); const presignedUrls = await res.data.data.elements.map((element: PresignedUrlElement) => element.preSignedUrl); console.log('api/s3/presigned response', res); diff --git a/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.module.scss b/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.module.scss index e4b928cc..ffed13fd 100644 --- a/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.module.scss +++ b/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { margin-top: 32px; @@ -20,8 +20,13 @@ position: relative; display: flex; gap: 8px; + padding: 10px 0; + + overflow-x: auto; + overflow-y: visible; &__addBox { + flex: 0 0 7.6rem; width: 7.6rem; height: 7.6rem; background-color: $neutral100; @@ -36,9 +41,7 @@ } &__box { - width: 7.6rem; - height: 7.6rem; - + flex: 0 0 7.6rem; display: flex; justify-content: center; align-items: center; @@ -62,4 +65,8 @@ } } } + + &__imageInput { + display: none; + } } diff --git a/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.tsx b/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.tsx index 963474a6..fc2cef18 100644 --- a/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.tsx +++ b/src/components/Detail/Contents/ReviewBottomSlide/ImagesWrapper/ImagesWrapper.tsx @@ -1,57 +1,86 @@ -import { IoMdCloseCircleOutline } from "react-icons/io"; -import { RiImageAddLine } from "react-icons/ri"; +import {IoMdCloseCircleOutline} from 'react-icons/io'; +import {RiImageAddLine} from 'react-icons/ri'; -import styles from "./ImagesWrapper.module.scss"; +import styles from './ImagesWrapper.module.scss'; +import CustomToast from '@/components/CustomToast/CustomToast'; + +interface imageWrapperProps { + imageUrls: string[]; + setImageUrls: React.Dispatch>; + setImageFileList: React.Dispatch>; +} + +function ImagesWrapper({imageUrls, setImageUrls, setImageFileList}: imageWrapperProps) { + const toast = CustomToast(); + + const handleImageChange = (e: React.ChangeEvent) => { + const files = e.target.files; + + if (files) { + const arr: File[] = []; + const urls: string[] = []; + + for (let i = 0; i < files.length && i < 5; i++) { + arr.push(files[i]); + + const file = files[i]; + const fileUrl = URL.createObjectURL(file); + urls.push(fileUrl); + } + + setImageUrls(urls); + setImageFileList(arr); + + if (files.length > 5) { + toast('파일의 개수가 5개를 초과하였습니다.'); + } + } + }; + + const handleRemoveImage = (index: number) => { + const updatedImageUrls = [...imageUrls]; + + updatedImageUrls.splice(index, 1); + + setImageUrls(updatedImageUrls); + }; + + console.log(imageUrls); -function ImagesWrapper() { return (
-

- 사진과 함께 리뷰를 남겨보세요. (선택) -

-

- 최대 5장까지 업로드 가능합니다. -

+

사진과 함께 리뷰를 남겨보세요. (선택)

+

최대 5장까지 업로드 가능합니다.

-
- -
-
- - # -
-
- - # -
-
- - # -
+ + + {imageUrls && + imageUrls.map((url: string, i: number) => ( +
+ handleRemoveImage(i)} + /> + # +
+ ))}
+
); } diff --git a/src/components/Detail/Contents/ReviewBottomSlide/ReviewBottomSlide.tsx b/src/components/Detail/Contents/ReviewBottomSlide/ReviewBottomSlide.tsx index 1279b494..c3e614c1 100644 --- a/src/components/Detail/Contents/ReviewBottomSlide/ReviewBottomSlide.tsx +++ b/src/components/Detail/Contents/ReviewBottomSlide/ReviewBottomSlide.tsx @@ -17,6 +17,7 @@ import StarsWrapper from './StarsWrapper/StarsWrapper'; import {ReviewBottomSlideProps} from '@/types/detail'; import {usePostReview} from '@/hooks/Detail/useReviews'; +import {s3Request} from '@/api/s3'; function ReviewBottomSlide({placeId, contentTypeId, title, slideOnClose}: ReviewBottomSlideProps) { const [isValuedInput, setIsValuedInput] = useState(false); @@ -29,6 +30,10 @@ function ReviewBottomSlide({placeId, contentTypeId, title, slideOnClose}: Review const [starCount, setStarCount] = useState(0); const [text, setText] = useState(''); const [time, setTime] = useState(new Date()); + const [imageUrls, setImageUrls] = useState([]); + const [imageFileList, setImageFileList] = useState(); + + console.log(imageFileList); const checkBeforeExit = { title: '잠깐!', @@ -51,13 +56,19 @@ function ReviewBottomSlide({placeId, contentTypeId, title, slideOnClose}: Review const postReview = usePostReview(); const handlePostReview = async () => { + const presignedUrls = await s3Request.uploadImages(imageFileList as File[]); + + presignedUrls.map((url: string, i: number) => { + presignedUrls[i] = url.split('?')[0]; + }); + await postReview.mutateAsync({ placeId, contentTypeId, title, rating: starCount, content: text, - images: [], + images: presignedUrls, visitedAt: `${time.getFullYear()}-${('00' + (time.getMonth() + 1).toString()).slice(-2)}-${( '00' + (time.getDay() + 1).toString() ).slice(-2)}`, @@ -104,7 +115,7 @@ function ReviewBottomSlide({placeId, contentTypeId, title, slideOnClose}: Review } /> - +