mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 20:35:07 +03:00
## Problem When editing email templates or edge functions, users may navigate away from the page without a warning indicating they may loose their changes. This is because we only handle the `beforeunload` event when we should also handle NextJS routing events. This is actually done for the observability reports. ## Solution Extract the logic from the observability reports into a reusable component and use it where needed ## How to test On staging, for each case: - Authentication email templates - Observability reports - Edge functions creation - Edge functions edition Test: - Modify the template/report/function - Navigate away using either the sidebar link, the browser back button or closing the tab - Cancel navigation in the confirmation dialog - Navigation should be prevented and you should not loose your changes Test: - Modify the template/report/function - Navigate away using either the sidebar link, the browser back button or closing the tab - Confirm navigation in the confirmation dialog - Navigation should not be prevented and you should have lost your changes
71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
import { useRouter } from 'next/router'
|
|
import { useEffect, useState } from 'react'
|
|
import { BASE_PATH } from 'lib/constants'
|
|
|
|
import {
|
|
DiscardChangesConfirmationDialog,
|
|
type DiscardChangesConfirmationDialogProps,
|
|
} from './DiscardChangesConfirmationDialog'
|
|
|
|
export const PreventNavigationOnUnsavedChanges = ({
|
|
hasChanges,
|
|
...props
|
|
}: { hasChanges: boolean } & Omit<
|
|
DiscardChangesConfirmationDialogProps,
|
|
'visible' | 'onClose' | 'onCancel'
|
|
>) => {
|
|
const router = useRouter()
|
|
const [navigateUrl, setNavigateUrl] = useState<string>()
|
|
const [confirmNavigate, setConfirmNavigate] = useState(false)
|
|
|
|
useEffect(() => {
|
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
if (hasChanges) {
|
|
e.preventDefault()
|
|
e.returnValue = '' // deprecated, but older browsers still require this
|
|
}
|
|
}
|
|
|
|
const handleBrowseAway = (url: string) => {
|
|
if (hasChanges && !confirmNavigate) {
|
|
setNavigateUrl(url)
|
|
throw 'Route change declined' // Just to prevent the route change
|
|
return
|
|
}
|
|
setNavigateUrl(undefined)
|
|
}
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
router.events.on('routeChangeStart', handleBrowseAway)
|
|
|
|
return () => {
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
router.events.off('routeChangeStart', handleBrowseAway)
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [confirmNavigate, hasChanges])
|
|
|
|
const handleCancel = () => {
|
|
setNavigateUrl(undefined)
|
|
}
|
|
|
|
const handleClose = () => {
|
|
setConfirmNavigate(true)
|
|
let urlToNavigate = navigateUrl ?? '/'
|
|
if (BASE_PATH && urlToNavigate.startsWith(BASE_PATH)) {
|
|
urlToNavigate = urlToNavigate.slice(BASE_PATH.length) || '/'
|
|
}
|
|
if (!urlToNavigate.startsWith('/')) urlToNavigate = `/${urlToNavigate}`
|
|
setNavigateUrl(undefined)
|
|
router.push(urlToNavigate)
|
|
}
|
|
|
|
return (
|
|
<DiscardChangesConfirmationDialog
|
|
visible={!!navigateUrl}
|
|
onCancel={handleCancel}
|
|
onClose={handleClose}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|