From 4cb67ff28f7cf7f59e704a25969847189ca0a0da Mon Sep 17 00:00:00 2001 From: Austin Lee Date: Wed, 18 Dec 2024 00:39:55 +0900 Subject: [PATCH] restore direct download when it's not NAVER/KAKAO in-app browser (#48) --- package.json | 3 + pnpm-lock.yaml | 37 ++++++++++++ src/components/atoms/button/Download.tsx | 74 ++++++++++++++++-------- 3 files changed, 90 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index eed6cb6..46c6de2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@mui/material": "^6.1.1", "@tanstack/react-query": "^5.56.2", "dayjs": "^1.11.13", + "file-saver": "^2.0.5", "html2canvas": "^1.4.1", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", @@ -21,6 +22,7 @@ "i18next-http-backend": "^2.6.1", "i18next-resources-to-backend": "^1.2.1", "react": "^18.3.1", + "react-device-detect": "^2.2.3", "react-dom": "^18.3.1", "react-i18next": "^15.0.2", "react-router-dom": "^6.26.2", @@ -30,6 +32,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/file-saver": "^2.0.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9a7191..73eb308 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + file-saver: + specifier: ^2.0.5 + version: 2.0.5 html2canvas: specifier: ^1.4.1 version: 1.4.1 @@ -41,6 +44,9 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 + react-device-detect: + specifier: ^2.2.3 + version: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) @@ -63,6 +69,9 @@ importers: '@eslint/js': specifier: ^9.11.1 version: 9.11.1 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 '@types/react': specifier: ^18.3.3 version: 18.3.10 @@ -659,6 +668,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/file-saver@2.0.7': + resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1063,6 +1075,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-saver@2.0.5: + resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1513,6 +1528,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-device-detect@2.2.3: + resolution: {integrity: sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==} + peerDependencies: + react: '>= 0.14.0' + react-dom: '>= 0.14.0' + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -1755,6 +1776,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + ua-parser-js@1.0.39: + resolution: {integrity: sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==} + hasBin: true + unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -2397,6 +2422,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/file-saver@2.0.7': {} + '@types/json-schema@7.0.15': {} '@types/parse-json@4.0.2': @@ -2995,6 +3022,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-saver@2.0.5: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3426,6 +3455,12 @@ snapshots: queue-microtask@1.2.3: {} + react-device-detect@2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + ua-parser-js: 1.0.39 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -3721,6 +3756,8 @@ snapshots: typescript@5.6.2: {} + ua-parser-js@1.0.39: {} + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 diff --git a/src/components/atoms/button/Download.tsx b/src/components/atoms/button/Download.tsx index 867e6fc..6e65285 100644 --- a/src/components/atoms/button/Download.tsx +++ b/src/components/atoms/button/Download.tsx @@ -7,6 +7,9 @@ import Fab from "@mui/material/Fab"; import { ModalType } from "../../../constants/enum"; import withReactContent from "sweetalert2-react-content"; import Swal from "sweetalert2"; +import { useTranslation } from "react-i18next"; +import saveAs from "file-saver"; +import { isIOS } from "react-device-detect"; const AnnounceSwal = withReactContent(Swal); @@ -23,6 +26,7 @@ interface DownloadProps { * @param tag */ const DownloadButton: React.FC = ({ tag }) => { + const { i18n } = useTranslation(); const theme = useTheme(); const { setModal, hideModal } = useModalStore(); @@ -40,30 +44,52 @@ const DownloadButton: React.FC = ({ tag }) => { ignoreElements: (element) => element.id === "downloader", }); - const body = { - file: canvas.toDataURL("image/jpeg"), - }; - - const uploadURL = `https://api.haulrest.me/file/aecheck`; - const res = await fetch(uploadURL, { - method: "POST", - headers: { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - Authorization: `Bearer ${import.meta.env.VITE_API_KEY}`, - }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(15000), - }); - const url = ((await res.json()) as APIResponse).data; - const link = document.createElement("a"); - - document.body.appendChild(link); - - link.href = url; - link.target = "_blank"; - link.rel = "noopener noreferrer"; - link.click(); + if (navigator.userAgent.match(/NAVER|KAKAOTALK/i)) { + Swal.fire({ + title: "Data Migration", + html: `

${ + i18n.language === "ko" + ? "인앱 브라우저는 서버에 파일을 업로드합니다." + : "Image will be downloaded after uploading to the server." + }

`, + width: 300, + showCancelButton: true, + }).then(async (result) => { + if (result.isConfirmed) { + const body = { + file: canvas.toDataURL("image/jpeg"), + }; + + const uploadURL = `https://api.haulrest.me/file/aecheck`; + const res = await fetch(uploadURL, { + method: "POST", + headers: { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + Authorization: `Bearer ${import.meta.env.VITE_API_KEY}`, + }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(15000), + }); + const url = ((await res.json()) as APIResponse).data; + const link = document.createElement("a"); + + document.body.appendChild(link); + + link.href = url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.click(); + } + }); + } else { + canvas.toBlob((blob) => { + if (!blob) { + return window.alert("!!!"); + } + saveAs(blob, `${Date.now().toString()}${isIOS ? "" : ".jpg"}`); + }); + } } catch { AnnounceSwal.fire({ icon: "error",