@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -0,0 +1,880 @@
1
+ /**
2
+ * @file Simplified Auth Provider
3
+ * @package @jmruthers/pace-core
4
+ * @module Providers
5
+ * @since 2.0.0
6
+ *
7
+ * BREAKING CHANGES FROM v1:
8
+ * - Single provider instead of nested contexts
9
+ * - No service classes (direct React state)
10
+ * - No observer pattern (React's built-in re-rendering)
11
+ * - No UnifiedAuthProvider nesting
12
+ *
13
+ * This provides:
14
+ * - Authentication (user, session)
15
+ * - Organisation management
16
+ * - Event management
17
+ * - Inactivity tracking
18
+ *
19
+ * All in one provider with React state management.
20
+ */
21
+
22
+ import React, {
23
+ createContext,
24
+ useContext,
25
+ useState,
26
+ useEffect,
27
+ useCallback,
28
+ useMemo,
29
+ useRef
30
+ } from 'react';
31
+ import {
32
+ type SupabaseClient,
33
+ type User,
34
+ type Session,
35
+ type AuthError
36
+ } from '@supabase/supabase-js';
37
+ import type { UUID } from '../rbac/types';
38
+
39
+ // ============================================================================
40
+ // TYPES
41
+ // ============================================================================
42
+
43
+ export interface Organisation {
44
+ id: UUID;
45
+ name: string;
46
+ display_name?: string;
47
+ subscription_tier?: string;
48
+ settings?: Record<string, any>;
49
+ is_active: boolean;
50
+ created_at: string;
51
+ updated_at?: string;
52
+ }
53
+
54
+ export interface Event {
55
+ id: string;
56
+ event_id: string;
57
+ name: string;
58
+ organisation_id: UUID;
59
+ start_date?: string;
60
+ end_date?: string;
61
+ status?: string;
62
+ settings?: Record<string, any>;
63
+ }
64
+
65
+ export interface AuthContextType {
66
+ // ========== Auth State ==========
67
+ user: User | null;
68
+ session: Session | null;
69
+ isAuthenticated: boolean;
70
+ authLoading: boolean;
71
+ authError: AuthError | null;
72
+ supabase: SupabaseClient;
73
+
74
+ // ========== Auth Methods ==========
75
+ signIn: (email: string, password?: string) => Promise<{ error: AuthError | null }>;
76
+ signUp: (email: string, password: string) => Promise<{ error: AuthError | null }>;
77
+ signOut: () => Promise<{ error: AuthError | null }>;
78
+ resetPassword: (email: string) => Promise<{ error: AuthError | null }>;
79
+ updatePassword: (password: string) => Promise<{ error: AuthError | null }>;
80
+ refreshSession: () => Promise<{ error: AuthError | null }>;
81
+
82
+ // ========== Organisation State ==========
83
+ selectedOrganisation: Organisation | null;
84
+ organisations: Organisation[];
85
+ organisationLoading: boolean;
86
+ organisationError: Error | null;
87
+ hasValidOrganisationContext: boolean;
88
+ isContextReady: boolean;
89
+
90
+ // ========== Organisation Methods ==========
91
+ switchOrganisation: (orgId: UUID) => Promise<void>;
92
+ refreshOrganisations: () => Promise<void>;
93
+ getUserRole: (orgId?: UUID) => string | null;
94
+ getPrimaryOrganisation: () => Organisation | null;
95
+
96
+ // ========== Event State ==========
97
+ events: Event[];
98
+ selectedEvent: Event | null;
99
+ eventLoading: boolean;
100
+ eventError: Error | null;
101
+
102
+ // ========== Event Methods ==========
103
+ setSelectedEvent: (event: Event | null) => void;
104
+ refreshEvents: () => Promise<void>;
105
+
106
+ // ========== Inactivity State ==========
107
+ showInactivityWarning: boolean;
108
+ inactivityTimeRemaining: number;
109
+ isIdle: boolean;
110
+ isTracking: boolean;
111
+
112
+ // ========== Inactivity Methods ==========
113
+ resetActivity: () => void;
114
+ startTracking: () => void;
115
+ stopTracking: () => void;
116
+
117
+ // ========== Session Management ==========
118
+ sessionId: string | null;
119
+ deviceFingerprint: string | null;
120
+ hasConcurrentSessions: boolean;
121
+ }
122
+
123
+ export interface AuthProviderProps {
124
+ children: React.ReactNode;
125
+ supabaseClient: SupabaseClient;
126
+ appName: string;
127
+ persistState?: boolean;
128
+ requireOrganisationContext?: boolean;
129
+
130
+ // Inactivity configuration (MANDATORY for security)
131
+ idleTimeoutMs: number;
132
+ warnBeforeMs: number;
133
+ onIdleLogout: (reason: 'inactivity') => void;
134
+ renderInactivityWarning?: (args: {
135
+ timeRemaining: number;
136
+ onStaySignedIn: () => void;
137
+ onSignOutNow: () => void;
138
+ }) => React.ReactNode;
139
+ }
140
+
141
+ // ============================================================================
142
+ // CONTEXT
143
+ // ============================================================================
144
+
145
+ const AuthContext = createContext<AuthContextType | null>(null);
146
+
147
+ // ============================================================================
148
+ // PROVIDER
149
+ // ============================================================================
150
+
151
+ export function AuthProvider({
152
+ children,
153
+ supabaseClient,
154
+ appName,
155
+ persistState = true,
156
+ requireOrganisationContext = true,
157
+ idleTimeoutMs = 30 * 60 * 1000, // 30 minutes - MANDATORY for security
158
+ warnBeforeMs = 60 * 1000, // 1 minute
159
+ onIdleLogout, // MANDATORY - must be provided
160
+ renderInactivityWarning,
161
+ }: AuthProviderProps) {
162
+ // MANDATORY: Inactivity timeout cannot be disabled
163
+ if (!idleTimeoutMs || idleTimeoutMs < 60000) {
164
+ throw new Error(
165
+ 'AuthProvider: idleTimeoutMs is MANDATORY and must be at least 60 seconds (60000ms) for security. ' +
166
+ 'The dangerouslyDisableInactivity flag has been removed.'
167
+ );
168
+ }
169
+
170
+ if (!onIdleLogout) {
171
+ throw new Error(
172
+ 'AuthProvider: onIdleLogout callback is MANDATORY and must be provided for security.'
173
+ );
174
+ }
175
+
176
+ // ==========================================================================
177
+ // AUTH STATE (replaces AuthService)
178
+ // ==========================================================================
179
+
180
+ const [user, setUser] = useState<User | null>(null);
181
+ const [session, setSession] = useState<Session | null>(null);
182
+ const [authLoading, setAuthLoading] = useState(true);
183
+ const [authError, setAuthError] = useState<AuthError | null>(null);
184
+
185
+ // ==========================================================================
186
+ // ORGANISATION STATE (replaces OrganisationService)
187
+ // ==========================================================================
188
+
189
+ const [selectedOrganisation, setSelectedOrganisation] = useState<Organisation | null>(null);
190
+ const [organisations, setOrganisations] = useState<Organisation[]>([]);
191
+ const [organisationLoading, setOrganisationLoading] = useState(false);
192
+ const [organisationError, setOrganisationError] = useState<Error | null>(null);
193
+
194
+ // ==========================================================================
195
+ // EVENT STATE (replaces EventService)
196
+ // ==========================================================================
197
+
198
+ const [events, setEvents] = useState<Event[]>([]);
199
+ const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
200
+ const [eventLoading, setEventLoading] = useState(false);
201
+ const [eventError, setEventError] = useState<Error | null>(null);
202
+
203
+ // ==========================================================================
204
+ // INACTIVITY STATE (replaces InactivityService)
205
+ // ==========================================================================
206
+
207
+ const [showInactivityWarning, setShowInactivityWarning] = useState(false);
208
+ const [inactivityTimeRemaining, setInactivityTimeRemaining] = useState(idleTimeoutMs);
209
+ const [isIdle, setIsIdle] = useState(false);
210
+ const [isTracking, setIsTracking] = useState(false);
211
+
212
+ const lastActivityRef = useRef<number>(Date.now());
213
+ const idleTimerRef = useRef<NodeJS.Timeout | null>(null);
214
+ const warningTimerRef = useRef<NodeJS.Timeout | null>(null);
215
+ const deviceFingerprintRef = useRef<string | null>(null);
216
+ const sessionIdRef = useRef<string | null>(null);
217
+
218
+ // ==========================================================================
219
+ // AUTH EFFECTS (replaces AuthService initialization)
220
+ // ==========================================================================
221
+
222
+ useEffect(() => {
223
+ // Setup auth state listener
224
+ const { data: { subscription } } = supabaseClient.auth.onAuthStateChange(
225
+ (event, newSession) => {
226
+ console.log('[AuthProvider] Auth state change:', event);
227
+
228
+ if (event === 'SIGNED_OUT') {
229
+ setSession(null);
230
+ setUser(null);
231
+ setAuthError(null);
232
+ } else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
233
+ setSession(newSession);
234
+ setUser(newSession?.user ?? null);
235
+ if (newSession) {
236
+ setAuthError(null);
237
+ }
238
+ } else if (event === 'INITIAL_SESSION') {
239
+ if (newSession) {
240
+ setSession(newSession);
241
+ setUser(newSession.user ?? null);
242
+ setAuthError(null);
243
+ }
244
+ }
245
+
246
+ setAuthLoading(false);
247
+ }
248
+ );
249
+
250
+ // Initial session check
251
+ supabaseClient.auth.getSession().then(({ data: { session: initialSession } }) => {
252
+ setSession(initialSession);
253
+ setUser(initialSession?.user ?? null);
254
+ setAuthLoading(false);
255
+ });
256
+
257
+ return () => {
258
+ subscription.unsubscribe();
259
+ };
260
+ }, [supabaseClient]);
261
+
262
+ // ==========================================================================
263
+ // ORGANISATION EFFECTS (replaces OrganisationService initialization)
264
+ // ==========================================================================
265
+
266
+ useEffect(() => {
267
+ if (!user) {
268
+ setOrganisations([]);
269
+ setSelectedOrganisation(null);
270
+ return;
271
+ }
272
+
273
+ // Load user's organisations
274
+ const loadOrganisations = async () => {
275
+ setOrganisationLoading(true);
276
+ setOrganisationError(null);
277
+
278
+ try {
279
+ // Use RPC to get organisations with roles
280
+ const { data, error } = await supabaseClient
281
+ .rpc('rbac_user_organisation_roles_get', {
282
+ p_user_id: user.id
283
+ }) as { data: Array<{ organisation_id: UUID }> | null; error: any };
284
+
285
+ if (error) throw error;
286
+
287
+ if (data && data.length > 0) {
288
+ // Fetch full organisation details
289
+ const orgIds = data.map(r => r.organisation_id);
290
+ const { data: orgs, error: orgsError } = await supabaseClient
291
+ .from('organisations')
292
+ .select('*')
293
+ .in('id', orgIds)
294
+ .eq('is_active', true) as { data: Organisation[] | null; error: any };
295
+
296
+ if (orgsError) throw orgsError;
297
+
298
+ setOrganisations(orgs || []);
299
+
300
+ // Auto-select first organisation if none selected
301
+ if (!selectedOrganisation && orgs && orgs.length > 0) {
302
+ const savedOrgId = persistState ? localStorage.getItem(`pace_${appName}_org`) : null;
303
+ const orgToSelect = savedOrgId
304
+ ? orgs.find(o => o.id === savedOrgId) || orgs[0]
305
+ : orgs[0];
306
+ setSelectedOrganisation(orgToSelect);
307
+ }
308
+ }
309
+ } catch (error) {
310
+ console.error('[AuthProvider] Failed to load organisations:', error);
311
+ setOrganisationError(error as Error);
312
+ } finally {
313
+ setOrganisationLoading(false);
314
+ }
315
+ };
316
+
317
+ loadOrganisations();
318
+ }, [user, supabaseClient, appName, persistState, selectedOrganisation]);
319
+
320
+ // ==========================================================================
321
+ // EVENT EFFECTS (replaces EventService initialization)
322
+ // ==========================================================================
323
+
324
+ useEffect(() => {
325
+ if (!user || !selectedOrganisation) {
326
+ setEvents([]);
327
+ setSelectedEvent(null);
328
+ return;
329
+ }
330
+
331
+ // Load user's events for the selected organisation
332
+ const loadEvents = async () => {
333
+ setEventLoading(true);
334
+ setEventError(null);
335
+
336
+ try {
337
+ // Use RPC to get user's events
338
+ const { data, error } = await supabaseClient
339
+ .rpc('data_user_events_get', {
340
+ p_user_id: user.id,
341
+ p_app_name: appName
342
+ }) as { data: Event[] | null; error: any };
343
+
344
+ if (error) throw error;
345
+
346
+ // Filter events for current organisation
347
+ const orgEvents = (data || []).filter(e => e.organisation_id === selectedOrganisation.id);
348
+ setEvents(orgEvents);
349
+
350
+ // Auto-select first event if none selected
351
+ if (!selectedEvent && orgEvents.length > 0) {
352
+ const savedEventId = persistState ? localStorage.getItem(`pace_${appName}_event`) : null;
353
+ const eventToSelect = savedEventId
354
+ ? orgEvents.find(e => e.event_id === savedEventId) || orgEvents[0]
355
+ : orgEvents[0];
356
+ setSelectedEvent(eventToSelect);
357
+ }
358
+ } catch (error) {
359
+ console.error('[AuthProvider] Failed to load events:', error);
360
+ setEventError(error as Error);
361
+ } finally {
362
+ setEventLoading(false);
363
+ }
364
+ };
365
+
366
+ loadEvents();
367
+ }, [user, selectedOrganisation, supabaseClient, appName, persistState, selectedEvent]);
368
+
369
+ // ==========================================================================
370
+ // INACTIVITY EFFECTS (replaces InactivityService)
371
+ // ==========================================================================
372
+
373
+ const resetActivity = useCallback(() => {
374
+ lastActivityRef.current = Date.now();
375
+ setInactivityTimeRemaining(idleTimeoutMs);
376
+ setShowInactivityWarning(false);
377
+ setIsIdle(false);
378
+ }, [idleTimeoutMs]);
379
+
380
+ const startTracking = useCallback(() => {
381
+ if (isTracking) return;
382
+
383
+ setIsTracking(true);
384
+ resetActivity();
385
+
386
+ // Activity event listeners
387
+ const activityEvents = ['mousedown', 'keydown', 'scroll', 'touchstart'];
388
+ const handleActivity = () => resetActivity();
389
+
390
+ activityEvents.forEach(event => {
391
+ window.addEventListener(event, handleActivity, { passive: true });
392
+ });
393
+
394
+ // Idle check interval
395
+ idleTimerRef.current = setInterval(() => {
396
+ const timeSinceActivity = Date.now() - lastActivityRef.current;
397
+ const remaining = idleTimeoutMs - timeSinceActivity;
398
+
399
+ setInactivityTimeRemaining(Math.max(0, remaining));
400
+
401
+ // Show warning
402
+ if (remaining <= warnBeforeMs && remaining > 0 && !showInactivityWarning) {
403
+ setShowInactivityWarning(true);
404
+ }
405
+
406
+ // Auto logout
407
+ if (remaining <= 0) {
408
+ setIsIdle(true);
409
+ onIdleLogout('inactivity');
410
+ }
411
+ }, 1000);
412
+
413
+ // Cleanup
414
+ return () => {
415
+ activityEvents.forEach(event => {
416
+ window.removeEventListener(event, handleActivity);
417
+ });
418
+ if (idleTimerRef.current) {
419
+ clearInterval(idleTimerRef.current);
420
+ }
421
+ if (warningTimerRef.current) {
422
+ clearTimeout(warningTimerRef.current);
423
+ }
424
+ };
425
+ }, [isTracking, idleTimeoutMs, warnBeforeMs, showInactivityWarning, onIdleLogout, resetActivity]);
426
+
427
+ const stopTracking = useCallback(() => {
428
+ setIsTracking(false);
429
+ if (idleTimerRef.current) {
430
+ clearInterval(idleTimerRef.current);
431
+ idleTimerRef.current = null;
432
+ }
433
+ if (warningTimerRef.current) {
434
+ clearTimeout(warningTimerRef.current);
435
+ warningTimerRef.current = null;
436
+ }
437
+ }, []);
438
+
439
+ // Generate and track device fingerprint
440
+ useEffect(() => {
441
+ if (user) {
442
+ // Generate device fingerprint (simple version - in production, use more sophisticated approach)
443
+ if (!deviceFingerprintRef.current) {
444
+ const fingerprint = btoa(JSON.stringify({
445
+ userAgent: navigator.userAgent,
446
+ language: navigator.language,
447
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
448
+ screen: `${window.screen.width}x${window.screen.height}`,
449
+ timestamp: Date.now()
450
+ })).substring(0, 64);
451
+ deviceFingerprintRef.current = fingerprint;
452
+ }
453
+
454
+ // Track session with device fingerprint
455
+ const trackSession = async () => {
456
+ if (!deviceFingerprintRef.current || !user) return;
457
+
458
+ try {
459
+ const { data, error } = await supabaseClient.rpc('rbac_session_track', {
460
+ p_user_id: user.id,
461
+ p_session_type: 'login',
462
+ p_app_id: null, // Will be resolved by the RPC function
463
+ p_ip_address: null, // Will be handled by backend
464
+ p_user_agent: navigator.userAgent,
465
+ p_device_fingerprint: deviceFingerprintRef.current
466
+ });
467
+
468
+ if (!error && data) {
469
+ sessionIdRef.current = data;
470
+
471
+ // Check for concurrent sessions
472
+ const { data: concurrentData } = await supabaseClient.rpc('rbac_detect_concurrent_sessions', {
473
+ p_user_id: user.id,
474
+ p_device_fingerprint: deviceFingerprintRef.current,
475
+ p_timeout_seconds: 300
476
+ });
477
+
478
+ if (concurrentData && concurrentData.has_concurrent) {
479
+ console.warn(
480
+ '[AuthProvider] Concurrent session detected:',
481
+ concurrentData.concurrent_sessions_count,
482
+ 'other session(s) active'
483
+ );
484
+ }
485
+ }
486
+ } catch (err) {
487
+ console.error('[AuthProvider] Session tracking failed:', err);
488
+ }
489
+ };
490
+
491
+ trackSession();
492
+ }
493
+ }, [user, supabaseClient]);
494
+
495
+ // Auto-start tracking when user is authenticated
496
+ useEffect(() => {
497
+ if (user && !authLoading) {
498
+ startTracking();
499
+ } else {
500
+ stopTracking();
501
+ }
502
+
503
+ return () => {
504
+ stopTracking();
505
+ };
506
+ }, [user, authLoading, startTracking, stopTracking]);
507
+
508
+ // ==========================================================================
509
+ // AUTH METHODS
510
+ // ==========================================================================
511
+
512
+ const signIn = useCallback(async (email: string, password?: string) => {
513
+ setAuthLoading(true);
514
+ setAuthError(null);
515
+
516
+ try {
517
+ let result;
518
+ if (password) {
519
+ // Email + password sign in
520
+ result = await supabaseClient.auth.signInWithPassword({ email, password });
521
+ } else {
522
+ // Magic link sign in
523
+ result = await supabaseClient.auth.signInWithOtp({ email });
524
+ }
525
+
526
+ if (result.error) {
527
+ setAuthError(result.error);
528
+ return { error: result.error };
529
+ }
530
+
531
+ return { error: null };
532
+ } catch (error) {
533
+ const authError = error as AuthError;
534
+ setAuthError(authError);
535
+ return { error: authError };
536
+ } finally {
537
+ setAuthLoading(false);
538
+ }
539
+ }, [supabaseClient]);
540
+
541
+ const signUp = useCallback(async (email: string, password: string) => {
542
+ setAuthLoading(true);
543
+ setAuthError(null);
544
+
545
+ try {
546
+ const { error } = await supabaseClient.auth.signUp({ email, password });
547
+
548
+ if (error) {
549
+ setAuthError(error);
550
+ return { error };
551
+ }
552
+
553
+ return { error: null };
554
+ } catch (error) {
555
+ const authError = error as AuthError;
556
+ setAuthError(authError);
557
+ return { error: authError };
558
+ } finally {
559
+ setAuthLoading(false);
560
+ }
561
+ }, [supabaseClient]);
562
+
563
+ const signOut = useCallback(async () => {
564
+ setAuthLoading(true);
565
+ setAuthError(null);
566
+
567
+ try {
568
+ const { error } = await supabaseClient.auth.signOut();
569
+
570
+ if (error) {
571
+ setAuthError(error);
572
+ return { error };
573
+ }
574
+
575
+ // Clear local state
576
+ setUser(null);
577
+ setSession(null);
578
+ setSelectedOrganisation(null);
579
+ setOrganisations([]);
580
+ setSelectedEvent(null);
581
+ setEvents([]);
582
+
583
+ // Clear persisted state
584
+ if (persistState) {
585
+ localStorage.removeItem(`pace_${appName}_org`);
586
+ localStorage.removeItem(`pace_${appName}_event`);
587
+ }
588
+
589
+ return { error: null };
590
+ } catch (error) {
591
+ const authError = error as AuthError;
592
+ setAuthError(authError);
593
+ return { error: authError };
594
+ } finally {
595
+ setAuthLoading(false);
596
+ }
597
+ }, [supabaseClient, appName, persistState]);
598
+
599
+ const resetPassword = useCallback(async (email: string) => {
600
+ setAuthError(null);
601
+
602
+ try {
603
+ const { error } = await supabaseClient.auth.resetPasswordForEmail(email);
604
+
605
+ if (error) {
606
+ setAuthError(error);
607
+ return { error };
608
+ }
609
+
610
+ return { error: null };
611
+ } catch (error) {
612
+ const authError = error as AuthError;
613
+ setAuthError(authError);
614
+ return { error: authError };
615
+ }
616
+ }, [supabaseClient]);
617
+
618
+ const updatePassword = useCallback(async (password: string) => {
619
+ setAuthError(null);
620
+
621
+ try {
622
+ const { error } = await supabaseClient.auth.updateUser({ password });
623
+
624
+ if (error) {
625
+ setAuthError(error);
626
+ return { error };
627
+ }
628
+
629
+ return { error: null };
630
+ } catch (error) {
631
+ const authError = error as AuthError;
632
+ setAuthError(authError);
633
+ return { error: authError };
634
+ }
635
+ }, [supabaseClient]);
636
+
637
+ const refreshSession = useCallback(async () => {
638
+ setAuthError(null);
639
+
640
+ try {
641
+ const { data, error } = await supabaseClient.auth.refreshSession();
642
+
643
+ if (error) {
644
+ setAuthError(error);
645
+ return { error };
646
+ }
647
+
648
+ if (data.session) {
649
+ setSession(data.session);
650
+ setUser(data.session.user);
651
+ }
652
+
653
+ return { error: null };
654
+ } catch (error) {
655
+ const authError = error as AuthError;
656
+ setAuthError(authError);
657
+ return { error: authError };
658
+ }
659
+ }, [supabaseClient]);
660
+
661
+ // ==========================================================================
662
+ // ORGANISATION METHODS
663
+ // ==========================================================================
664
+
665
+ const switchOrganisation = useCallback(async (orgId: UUID) => {
666
+ const org = organisations.find(o => o.id === orgId);
667
+ if (!org) {
668
+ throw new Error(`Organisation ${orgId} not found`);
669
+ }
670
+
671
+ setSelectedOrganisation(org);
672
+
673
+ if (persistState) {
674
+ localStorage.setItem(`pace_${appName}_org`, orgId);
675
+ }
676
+
677
+ // Clear event selection when switching orgs
678
+ setSelectedEvent(null);
679
+ if (persistState) {
680
+ localStorage.removeItem(`pace_${appName}_event`);
681
+ }
682
+ }, [organisations, appName, persistState]);
683
+
684
+ const refreshOrganisations = useCallback(async () => {
685
+ if (!user) return;
686
+
687
+ setOrganisationLoading(true);
688
+ setOrganisationError(null);
689
+
690
+ try {
691
+ const { data, error } = await supabaseClient
692
+ .rpc('rbac_user_organisation_roles_get', {
693
+ p_user_id: user.id
694
+ }) as { data: Array<{ organisation_id: UUID }> | null; error: any };
695
+
696
+ if (error) throw error;
697
+
698
+ if (data && data.length > 0) {
699
+ const orgIds = data.map(r => r.organisation_id);
700
+ const { data: orgs, error: orgsError } = await supabaseClient
701
+ .from('organisations')
702
+ .select('*')
703
+ .in('id', orgIds)
704
+ .eq('is_active', true) as { data: Organisation[] | null; error: any };
705
+
706
+ if (orgsError) throw orgsError;
707
+
708
+ setOrganisations(orgs || []);
709
+ }
710
+ } catch (error) {
711
+ console.error('[AuthProvider] Failed to refresh organisations:', error);
712
+ setOrganisationError(error as Error);
713
+ } finally {
714
+ setOrganisationLoading(false);
715
+ }
716
+ }, [user, supabaseClient]);
717
+
718
+ const getUserRole = useCallback((orgId?: UUID): string | null => {
719
+ // This would require fetching role data - placeholder for now
720
+ // In real implementation, we'd cache role data when loading organisations
721
+ return 'member';
722
+ }, []);
723
+
724
+ const getPrimaryOrganisation = useCallback((): Organisation | null => {
725
+ return organisations[0] || null;
726
+ }, [organisations]);
727
+
728
+ // ==========================================================================
729
+ // EVENT METHODS
730
+ // ==========================================================================
731
+
732
+ const handleSetSelectedEvent = useCallback((event: Event | null) => {
733
+ setSelectedEvent(event);
734
+
735
+ if (persistState) {
736
+ if (event) {
737
+ localStorage.setItem(`pace_${appName}_event`, event.event_id);
738
+ } else {
739
+ localStorage.removeItem(`pace_${appName}_event`);
740
+ }
741
+ }
742
+ }, [appName, persistState]);
743
+
744
+ const refreshEvents = useCallback(async () => {
745
+ if (!user || !selectedOrganisation) return;
746
+
747
+ setEventLoading(true);
748
+ setEventError(null);
749
+
750
+ try {
751
+ const { data, error } = await supabaseClient
752
+ .rpc('data_user_events_get', {
753
+ p_user_id: user.id,
754
+ p_app_name: appName
755
+ }) as { data: Event[] | null; error: any };
756
+
757
+ if (error) throw error;
758
+
759
+ const orgEvents = (data || []).filter(e => e.organisation_id === selectedOrganisation.id);
760
+ setEvents(orgEvents);
761
+
762
+ // Auto-select first event if none selected
763
+ if (!selectedEvent && orgEvents.length > 0) {
764
+ const savedEventId = persistState ? localStorage.getItem(`pace_${appName}_event`) : null;
765
+ const eventToSelect = savedEventId
766
+ ? orgEvents.find(e => e.event_id === savedEventId) || orgEvents[0]
767
+ : orgEvents[0];
768
+ setSelectedEvent(eventToSelect);
769
+ }
770
+ } catch (error) {
771
+ console.error('[AuthProvider] Failed to refresh events:', error);
772
+ setEventError(error as Error);
773
+ } finally {
774
+ setEventLoading(false);
775
+ }
776
+ }, [user, selectedOrganisation, supabaseClient, appName, selectedEvent, persistState]);
777
+
778
+ // ==========================================================================
779
+ // COMPUTED VALUES
780
+ // ==========================================================================
781
+
782
+ const isAuthenticated = !!(user && session);
783
+ const hasValidOrganisationContext = !!(selectedOrganisation && !organisationLoading);
784
+ const isContextReady = isAuthenticated && (!requireOrganisationContext || hasValidOrganisationContext);
785
+
786
+ // ==========================================================================
787
+ // CONTEXT VALUE
788
+ // ==========================================================================
789
+
790
+ const contextValue = useMemo<AuthContextType>(() => ({
791
+ // Auth
792
+ user,
793
+ session,
794
+ isAuthenticated,
795
+ authLoading,
796
+ authError,
797
+ supabase: supabaseClient,
798
+ signIn,
799
+ signUp,
800
+ signOut,
801
+ resetPassword,
802
+ updatePassword,
803
+ refreshSession,
804
+
805
+ // Organisation
806
+ selectedOrganisation,
807
+ organisations,
808
+ organisationLoading,
809
+ organisationError,
810
+ hasValidOrganisationContext,
811
+ isContextReady: hasValidOrganisationContext,
812
+ switchOrganisation,
813
+ refreshOrganisations,
814
+ getUserRole,
815
+ getPrimaryOrganisation,
816
+
817
+ // Event
818
+ events,
819
+ selectedEvent,
820
+ eventLoading,
821
+ eventError,
822
+ setSelectedEvent: handleSetSelectedEvent,
823
+ refreshEvents,
824
+
825
+ // Inactivity
826
+ showInactivityWarning,
827
+ inactivityTimeRemaining,
828
+ isIdle,
829
+ isTracking,
830
+ resetActivity,
831
+ startTracking,
832
+ stopTracking,
833
+
834
+ // Session Management
835
+ sessionId: sessionIdRef.current,
836
+ deviceFingerprint: deviceFingerprintRef.current,
837
+ hasConcurrentSessions: false, // TODO: implement state tracking
838
+ }), [
839
+ user, session, isAuthenticated, authLoading, authError,
840
+ selectedOrganisation, organisations, organisationLoading, organisationError,
841
+ hasValidOrganisationContext, isContextReady,
842
+ events, selectedEvent, eventLoading, eventError,
843
+ showInactivityWarning, inactivityTimeRemaining, isIdle, isTracking,
844
+ signIn, signUp, signOut, resetPassword, updatePassword, refreshSession,
845
+ switchOrganisation, refreshOrganisations, getUserRole, getPrimaryOrganisation,
846
+ handleSetSelectedEvent, refreshEvents,
847
+ resetActivity, startTracking, stopTracking,
848
+ supabaseClient
849
+ ]);
850
+
851
+ // ==========================================================================
852
+ // RENDER
853
+ // ==========================================================================
854
+
855
+ return (
856
+ <AuthContext.Provider value={contextValue}>
857
+ {children}
858
+ {showInactivityWarning && renderInactivityWarning && renderInactivityWarning({
859
+ timeRemaining: inactivityTimeRemaining,
860
+ onStaySignedIn: resetActivity,
861
+ onSignOutNow: signOut,
862
+ })}
863
+ </AuthContext.Provider>
864
+ );
865
+ }
866
+
867
+ // ============================================================================
868
+ // HOOK
869
+ // ============================================================================
870
+
871
+ export function useAuth(): AuthContextType {
872
+ const context = useContext(AuthContext);
873
+
874
+ if (!context) {
875
+ throw new Error('useAuth must be used within AuthProvider');
876
+ }
877
+
878
+ return context;
879
+ }
880
+