From d8084e401d7572b2d6e38ffa20cb678a0fb0e615 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Thu, 14 Nov 2024 18:04:11 +0100 Subject: [PATCH] feat: add confirm dialog for leaving the page if an upload is in progress --- frontend/src/hooks/confirm-leave.hook.ts | 42 +++++++++++++++++++++ frontend/src/i18n/translations/en-US.ts | 2 + frontend/src/pages/share/[shareId]/edit.tsx | 13 +++++-- frontend/src/pages/upload/index.tsx | 6 +++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 frontend/src/hooks/confirm-leave.hook.ts diff --git a/frontend/src/hooks/confirm-leave.hook.ts b/frontend/src/hooks/confirm-leave.hook.ts new file mode 100644 index 0000000..4174039 --- /dev/null +++ b/frontend/src/hooks/confirm-leave.hook.ts @@ -0,0 +1,42 @@ +import { useRouter } from "next/router"; +import { useEffect } from "react"; + +const useConfirmLeave = ({ + message, + enabled, +}: { + message: string; + enabled: boolean; +}) => { + const router = useRouter(); + + useEffect(() => { + if (!enabled) return; + + // Show confirmation dialog when route changes + const handleRouteChange = () => { + const confirmLeave = window.confirm(message); + if (!confirmLeave) { + router.events.emit("routeChangeError"); + throw "Route change aborted."; + } + }; + + // Show confirmation when the user tries to leave or reload the page + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + event.preventDefault(); + event.returnValue = message; + return message; + }; + + router.events.on("routeChangeStart", handleRouteChange); + window.addEventListener("beforeunload", handleBeforeUnload); + + return () => { + router.events.off("routeChangeStart", handleRouteChange); + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + }, [router, message, enabled]); +}; + +export default useConfirmLeave; diff --git a/frontend/src/i18n/translations/en-US.ts b/frontend/src/i18n/translations/en-US.ts index 7c4bdcd..1644ff8 100644 --- a/frontend/src/i18n/translations/en-US.ts +++ b/frontend/src/i18n/translations/en-US.ts @@ -286,6 +286,8 @@ export default { // /upload "upload.title": "Upload", + "upload.notify.confirm-leave": + "Are you sure you want to leave this page? Your upload will be canceled.", "upload.notify.generic-error": "An error occurred while finishing your share.", "upload.notify.count-failed": "{count} files failed to upload. Trying again.", diff --git a/frontend/src/pages/share/[shareId]/edit.tsx b/frontend/src/pages/share/[shareId]/edit.tsx index cab615d..a438130 100644 --- a/frontend/src/pages/share/[shareId]/edit.tsx +++ b/frontend/src/pages/share/[shareId]/edit.tsx @@ -2,12 +2,13 @@ import { LoadingOverlay } from "@mantine/core"; import { useModals } from "@mantine/modals"; import { GetServerSidePropsContext } from "next"; import { useEffect, useState } from "react"; +import Meta from "../../../components/Meta"; import showErrorModal from "../../../components/share/showErrorModal"; +import EditableUpload from "../../../components/upload/EditableUpload"; +import useConfirmLeave from "../../../hooks/confirm-leave.hook"; +import useTranslate from "../../../hooks/useTranslate.hook"; import shareService from "../../../services/share.service"; import { Share as ShareType } from "../../../types/share.type"; -import useTranslate from "../../../hooks/useTranslate.hook"; -import EditableUpload from "../../../components/upload/EditableUpload"; -import Meta from "../../../components/Meta"; export function getServerSideProps(context: GetServerSidePropsContext) { return { @@ -18,9 +19,15 @@ export function getServerSideProps(context: GetServerSidePropsContext) { const Share = ({ shareId }: { shareId: string }) => { const t = useTranslate(); const modals = useModals(); + const [isLoading, setIsLoading] = useState(true); const [share, setShare] = useState(); + useConfirmLeave({ + message: t("upload.notify.confirm-leave"), + enabled: isLoading, + }); + useEffect(() => { shareService .getFromOwner(shareId) diff --git a/frontend/src/pages/upload/index.tsx b/frontend/src/pages/upload/index.tsx index 7dd3d4d..d965bda 100644 --- a/frontend/src/pages/upload/index.tsx +++ b/frontend/src/pages/upload/index.tsx @@ -11,6 +11,7 @@ import FileList from "../../components/upload/FileList"; import showCompletedUploadModal from "../../components/upload/modals/showCompletedUploadModal"; import showCreateUploadModal from "../../components/upload/modals/showCreateUploadModal"; import useConfig from "../../hooks/config.hook"; +import useConfirmLeave from "../../hooks/confirm-leave.hook"; import useTranslate from "../../hooks/useTranslate.hook"; import useUser from "../../hooks/user.hook"; import shareService from "../../services/share.service"; @@ -39,6 +40,11 @@ const Upload = ({ const [files, setFiles] = useState([]); const [isUploading, setisUploading] = useState(false); + useConfirmLeave({ + message: t("upload.notify.confirm-leave"), + enabled: isUploading, + }); + const chunkSize = useRef(parseInt(config.get("share.chunkSize"))); maxShareSize ??= parseInt(config.get("share.maxSize"));