@jmruthers/pace-core 0.5.185 → 0.5.186

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +4 -2
  2. package/dist/{chunk-STTZQK2I.js → chunk-DAGICKHT.js} +7 -5
  3. package/dist/chunk-DAGICKHT.js.map +1 -0
  4. package/dist/{chunk-AISXLWGZ.js → chunk-GRIQLQ52.js} +2 -2
  5. package/dist/{chunk-HC67NW5K.js → chunk-HDCUMOOI.js} +125 -47
  6. package/dist/chunk-HDCUMOOI.js.map +1 -0
  7. package/dist/{chunk-OKI34GZD.js → chunk-OALXJH4Y.js} +2 -2
  8. package/dist/{chunk-MX3EIJGQ.js → chunk-TC7D3CR3.js} +86 -7
  9. package/dist/chunk-TC7D3CR3.js.map +1 -0
  10. package/dist/{chunk-IXSNYUCT.js → chunk-UQWSHFVX.js} +1 -1
  11. package/dist/chunk-UQWSHFVX.js.map +1 -0
  12. package/dist/components.d.ts +2 -2
  13. package/dist/components.js +3 -3
  14. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  15. package/dist/{file-reference-BjR39ktt.d.ts → file-reference-PRTSLxKx.d.ts} +3 -0
  16. package/dist/hooks.d.ts +49 -5
  17. package/dist/hooks.js +6 -4
  18. package/dist/hooks.js.map +1 -1
  19. package/dist/index.d.ts +5 -5
  20. package/dist/index.js +9 -8
  21. package/dist/index.js.map +1 -1
  22. package/dist/rbac/index.d.ts +1 -1
  23. package/dist/rbac/index.js +2 -2
  24. package/dist/types.d.ts +2 -2
  25. package/dist/types.js +1 -1
  26. package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +2 -2
  27. package/dist/utils.d.ts +1 -1
  28. package/docs/api/classes/ColumnFactory.md +1 -1
  29. package/docs/api/classes/ErrorBoundary.md +1 -1
  30. package/docs/api/classes/InvalidScopeError.md +1 -1
  31. package/docs/api/classes/Logger.md +1 -1
  32. package/docs/api/classes/MissingUserContextError.md +1 -1
  33. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  34. package/docs/api/classes/PermissionDeniedError.md +1 -1
  35. package/docs/api/classes/RBACAuditManager.md +2 -2
  36. package/docs/api/classes/RBACCache.md +1 -1
  37. package/docs/api/classes/RBACEngine.md +2 -2
  38. package/docs/api/classes/RBACError.md +1 -1
  39. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  40. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  41. package/docs/api/classes/StorageUtils.md +1 -1
  42. package/docs/api/enums/FileCategory.md +1 -1
  43. package/docs/api/enums/LogLevel.md +1 -1
  44. package/docs/api/enums/RBACErrorCode.md +1 -1
  45. package/docs/api/enums/RPCFunction.md +1 -1
  46. package/docs/api/interfaces/AggregateConfig.md +1 -1
  47. package/docs/api/interfaces/BadgeProps.md +1 -1
  48. package/docs/api/interfaces/ButtonProps.md +1 -1
  49. package/docs/api/interfaces/CalendarProps.md +1 -1
  50. package/docs/api/interfaces/CardProps.md +1 -1
  51. package/docs/api/interfaces/ColorPalette.md +1 -1
  52. package/docs/api/interfaces/ColorShade.md +1 -1
  53. package/docs/api/interfaces/ComplianceResult.md +1 -1
  54. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  55. package/docs/api/interfaces/DataRecord.md +1 -1
  56. package/docs/api/interfaces/DataTableAction.md +1 -1
  57. package/docs/api/interfaces/DataTableColumn.md +1 -1
  58. package/docs/api/interfaces/DataTableProps.md +1 -1
  59. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  60. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  61. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  62. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  63. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  64. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  65. package/docs/api/interfaces/ExportColumn.md +1 -1
  66. package/docs/api/interfaces/ExportOptions.md +1 -1
  67. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  68. package/docs/api/interfaces/FileMetadata.md +1 -1
  69. package/docs/api/interfaces/FileReference.md +1 -1
  70. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  71. package/docs/api/interfaces/FileUploadOptions.md +33 -9
  72. package/docs/api/interfaces/FileUploadProps.md +36 -14
  73. package/docs/api/interfaces/FooterProps.md +1 -1
  74. package/docs/api/interfaces/FormFieldProps.md +1 -1
  75. package/docs/api/interfaces/FormProps.md +1 -1
  76. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  77. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  78. package/docs/api/interfaces/InputProps.md +1 -1
  79. package/docs/api/interfaces/LabelProps.md +1 -1
  80. package/docs/api/interfaces/LoggerConfig.md +1 -1
  81. package/docs/api/interfaces/LoginFormProps.md +1 -1
  82. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  83. package/docs/api/interfaces/NavigationContextType.md +1 -1
  84. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  85. package/docs/api/interfaces/NavigationItem.md +1 -1
  86. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  87. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  88. package/docs/api/interfaces/Organisation.md +1 -1
  89. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  90. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  91. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  92. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  93. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  94. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  95. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  96. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  97. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  98. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  99. package/docs/api/interfaces/PaletteData.md +1 -1
  100. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  101. package/docs/api/interfaces/ProgressProps.md +1 -1
  102. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  103. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  104. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  105. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  106. package/docs/api/interfaces/QuickFix.md +1 -1
  107. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  108. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  109. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  110. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  111. package/docs/api/interfaces/RBACConfig.md +2 -2
  112. package/docs/api/interfaces/RBACContext.md +1 -1
  113. package/docs/api/interfaces/RBACLogger.md +1 -1
  114. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  115. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  116. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  117. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  118. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  119. package/docs/api/interfaces/RBACResult.md +1 -1
  120. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  121. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  122. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  123. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  124. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  125. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  126. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  127. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  128. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  129. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  130. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  131. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  132. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  134. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  136. package/docs/api/interfaces/RouteConfig.md +1 -1
  137. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  138. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  139. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  140. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  141. package/docs/api/interfaces/SetupIssue.md +1 -1
  142. package/docs/api/interfaces/StorageConfig.md +1 -1
  143. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  144. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  145. package/docs/api/interfaces/StorageListOptions.md +1 -1
  146. package/docs/api/interfaces/StorageListResult.md +1 -1
  147. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  148. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  149. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  150. package/docs/api/interfaces/StyleImport.md +1 -1
  151. package/docs/api/interfaces/SwitchProps.md +1 -1
  152. package/docs/api/interfaces/TabsContentProps.md +1 -1
  153. package/docs/api/interfaces/TabsListProps.md +1 -1
  154. package/docs/api/interfaces/TabsProps.md +1 -1
  155. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  156. package/docs/api/interfaces/TextareaProps.md +1 -1
  157. package/docs/api/interfaces/ToastActionElement.md +1 -1
  158. package/docs/api/interfaces/ToastProps.md +1 -1
  159. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  160. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  161. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  162. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  163. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  164. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  165. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  166. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  167. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  168. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  169. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  170. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  171. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  172. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  173. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  174. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  175. package/docs/api/interfaces/UserEventAccess.md +1 -1
  176. package/docs/api/interfaces/UserMenuProps.md +1 -1
  177. package/docs/api/interfaces/UserProfile.md +1 -1
  178. package/docs/api/modules.md +17 -17
  179. package/docs/api-reference/components.md +26 -12
  180. package/docs/implementation-guides/file-reference-system.md +24 -2
  181. package/docs/implementation-guides/file-upload-storage.md +9 -1
  182. package/package.json +1 -1
  183. package/scripts/check-pace-core-compliance.js +512 -0
  184. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  185. package/src/components/FileUpload/FileUpload.tsx +7 -1
  186. package/src/components/Header/Header.tsx +2 -5
  187. package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
  188. package/src/hooks/index.ts +3 -0
  189. package/src/hooks/useFileReference.test.ts +1 -0
  190. package/src/hooks/usePreventTabReload.ts +106 -0
  191. package/src/hooks/useSecureDataAccess.ts +2 -2
  192. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  193. package/src/styles/core.css +5 -5
  194. package/src/types/database.generated.ts +63 -9
  195. package/src/types/file-reference.ts +3 -0
  196. package/src/utils/file-reference/__tests__/file-reference.test.ts +58 -4
  197. package/src/utils/file-reference/index.ts +12 -2
  198. package/src/utils/security/secureDataAccess.ts +1 -1
  199. package/src/utils/storage/helpers.ts +68 -0
  200. package/dist/chunk-HC67NW5K.js.map +0 -1
  201. package/dist/chunk-IXSNYUCT.js.map +0 -1
  202. package/dist/chunk-MX3EIJGQ.js.map +0 -1
  203. package/dist/chunk-STTZQK2I.js.map +0 -1
  204. /package/dist/{chunk-AISXLWGZ.js.map → chunk-GRIQLQ52.js.map} +0 -0
  205. /package/dist/{chunk-OKI34GZD.js.map → chunk-OALXJH4Y.js.map} +0 -0
@@ -68,7 +68,7 @@
68
68
  * - LoadingSpinner - Loading state UI
69
69
  */
70
70
 
71
- import React, { useMemo } from 'react';
71
+ import React, { useMemo, useEffect, useRef, useState } from 'react';
72
72
  import { Navigate, Outlet } from 'react-router-dom';
73
73
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
74
74
  import { useSessionRestoration } from '../../hooks/useSessionRestoration';
@@ -77,6 +77,7 @@ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
77
77
  import { SessionRestorationLoader } from '../SessionRestorationLoader';
78
78
  import { Alert, AlertDescription, AlertTitle } from '../Alert/Alert';
79
79
  import { logger } from '../../utils/core/logger';
80
+ import { usePreventTabReload } from '../../hooks/usePreventTabReload';
80
81
 
81
82
  export interface ProtectedRouteProps {
82
83
  /**
@@ -148,6 +149,96 @@ export function ProtectedRoute({
148
149
 
149
150
  const sessionRestoration = useSessionRestoration();
150
151
 
152
+ // Prevent full page reloads when switching tabs (handles bfcache and visibility changes)
153
+ usePreventTabReload({ enabled: true, gracePeriodMs: 2000 });
154
+
155
+ // Track if user was previously authenticated to prevent redirects during session refresh
156
+ const wasAuthenticatedRef = useRef(false);
157
+ const [shouldRedirect, setShouldRedirect] = useState(false);
158
+ const tabJustBecameVisibleRef = useRef(false);
159
+
160
+ // Track authentication state to detect when user was previously logged in
161
+ useEffect(() => {
162
+ if (isAuthenticated) {
163
+ wasAuthenticatedRef.current = true;
164
+ setShouldRedirect(false);
165
+ tabJustBecameVisibleRef.current = false; // Clear visibility flag when authenticated
166
+ }
167
+ }, [isAuthenticated]);
168
+
169
+ // Handle tab visibility changes - prevent immediate redirects when tab becomes visible
170
+ // This prevents the page from refreshing when switching back to the tab
171
+ useEffect(() => {
172
+ if (typeof document === 'undefined') return;
173
+
174
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
175
+ let wasHidden = document.hidden;
176
+
177
+ const handleVisibilityChange = () => {
178
+ const isNowVisible = !document.hidden;
179
+
180
+ // When tab becomes visible, immediately prevent redirects and give session refresh time
181
+ if (isNowVisible && wasHidden) {
182
+ // Tab just became visible - immediately prevent redirects
183
+ if (!isAuthenticated && wasAuthenticatedRef.current) {
184
+ tabJustBecameVisibleRef.current = true;
185
+ setShouldRedirect(false); // Immediately clear redirect flag
186
+
187
+ // Clear any existing timeout
188
+ if (timeoutId) {
189
+ clearTimeout(timeoutId);
190
+ }
191
+
192
+ // Wait a bit to see if session refresh completes
193
+ timeoutId = setTimeout(() => {
194
+ // Only allow redirect if still not authenticated after delay
195
+ tabJustBecameVisibleRef.current = false;
196
+ // Use a function to get the latest state
197
+ setShouldRedirect((prev) => {
198
+ // Only set to true if we're still not authenticated
199
+ // This will be checked again in the render logic
200
+ return prev;
201
+ });
202
+ }, 2000); // 2 second grace period for session refresh
203
+ }
204
+ } else if (!isNowVisible) {
205
+ // Tab became hidden - clear the visibility flag
206
+ tabJustBecameVisibleRef.current = false;
207
+ if (timeoutId) {
208
+ clearTimeout(timeoutId);
209
+ timeoutId = null;
210
+ }
211
+ }
212
+
213
+ wasHidden = !isNowVisible;
214
+ };
215
+
216
+ // Check initial state - if tab is visible and user appears logged out, give grace period
217
+ if (!document.hidden && !isAuthenticated && wasAuthenticatedRef.current) {
218
+ tabJustBecameVisibleRef.current = true;
219
+ setShouldRedirect(false);
220
+ timeoutId = setTimeout(() => {
221
+ tabJustBecameVisibleRef.current = false;
222
+ }, 2000);
223
+ }
224
+
225
+ document.addEventListener('visibilitychange', handleVisibilityChange);
226
+ return () => {
227
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
228
+ if (timeoutId) {
229
+ clearTimeout(timeoutId);
230
+ }
231
+ };
232
+ }, [isAuthenticated]);
233
+
234
+ // Reset redirect flag when authenticated
235
+ useEffect(() => {
236
+ if (isAuthenticated) {
237
+ setShouldRedirect(false);
238
+ tabJustBecameVisibleRef.current = false;
239
+ }
240
+ }, [isAuthenticated]);
241
+
151
242
  const isRestoringSession = useMemo(() => {
152
243
  return sessionRestoration.isRestoring &&
153
244
  !sessionRestoration.restorationComplete &&
@@ -182,13 +273,55 @@ export function ProtectedRoute({
182
273
  }
183
274
 
184
275
  // Redirect to login if not authenticated
276
+ // Priority order:
277
+ // 1. If session restoration has timed out or errored → redirect immediately (even if loading)
278
+ // 2. If user was never authenticated → redirect immediately (even if loading)
279
+ // 3. If tab just became visible → show loading (prevent redirect during grace period)
280
+ // 4. If we've confirmed they should redirect (after visibility change grace period) → redirect
281
+ // 5. Otherwise, if loading → show loading spinner (session might be refreshing)
282
+ // 6. Otherwise → redirect (user is not authenticated and not loading)
185
283
  if (!isAuthenticated) {
284
+ // Session restoration timeout/error always redirects immediately
186
285
  if (sessionRestoration.hasTimedOut || sessionRestoration.restorationError) {
187
286
  logger.warn('ProtectedRoute', 'Session restoration failed, redirecting to login', {
188
287
  timedOut: sessionRestoration.hasTimedOut,
189
288
  error: sessionRestoration.restorationError?.message
190
289
  });
290
+ return <Navigate to={loginPath} replace />;
291
+ }
292
+
293
+ // User was never authenticated → redirect immediately
294
+ if (!wasAuthenticatedRef.current) {
295
+ return <Navigate to={loginPath} replace />;
296
+ }
297
+
298
+ // Tab just became visible - show loading to prevent redirect during grace period
299
+ // Also check document visibility state directly as a fallback
300
+ const isTabVisible = typeof document !== 'undefined' && !document.hidden;
301
+ if (tabJustBecameVisibleRef.current || (isTabVisible && wasAuthenticatedRef.current && isLoading)) {
302
+ return loadingFallback || (
303
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
304
+ <LoadingSpinner />
305
+ </div>
306
+ );
191
307
  }
308
+
309
+ // We've confirmed redirect after grace period → redirect
310
+ if (shouldRedirect) {
311
+ return <Navigate to={loginPath} replace />;
312
+ }
313
+
314
+ // User was authenticated before but now appears logged out
315
+ // Show loading state while we wait for session refresh (unless we're not loading)
316
+ if (isLoading) {
317
+ return loadingFallback || (
318
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
319
+ <LoadingSpinner />
320
+ </div>
321
+ );
322
+ }
323
+
324
+ // Not loading and not authenticated → redirect
192
325
  return <Navigate to={loginPath} replace />;
193
326
  }
194
327
 
@@ -64,6 +64,9 @@ export type { UseStorageOptions, UseStorageReturn } from './useStorage';
64
64
  // === PUBLIC DATA ACCESS HOOKS ===
65
65
  export * from './public';
66
66
 
67
+ // === PAGE LIFECYCLE HOOKS ===
68
+ export { usePreventTabReload } from './usePreventTabReload';
69
+ export type { UsePreventTabReloadOptions } from './usePreventTabReload';
67
70
 
68
71
  // RBAC Hooks - Use @jmruthers/pace-core/rbac instead
69
72
  // Note: RBAC functionality has been moved to the dedicated RBAC module
@@ -67,6 +67,7 @@ const mockFileUploadOptions = {
67
67
  organisation_id: 'test-org-123',
68
68
  app_id: 'test-app-123',
69
69
  category: FileCategory.GENERAL_DOCUMENTS,
70
+ folder: 'documents',
70
71
  pageContext: 'configuration',
71
72
  is_public: false
72
73
  };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @file usePreventTabReload Hook
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks
5
+ * @since 0.6.0
6
+ *
7
+ * Prevents full page reloads when switching browser tabs.
8
+ * Handles browser back-forward cache (bfcache) restoration and tab visibility changes.
9
+ */
10
+
11
+ import { useEffect, useRef } from 'react';
12
+
13
+ export interface UsePreventTabReloadOptions {
14
+ /**
15
+ * Whether to enable the prevention logic
16
+ * @default true
17
+ */
18
+ enabled?: boolean;
19
+
20
+ /**
21
+ * Grace period in milliseconds to wait after tab becomes visible
22
+ * before allowing any potential reloads
23
+ * @default 2000
24
+ */
25
+ gracePeriodMs?: number;
26
+ }
27
+
28
+ /**
29
+ * Hook to prevent full page reloads when switching browser tabs.
30
+ *
31
+ * This hook handles:
32
+ * - Browser back-forward cache (bfcache) restoration
33
+ * - Tab visibility changes
34
+ * - Prevents unwanted page reloads when returning to a tab
35
+ *
36
+ * @param options - Configuration options
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * import { usePreventTabReload } from '@jmruthers/pace-core';
41
+ *
42
+ * function MyComponent() {
43
+ * usePreventTabReload();
44
+ * // Component code...
45
+ * }
46
+ * ```
47
+ */
48
+ export function usePreventTabReload(options: UsePreventTabReloadOptions = {}): void {
49
+ const { enabled = true, gracePeriodMs = 2000 } = options;
50
+ const isRestoringFromCacheRef = useRef(false);
51
+ const gracePeriodTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
52
+
53
+ useEffect(() => {
54
+ if (!enabled || typeof window === 'undefined') return;
55
+
56
+ // Handle pageshow event - detects when page is restored from bfcache
57
+ const handlePageShow = (event: PageTransitionEvent) => {
58
+ // If page was restored from bfcache, prevent any reload behavior
59
+ if (event.persisted) {
60
+ isRestoringFromCacheRef.current = true;
61
+
62
+ // Clear any existing timeout
63
+ if (gracePeriodTimeoutRef.current) {
64
+ clearTimeout(gracePeriodTimeoutRef.current);
65
+ }
66
+
67
+ // Set grace period to prevent reloads
68
+ gracePeriodTimeoutRef.current = setTimeout(() => {
69
+ isRestoringFromCacheRef.current = false;
70
+ }, gracePeriodMs);
71
+ }
72
+ };
73
+
74
+ // Handle visibility changes - when tab becomes visible
75
+ const handleVisibilityChange = () => {
76
+ if (!document.hidden) {
77
+ // Tab just became visible - set flag to prevent reloads
78
+ isRestoringFromCacheRef.current = true;
79
+
80
+ // Clear any existing timeout
81
+ if (gracePeriodTimeoutRef.current) {
82
+ clearTimeout(gracePeriodTimeoutRef.current);
83
+ }
84
+
85
+ // Set grace period
86
+ gracePeriodTimeoutRef.current = setTimeout(() => {
87
+ isRestoringFromCacheRef.current = false;
88
+ }, gracePeriodMs);
89
+ }
90
+ };
91
+
92
+ // Add event listeners
93
+ window.addEventListener('pageshow', handlePageShow);
94
+ document.addEventListener('visibilitychange', handleVisibilityChange);
95
+
96
+ return () => {
97
+ window.removeEventListener('pageshow', handlePageShow);
98
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
99
+
100
+ if (gracePeriodTimeoutRef.current) {
101
+ clearTimeout(gracePeriodTimeoutRef.current);
102
+ }
103
+ };
104
+ }, [enabled, gracePeriodMs]);
105
+ }
106
+
@@ -219,7 +219,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
219
219
  'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
220
220
  // SECURITY: Phase 3A additions - medical and personal data
221
221
  'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
222
- 'pace_consent', 'pace_contact', 'pace_id_documents', 'pace_qualifications',
222
+ 'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
223
223
  'form_responses', 'form_response_values', 'forms',
224
224
  // SECURITY: Phase 3B additions - remaining critical tables
225
225
  'invoice', 'line_item', 'credit_balance', 'payment_method',
@@ -379,7 +379,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
379
379
  'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
380
380
  // SECURITY: Phase 3A additions - medical and personal data
381
381
  'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
382
- 'pace_consent', 'pace_contact', 'pace_id_documents', 'pace_qualifications',
382
+ 'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
383
383
  'form_responses', 'form_response_values', 'forms',
384
384
  // SECURITY: Phase 3B additions - remaining critical tables
385
385
  'invoice', 'line_item', 'credit_balance', 'payment_method',