mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-14 22:42:14 -07:00
Implement RefreshGuard system to prevent accidental page navigation
- Add useRefreshGuard hook with proper beforeunload/unload event handling - Add useNavigationGuard for React Router SPA navigation blocking - Add useGameGuard combining both guards with game state detection - Add RefreshGuardModal component for user warnings - Add RefreshGuardProvider to wrap application - Fix Room component crash with defensive null checks - Add Local Server to default hosts for localhost development
This commit is contained in:
parent
6b03f40576
commit
2c039bda8c
11 changed files with 591 additions and 7 deletions
77
webclient/src/hooks/useRefreshGuard.ts
Normal file
77
webclient/src/hooks/useRefreshGuard.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface UseRefreshGuardOptions {
|
||||
shouldGuard: boolean;
|
||||
message?: string;
|
||||
onUnload?: () => void; // Called only when page is actually unloading (not on beforeunload)
|
||||
}
|
||||
|
||||
interface UseRefreshGuardReturn {
|
||||
showModal: boolean;
|
||||
setShowModal: (show: boolean) => void;
|
||||
guardMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to guard against accidental page refresh or navigation away from the site.
|
||||
* Shows both browser's native beforeunload dialog and a custom modal for better UX.
|
||||
*
|
||||
* @param options Configuration for the refresh guard
|
||||
* @returns Modal state controls and current guard message
|
||||
*/
|
||||
export const useRefreshGuard = ({
|
||||
shouldGuard,
|
||||
message = 'You have unsaved changes that will be lost.',
|
||||
onUnload
|
||||
}: UseRefreshGuardOptions): UseRefreshGuardReturn => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (!shouldGuard) {
|
||||
return;
|
||||
}
|
||||
|
||||
// DO NOT call onBeforeUnload here!
|
||||
// This fires before user chooses "Stay" or "Leave"
|
||||
// Calling disconnect here will log out the user even if they choose "Stay"
|
||||
|
||||
// DO NOT set React state during beforeunload - it causes state corruption
|
||||
// Only show browser's native dialog for page refresh/navigation
|
||||
event.preventDefault();
|
||||
event.returnValue = ''; // Required for Chrome
|
||||
return ''; // Required for other browsers
|
||||
};
|
||||
|
||||
const handleUnload = () => {
|
||||
// Only disconnect when page is actually unloading (user chose "Leave")
|
||||
if (onUnload) {
|
||||
onUnload();
|
||||
}
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
if (shouldGuard) {
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
window.addEventListener('unload', handleUnload);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
window.removeEventListener('unload', handleUnload);
|
||||
};
|
||||
}, [shouldGuard, message, onUnload]);
|
||||
|
||||
// Auto-hide modal if guard is disabled
|
||||
useEffect(() => {
|
||||
if (!shouldGuard && showModal) {
|
||||
setShowModal(false);
|
||||
}
|
||||
}, [shouldGuard, showModal]);
|
||||
|
||||
return {
|
||||
showModal,
|
||||
setShowModal,
|
||||
guardMessage: message
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue