diff --git a/package-lock.json b/package-lock.json index c16d72a..2556a74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.19.2", - "jszip": "^3.10.1", "react": "^18.3.1", "react-cookie": "^7.2.0", "react-dom": "^18.3.1", @@ -9191,11 +9190,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -11009,49 +11003,6 @@ "node": ">=4.0" } }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11129,14 +11080,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -11895,11 +11838,6 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -14836,11 +14774,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", diff --git a/package.json b/package.json index 2535b43..c9ab72e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.19.2", - "jszip": "^3.10.1", "react": "^18.3.1", "react-cookie": "^7.2.0", "react-dom": "^18.3.1", diff --git a/src/apis/getDownloadPhotos.ts b/src/apis/getDownloadPhotos.ts deleted file mode 100644 index 1c0e5c2..0000000 --- a/src/apis/getDownloadPhotos.ts +++ /dev/null @@ -1,19 +0,0 @@ -// photo 관련 다운로드 API는 여기에 작성 바람 - -import { AxiosResponse } from 'axios'; -import { authInstance } from './instance'; - -export const getDownloadPhotosAll = async ( - shareGroupId: number, - profileId: number, -): Promise => { - try { - const res = await authInstance().get( - `/photos/download/all?shareGroupId=${shareGroupId}&profileId=${profileId}`, - ); - return res; - } catch (error) { - console.error('Error: ', error); - throw error; - } -}; diff --git a/src/apis/getPhotos.ts b/src/apis/getPhotos.ts index bdcd559..e437ec7 100644 --- a/src/apis/getPhotos.ts +++ b/src/apis/getPhotos.ts @@ -1,3 +1,4 @@ +import { AxiosResponse } from 'axios'; import { authInstance } from './instance'; interface requestProp { @@ -7,6 +8,63 @@ interface requestProp { size?: number; } +export const getPhotosAll = async ( + requestData: requestProp, +): Promise<{ status: number; data: any }> => { + try { + const { shareGroupId, page, size } = requestData; + // 쿼리 문자열 생성 + const params = new URLSearchParams(); + params.append('shareGroupId', shareGroupId.toString()); + if (page !== undefined) { + params.append('page', page.toString()); + } + if (size !== undefined) { + params.append('size', size.toString()); + } + + const res = await authInstance().get(`/photos/all?${params.toString()}`); + const { status, code, message, data } = res.data; + if (status === 200) { + return { status, data }; + } else { + throw new Error(`Error ${code}: ${message}`); + } + } catch (error) { + console.error('Error: ', error); + throw error; + } +}; + +export const getPhotosEtc = async ( + requestData: requestProp, +): Promise => { + try { + const { shareGroupId, page, size } = requestData; + // 쿼리 문자열 생성 + const params = new URLSearchParams(); + params.append('shareGroupId', shareGroupId.toString()); + if (page !== undefined) { + params.append('page', page.toString()); + } + if (size !== undefined) { + params.append('size', size.toString()); + } + + const res = await authInstance().get(`/photos/etc?${params.toString()}`); + const { status, code, message, data } = res.data; + if (status === 200) { + console.log(data); + return res.data; + } else { + throw new Error(`Error ${code}: ${message}`); + } + } catch (error) { + console.error('Error: ', error); + throw error; + } +}; + export const getPhotos = async ( requestData: requestProp, ): Promise<{ status: number; data: any }> => { diff --git a/src/apis/getPhotosAll.ts b/src/apis/getPhotosDownload.ts similarity index 55% rename from src/apis/getPhotosAll.ts rename to src/apis/getPhotosDownload.ts index f24c516..d1f77eb 100644 --- a/src/apis/getPhotosAll.ts +++ b/src/apis/getPhotosDownload.ts @@ -1,5 +1,6 @@ import getApiResponse from 'recoil/types/apis'; import { authInstance } from './instance'; +import { AxiosResponse } from 'axios'; interface requestProp { shareGroupId: number; @@ -7,25 +8,21 @@ interface requestProp { size?: number; } -export const getPhotosAll = async ( - requestData: requestProp, -): Promise<{ status: number; data: any }> => { +export const getPhotosAllDownload = async ( + shareGroupId: number, +): Promise => { try { - const { shareGroupId, page, size } = requestData; // 쿼리 문자열 생성 const params = new URLSearchParams(); params.append('shareGroupId', shareGroupId.toString()); - if (page !== undefined) { - params.append('page', page.toString()); - } - if (size !== undefined) { - params.append('size', size.toString()); - } - const res = await authInstance().get(`/photos/all?${params.toString()}`); + const res = await authInstance().get( + `/photos/download/all?${params.toString()}`, + ); const { status, code, message, data } = res.data; if (status === 200) { - return { status, data }; + console.log(data); + return res.data; } else { throw new Error(`Error ${code}: ${message}`); } @@ -35,22 +32,17 @@ export const getPhotosAll = async ( } }; -export const getPhotosEtc = async ( - requestData: requestProp, +export const getPhotosEtcDownload = async ( + shareGroupId: number, ): Promise => { try { - const { shareGroupId, page, size } = requestData; // 쿼리 문자열 생성 const params = new URLSearchParams(); params.append('shareGroupId', shareGroupId.toString()); - if (page !== undefined) { - params.append('page', page.toString()); - } - if (size !== undefined) { - params.append('size', size.toString()); - } - const res = await authInstance().get(`/photos/etc?${params.toString()}`); + const res = await authInstance().get( + `/photos/download/etc?${params.toString()}`, + ); const { status, code, message, data } = res.data; if (status === 200) { console.log(data); @@ -63,3 +55,18 @@ export const getPhotosEtc = async ( throw error; } }; + +export const getPhotosAlbumDownload = async ( + shareGroupId: number, + profileId: number, +): Promise => { + try { + const res = await authInstance().get( + `/photos/download/album?shareGroupId=${shareGroupId}&profileId=${profileId}`, + ); + return res; + } catch (error) { + console.error('Error: ', error); + throw error; + } +}; diff --git a/src/apis/instance.ts b/src/apis/instance.ts index c32da74..f112bcf 100644 --- a/src/apis/instance.ts +++ b/src/apis/instance.ts @@ -17,8 +17,8 @@ export const baseInstance = ( export const authInstance = ( options: AxiosRequestConfig = {}, ): AxiosInstance => { - const TOKEN = getCookie('access-token'); - // const TOKEN = process.env.REACT_APP_REFRESH_TOKEN; + // const TOKEN = getCookie('access-token'); + const TOKEN = process.env.REACT_APP_REFRESH_TOKEN; return axios.create({ baseURL: BASE_URL, headers: { diff --git a/src/components/ShareGroup/ShareGroupCarousel/ShareGroupCarousel.tsx b/src/components/ShareGroup/ShareGroupCarousel/ShareGroupCarousel.tsx index dbf0824..08fd6ce 100644 --- a/src/components/ShareGroup/ShareGroupCarousel/ShareGroupCarousel.tsx +++ b/src/components/ShareGroup/ShareGroupCarousel/ShareGroupCarousel.tsx @@ -25,6 +25,9 @@ const ShareGroupCarousel: React.FC = ({ items }) => { handleDragStart, handleDragMove, handleDragEnd, + handleTouchStart, + handleTouchMove, + handleTouchEnd, handleKeyDown, } = useCarousel(items.length, containerRef); @@ -45,12 +48,12 @@ const ShareGroupCarousel: React.FC = ({ items }) => { return ( handleDragStart(e.touches[0].clientX)} - onTouchMove={(e) => handleDragMove(e.touches[0].clientX)} - onTouchEnd={handleDragEnd} - onMouseDown={(e) => handleDragStart(e.clientX)} - onMouseMove={(e) => handleDragMove(e.clientX)} + onMouseDown={(e) => handleDragStart(e.clientX, e.clientY)} + onMouseMove={(e) => handleDragMove(e.clientX, e.clientY)} onMouseUp={handleDragEnd} + onTouchStart={(e) => handleTouchStart(e)} + onTouchMove={(e) => handleTouchMove(e)} + onTouchEnd={handleTouchEnd} onMouseLeave={handleDragEnd} tabIndex={0} onKeyDown={handleKeyDown} diff --git a/src/components/ShareGroup/ShareGroupCarouselItem/ShareGroupCarouselItem.tsx b/src/components/ShareGroup/ShareGroupCarouselItem/ShareGroupCarouselItem.tsx index 3fdaf59..33b3dd0 100644 --- a/src/components/ShareGroup/ShareGroupCarouselItem/ShareGroupCarouselItem.tsx +++ b/src/components/ShareGroup/ShareGroupCarouselItem/ShareGroupCarouselItem.tsx @@ -1,7 +1,7 @@ import React from 'react'; import * as S from './Styles'; import { useNavigate, useParams } from 'react-router-dom'; -import defaultProfile from '../../../assets/samples/emptyProfile.png'; +import defaultProfile from '../../../assets/samples/emptyProfile.png'; import { useSetRecoilState } from 'recoil'; import { dropDownTitle, diff --git a/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx b/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx index 3410d59..c8f0b27 100644 --- a/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx +++ b/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx @@ -9,8 +9,13 @@ import { folderCurrentIndex, shareGroupMemberListState, } from 'recoil/states/share_group'; -import { getDownloadPhotosAll } from 'apis/getDownloadPhotos'; import imageZipDownloader from 'utils/ImageZipDownloader'; +import { + getPhotosAlbumDownload, + getPhotosAllDownload, + getPhotosEtcDownload, +} from 'apis/getPhotosDownload'; +import { itemProp } from '../ShareGroupImageList/ShareGroupImageList'; interface responseProp { photoName: string; @@ -84,20 +89,60 @@ const ShareGroupCloudButton: React.FC = () => { }; const handleDownload = async (): Promise => { - const shareGroupId: number = parseInt(id || '0'); + if (id === undefined) { + alert('다운로드 중 오류가 발생했습니다.'); + console.error('Error: shareGroupId is undefined'); + return; + } + const shareGroupId: number = parseInt(id); const profileId: number = items[currentIndex].profileId; - if (profileId <= 0) return; // 모든 사진 폴더, 기타 사진 폴더는 다운로드 불가 console.log(shareGroupId, profileId); - if (shareGroupId === 0) return; - try { - const response = await getDownloadPhotosAll(shareGroupId, profileId); - console.log(response.data.data); - if (response.status === 200) { - await imageZipDownloader(response.data.data.photoDownloadUrlList); + if (profileId === 0) { + // 모든 사진 다운로드 + try { + const response = await getPhotosAllDownload(shareGroupId); + console.log(response); + if (response.status === 200) { + await imageZipDownloader(response.data.photoDownloadUrlList); + alert('다운로드가 완료되었습니다.'); + } + } catch (error) { + console.error(error); + alert('다운로드 중 오류가 발생했습니다.'); + return; + } + } else if (profileId === -1) { + // 기타 사진 다운로드 + try { + const response = await getPhotosEtcDownload(shareGroupId); + console.log(response); + if ( + response.status === 200 && + response.data.photoDownloadUrlList.length > 0 + ) { + await imageZipDownloader(response.data.photoDownloadUrlList); + alert('다운로드가 완료되었습니다.'); + } else { + alert('다운로드할 사진이 없습니다.'); + } + } catch (error) { + console.error(error); + alert('다운로드 중 오류가 발생했습니다.'); + return; + } + } else { + if (shareGroupId === 0) return; + try { + const response = await getPhotosAlbumDownload(shareGroupId, profileId); + console.log(response); + if (response.status === 200) { + await imageZipDownloader(response.data.data.photoDownloadUrlList); + alert('다운로드가 완료되었습니다.'); + } + } catch (error) { + console.log(error); + alert('다운로드 중 오류가 발생했습니다.'); } - } catch (error) { - console.log(error); - alert('다운로드 중 오류가 발생했습니다.'); } }; diff --git a/src/components/ShareGroup/ShareGroupImageItem/ShareGroupImageItem.tsx b/src/components/ShareGroup/ShareGroupImageItem/ShareGroupImageItem.tsx index 5985d4c..158f7c2 100644 --- a/src/components/ShareGroup/ShareGroupImageItem/ShareGroupImageItem.tsx +++ b/src/components/ShareGroup/ShareGroupImageItem/ShareGroupImageItem.tsx @@ -18,10 +18,9 @@ const ShareGroupImageItem: React.FC = ({ }) => { const [isClicked, setIsClicked] = React.useState(false); - React.useEffect(() => { - setIsClicked(selected); - }, [selected]); - + React.useEffect(() => + setIsClicked(isDownload); + }, [isDownload]); return ( diff --git a/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx b/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx index 7b9dc3d..35f12bd 100644 --- a/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx +++ b/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx @@ -11,8 +11,7 @@ import { useRecoilState } from 'recoil'; import ShareGroupBottomBar from '../ShareGroupBottomBar/ShareGroupBottomBar'; import { deletePhoto } from 'apis/deletePhoto'; import { useLocation, useNavigate } from 'react-router-dom'; -import { getPhotosAll, getPhotosEtc } from 'apis/getPhotosAll'; -import { getPhotos } from 'apis/getPhotos'; +import { getPhotos, getPhotosAll, getPhotosEtc } from 'apis/getPhotos'; export interface itemProp { createdAt: string; diff --git a/src/components/ShareGroup/ShareGroupListItem/ShareGroupListItem.tsx b/src/components/ShareGroup/ShareGroupListItem/ShareGroupListItem.tsx index 45fae99..37bc4f2 100644 --- a/src/components/ShareGroup/ShareGroupListItem/ShareGroupListItem.tsx +++ b/src/components/ShareGroup/ShareGroupListItem/ShareGroupListItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; import * as S from './Styles'; -import defaultProfile from '../../../assets/samples/emptyProfile.png'; +import defaultProfile from '../../../assets/samples/emptyProfile.png'; interface ListProps { name: string; diff --git a/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx b/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx index a997e75..84d45f4 100644 --- a/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx +++ b/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx @@ -7,14 +7,13 @@ import ShareGroupImageList, { } from 'components/ShareGroup/ShareGroupImageList/ShareGroupImageList'; import { useLocation, useNavigate } from 'react-router-dom'; import Loading from 'components/Loading/Loading'; -import { getPhotosAll, getPhotosEtc } from 'apis/getPhotosAll'; -import { getPhotos } from 'apis/getPhotos'; import { useRecoilState, useRecoilValue } from 'recoil'; import { photoRequestState, photoTypeState, shareGroupId, } from 'recoil/states/share_group'; +import { getPhotos, getPhotosAll, getPhotosEtc } from 'apis/getPhotos'; const ShareGroupDetailPage: React.FC = () => { const [isLoading, setIsLoading] = useState(false); diff --git a/src/pages/ShareGroup/ShareGroupFolder/ShareGroupFolder.tsx b/src/pages/ShareGroup/ShareGroupFolder/ShareGroupFolder.tsx index 0b9ebfb..47f28b0 100644 --- a/src/pages/ShareGroup/ShareGroupFolder/ShareGroupFolder.tsx +++ b/src/pages/ShareGroup/ShareGroupFolder/ShareGroupFolder.tsx @@ -52,7 +52,7 @@ const ShareGroupFolder: React.FC = () => { isAllPhoto: true, }, { - profileId: 0, + profileId: -1, name: '기타 사진', image: '', memberId: 0, diff --git a/src/utils/UseCarousel.ts b/src/utils/UseCarousel.ts index 27f31f8..293e48a 100644 --- a/src/utils/UseCarousel.ts +++ b/src/utils/UseCarousel.ts @@ -10,6 +10,8 @@ export const useCarousel = ( const [offset, setOffset] = useState(0); const [isDragging, setIsDragging] = useState(false); const startX = useRef(0); + const startY = useRef(0); // 추가된 부분 + const clickTimeout = useRef(null); const updateOffset = useCallback(() => { if (containerRef.current) { @@ -37,17 +39,24 @@ export const useCarousel = ( [currentIndex, itemCount], ); - const handleDragStart = useCallback((clientX: number) => { + const handleDragStart = useCallback((clientX: number, clientY: number) => { setIsDragging(true); startX.current = clientX; + startY.current = clientY; // 추가된 부분 + if (clickTimeout.current) { + clearTimeout(clickTimeout.current); + } }, []); const handleDragMove = useCallback( - (clientX: number) => { + (clientX: number, clientY: number) => { if (!isDragging) return; - const diff = startX.current - clientX; - if (Math.abs(diff) > 50) { - handleSwipe(diff > 0 ? 'left' : 'right'); + const diffX = startX.current - clientX; + const diffY = startY.current - clientY; // 추가된 부분 + + // 수평 이동 거리가 수직 이동 거리보다 큰 경우에만 드래그로 간주 + if (Math.abs(diffX) > 50 && Math.abs(diffX) > Math.abs(diffY)) { + handleSwipe(diffX > 0 ? 'left' : 'right'); setIsDragging(false); } }, @@ -55,9 +64,31 @@ export const useCarousel = ( ); const handleDragEnd = useCallback(() => { - setIsDragging(false); + clickTimeout.current = setTimeout(() => { + setIsDragging(false); + }, 300); // 300ms 이내에 드래그가 끝나면 클릭으로 간주 }, []); + const handleTouchStart = useCallback( + (e: React.TouchEvent) => { + const touch = e.touches[0]; + handleDragStart(touch.clientX, touch.clientY); + }, + [handleDragStart], + ); + + const handleTouchMove = useCallback( + (e: React.TouchEvent) => { + const touch = e.touches[0]; + handleDragMove(touch.clientX, touch.clientY); + }, + [handleDragMove], + ); + + const handleTouchEnd = useCallback(() => { + handleDragEnd(); + }, [handleDragEnd]); + const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'ArrowLeft') { @@ -76,6 +107,9 @@ export const useCarousel = ( handleDragStart, handleDragMove, handleDragEnd, + handleTouchStart, // 추가된 부분 + handleTouchMove, // 추가된 부분 + handleTouchEnd, // 추가된 부분 handleKeyDown, }; };