From 4863364225678b0c2be5aea6e2ac809fe1bdeed7 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 17 Nov 2023 23:25:59 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20confirm=20dialog=20for=20leav?= =?UTF-8?q?ing=20board=20page=20in=20edit=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Board/context.tsx | 69 ++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/components/Board/context.tsx b/src/components/Board/context.tsx index 979c0e810..6495b3cb9 100644 --- a/src/components/Board/context.tsx +++ b/src/components/Board/context.tsx @@ -1,7 +1,10 @@ -import { useRouter } from 'next/router'; -import { createContext, useContext } from 'react'; +import { useEventListener } from '@mantine/hooks'; +import Router, { useRouter } from 'next/router'; +import { createContext, useContext, useEffect } from 'react'; import { RouterOutputs, api } from '~/utils/api'; +import { useEditModeStore } from './useEditModeStore'; + type BoardContextType = { layout?: string; board: RouterOutputs['boards']['byName']; @@ -13,6 +16,7 @@ type BoardProviderProps = { children: React.ReactNode; }; export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderProps) => { + const { enabled } = useEditModeStore(); const router = useRouter(); const { layout } = router.query; const { data: board } = api.boards.byName.useQuery( @@ -23,10 +27,12 @@ export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderPr }, { initialData: props.initialBoard, - enabled: !!layout, + //enabled: !!layout, } ); + useConfirmLeavePage(enabled); + return ( = TItem exte : never; export type AppItem = ItemOfType; export type WidgetItem = ItemOfType; + +const useConfirmLeavePage = (enabled: boolean) => { + const utils = api.useUtils(); + const { toggleEditMode } = useEditModeStore(); + + useEffect(() => { + const handleRouteChange = (url: string) => { + if (enabled && Router.pathname !== url) { + Router.events.emit('routeChangeError'); + const shouldLeave = window.confirm( + 'You have unsaved changes. Are you sure you want to leave?' + ); + if (!shouldLeave) { + throw 'abort-navigation'; + } + + toggleEditMode(); + utils.boards.byName.invalidate(); + } + }; + + const catchAbortNavigationError = (event: PromiseRejectionEvent) => { + if (event.reason === 'abort-navigation') { + event.preventDefault(); + } + }; + + Router.beforePopState(({ url }) => { + if (enabled) { + if (Router.pathname !== url) { + window.history.pushState('', '', url); + return false; + } + } + return true; + }); + + // For changing in-app route. + if (enabled) { + Router.events.on('routeChangeStart', handleRouteChange); + window.addEventListener('unhandledrejection', catchAbortNavigationError); + window.onbeforeunload = () => ''; + window.close = () => ''; + } + return () => { + Router.beforePopState(() => true); + Router.events.off('routeChangeStart', handleRouteChange); + window.removeEventListener('unhandledrejection', catchAbortNavigationError); + window.onbeforeunload = null; + window.close = () => null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [enabled]); + useEventListener('close', () => { + if (enabled) return ''; + }); +};