diff --git a/webclient/src/components/RefreshGuardProvider/RefreshGuardProvider.tsx b/webclient/src/components/RefreshGuardProvider/RefreshGuardProvider.tsx index 3443453a6..daf984446 100644 --- a/webclient/src/components/RefreshGuardProvider/RefreshGuardProvider.tsx +++ b/webclient/src/components/RefreshGuardProvider/RefreshGuardProvider.tsx @@ -17,18 +17,20 @@ export const RefreshGuardProvider: React.FC = ({ chil handleConfirmNavigation, handleCancelNavigation, navigationMessage, + showRefreshModal, + setShowRefreshModal, + handleConfirmRefresh, + handleCancelRefresh, + refreshMessage, isInActiveGame, shouldGuard } = useGameGuard(); - // Only show custom modal for SPA navigation, not for browser refresh - // Browser refresh is handled by the browser's native beforeunload dialog - return ( <> {children} - {/* Custom modal only for SPA navigation (React Router) */} + {/* Custom modal for SPA navigation (React Router) */} = ({ chil confirmButtonText={isInActiveGame ? "Leave Game" : "Leave Anyway"} cancelButtonText="Stay" /> + + {/* Custom modal for browser refresh (after native dialog) */} + ); }; \ No newline at end of file diff --git a/webclient/src/hooks/useGameGuard.ts b/webclient/src/hooks/useGameGuard.ts index f8e6f7541..64ff6dccd 100644 --- a/webclient/src/hooks/useGameGuard.ts +++ b/webclient/src/hooks/useGameGuard.ts @@ -14,6 +14,13 @@ interface UseGameGuardReturn { handleCancelNavigation: () => void; navigationMessage: string; + // Refresh guard (browser refresh) + showRefreshModal: boolean; + setShowRefreshModal: (show: boolean) => void; + handleConfirmRefresh: () => void; + handleCancelRefresh: () => void; + refreshMessage: string; + // Guard state isInActiveGame: boolean; isConnected: boolean; @@ -103,6 +110,18 @@ export const useGameGuard = (): UseGameGuardReturn => { handleCancelNavigation: navigationGuard.handleCancelNavigation, 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 isInActiveGame, isConnected, diff --git a/webclient/src/hooks/useRefreshGuard.ts b/webclient/src/hooks/useRefreshGuard.ts index 774337991..27180db10 100644 --- a/webclient/src/hooks/useRefreshGuard.ts +++ b/webclient/src/hooks/useRefreshGuard.ts @@ -32,14 +32,18 @@ export const useRefreshGuard = ({ 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 + // Show browser's native dialog first event.preventDefault(); 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 };