Add custom modal after browser refresh dialog

This commit is contained in:
Paul Carroll 2025-08-08 15:33:52 -04:00
parent 305216102e
commit c31291e95a
3 changed files with 46 additions and 10 deletions

View file

@ -17,18 +17,20 @@ export const RefreshGuardProvider: React.FC<RefreshGuardProviderProps> = ({ chil
handleConfirmNavigation, handleConfirmNavigation,
handleCancelNavigation, handleCancelNavigation,
navigationMessage, navigationMessage,
showRefreshModal,
setShowRefreshModal,
handleConfirmRefresh,
handleCancelRefresh,
refreshMessage,
isInActiveGame, isInActiveGame,
shouldGuard shouldGuard
} = useGameGuard(); } = useGameGuard();
// Only show custom modal for SPA navigation, not for browser refresh
// Browser refresh is handled by the browser's native beforeunload dialog
return ( return (
<> <>
{children} {children}
{/* Custom modal only for SPA navigation (React Router) */} {/* Custom modal for SPA navigation (React Router) */}
<RefreshGuardModal <RefreshGuardModal
open={showNavigationModal} open={showNavigationModal}
onClose={handleCancelNavigation} onClose={handleCancelNavigation}
@ -38,6 +40,17 @@ export const RefreshGuardProvider: React.FC<RefreshGuardProviderProps> = ({ chil
confirmButtonText={isInActiveGame ? "Leave Game" : "Leave Anyway"} confirmButtonText={isInActiveGame ? "Leave Game" : "Leave Anyway"}
cancelButtonText="Stay" cancelButtonText="Stay"
/> />
{/* Custom modal for browser refresh (after native dialog) */}
<RefreshGuardModal
open={showRefreshModal}
onClose={handleCancelRefresh}
onConfirm={handleConfirmRefresh}
message={refreshMessage}
title={isInActiveGame ? "Page Refresh Warning" : "Connection Warning"}
confirmButtonText="Refresh Anyway"
cancelButtonText="Stay on Page"
/>
</> </>
); );
}; };

View file

@ -14,6 +14,13 @@ interface UseGameGuardReturn {
handleCancelNavigation: () => void; handleCancelNavigation: () => void;
navigationMessage: string; navigationMessage: string;
// Refresh guard (browser refresh)
showRefreshModal: boolean;
setShowRefreshModal: (show: boolean) => void;
handleConfirmRefresh: () => void;
handleCancelRefresh: () => void;
refreshMessage: string;
// Guard state // Guard state
isInActiveGame: boolean; isInActiveGame: boolean;
isConnected: boolean; isConnected: boolean;
@ -103,6 +110,18 @@ export const useGameGuard = (): UseGameGuardReturn => {
handleCancelNavigation: navigationGuard.handleCancelNavigation, handleCancelNavigation: navigationGuard.handleCancelNavigation,
navigationMessage: navigationGuard.guardMessage, navigationMessage: navigationGuard.guardMessage,
// Refresh guard (browser refresh)
showRefreshModal: refreshGuard.showModal,
setShowRefreshModal: refreshGuard.setShowModal,
handleConfirmRefresh: async () => {
await handleGracefulDisconnect();
window.location.reload();
},
handleCancelRefresh: () => {
refreshGuard.setShowModal(false);
},
refreshMessage: refreshGuard.guardMessage,
// State // State
isInActiveGame, isInActiveGame,
isConnected, isConnected,

View file

@ -32,14 +32,18 @@ export const useRefreshGuard = ({
return; return;
} }
// DO NOT call onBeforeUnload here! // Show browser's native dialog first
// 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.preventDefault();
event.returnValue = ''; // Required for Chrome event.returnValue = ''; // Required for Chrome
// Schedule custom modal to show after browser dialog is dismissed
// This only happens if user chooses "Stay" on the browser dialog
setTimeout(() => {
if (shouldGuard) {
setShowModal(true);
}
}, 100);
return ''; // Required for other browsers return ''; // Required for other browsers
}; };