diff --git a/src/App.tsx b/src/App.tsx index a5ae85a..c2dff24 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,6 +24,7 @@ import { useRegisterSW } from "virtual:pwa-register/react"; import { estimateCalories, getSavedUserWeight } from "./utils/calorieEstimator"; import { CursorGlow } from "./components/CursorGlow"; import { PageErrorBoundary } from "./components/PageErrorBoundary"; +import { ExitConfirmModal } from "./components/ExitConfirmModal"; const WelcomeScreen = lazy(() => import("./components/WelcomeScreen").then(m => ({ default: m.WelcomeScreen }))); const SummaryScreen = lazy(() => import("./components/SummaryScreen").then(m => ({ default: m.SummaryScreen }))); const TrophyRoom = lazy(() => import("./components/TrophyRoom").then(m => ({ default: m.TrophyRoom }))); @@ -488,79 +489,17 @@ function App() { )} {showExitModal && ( -
setShowExitModal(false)} + onExit={() => { + setShowExitModal(false); + if (user?.uid) { + localStorage.removeItem(`spectrax_telemetry_snapshot_${user.uid}`); + } + navigateTo('welcome'); }} - > -
-

Confirm Exit

- -

Are you sure you want to end your session?

- -
- - - -
-
-
+ /> )} ); diff --git a/src/components/ExitConfirmModal.tsx b/src/components/ExitConfirmModal.tsx new file mode 100644 index 0000000..6a40b14 --- /dev/null +++ b/src/components/ExitConfirmModal.tsx @@ -0,0 +1,84 @@ +import React, { useEffect, useRef, useId } from 'react'; + +interface ExitConfirmModalProps { + message: string; + onStay: () => void; + onExit: () => void; +} + +export const ExitConfirmModal: React.FC = ({ message, onStay, onExit }) => { + const dialogRef = useRef(null); + const stayButtonRef = useRef(null); + const onStayRef = useRef(onStay); + onStayRef.current = onStay; + const titleId = useId(); + + useEffect(() => { + const previouslyFocused = document.activeElement as HTMLElement | null; + stayButtonRef.current?.focus(); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + onStayRef.current(); + return; + } + if (e.key !== 'Tab') return; + const focusable = dialogRef.current?.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('keydown', handleKeyDown); + previouslyFocused?.focus?.(); + }; + }, []); + + return ( +
+
+

Confirm Exit

+

{message}

+
+ + +
+
+
+ ); +}; + +export default ExitConfirmModal; diff --git a/src/components/WorkoutScreen.tsx b/src/components/WorkoutScreen.tsx index 6fbaa0a..29422ba 100644 --- a/src/components/WorkoutScreen.tsx +++ b/src/components/WorkoutScreen.tsx @@ -18,6 +18,7 @@ import { initialSquatDepthStats } from '../services/Squat_depth_classifier'; import { useWorkoutSync } from '../hooks/useWorkoutSync'; import { useDisplayConfig } from '../hooks/useDisplayConfig'; import { audioFeedbackService } from '../services/audioFeedbackService'; +import { ExitConfirmModal } from './ExitConfirmModal'; import { useWorkoutWebSocket } from '../hooks/useWorkoutWebSocket'; import { useOffscreenCanvas } from '../hooks/useOffscreenCanvas'; import { injuryRiskEngine } from '../services/injuryRiskEngine'; @@ -1924,59 +1925,11 @@ export const WorkoutScreen: React.FC = ({ exercise, onEnd, o `} {showExitModal && ( -
-
-

Confirm Exit

-

Are you sure you want to end your workout session?

-
- - -
-
-
+ setShowExitModal(false)} + onExit={handleEnd} + /> )}