@jmruthers/pace-core 0.5.181 → 0.5.182
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.
- package/CHANGELOG.md +1 -1
- package/README.md +16 -2
- package/dist/{AuthService-DYuQPJj6.d.ts → AuthService-B-cd2MA4.d.ts} +9 -11
- package/dist/{DataTable-CWAZZcXC.d.ts → DataTable-Bz8ffqyA.d.ts} +1 -1
- package/dist/{DataTable-UA6CL4JI.js → DataTable-QAB34V6K.js} +14 -15
- package/dist/UnifiedAuthProvider-7F6T4B6K.js +13 -0
- package/dist/{UnifiedAuthProvider-DJxGTftH.d.ts → UnifiedAuthProvider-F86d7dSi.d.ts} +5 -6
- package/dist/{api-45XYYO2A.js → api-ROMBCNKU.js} +5 -5
- package/dist/{audit-64X3VJXB.js → audit-WRS3KJKI.js} +4 -4
- package/dist/auth-BZOJqrdd.d.ts +49 -0
- package/dist/{chunk-CX5M4ZAG.js → chunk-5DRSZLL2.js} +1 -1
- package/dist/chunk-5DRSZLL2.js.map +1 -0
- package/dist/{chunk-BESYRHQM.js → chunk-6C4YBBJM.js} +10 -7
- package/dist/chunk-6C4YBBJM.js.map +1 -0
- package/dist/{chunk-PLDDJCW6.js → chunk-7D4SUZUM.js} +2 -13
- package/dist/{chunk-HRO5HWN2.js → chunk-CSOFYHAG.js} +55 -162
- package/dist/chunk-CSOFYHAG.js.map +1 -0
- package/dist/{chunk-ANBQRTPX.js → chunk-E66EQZE6.js} +3 -5
- package/dist/{chunk-ANBQRTPX.js.map → chunk-E66EQZE6.js.map} +1 -1
- package/dist/{chunk-Q5QRDWKI.js → chunk-F2IMUDXZ.js} +4 -6
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-SBVILCCA.js → chunk-FSFQFJCU.js} +28 -6
- package/dist/chunk-FSFQFJCU.js.map +1 -0
- package/dist/chunk-FUEYYMX5.js +2296 -0
- package/dist/chunk-FUEYYMX5.js.map +1 -0
- package/dist/{chunk-FFKNH6U5.js → chunk-HKIT6O7W.js} +3 -5
- package/dist/{chunk-FFKNH6U5.js.map → chunk-HKIT6O7W.js.map} +1 -1
- package/dist/chunk-KQCRWDSA.js +1 -0
- package/dist/{chunk-S5OFRT4M.js → chunk-KUEN3HFB.js} +6 -6
- package/dist/chunk-KUEN3HFB.js.map +1 -0
- package/dist/chunk-LMC26NLJ.js +84 -0
- package/dist/chunk-LMC26NLJ.js.map +1 -0
- package/dist/{chunk-BVYWGZVV.js → chunk-M7W4CP3M.js} +52 -19
- package/dist/chunk-M7W4CP3M.js.map +1 -0
- package/dist/{chunk-HZLDFOE4.js → chunk-MI7HBHN3.js} +164 -243
- package/dist/chunk-MI7HBHN3.js.map +1 -0
- package/dist/{chunk-PPMP5J6T.js → chunk-PWAHJW4G.js} +180 -29
- package/dist/chunk-PWAHJW4G.js.map +1 -0
- package/dist/chunk-PWLANIRT.js +127 -0
- package/dist/{chunk-XDNLUEXI.js.map → chunk-PWLANIRT.js.map} +1 -1
- package/dist/chunk-QCDXODCA.js +75 -0
- package/dist/chunk-QCDXODCA.js.map +1 -0
- package/dist/{chunk-D7LCGMVS.js → chunk-QETLRQI6.js} +526 -887
- package/dist/chunk-QETLRQI6.js.map +1 -0
- package/dist/{chunk-5MT24GKJ.js → chunk-QUVSNGIP.js} +264 -262
- package/dist/chunk-QUVSNGIP.js.map +1 -0
- package/dist/chunk-QXHPKYJV.js +113 -0
- package/dist/chunk-QXHPKYJV.js.map +1 -0
- package/dist/{chunk-OWAG3GSU.js → chunk-R77UEZ4E.js} +11 -1
- package/dist/chunk-R77UEZ4E.js.map +1 -0
- package/dist/{chunk-ZYTYSTO5.js → chunk-RA3JUFMW.js} +314 -161
- package/dist/chunk-RA3JUFMW.js.map +1 -0
- package/dist/{chunk-ERISIBYU.js → chunk-SQGMNID3.js} +3 -8
- package/dist/chunk-SQGMNID3.js.map +1 -0
- package/dist/{chunk-XJ2HZOBU.js → chunk-UHNYIBXL.js} +1 -1
- package/dist/chunk-UHNYIBXL.js.map +1 -0
- package/{src/utils/secureStorage.ts → dist/chunk-VBXEHIUJ.js} +113 -88
- package/dist/{chunk-7QCC6MCP.js.map → chunk-VBXEHIUJ.js.map} +1 -1
- package/dist/{chunk-VZ4VDGTB.js → chunk-W22JP75J.js} +5 -13
- package/dist/{chunk-VZ4VDGTB.js.map → chunk-W22JP75J.js.map} +1 -1
- package/dist/components.d.ts +12 -93
- package/dist/components.js +23 -106
- package/dist/components.js.map +1 -1
- package/dist/core-CUElvH_C.d.ts +164 -0
- package/dist/database.generated-CBmg2950.d.ts +8284 -0
- package/dist/event-CW5YB_2p.d.ts +239 -0
- package/dist/{file-reference-C6Gkn77H.d.ts → file-reference-D06mEEWW.d.ts} +7 -5
- package/dist/functions-D_kgHktt.d.ts +208 -0
- package/dist/hooks.d.ts +54 -7
- package/dist/hooks.js +204 -17
- package/dist/hooks.js.map +1 -1
- package/dist/{EventLogo-B3V3otev.d.ts → index-Bl--n7-T.d.ts} +387 -397
- package/dist/index.d.ts +94 -261
- package/dist/index.js +314 -126
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +7 -8
- package/dist/providers.js +6 -13
- package/dist/rbac/index.d.ts +171 -101
- package/dist/rbac/index.js +23 -17
- package/dist/styles/index.d.ts +1 -3
- package/dist/styles/index.js +2 -17
- package/dist/theming/runtime.js +3 -3
- package/dist/types-UU913iLA.d.ts +102 -0
- package/dist/{types-Dfz9dmVH.d.ts → types-_x1f4QBF.d.ts} +6 -6
- package/dist/types.d.ts +88 -227
- package/dist/types.js +64 -112
- package/dist/types.js.map +1 -1
- package/dist/{usePublicRouteParams-B7PabvuH.d.ts → usePublicRouteParams-JJczomYq.d.ts} +203 -6
- package/dist/utils.d.ts +299 -13
- package/dist/utils.js +481 -55
- package/dist/utils.js.map +1 -1
- package/dist/validation-643vUDZW.d.ts +177 -0
- package/docs/DOCUMENTATION_REVIEW_TRACKER.md +511 -0
- package/docs/README.md +9 -8
- package/docs/api/README.md +16 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +14 -14
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +29 -9
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +17 -17
- package/docs/api/enums/RBACErrorCode.md +228 -0
- package/docs/api/enums/RPCFunction.md +118 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +2 -2
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +29 -3
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +2 -2
- package/docs/api/interfaces/DataTableColumn.md +6 -6
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +2 -2
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +5 -5
- package/docs/api/interfaces/ExportOptions.md +4 -4
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +13 -13
- package/docs/api/interfaces/FileReference.md +12 -12
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +10 -10
- package/docs/api/interfaces/FileUploadProps.md +19 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +166 -0
- package/docs/api/interfaces/FormProps.md +113 -0
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +8 -8
- package/docs/api/interfaces/InputProps.md +2 -2
- package/docs/api/interfaces/LabelProps.md +8 -8
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +17 -73
- package/docs/api/interfaces/NavigationMenuProps.md +38 -53
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +13 -13
- package/docs/api/interfaces/OrganisationContextType.md +21 -21
- package/docs/api/interfaces/OrganisationMembership.md +15 -15
- package/docs/api/interfaces/OrganisationProviderProps.md +59 -2
- package/docs/api/interfaces/OrganisationSecurityError.md +5 -5
- package/docs/api/interfaces/PaceAppLayoutProps.md +26 -39
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +50 -0
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +15 -15
- package/docs/api/interfaces/RBACAccessValidateParams.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateResult.md +41 -0
- package/docs/api/interfaces/RBACAuditLogParams.md +85 -0
- package/docs/api/interfaces/RBACAuditLogResult.md +52 -0
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +52 -0
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +74 -0
- package/docs/api/interfaces/RBACPermissionCheckParams.md +74 -0
- package/docs/api/interfaces/RBACPermissionCheckResult.md +52 -0
- package/docs/api/interfaces/RBACPermissionsGetParams.md +63 -0
- package/docs/api/interfaces/RBACPermissionsGetResult.md +63 -0
- package/docs/api/interfaces/RBACResult.md +58 -0
- package/docs/api/interfaces/RBACRoleGrantParams.md +63 -0
- package/docs/api/interfaces/RBACRoleGrantResult.md +52 -0
- package/docs/api/interfaces/RBACRoleRevokeParams.md +63 -0
- package/docs/api/interfaces/RBACRoleRevokeResult.md +52 -0
- package/docs/api/interfaces/RBACRoleValidateParams.md +52 -0
- package/docs/api/interfaces/RBACRoleValidateResult.md +63 -0
- package/docs/api/interfaces/RBACRolesListParams.md +52 -0
- package/docs/api/interfaces/RBACRolesListResult.md +74 -0
- package/docs/api/interfaces/RBACSessionTrackParams.md +74 -0
- package/docs/api/interfaces/RBACSessionTrackResult.md +52 -0
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +15 -2
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +43 -2
- package/docs/api/interfaces/TextareaProps.md +2 -2
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +61 -61
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +87 -0
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +81 -0
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +5 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +4 -4
- package/docs/api/interfaces/UserProfile.md +7 -7
- package/docs/api/modules.md +484 -462
- package/docs/api-reference/components.md +186 -15
- package/docs/api-reference/deprecated.md +376 -0
- package/docs/api-reference/hooks.md +149 -19
- package/docs/api-reference/providers.md +61 -6
- package/docs/api-reference/rpc-functions.md +397 -0
- package/docs/api-reference/types.md +135 -78
- package/docs/api-reference/utilities.md +51 -380
- package/docs/architecture/README.md +49 -3
- package/docs/architecture/database-schema-requirements.md +40 -3
- package/docs/architecture/rbac-security-architecture.md +41 -4
- package/docs/architecture/services.md +127 -42
- package/docs/best-practices/README.md +51 -5
- package/docs/best-practices/accessibility.md +32 -3
- package/docs/best-practices/common-patterns.md +50 -3
- package/docs/best-practices/deployment.md +50 -4
- package/docs/best-practices/performance.md +50 -3
- package/docs/best-practices/security.md +94 -41
- package/docs/best-practices/testing.md +33 -4
- package/docs/core-concepts/authentication.md +5 -5
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +3 -3
- package/docs/core-concepts/permissions.md +3 -3
- package/docs/core-concepts/rbac-system.md +5 -5
- package/docs/documentation-index.md +30 -8
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +7 -5
- package/docs/getting-started/examples/basic-auth-app.md +3 -0
- package/docs/getting-started/examples/full-featured-app.md +5 -3
- package/docs/getting-started/faq.md +6 -6
- package/docs/getting-started/installation-guide.md +192 -13
- package/docs/getting-started/local-development.md +303 -0
- package/docs/getting-started/quick-reference.md +3 -3
- package/docs/getting-started/quick-start.md +517 -0
- package/docs/implementation-guides/app-layout.md +45 -3
- package/docs/implementation-guides/authentication.md +66 -7
- package/docs/implementation-guides/component-styling.md +53 -3
- package/docs/implementation-guides/data-tables.md +76 -7
- package/docs/implementation-guides/datatable-filtering.md +1 -2
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -1
- package/docs/implementation-guides/dynamic-colors.md +155 -4
- package/docs/implementation-guides/file-reference-system.md +72 -3
- package/docs/implementation-guides/file-upload-storage.md +72 -3
- package/docs/implementation-guides/forms.md +53 -3
- package/docs/implementation-guides/inactivity-tracking.md +53 -3
- package/docs/implementation-guides/large-datasets.md +1 -1
- package/docs/implementation-guides/navigation.md +55 -5
- package/docs/implementation-guides/organisation-security.md +72 -3
- package/docs/implementation-guides/performance.md +57 -1
- package/docs/implementation-guides/permission-enforcement.md +81 -8
- package/docs/implementation-guides/public-pages.md +560 -14
- package/docs/migration/MIGRATION_GUIDE.md +409 -50
- package/docs/migration/README.md +37 -3
- package/docs/migration/organisation-context-timing-fix.md +39 -4
- package/docs/migration/quick-migration-guide.md +41 -5
- package/docs/migration/rbac-migration.md +59 -3
- package/docs/migration/service-architecture.md +77 -14
- package/docs/rbac/README.md +79 -3
- package/docs/rbac/advanced-patterns.md +47 -3
- package/docs/rbac/api-reference.md +77 -8
- package/docs/rbac/event-based-apps.md +50 -5
- package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
- package/docs/rbac/examples.md +39 -3
- package/docs/rbac/getting-started.md +63 -4
- package/docs/rbac/quick-start.md +57 -5
- package/docs/rbac/rbac-rls-integration.md +68 -6
- package/docs/rbac/super-admin-guide.md +47 -3
- package/docs/rbac/troubleshooting.md +3 -3
- package/docs/security/README.md +68 -3
- package/docs/security/checklist.md +50 -3
- package/docs/standards/01-architecture-standard.md +39 -0
- package/docs/standards/02-api-and-rpc-standard.md +39 -0
- package/docs/standards/03-component-standard.md +32 -0
- package/docs/standards/04-code-style-standard.md +32 -0
- package/docs/standards/05-security-standard.md +30 -0
- package/docs/standards/06-testing-and-docs-standard.md +29 -0
- package/docs/standards/README.md +35 -0
- package/docs/styles/README.md +89 -8
- package/docs/testing/README.md +175 -24
- package/docs/troubleshooting/README.md +50 -3
- package/docs/troubleshooting/common-issues.md +271 -5
- package/docs/troubleshooting/debugging.md +54 -1
- package/docs/troubleshooting/migration.md +54 -1
- package/docs/troubleshooting/organisation-context-setup.md +29 -3
- package/docs/troubleshooting/styling-issues.md +246 -4
- package/{src/components/DataTable/examples → examples/DataTable}/GroupingAggregationExample.tsx +1 -1
- package/examples/{components 2/DataTable/HierarchicalActionsExample.tsx → DataTable/HierarchicalActionsExample.tsx} +7 -6
- package/{src/components/DataTable/examples → examples/DataTable}/HierarchicalExample.tsx +8 -6
- package/examples/{components 2/DataTable/PerformanceExample.tsx → DataTable/PerformanceExample.tsx} +2 -2
- package/examples/{components 2/DataTable/index.ts → DataTable/index.ts} +1 -0
- package/{src/components/Dialog/examples → examples/Dialog}/HtmlDialogExample.tsx +3 -3
- package/examples/{components 2/Dialog/ScrollableDialogExample.tsx → Dialog/ScrollableDialogExample.tsx} +1 -1
- package/{src/components/Dialog/examples → examples/Dialog}/SmartDialogExample.tsx +1 -1
- package/examples/{components 2/Dialog/index.ts → Dialog/index.ts} +0 -3
- package/examples/{features/public-pages → PublicPages}/CorrectPublicPageImplementation.tsx +52 -17
- package/examples/{features/public-pages → PublicPages}/PublicEventPage.tsx +65 -35
- package/examples/{features/public-pages → PublicPages}/PublicPageApp.tsx +52 -18
- package/examples/{features/public-pages → PublicPages}/PublicPageUsageExample.tsx +28 -15
- package/examples/README.md +81 -33
- package/examples/index.ts +14 -12
- package/examples/{RBAC → rbac}/CompleteRBACExample.tsx +1 -1
- package/examples/{features/rbac → rbac}/EventBasedApp.tsx +4 -4
- package/examples/{features/rbac → rbac}/PermissionExample.tsx +5 -3
- package/package.json +21 -27
- package/src/__tests__/helpers/test-utils.tsx +29 -3
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +7 -5
- package/src/components/Alert/Alert.test.tsx +2 -2
- package/src/components/Alert/Alert.tsx +4 -4
- package/src/components/Avatar/Avatar.test.tsx +17 -6
- package/src/components/Badge/Badge.test.tsx +1 -1
- package/src/components/Badge/Badge.tsx +2 -2
- package/src/components/Button/Button.test.tsx +2 -2
- package/src/components/Button/Button.tsx +11 -7
- package/src/components/Calendar/Calendar.test.tsx +41 -8
- package/src/components/Calendar/Calendar.tsx +39 -36
- package/src/components/Card/Card.tsx +51 -13
- package/src/components/Checkbox/Checkbox.test.tsx +36 -12
- package/src/components/DataTable/DataTable.test.tsx +1 -1
- package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +13 -7
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +14 -42
- package/src/components/DataTable/__tests__/DataTable.export.test.tsx +13 -10
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +14 -11
- package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +4 -2
- package/src/components/DataTable/__tests__/DataTable.test.tsx +13 -7
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +13 -10
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +15 -11
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +12 -6
- package/src/components/DataTable/__tests__/keyboard.test.tsx +12 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +10 -6
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +1 -1
- package/src/components/DataTable/components/DataTableBody.tsx +10 -25
- package/src/components/DataTable/components/DataTableCore.tsx +1 -1
- package/src/components/DataTable/components/FilterRow.tsx +3 -1
- package/src/components/DataTable/components/ImportModal.tsx +1 -1
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +9 -9
- package/src/components/DataTable/core/ColumnFactory.ts +6 -6
- package/src/components/DataTable/core/DataTableContext.tsx +14 -10
- package/src/components/DataTable/core/LocalDataAdapter.ts +2 -1
- package/src/components/DataTable/core/PluginRegistry.ts +3 -3
- package/src/components/DataTable/core/StateManager.ts +12 -11
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +104 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +101 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +102 -0
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +104 -0
- package/src/components/DataTable/core/interfaces.ts +17 -17
- package/src/components/DataTable/hooks/__tests__/useDataTableConfiguration.test.ts +124 -0
- package/src/components/DataTable/hooks/__tests__/useDataTableDataPipeline.test.ts +117 -0
- package/src/components/DataTable/hooks/__tests__/useDataTablePermissions.test.ts +102 -0
- package/src/components/DataTable/hooks/__tests__/useEffectiveColumnOrder.test.ts +53 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +0 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +9 -8
- package/src/components/DataTable/types.ts +5 -5
- package/src/components/DataTable/utils/aggregationUtils.ts +4 -4
- package/src/components/DataTable/utils/columnUtils.ts +3 -2
- package/src/components/DataTable/utils/debugTools.ts +1 -1
- package/src/components/DataTable/utils/exportUtils.ts +6 -6
- package/src/components/DataTable/utils/hierarchicalSorting.ts +6 -6
- package/src/components/DataTable/utils/hierarchicalUtils.ts +0 -8
- package/src/components/DataTable/utils/index.ts +0 -1
- package/src/components/DataTable/utils/performanceUtils.ts +9 -4
- package/src/components/Dialog/Dialog.test.tsx +49 -27
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +13 -8
- package/src/components/EventSelector/EventSelector.test.tsx +60 -12
- package/src/components/EventSelector/EventSelector.tsx +38 -15
- package/src/components/EventSelector/index.ts +2 -2
- package/src/components/FileDisplay/FileDisplay.test.tsx +143 -85
- package/src/components/FileDisplay/FileDisplay.tsx +1 -0
- package/src/components/FileUpload/FileUpload.test.tsx +532 -152
- package/src/components/FileUpload/FileUpload.tsx +43 -8
- package/src/components/Footer/Footer.test.tsx +19 -14
- package/src/components/Form/Form.test.tsx +96 -14
- package/src/components/Form/Form.tsx +210 -1
- package/src/components/Form/index.ts +3 -7
- package/src/components/Header/Header.test.tsx +24 -17
- package/src/components/Header/Header.tsx +3 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -4
- package/src/components/Input/Input.test.tsx +61 -36
- package/src/components/Label/{__tests__/Label.test.tsx → Label.test.tsx} +2 -2
- package/src/components/Label/Label.tsx +2 -3
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +6 -5
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +6 -2
- package/src/components/LoginForm/LoginForm.test.tsx +14 -13
- package/src/components/LoginForm/LoginForm.tsx +1 -1
- package/src/components/LoginForm/index.ts +7 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +233 -20
- package/src/components/NavigationMenu/NavigationMenu.tsx +191 -55
- package/src/components/NavigationMenu/index.ts +1 -1
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +20 -11
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +1 -1
- package/src/components/PaceAppLayout/{__tests__/PaceAppLayout.integration.test.tsx → PaceAppLayout.integration.test.tsx} +272 -79
- package/src/components/PaceAppLayout/{__tests__/PaceAppLayout.performance.test.tsx → PaceAppLayout.performance.test.tsx} +155 -32
- package/src/components/PaceAppLayout/{__tests__/PaceAppLayout.security.test.tsx → PaceAppLayout.security.test.tsx} +211 -65
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +498 -210
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +63 -64
- package/src/components/PaceAppLayout/test-setup.tsx +192 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +193 -39
- package/src/components/{PasswordReset → PasswordChange}/PasswordChangeForm.test.tsx +2 -2
- package/src/components/{PasswordReset → PasswordChange}/PasswordChangeForm.tsx +10 -4
- package/src/components/PasswordChange/index.ts +2 -0
- package/src/components/Progress/Progress.test.tsx +11 -0
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Progress/index.ts +10 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +2 -1
- package/src/components/PublicLayout/PublicLayout.test.tsx +1210 -0
- package/src/components/PublicLayout/PublicPageLayout.tsx +190 -36
- package/src/components/PublicLayout/PublicPageProvider.tsx +8 -7
- package/src/components/PublicLayout/index.ts +10 -28
- package/src/components/Select/Select.test.tsx +7 -7
- package/src/components/Select/Select.tsx +277 -11
- package/src/components/Select/index.ts +1 -2
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.test.tsx +232 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +40 -19
- package/src/components/Table/{__tests__/Table.test.tsx → Table.test.tsx} +94 -41
- package/src/components/Tabs/Tabs.test.tsx +10 -9
- package/src/components/Tabs/Tabs.tsx +61 -33
- package/src/components/Textarea/Textarea.test.tsx +31 -18
- package/src/components/Toast/Toast.tsx +2 -2
- package/src/components/Tooltip/Tooltip.test.tsx +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +7 -6
- package/src/components/UserMenu/UserMenu.tsx +2 -2
- package/src/components/index.ts +5 -4
- package/src/constants/performance.ts +19 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +21 -22
- package/src/hooks/__tests__/useEvents.unit.test.ts +5 -4
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +2 -2
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +17 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +16 -11
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +1 -3
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +1 -3
- package/src/hooks/__tests__/useRBAC.unit.test.ts +24 -2
- package/src/hooks/index.ts +4 -0
- package/src/hooks/public/index.ts +2 -0
- package/src/hooks/public/usePublicEvent.ts +4 -6
- package/src/hooks/public/usePublicRouteParams.ts +1 -1
- package/src/hooks/services/useAuth.ts +2 -4
- package/src/hooks/services/useCurrentEvent.ts +1 -1
- package/src/hooks/useAppConfig.ts +1 -1
- package/src/hooks/useDataTablePerformance.ts +2 -2
- package/src/hooks/useEventTheme.ts +1 -1
- package/src/hooks/useEvents.ts +51 -10
- package/src/hooks/useOrganisationPermissions.test.ts +3 -3
- package/src/hooks/useOrganisationPermissions.ts +1 -1
- package/src/hooks/useOrganisationSecurity.ts +2 -2
- package/src/hooks/usePermissionCache.test.ts +9 -9
- package/src/hooks/usePermissionCache.ts +2 -2
- package/src/index.ts +19 -12
- package/src/providers/OrganisationProvider.tsx +73 -9
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +113 -13
- package/src/providers/__tests__/AuthProvider.test.tsx +2 -1
- package/src/providers/__tests__/EventProvider.test.tsx +24 -15
- package/src/providers/__tests__/OrganisationProvider.test.tsx +87 -36
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +80 -24
- package/src/providers/index.ts +0 -3
- package/src/providers/services/AuthServiceProvider.tsx +2 -17
- package/src/providers/services/EventServiceProvider.tsx +11 -16
- package/src/providers/services/InactivityServiceProvider.tsx +9 -12
- package/src/providers/services/OrganisationServiceProvider.tsx +9 -12
- package/src/providers/services/UnifiedAuthProvider.tsx +85 -18
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +11 -4
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +105 -21
- package/src/rbac/adapters.tsx +1 -1
- package/src/rbac/api.ts +20 -4
- package/src/rbac/audit-enhanced.ts +47 -2
- package/src/rbac/audit.ts +47 -2
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.test.tsx +7 -6
- package/src/rbac/components/NavigationProvider.tsx +1 -1
- package/src/rbac/components/PagePermissionGuard.tsx +1 -1
- package/src/rbac/components/PagePermissionProvider.test.tsx +7 -6
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +1 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +7 -6
- package/src/rbac/components/SecureDataProvider.tsx +1 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +6 -6
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +11 -10
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +10 -11
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +19 -15
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +13 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +19 -15
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +18 -18
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +11 -10
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +8 -7
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +10 -11
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +48 -19
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +476 -0
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/usePermissions.ts +31 -85
- package/src/rbac/hooks/useRBAC.test.ts +13 -1
- package/src/rbac/hooks/useRBAC.ts +13 -67
- package/src/rbac/hooks/useResolvedScope.ts +11 -0
- package/src/rbac/hooks/useSecureSupabase.ts +308 -0
- package/src/rbac/index.ts +3 -0
- package/src/rbac/secureClient.ts +53 -6
- package/src/rbac/security.ts +37 -1
- package/src/{types/rbac-functions.ts → rbac/types/functions.ts} +30 -30
- package/src/rbac/types.ts +3 -2
- package/src/services/AuthService.ts +33 -25
- package/src/services/EventService.ts +56 -44
- package/src/services/InactivityService.ts +33 -53
- package/src/services/OrganisationService.ts +36 -40
- package/src/services/__tests__/AuthService.restoreSession.test.ts +6 -2
- package/src/services/__tests__/EventService.test.ts +67 -33
- package/src/services/interfaces/IEventService.ts +1 -1
- package/src/styles/core.css +2 -2
- package/src/styles/index.ts +1 -5
- package/src/types/__tests__/guards.test.ts +1 -1
- package/src/types/__tests__/type-validation.test.ts +0 -1
- package/src/types/auth.ts +42 -2
- package/src/types/core.ts +251 -0
- package/src/types/database.ts +11 -496
- package/src/types/event.ts +102 -0
- package/src/types/file-reference.ts +6 -4
- package/src/types/guards.ts +2 -1
- package/src/types/index.ts +48 -14
- package/src/types/lodash.debounce.d.ts +15 -0
- package/src/types/organisation.ts +14 -10
- package/src/types/supabase.ts +15 -17
- package/src/utils/__tests__/secureErrors.unit.test.ts +1 -1
- package/src/utils/__tests__/validationUtils.unit.test.ts +0 -29
- package/src/utils/app/appNameResolver.ts +1 -1
- package/src/utils/dynamic/dynamicUtils.ts +3 -2
- package/src/utils/file-reference/index.ts +25 -6
- package/src/utils/security/secureErrors.ts +1 -1
- package/src/utils/validation/index.ts +6 -12
- package/src/utils/validation/validationUtils.ts +0 -13
- package/dist/UnifiedAuthProvider-B37ATQHE.js +0 -16
- package/dist/auth-DReDSLq9.d.ts +0 -16
- package/dist/chunk-3JI76CYK.js +0 -2444
- package/dist/chunk-3JI76CYK.js.map +0 -1
- package/dist/chunk-56XJ3TU6.js +0 -11
- package/dist/chunk-56XJ3TU6.js.map +0 -1
- package/dist/chunk-5MT24GKJ.js.map +0 -1
- package/dist/chunk-7QCC6MCP.js +0 -288
- package/dist/chunk-BESYRHQM.js.map +0 -1
- package/dist/chunk-BJPBT3CU.js +0 -21
- package/dist/chunk-BJPBT3CU.js.map +0 -1
- package/dist/chunk-BVYWGZVV.js.map +0 -1
- package/dist/chunk-CX5M4ZAG.js.map +0 -1
- package/dist/chunk-D7LCGMVS.js.map +0 -1
- package/dist/chunk-EGI6MUL6.js +0 -27
- package/dist/chunk-EGI6MUL6.js.map +0 -1
- package/dist/chunk-ERISIBYU.js.map +0 -1
- package/dist/chunk-HRO5HWN2.js.map +0 -1
- package/dist/chunk-HZLDFOE4.js.map +0 -1
- package/dist/chunk-JISYG63F.js +0 -70
- package/dist/chunk-JISYG63F.js.map +0 -1
- package/dist/chunk-LIMSTKYD.js +0 -61
- package/dist/chunk-LIMSTKYD.js.map +0 -1
- package/dist/chunk-OWAG3GSU.js.map +0 -1
- package/dist/chunk-PPMP5J6T.js.map +0 -1
- package/dist/chunk-Q5QRDWKI.js.map +0 -1
- package/dist/chunk-S5OFRT4M.js.map +0 -1
- package/dist/chunk-SBVILCCA.js.map +0 -1
- package/dist/chunk-TUMEWN34.js +0 -15
- package/dist/chunk-TUMEWN34.js.map +0 -1
- package/dist/chunk-XDNLUEXI.js +0 -138
- package/dist/chunk-XJ2HZOBU.js.map +0 -1
- package/dist/chunk-ZYTYSTO5.js.map +0 -1
- package/dist/chunk-ZZ2SS7NI.js +0 -237
- package/dist/chunk-ZZ2SS7NI.js.map +0 -1
- package/dist/database-C6jy7EOu.d.ts +0 -500
- package/dist/organisation-D6qRDtbF.d.ts +0 -93
- package/dist/schema-DTDZQe2u.d.ts +0 -28
- package/dist/unified-DQ4VcT7H.d.ts +0 -198
- package/dist/useInactivityTracker-TO6ZOF35.js +0 -11
- package/dist/validation.d.ts +0 -47
- package/dist/validation.js +0 -24
- package/dist/validation.js.map +0 -1
- package/docs/DOCUMENTATION_AUDIT.md +0 -172
- package/docs/DOCUMENTATION_STANDARD.md +0 -137
- package/docs/api/classes/PublicErrorBoundary.md +0 -132
- package/docs/api/interfaces/EventLogoProps.md +0 -152
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +0 -94
- package/docs/api/interfaces/PublicErrorBoundaryState.md +0 -68
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +0 -86
- package/docs/architecture/rpc-function-standards.md +0 -1106
- package/docs/getting-started/consuming-app-vite-config.md +0 -239
- package/docs/implementation-guides/event-theming-summary.md +0 -226
- package/docs/implementation-guides/public-pages-advanced.md +0 -1038
- package/docs/migration/v0.4.15-tailwind-scanning.md +0 -278
- package/docs/migration/v0.4.16-css-first-approach.md +0 -312
- package/docs/migration/v0.4.17-source-path-fix.md +0 -235
- package/docs/rbac/RBAC_EVENT_CONTEXT_LOADING.md +0 -222
- package/docs/rbac/RBAC_LOGIN_SAFETY_FIX.md +0 -95
- package/docs/rbac/RBAC_V0.5.147_FIX.md +0 -117
- package/docs/rbac/README-rbac-rls-integration.md +0 -374
- package/docs/styles/usage.md +0 -227
- package/docs/testing/visual-testing.md +0 -120
- package/docs/troubleshooting/DEBUG_NETWORK_ERROR.md +0 -152
- package/docs/troubleshooting/FIX_SUPABASE_CORS.md +0 -184
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +0 -193
- package/docs/troubleshooting/database-view-compatibility.md +0 -125
- package/docs/troubleshooting/react-hooks-issue-analysis.md +0 -172
- package/docs/troubleshooting/tailwind-content-scanning.md +0 -219
- package/examples/RBAC/EventBasedApp.tsx +0 -239
- package/examples/RBAC/PermissionExample.tsx +0 -151
- package/examples/STRUCTURE.md +0 -125
- package/examples/components 2/DataTable/HierarchicalExample.tsx +0 -475
- package/examples/components 2/Dialog/BasicHtmlTest.tsx +0 -55
- package/examples/components 2/Dialog/DebugHtmlExample.tsx +0 -68
- package/examples/components 2/Dialog/HtmlDialogExample.tsx +0 -202
- package/examples/components 2/Dialog/SimpleHtmlTest.tsx +0 -61
- package/examples/components 2/Dialog/SmartDialogExample.tsx +0 -322
- package/examples/components 2/index.ts +0 -11
- package/examples/features/index.ts +0 -12
- package/examples/features/rbac/CompleteRBACExample.tsx +0 -324
- package/examples/features/rbac/index.ts +0 -13
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +0 -301
- package/examples/public-pages/PublicEventPage.tsx +0 -274
- package/examples/public-pages/PublicPageApp.tsx +0 -308
- package/examples/public-pages/PublicPageUsageExample.tsx +0 -216
- package/examples/public-pages/index.ts +0 -14
- package/src/__tests__/TEST_STANDARD.md +0 -1008
- package/src/components/Checkbox/__mocks__/Checkbox.tsx +0 -2
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +0 -421
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +0 -177
- package/src/components/DataTable/examples/PerformanceExample.tsx +0 -506
- package/src/components/DataTable/examples/__tests__/HierarchicalActionsExample.test.tsx +0 -316
- package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +0 -45
- package/src/components/DataTable/examples/__tests__/InitialPageSizeExample.test.tsx +0 -211
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +0 -126
- package/src/components/Dialog/README.md +0 -804
- package/src/components/Dialog/examples/BasicHtmlTest.tsx +0 -55
- package/src/components/Dialog/examples/DebugHtmlExample.tsx +0 -68
- package/src/components/Dialog/examples/ScrollableDialogExample.tsx +0 -290
- package/src/components/Dialog/examples/SimpleHtmlTest.tsx +0 -61
- package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +0 -71
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +0 -122
- package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +0 -147
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +0 -611
- package/src/components/Dialog/utils/safeHtml.ts +0 -185
- package/src/components/EventSelector/types.ts +0 -79
- package/src/components/Form/FormErrorSummary.tsx +0 -113
- package/src/components/Form/FormField.tsx +0 -249
- package/src/components/Form/FormFieldset.tsx +0 -127
- package/src/components/Form/FormLiveRegion.tsx +0 -198
- package/src/components/Input/__mocks__/Input.tsx +0 -2
- package/src/components/NavigationMenu/types.ts +0 -85
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +0 -326
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +0 -1078
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +0 -597
- package/src/components/PasswordReset/PasswordResetForm.tsx +0 -201
- package/src/components/PasswordReset/index.ts +0 -2
- package/src/components/ProtectedRoute/README.md +0 -164
- package/src/components/PublicLayout/EventLogo.tsx +0 -175
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +0 -282
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +0 -216
- package/src/components/PublicLayout/PublicPageContextChecker.tsx +0 -131
- package/src/components/PublicLayout/PublicPageDebugger.tsx +0 -104
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +0 -162
- package/src/components/PublicLayout/PublicPageFooter.tsx +0 -124
- package/src/components/PublicLayout/PublicPageHeader.tsx +0 -209
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +0 -449
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +0 -393
- package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +0 -192
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +0 -351
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +0 -402
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +0 -460
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +0 -313
- package/src/components/Select/hooks.ts +0 -289
- package/src/hooks/useCounter.test.ts +0 -131
- package/src/hooks/useDebounce.test.ts +0 -375
- package/src/providers/AuthProvider.tsx +0 -15
- package/src/providers/EventProvider.tsx +0 -16
- package/src/providers/InactivityProvider.tsx +0 -15
- package/src/providers/OrganisationProvider.context.test.tsx +0 -169
- package/src/providers/UnifiedAuthProvider.tsx +0 -15
- package/src/types/theme.ts +0 -6
- package/src/types/unified.ts +0 -265
- package/src/utils/appConfig.ts +0 -47
- package/src/utils/appIdResolver.test.ts +0 -499
- package/src/utils/appIdResolver.ts +0 -130
- package/src/utils/appNameResolver.simple.test.ts +0 -212
- package/src/utils/appNameResolver.test.ts +0 -121
- package/src/utils/appNameResolver.ts +0 -191
- package/src/utils/audit.ts +0 -127
- package/src/utils/auth-utils.ts +0 -96
- package/src/utils/bundleAnalysis.ts +0 -129
- package/src/utils/debugLogger.ts +0 -67
- package/src/utils/deviceFingerprint.ts +0 -215
- package/src/utils/dynamicUtils.ts +0 -105
- package/src/utils/file-reference.test.ts +0 -788
- package/src/utils/file-reference.ts +0 -519
- package/src/utils/formatDate.test.ts +0 -237
- package/src/utils/formatting.ts +0 -170
- package/src/utils/lazyLoad.tsx +0 -44
- package/src/utils/logger.ts +0 -179
- package/src/utils/organisationContext.test.ts +0 -322
- package/src/utils/organisationContext.ts +0 -153
- package/src/utils/performanceBenchmark.ts +0 -64
- package/src/utils/performanceBudgets.ts +0 -110
- package/src/utils/permissionTypes.ts +0 -37
- package/src/utils/permissionUtils.test.ts +0 -393
- package/src/utils/permissionUtils.ts +0 -34
- package/src/utils/sanitization.ts +0 -264
- package/src/utils/schemaUtils.ts +0 -37
- package/src/utils/secureDataAccess.test.ts +0 -711
- package/src/utils/secureDataAccess.ts +0 -377
- package/src/utils/secureErrors.ts +0 -79
- package/src/utils/security.ts +0 -156
- package/src/utils/securityMonitor.ts +0 -45
- package/src/utils/sessionTracking.ts +0 -126
- package/src/utils/validation.ts +0 -111
- package/src/utils/validationUtils.ts +0 -120
- package/src/validation/index.ts +0 -12
- /package/dist/{DataTable-UA6CL4JI.js.map → DataTable-QAB34V6K.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-B37ATQHE.js.map → UnifiedAuthProvider-7F6T4B6K.js.map} +0 -0
- /package/dist/{api-45XYYO2A.js.map → api-ROMBCNKU.js.map} +0 -0
- /package/dist/{audit-64X3VJXB.js.map → audit-WRS3KJKI.js.map} +0 -0
- /package/dist/{chunk-PLDDJCW6.js.map → chunk-7D4SUZUM.js.map} +0 -0
- /package/dist/{useInactivityTracker-TO6ZOF35.js.map → chunk-KQCRWDSA.js.map} +0 -0
- /package/examples/{components 2/DataTable → DataTable}/InitialPageSizeExample.tsx +0 -0
- /package/examples/{features/public-pages → PublicPages}/index.ts +0 -0
- /package/examples/{RBAC → rbac}/index.ts +0 -0
|
@@ -0,0 +1,2296 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertOrganisationId,
|
|
3
|
+
assertUserId
|
|
4
|
+
} from "./chunk-QXHPKYJV.js";
|
|
5
|
+
import {
|
|
6
|
+
secureStorage,
|
|
7
|
+
setOrganisationContext
|
|
8
|
+
} from "./chunk-VBXEHIUJ.js";
|
|
9
|
+
import {
|
|
10
|
+
createLogger,
|
|
11
|
+
logger
|
|
12
|
+
} from "./chunk-PWLANIRT.js";
|
|
13
|
+
|
|
14
|
+
// src/providers/services/UnifiedAuthProvider.tsx
|
|
15
|
+
import { createContext as createContext5, useContext as useContext6, useMemo as useMemo6, useCallback, useRef as useRef4, useEffect as useEffect10, useState as useState3 } from "react";
|
|
16
|
+
|
|
17
|
+
// src/providers/services/AuthServiceProvider.tsx
|
|
18
|
+
import { createContext, useMemo, useEffect, useState } from "react";
|
|
19
|
+
|
|
20
|
+
// src/services/AuthService.ts
|
|
21
|
+
import { AuthError } from "@supabase/supabase-js";
|
|
22
|
+
|
|
23
|
+
// src/services/base/BaseService.ts
|
|
24
|
+
var BaseService = class {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.subscribers = [];
|
|
27
|
+
this.isInitialized = false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to state changes
|
|
31
|
+
* @param callback Function to call when state changes
|
|
32
|
+
* @returns Unsubscribe function
|
|
33
|
+
*/
|
|
34
|
+
subscribe(callback) {
|
|
35
|
+
this.subscribers.push(callback);
|
|
36
|
+
return () => {
|
|
37
|
+
const index = this.subscribers.indexOf(callback);
|
|
38
|
+
if (index > -1) {
|
|
39
|
+
this.subscribers.splice(index, 1);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Notify all subscribers of state changes
|
|
45
|
+
* This triggers React re-renders
|
|
46
|
+
*/
|
|
47
|
+
notify() {
|
|
48
|
+
this.subscribers.forEach((callback) => {
|
|
49
|
+
try {
|
|
50
|
+
callback();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.error("BaseService", "Error in subscriber callback:", error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Initialize the service
|
|
58
|
+
* Override in subclasses to implement initialization logic
|
|
59
|
+
*/
|
|
60
|
+
async initialize() {
|
|
61
|
+
if (this.isInitialized) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await this.doInitialize();
|
|
65
|
+
this.isInitialized = true;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Cleanup the service
|
|
69
|
+
* Override in subclasses to implement cleanup logic
|
|
70
|
+
*/
|
|
71
|
+
cleanup() {
|
|
72
|
+
this.subscribers = [];
|
|
73
|
+
this.doCleanup();
|
|
74
|
+
this.isInitialized = false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if service is initialized
|
|
78
|
+
*/
|
|
79
|
+
getInitialized() {
|
|
80
|
+
return this.isInitialized;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reset initialization state (allows re-initialization)
|
|
84
|
+
* Use when dependencies change and service needs to re-initialize
|
|
85
|
+
*/
|
|
86
|
+
resetInitialization() {
|
|
87
|
+
this.isInitialized = false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/services/AuthService.ts
|
|
92
|
+
var AuthService = class extends BaseService {
|
|
93
|
+
constructor(supabaseClient, appName) {
|
|
94
|
+
super();
|
|
95
|
+
this.user = null;
|
|
96
|
+
this.session = null;
|
|
97
|
+
this.authLoading = false;
|
|
98
|
+
this.authError = null;
|
|
99
|
+
this.supabaseClient = null;
|
|
100
|
+
this.authStateSubscription = null;
|
|
101
|
+
this.sessionRestorationState = {
|
|
102
|
+
isRestoring: false,
|
|
103
|
+
restorationComplete: false,
|
|
104
|
+
restorationError: null
|
|
105
|
+
};
|
|
106
|
+
this.restorationTimeoutId = null;
|
|
107
|
+
this.restorationTimeoutMs = 5e3;
|
|
108
|
+
this.restorationStartTime = null;
|
|
109
|
+
this.appName = void 0;
|
|
110
|
+
this.errorHandler = null;
|
|
111
|
+
this.unhandledRejectionHandler = null;
|
|
112
|
+
this.supabaseClient = supabaseClient;
|
|
113
|
+
this.appName = appName;
|
|
114
|
+
}
|
|
115
|
+
// Auth state getters
|
|
116
|
+
getUser() {
|
|
117
|
+
return this.user;
|
|
118
|
+
}
|
|
119
|
+
getSession() {
|
|
120
|
+
return this.session;
|
|
121
|
+
}
|
|
122
|
+
isAuthenticated() {
|
|
123
|
+
return !!(this.user && this.session);
|
|
124
|
+
}
|
|
125
|
+
isLoading() {
|
|
126
|
+
return this.authLoading;
|
|
127
|
+
}
|
|
128
|
+
getError() {
|
|
129
|
+
return this.authError;
|
|
130
|
+
}
|
|
131
|
+
getSupabaseClient() {
|
|
132
|
+
return this.supabaseClient;
|
|
133
|
+
}
|
|
134
|
+
getSessionRestorationState() {
|
|
135
|
+
return { ...this.sessionRestorationState };
|
|
136
|
+
}
|
|
137
|
+
// Auth methods
|
|
138
|
+
async signIn(email, password) {
|
|
139
|
+
if (!this.supabaseClient) {
|
|
140
|
+
const error = new AuthError("Supabase client not available");
|
|
141
|
+
this.authError = error;
|
|
142
|
+
this.notify();
|
|
143
|
+
return { user: null, session: null, error };
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const { data, error } = await this.supabaseClient.auth.signInWithPassword({
|
|
147
|
+
email,
|
|
148
|
+
password: password || ""
|
|
149
|
+
});
|
|
150
|
+
if (error) {
|
|
151
|
+
this.authError = error;
|
|
152
|
+
this.user = null;
|
|
153
|
+
this.session = null;
|
|
154
|
+
} else {
|
|
155
|
+
this.authError = null;
|
|
156
|
+
this.user = data.user;
|
|
157
|
+
this.session = data.session;
|
|
158
|
+
}
|
|
159
|
+
this.notify();
|
|
160
|
+
return { user: data.user, session: data.session, error };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const authError = error instanceof AuthError ? error : new AuthError(error instanceof Error ? error.message : "Authentication failed");
|
|
163
|
+
this.authError = authError;
|
|
164
|
+
this.user = null;
|
|
165
|
+
this.session = null;
|
|
166
|
+
this.notify();
|
|
167
|
+
return { user: null, session: null, error: authError };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async signUp(email, password) {
|
|
171
|
+
if (!this.supabaseClient) {
|
|
172
|
+
const error = new AuthError("Supabase client not available");
|
|
173
|
+
this.authError = error;
|
|
174
|
+
this.notify();
|
|
175
|
+
return { user: null, session: null, error };
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const { data, error } = await this.supabaseClient.auth.signUp({
|
|
179
|
+
email,
|
|
180
|
+
password
|
|
181
|
+
});
|
|
182
|
+
if (error) {
|
|
183
|
+
this.authError = error;
|
|
184
|
+
this.user = null;
|
|
185
|
+
this.session = null;
|
|
186
|
+
} else {
|
|
187
|
+
this.authError = null;
|
|
188
|
+
this.user = data.user;
|
|
189
|
+
this.session = data.session;
|
|
190
|
+
}
|
|
191
|
+
this.notify();
|
|
192
|
+
return { user: data.user, session: data.session, error };
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const authError = error instanceof AuthError ? error : new AuthError(error instanceof Error ? error.message : "Authentication failed");
|
|
195
|
+
this.authError = authError;
|
|
196
|
+
this.user = null;
|
|
197
|
+
this.session = null;
|
|
198
|
+
this.notify();
|
|
199
|
+
return { user: null, session: null, error: authError };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async signOut() {
|
|
203
|
+
if (!this.supabaseClient) {
|
|
204
|
+
const error = new AuthError("Supabase client not available");
|
|
205
|
+
this.authError = error;
|
|
206
|
+
this.notify();
|
|
207
|
+
return { user: null, session: null, error };
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const { error } = await this.supabaseClient.auth.signOut();
|
|
211
|
+
if (error) {
|
|
212
|
+
this.authError = error;
|
|
213
|
+
} else {
|
|
214
|
+
this.authError = null;
|
|
215
|
+
this.user = null;
|
|
216
|
+
this.session = null;
|
|
217
|
+
}
|
|
218
|
+
this.notify();
|
|
219
|
+
return { user: null, session: null, error };
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const authError = error instanceof AuthError ? error : new AuthError(error instanceof Error ? error.message : "Authentication failed");
|
|
222
|
+
this.authError = authError;
|
|
223
|
+
this.user = null;
|
|
224
|
+
this.session = null;
|
|
225
|
+
this.notify();
|
|
226
|
+
return { user: null, session: null, error: authError };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async resetPassword(email) {
|
|
230
|
+
if (!this.supabaseClient) {
|
|
231
|
+
const error = new AuthError("Supabase client not available");
|
|
232
|
+
this.authError = error;
|
|
233
|
+
this.notify();
|
|
234
|
+
return { user: null, session: null, error };
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const { error } = await this.supabaseClient.auth.resetPasswordForEmail(email);
|
|
238
|
+
if (error) {
|
|
239
|
+
this.authError = error;
|
|
240
|
+
} else {
|
|
241
|
+
this.authError = null;
|
|
242
|
+
}
|
|
243
|
+
this.notify();
|
|
244
|
+
return { user: null, session: null, error };
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const authError = error instanceof AuthError ? error : new AuthError(error instanceof Error ? error.message : "Authentication failed");
|
|
247
|
+
this.authError = authError;
|
|
248
|
+
this.notify();
|
|
249
|
+
return { user: null, session: null, error: authError };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async updatePassword(password) {
|
|
253
|
+
if (!this.supabaseClient) {
|
|
254
|
+
const error = new AuthError("Supabase client not available");
|
|
255
|
+
this.authError = error;
|
|
256
|
+
this.notify();
|
|
257
|
+
return { user: null, session: null, error };
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const { error } = await this.supabaseClient.auth.updateUser({
|
|
261
|
+
password
|
|
262
|
+
});
|
|
263
|
+
if (error) {
|
|
264
|
+
this.authError = error;
|
|
265
|
+
} else {
|
|
266
|
+
this.authError = null;
|
|
267
|
+
}
|
|
268
|
+
this.notify();
|
|
269
|
+
return { user: null, session: null, error };
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const authError = error;
|
|
272
|
+
this.authError = authError;
|
|
273
|
+
this.notify();
|
|
274
|
+
return { user: null, session: null, error: authError };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async refreshSession() {
|
|
278
|
+
if (!this.supabaseClient) {
|
|
279
|
+
const error = new AuthError("Supabase client not available");
|
|
280
|
+
this.authError = error;
|
|
281
|
+
this.notify();
|
|
282
|
+
return { user: null, session: null, error };
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const { data, error } = await this.supabaseClient.auth.refreshSession();
|
|
286
|
+
if (error) {
|
|
287
|
+
this.authError = error;
|
|
288
|
+
this.user = null;
|
|
289
|
+
this.session = null;
|
|
290
|
+
} else {
|
|
291
|
+
this.authError = null;
|
|
292
|
+
if (data?.user && data?.session) {
|
|
293
|
+
this.user = data.user;
|
|
294
|
+
this.session = data.session;
|
|
295
|
+
} else {
|
|
296
|
+
this.user = null;
|
|
297
|
+
this.session = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
this.notify();
|
|
301
|
+
return {
|
|
302
|
+
user: data?.user && data?.session ? data.user : null,
|
|
303
|
+
session: data?.session ?? null,
|
|
304
|
+
error
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
const authError = error instanceof AuthError ? error : new AuthError(error instanceof Error ? error.message : "Authentication failed");
|
|
308
|
+
this.authError = authError;
|
|
309
|
+
this.user = null;
|
|
310
|
+
this.session = null;
|
|
311
|
+
this.notify();
|
|
312
|
+
return { user: null, session: null, error: authError };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Lifecycle methods
|
|
316
|
+
async initialize() {
|
|
317
|
+
await super.initialize();
|
|
318
|
+
await this.setupAuthStateListener();
|
|
319
|
+
await this.restoreSession();
|
|
320
|
+
}
|
|
321
|
+
cleanup() {
|
|
322
|
+
if (this.authStateSubscription) {
|
|
323
|
+
this.authStateSubscription.unsubscribe();
|
|
324
|
+
this.authStateSubscription = null;
|
|
325
|
+
}
|
|
326
|
+
this.clearRestorationTimeout();
|
|
327
|
+
this.restorationStartTime = null;
|
|
328
|
+
this.sessionRestorationState = {
|
|
329
|
+
isRestoring: false,
|
|
330
|
+
restorationComplete: false,
|
|
331
|
+
restorationError: null
|
|
332
|
+
};
|
|
333
|
+
this.authLoading = false;
|
|
334
|
+
super.cleanup();
|
|
335
|
+
}
|
|
336
|
+
async doInitialize() {
|
|
337
|
+
this.setupErrorHandlers();
|
|
338
|
+
}
|
|
339
|
+
doCleanup() {
|
|
340
|
+
this.removeErrorHandlers();
|
|
341
|
+
}
|
|
342
|
+
startSessionRestoration() {
|
|
343
|
+
this.clearRestorationTimeout();
|
|
344
|
+
this.sessionRestorationState = {
|
|
345
|
+
isRestoring: true,
|
|
346
|
+
restorationComplete: false,
|
|
347
|
+
restorationError: null
|
|
348
|
+
};
|
|
349
|
+
this.authLoading = true;
|
|
350
|
+
this.restorationStartTime = Date.now();
|
|
351
|
+
this.notify();
|
|
352
|
+
this.restorationTimeoutId = setTimeout(() => {
|
|
353
|
+
logger.warn("AuthService", "Session restoration timed out after", this.restorationTimeoutMs, "ms");
|
|
354
|
+
const timeoutError = new Error(`Session restoration timed out after ${this.restorationTimeoutMs}ms`);
|
|
355
|
+
timeoutError.name = "SessionRestorationTimeoutError";
|
|
356
|
+
this.finishSessionRestoration(timeoutError);
|
|
357
|
+
}, this.restorationTimeoutMs);
|
|
358
|
+
}
|
|
359
|
+
finishSessionRestoration(error) {
|
|
360
|
+
if (!this.sessionRestorationState.isRestoring && !error) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.clearRestorationTimeout();
|
|
364
|
+
const completedAt = Date.now();
|
|
365
|
+
const duration = this.restorationStartTime ? completedAt - this.restorationStartTime : null;
|
|
366
|
+
this.restorationStartTime = null;
|
|
367
|
+
const restorationComplete = !error;
|
|
368
|
+
this.sessionRestorationState = {
|
|
369
|
+
isRestoring: false,
|
|
370
|
+
restorationComplete,
|
|
371
|
+
restorationError: error ?? null
|
|
372
|
+
};
|
|
373
|
+
this.authLoading = false;
|
|
374
|
+
if (error) {
|
|
375
|
+
logger.warn("AuthService", "Session restoration finished with error:", error);
|
|
376
|
+
}
|
|
377
|
+
this.notify();
|
|
378
|
+
}
|
|
379
|
+
clearRestorationTimeout() {
|
|
380
|
+
if (this.restorationTimeoutId) {
|
|
381
|
+
clearTimeout(this.restorationTimeoutId);
|
|
382
|
+
this.restorationTimeoutId = null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async setupAuthStateListener() {
|
|
386
|
+
if (!this.supabaseClient) {
|
|
387
|
+
this.authLoading = false;
|
|
388
|
+
this.notify();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const subscription = this.supabaseClient.auth.onAuthStateChange(
|
|
393
|
+
(event, session) => {
|
|
394
|
+
try {
|
|
395
|
+
if (event === "SIGNED_OUT") {
|
|
396
|
+
this.session = null;
|
|
397
|
+
this.user = null;
|
|
398
|
+
this.authError = null;
|
|
399
|
+
if (session?.user) {
|
|
400
|
+
this.trackSession("logout", session).catch((err) => {
|
|
401
|
+
logger.warn("AuthService", "Failed to track logout session:", err);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
} else if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
|
|
405
|
+
this.session = session;
|
|
406
|
+
this.user = session?.user ?? null;
|
|
407
|
+
if (session) {
|
|
408
|
+
this.authError = null;
|
|
409
|
+
}
|
|
410
|
+
if (event === "SIGNED_IN" && session?.user) {
|
|
411
|
+
this.trackSession("login", session).catch((err) => {
|
|
412
|
+
logger.warn("AuthService", "Failed to track login session:", err);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
} else if (event === "INITIAL_SESSION") {
|
|
416
|
+
if (session) {
|
|
417
|
+
this.session = session;
|
|
418
|
+
this.user = session.user ?? null;
|
|
419
|
+
this.authError = null;
|
|
420
|
+
const hasTimeoutError = this.sessionRestorationState.restorationError?.name === "SessionRestorationTimeoutError";
|
|
421
|
+
if (this.sessionRestorationState.isRestoring || this.sessionRestorationState.restorationError || hasTimeoutError && session) {
|
|
422
|
+
this.finishSessionRestoration();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (this.sessionRestorationState.isRestoring) {
|
|
427
|
+
this.finishSessionRestoration();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.authLoading = false;
|
|
432
|
+
this.notify();
|
|
433
|
+
} catch (error) {
|
|
434
|
+
logger.warn("AuthService", "Error in auth state change handler:", error);
|
|
435
|
+
this.authLoading = false;
|
|
436
|
+
this.notify();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
this.authStateSubscription = subscription.data.subscription;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
logger.error("AuthService", "Failed to setup auth state listener:", error);
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async restoreSession() {
|
|
447
|
+
if (!this.supabaseClient) {
|
|
448
|
+
const error = new Error("Supabase client not available during session restoration");
|
|
449
|
+
logger.error("AuthService", "Unable to restore session:", error);
|
|
450
|
+
this.finishSessionRestoration(error);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.startSessionRestoration();
|
|
454
|
+
try {
|
|
455
|
+
let currentSession = null;
|
|
456
|
+
let sessionError = null;
|
|
457
|
+
try {
|
|
458
|
+
const { data, error } = await this.supabaseClient.auth.getSession();
|
|
459
|
+
currentSession = data?.session ?? null;
|
|
460
|
+
sessionError = error ?? null;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
currentSession = null;
|
|
463
|
+
sessionError = null;
|
|
464
|
+
}
|
|
465
|
+
if (sessionError) {
|
|
466
|
+
this.authError = sessionError;
|
|
467
|
+
try {
|
|
468
|
+
const { data, error } = await this.supabaseClient.auth.getUser();
|
|
469
|
+
const currentUser = data?.user ?? null;
|
|
470
|
+
const userError = error ?? null;
|
|
471
|
+
if (currentUser) {
|
|
472
|
+
this.user = currentUser;
|
|
473
|
+
this.session = null;
|
|
474
|
+
}
|
|
475
|
+
if (userError && !this.authError) {
|
|
476
|
+
this.authError = userError;
|
|
477
|
+
}
|
|
478
|
+
} catch (getUserError) {
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (currentSession) {
|
|
482
|
+
this.session = currentSession;
|
|
483
|
+
this.user = currentSession.user;
|
|
484
|
+
this.authError = null;
|
|
485
|
+
} else if (!sessionError) {
|
|
486
|
+
this.session = null;
|
|
487
|
+
this.user = null;
|
|
488
|
+
this.authError = null;
|
|
489
|
+
}
|
|
490
|
+
this.finishSessionRestoration();
|
|
491
|
+
} catch (error) {
|
|
492
|
+
const restorationError = error instanceof Error ? error : new Error("Unknown error during auth initialization");
|
|
493
|
+
logger.error("AuthService", "Error during auth initialization:", restorationError);
|
|
494
|
+
if (restorationError instanceof AuthError) {
|
|
495
|
+
this.authError = restorationError;
|
|
496
|
+
}
|
|
497
|
+
this.finishSessionRestoration(restorationError);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Automatically track user session using rbac_session_track
|
|
502
|
+
* This method is called automatically on SIGNED_IN and SIGNED_OUT events.
|
|
503
|
+
* It's non-blocking and failures are logged as warnings.
|
|
504
|
+
*/
|
|
505
|
+
async trackSession(sessionType, session) {
|
|
506
|
+
if (!this.supabaseClient || !session?.user) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
let appId = void 0;
|
|
511
|
+
if (this.appName) {
|
|
512
|
+
const { data, error: error2 } = await this.supabaseClient.from("rbac_apps").select("id").eq("name", this.appName).eq("is_active", true).single();
|
|
513
|
+
if (!error2 && data) {
|
|
514
|
+
appId = data.id;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const ipAddress = void 0;
|
|
518
|
+
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : void 0;
|
|
519
|
+
const deviceFingerprint = void 0;
|
|
520
|
+
const { error } = await this.supabaseClient.rpc("rbac_session_track", {
|
|
521
|
+
p_user_id: session.user.id,
|
|
522
|
+
p_session_type: sessionType,
|
|
523
|
+
p_event_id: null,
|
|
524
|
+
// Event ID should come from context, not auth service
|
|
525
|
+
p_app_id: appId,
|
|
526
|
+
p_ip_address: ipAddress,
|
|
527
|
+
p_user_agent: userAgent,
|
|
528
|
+
p_device_fingerprint: deviceFingerprint
|
|
529
|
+
});
|
|
530
|
+
if (error) {
|
|
531
|
+
logger.warn("AuthService", `Failed to track ${sessionType} session:`, error);
|
|
532
|
+
}
|
|
533
|
+
} catch (error) {
|
|
534
|
+
logger.warn("AuthService", `Error tracking ${sessionType} session:`, error);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
setupErrorHandlers() {
|
|
538
|
+
if (typeof window === "undefined") return;
|
|
539
|
+
this.errorHandler = (event) => {
|
|
540
|
+
if (event.error?.message?.includes("AuthSessionMissingError") || event.error?.message?.includes("Auth session missing")) {
|
|
541
|
+
logger.warn("AuthService", "Suppressing AuthSessionMissingError during logout");
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
this.unhandledRejectionHandler = (event) => {
|
|
547
|
+
if (event.reason?.message?.includes("AuthSessionMissingError") || event.reason?.message?.includes("Auth session missing")) {
|
|
548
|
+
logger.warn("AuthService", "Suppressing unhandled AuthSessionMissingError");
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
window.addEventListener("error", this.errorHandler);
|
|
554
|
+
window.addEventListener("unhandledrejection", this.unhandledRejectionHandler);
|
|
555
|
+
}
|
|
556
|
+
removeErrorHandlers() {
|
|
557
|
+
if (typeof window === "undefined") return;
|
|
558
|
+
if (this.errorHandler) {
|
|
559
|
+
window.removeEventListener("error", this.errorHandler);
|
|
560
|
+
this.errorHandler = null;
|
|
561
|
+
}
|
|
562
|
+
if (this.unhandledRejectionHandler) {
|
|
563
|
+
window.removeEventListener("unhandledrejection", this.unhandledRejectionHandler);
|
|
564
|
+
this.unhandledRejectionHandler = null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// src/providers/services/AuthServiceProvider.tsx
|
|
570
|
+
import { jsx } from "react/jsx-runtime";
|
|
571
|
+
var AuthServiceContext = createContext(null);
|
|
572
|
+
function AuthServiceProvider({ children, supabaseClient, appName }) {
|
|
573
|
+
const authService = useMemo(
|
|
574
|
+
() => new AuthService(supabaseClient, appName),
|
|
575
|
+
[supabaseClient, appName]
|
|
576
|
+
);
|
|
577
|
+
const [sessionRestoration, setSessionRestoration] = useState(
|
|
578
|
+
() => authService.getSessionRestorationState()
|
|
579
|
+
);
|
|
580
|
+
useEffect(() => {
|
|
581
|
+
const unsubscribe = authService.subscribe(() => {
|
|
582
|
+
const restorationState = authService.getSessionRestorationState();
|
|
583
|
+
setSessionRestoration(restorationState);
|
|
584
|
+
});
|
|
585
|
+
return () => {
|
|
586
|
+
unsubscribe();
|
|
587
|
+
};
|
|
588
|
+
}, [authService]);
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
authService.initialize().catch((error) => {
|
|
591
|
+
logger.error("AuthServiceProvider", "Failed to initialize auth service:", error);
|
|
592
|
+
});
|
|
593
|
+
return () => {
|
|
594
|
+
authService.cleanup();
|
|
595
|
+
};
|
|
596
|
+
}, [authService]);
|
|
597
|
+
const contextValue = useMemo(() => ({
|
|
598
|
+
authService,
|
|
599
|
+
sessionRestoration
|
|
600
|
+
}), [authService, sessionRestoration]);
|
|
601
|
+
return /* @__PURE__ */ jsx(AuthServiceContext.Provider, { value: contextValue, children });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/providers/services/OrganisationServiceProvider.tsx
|
|
605
|
+
import { createContext as createContext2, useMemo as useMemo2, useEffect as useEffect2, useRef } from "react";
|
|
606
|
+
|
|
607
|
+
// src/services/OrganisationService.ts
|
|
608
|
+
var OrganisationService = class extends BaseService {
|
|
609
|
+
constructor(supabaseClient, user, session) {
|
|
610
|
+
super();
|
|
611
|
+
this._selectedOrganisation = null;
|
|
612
|
+
this._organisations = [];
|
|
613
|
+
this._userMemberships = [];
|
|
614
|
+
this._roleMapState = /* @__PURE__ */ new Map();
|
|
615
|
+
this._isLoading = false;
|
|
616
|
+
this._error = null;
|
|
617
|
+
this._isContextReady = false;
|
|
618
|
+
this.retryCount = 0;
|
|
619
|
+
// Dependencies
|
|
620
|
+
this.supabaseClient = null;
|
|
621
|
+
this.user = null;
|
|
622
|
+
this.session = null;
|
|
623
|
+
// Internal state management
|
|
624
|
+
this.isLoadingRef = false;
|
|
625
|
+
this.lastLoadTimeRef = 0;
|
|
626
|
+
this.hasFailedRef = false;
|
|
627
|
+
this.abortControllerRef = null;
|
|
628
|
+
this.supabaseClient = supabaseClient;
|
|
629
|
+
this.user = user;
|
|
630
|
+
this.session = session;
|
|
631
|
+
}
|
|
632
|
+
// Interface implementation
|
|
633
|
+
getSelectedOrganisation() {
|
|
634
|
+
return this._selectedOrganisation;
|
|
635
|
+
}
|
|
636
|
+
getOrganisations() {
|
|
637
|
+
return this._organisations;
|
|
638
|
+
}
|
|
639
|
+
getUserMemberships() {
|
|
640
|
+
return this._userMemberships;
|
|
641
|
+
}
|
|
642
|
+
isLoading() {
|
|
643
|
+
return this._isLoading;
|
|
644
|
+
}
|
|
645
|
+
getError() {
|
|
646
|
+
return this._error;
|
|
647
|
+
}
|
|
648
|
+
hasValidOrganisationContext() {
|
|
649
|
+
return !!(this._selectedOrganisation && !this._isLoading && !this._error && this._isContextReady);
|
|
650
|
+
}
|
|
651
|
+
isContextReady() {
|
|
652
|
+
return this._isContextReady;
|
|
653
|
+
}
|
|
654
|
+
// Additional methods for testing
|
|
655
|
+
setSelectedOrganisation(organisation) {
|
|
656
|
+
this._selectedOrganisation = organisation;
|
|
657
|
+
if (organisation) {
|
|
658
|
+
localStorage.setItem("pace-core-selected-organisation", JSON.stringify(organisation));
|
|
659
|
+
this.setDatabaseOrganisationContext(organisation);
|
|
660
|
+
} else {
|
|
661
|
+
localStorage.removeItem("pace-core-selected-organisation");
|
|
662
|
+
this._isContextReady = false;
|
|
663
|
+
}
|
|
664
|
+
this.notify();
|
|
665
|
+
}
|
|
666
|
+
// For testing: expose dependencies
|
|
667
|
+
getDependencies() {
|
|
668
|
+
return {
|
|
669
|
+
user: this.user,
|
|
670
|
+
session: this.session,
|
|
671
|
+
supabaseClient: this.supabaseClient
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
// For testing: manually set state
|
|
675
|
+
setTestState(organisations, memberships, roleMap, selectedOrg = null) {
|
|
676
|
+
this._organisations = organisations;
|
|
677
|
+
this._userMemberships = memberships;
|
|
678
|
+
this._roleMapState = roleMap;
|
|
679
|
+
if (selectedOrg) {
|
|
680
|
+
this._selectedOrganisation = selectedOrg;
|
|
681
|
+
} else if (organisations.length > 0) {
|
|
682
|
+
this._selectedOrganisation = organisations[0];
|
|
683
|
+
}
|
|
684
|
+
this._isLoading = false;
|
|
685
|
+
this._error = null;
|
|
686
|
+
this.notify();
|
|
687
|
+
}
|
|
688
|
+
// Update dependencies
|
|
689
|
+
updateDependencies(user, session) {
|
|
690
|
+
const wasAuthenticated = !!(this.user && this.session);
|
|
691
|
+
const isAuthenticated = !!(user && session);
|
|
692
|
+
this.user = user;
|
|
693
|
+
this.session = session;
|
|
694
|
+
if (wasAuthenticated && !isAuthenticated) {
|
|
695
|
+
this.resetInitialization();
|
|
696
|
+
}
|
|
697
|
+
this.notify();
|
|
698
|
+
}
|
|
699
|
+
// Organisation methods
|
|
700
|
+
async switchOrganisation(orgId) {
|
|
701
|
+
if (!this.validateOrganisationAccess(orgId)) {
|
|
702
|
+
throw new Error(`User does not have access to organisation ${orgId}`);
|
|
703
|
+
}
|
|
704
|
+
const targetOrg = this._organisations.find((org) => org.id === orgId);
|
|
705
|
+
if (!targetOrg) {
|
|
706
|
+
throw new Error(`Organisation ${orgId} not found in user's organisations`);
|
|
707
|
+
}
|
|
708
|
+
this._selectedOrganisation = targetOrg;
|
|
709
|
+
localStorage.setItem("pace-core-selected-organisation", JSON.stringify(targetOrg));
|
|
710
|
+
await this.setDatabaseOrganisationContext(targetOrg);
|
|
711
|
+
this.notify();
|
|
712
|
+
}
|
|
713
|
+
getUserRole(orgId) {
|
|
714
|
+
const targetOrgId = orgId || this._selectedOrganisation?.id;
|
|
715
|
+
if (!targetOrgId) return "no_access";
|
|
716
|
+
return this._roleMapState.get(targetOrgId) || "no_access";
|
|
717
|
+
}
|
|
718
|
+
validateOrganisationAccess(orgId) {
|
|
719
|
+
return this._userMemberships.some(
|
|
720
|
+
(m) => m.organisation_id === orgId && m.status === "active" && m.revoked_at === null
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
async refreshOrganisations() {
|
|
724
|
+
if (!this.user || !this.session || !this.supabaseClient) return;
|
|
725
|
+
this._isLoading = true;
|
|
726
|
+
this.notify();
|
|
727
|
+
await this.loadUserOrganisations();
|
|
728
|
+
}
|
|
729
|
+
ensureOrganisationContext() {
|
|
730
|
+
if (!this._selectedOrganisation) {
|
|
731
|
+
throw new Error("Organisation context is required but not available");
|
|
732
|
+
}
|
|
733
|
+
return this._selectedOrganisation;
|
|
734
|
+
}
|
|
735
|
+
isOrganisationSecure() {
|
|
736
|
+
return !!(this._selectedOrganisation && this.user);
|
|
737
|
+
}
|
|
738
|
+
getPrimaryOrganisation() {
|
|
739
|
+
const rolePriority = ["org_admin", "leader", "member"];
|
|
740
|
+
for (const role of rolePriority) {
|
|
741
|
+
const membership = this._userMemberships.find((m) => m.role === role);
|
|
742
|
+
if (membership) {
|
|
743
|
+
return this._organisations.find((org) => org.id === membership.organisation_id) || null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
buildOrganisationHierarchy(orgs) {
|
|
749
|
+
const orgMap = /* @__PURE__ */ new Map();
|
|
750
|
+
orgs.forEach((org) => orgMap.set(org.id, org));
|
|
751
|
+
const roots = [];
|
|
752
|
+
orgs.forEach((org) => {
|
|
753
|
+
if (!org.parent_id) {
|
|
754
|
+
roots.push({
|
|
755
|
+
organisation: org,
|
|
756
|
+
children: [],
|
|
757
|
+
depth: 0
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
return roots;
|
|
762
|
+
}
|
|
763
|
+
// Lifecycle methods
|
|
764
|
+
async initialize() {
|
|
765
|
+
await super.initialize();
|
|
766
|
+
if (!this.isLoadingRef) {
|
|
767
|
+
await this.loadUserOrganisations();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
cleanup() {
|
|
771
|
+
this.isLoadingRef = false;
|
|
772
|
+
this.hasFailedRef = false;
|
|
773
|
+
this.lastLoadTimeRef = 0;
|
|
774
|
+
if (this.abortControllerRef) {
|
|
775
|
+
this.abortControllerRef = null;
|
|
776
|
+
}
|
|
777
|
+
this._selectedOrganisation = null;
|
|
778
|
+
this._organisations = [];
|
|
779
|
+
this._userMemberships = [];
|
|
780
|
+
this._roleMapState = /* @__PURE__ */ new Map();
|
|
781
|
+
this._isLoading = false;
|
|
782
|
+
this._error = null;
|
|
783
|
+
this._isContextReady = false;
|
|
784
|
+
super.cleanup();
|
|
785
|
+
}
|
|
786
|
+
async doInitialize() {
|
|
787
|
+
}
|
|
788
|
+
doCleanup() {
|
|
789
|
+
}
|
|
790
|
+
async setDatabaseOrganisationContext(organisation) {
|
|
791
|
+
if (!this.supabaseClient || !this.session) {
|
|
792
|
+
logger.warn("OrganisationService", "No Supabase client or session available for setting organisation context");
|
|
793
|
+
this._isContextReady = false;
|
|
794
|
+
this.notify();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
799
|
+
setTimeout(() => reject(new Error("Context setting timeout after 5 seconds")), 5e3);
|
|
800
|
+
});
|
|
801
|
+
const contextPromise = setOrganisationContext(this.supabaseClient, organisation.id);
|
|
802
|
+
await Promise.race([contextPromise, timeoutPromise]);
|
|
803
|
+
this._isContextReady = true;
|
|
804
|
+
this.notify();
|
|
805
|
+
} catch (error) {
|
|
806
|
+
logger.error("OrganisationService", "Failed to set database organisation context:", error);
|
|
807
|
+
this._isContextReady = true;
|
|
808
|
+
this.notify();
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async loadUserOrganisations() {
|
|
812
|
+
if (!this.user || !this.session || !this.supabaseClient) {
|
|
813
|
+
this._selectedOrganisation = null;
|
|
814
|
+
this._organisations = [];
|
|
815
|
+
this._userMemberships = [];
|
|
816
|
+
this._isLoading = false;
|
|
817
|
+
this._error = null;
|
|
818
|
+
this.notify();
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (this.isLoadingRef) {
|
|
822
|
+
this._isLoading = true;
|
|
823
|
+
this.notify();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const now = Date.now();
|
|
827
|
+
if (now - this.lastLoadTimeRef < 2e3) {
|
|
828
|
+
if (this._organisations.length > 0 || this._selectedOrganisation) {
|
|
829
|
+
this._isLoading = false;
|
|
830
|
+
} else {
|
|
831
|
+
this._isLoading = true;
|
|
832
|
+
}
|
|
833
|
+
this.notify();
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (this.abortControllerRef) {
|
|
837
|
+
this.abortControllerRef.abort();
|
|
838
|
+
}
|
|
839
|
+
this.abortControllerRef = new AbortController();
|
|
840
|
+
const abortSignal = this.abortControllerRef.signal;
|
|
841
|
+
this.lastLoadTimeRef = now;
|
|
842
|
+
this.isLoadingRef = true;
|
|
843
|
+
this._isLoading = true;
|
|
844
|
+
this._error = null;
|
|
845
|
+
this.notify();
|
|
846
|
+
try {
|
|
847
|
+
let memberships, membershipError;
|
|
848
|
+
try {
|
|
849
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
850
|
+
const timeoutId = setTimeout(() => reject(new Error("RPC call timeout after 10 seconds")), 1e4);
|
|
851
|
+
abortSignal.addEventListener("abort", () => {
|
|
852
|
+
clearTimeout(timeoutId);
|
|
853
|
+
reject(new Error("Request aborted"));
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
const rpcPromise = this.supabaseClient.rpc("data_user_organisation_roles_get", {
|
|
857
|
+
p_user_id: this.user.id,
|
|
858
|
+
p_organisation_id: null
|
|
859
|
+
});
|
|
860
|
+
if (abortSignal.aborted) {
|
|
861
|
+
throw new Error("Request aborted");
|
|
862
|
+
}
|
|
863
|
+
const result = await Promise.race([rpcPromise, timeoutPromise]);
|
|
864
|
+
memberships = result.data?.filter(
|
|
865
|
+
(role) => ["org_admin", "leader", "member"].includes(role.role)
|
|
866
|
+
).map((m) => ({
|
|
867
|
+
...m,
|
|
868
|
+
user_id: assertUserId(m.user_id),
|
|
869
|
+
organisation_id: assertOrganisationId(m.organisation_id)
|
|
870
|
+
})) || [];
|
|
871
|
+
membershipError = result.error;
|
|
872
|
+
} catch (queryError) {
|
|
873
|
+
membershipError = queryError instanceof Error ? queryError : new Error(String(queryError));
|
|
874
|
+
}
|
|
875
|
+
if (membershipError) {
|
|
876
|
+
logger.error("OrganisationService", "Error loading memberships:", membershipError);
|
|
877
|
+
if (membershipError.message?.includes("timeout")) {
|
|
878
|
+
try {
|
|
879
|
+
if (abortSignal.aborted) {
|
|
880
|
+
throw new Error("Request aborted");
|
|
881
|
+
}
|
|
882
|
+
const { data: fallbackData, error: fallbackError } = await this.supabaseClient.from("rbac_organisation_roles").select(`
|
|
883
|
+
id,
|
|
884
|
+
user_id,
|
|
885
|
+
organisation_id,
|
|
886
|
+
role,
|
|
887
|
+
status,
|
|
888
|
+
granted_at,
|
|
889
|
+
granted_by,
|
|
890
|
+
revoked_at,
|
|
891
|
+
revoked_by,
|
|
892
|
+
notes,
|
|
893
|
+
created_at,
|
|
894
|
+
updated_at,
|
|
895
|
+
organisations!inner(
|
|
896
|
+
id,
|
|
897
|
+
name,
|
|
898
|
+
display_name,
|
|
899
|
+
subscription_tier,
|
|
900
|
+
settings,
|
|
901
|
+
is_active,
|
|
902
|
+
parent_id,
|
|
903
|
+
created_at,
|
|
904
|
+
updated_at
|
|
905
|
+
)
|
|
906
|
+
`).eq("user_id", this.user.id).eq("status", "active").is("revoked_at", null).in("role", ["org_admin", "leader", "member"]);
|
|
907
|
+
if (fallbackError) {
|
|
908
|
+
logger.error("OrganisationService", "Fallback query also failed:", fallbackError);
|
|
909
|
+
throw membershipError;
|
|
910
|
+
}
|
|
911
|
+
memberships = fallbackData?.map((m) => ({
|
|
912
|
+
...m,
|
|
913
|
+
user_id: assertUserId(m.user_id),
|
|
914
|
+
organisation_id: assertOrganisationId(m.organisation_id)
|
|
915
|
+
})) || [];
|
|
916
|
+
membershipError = null;
|
|
917
|
+
} catch (fallbackErr) {
|
|
918
|
+
logger.error("OrganisationService", "Fallback query failed:", fallbackErr);
|
|
919
|
+
throw membershipError;
|
|
920
|
+
}
|
|
921
|
+
} else {
|
|
922
|
+
throw membershipError;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (!memberships || memberships.length === 0) {
|
|
926
|
+
throw new Error("User has no active organisation memberships");
|
|
927
|
+
}
|
|
928
|
+
const organisationIds = memberships.map((m) => m.organisation_id).filter((id) => {
|
|
929
|
+
if (!id || typeof id !== "string") {
|
|
930
|
+
logger.warn("OrganisationService", "Invalid organisation ID (not string):", id);
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
const trimmedId = id.trim();
|
|
934
|
+
if (trimmedId === "") {
|
|
935
|
+
logger.warn("OrganisationService", "Empty organisation ID found");
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmedId);
|
|
939
|
+
if (!isValidUuid) {
|
|
940
|
+
logger.warn("OrganisationService", "Invalid UUID format:", trimmedId);
|
|
941
|
+
}
|
|
942
|
+
return isValidUuid;
|
|
943
|
+
});
|
|
944
|
+
if (organisationIds.length === 0) {
|
|
945
|
+
logger.warn("OrganisationService", "No valid organisation IDs found in memberships:", memberships);
|
|
946
|
+
throw new Error("No valid organisation IDs found in memberships");
|
|
947
|
+
}
|
|
948
|
+
if (abortSignal.aborted) {
|
|
949
|
+
throw new Error("Request aborted");
|
|
950
|
+
}
|
|
951
|
+
const { data: allOrganisations, error: orgError } = await this.supabaseClient.from("organisations").select("id, name, display_name, subscription_tier, settings, is_active, parent_id, created_at, updated_at");
|
|
952
|
+
if (orgError) {
|
|
953
|
+
logger.error("OrganisationService", "Error loading organisations:", orgError);
|
|
954
|
+
throw orgError;
|
|
955
|
+
}
|
|
956
|
+
const organisations = allOrganisations?.filter(
|
|
957
|
+
(org) => organisationIds.includes(org.id)
|
|
958
|
+
) || [];
|
|
959
|
+
const roleMap = /* @__PURE__ */ new Map();
|
|
960
|
+
memberships?.forEach((membership) => {
|
|
961
|
+
roleMap.set(membership.organisation_id, membership.role);
|
|
962
|
+
});
|
|
963
|
+
const orgs = organisations;
|
|
964
|
+
const activeOrgs = orgs.filter((org) => org.is_active);
|
|
965
|
+
if (activeOrgs.length === 0) {
|
|
966
|
+
throw new Error("User has no access to active organisations");
|
|
967
|
+
}
|
|
968
|
+
this._organisations = activeOrgs;
|
|
969
|
+
this._userMemberships = memberships;
|
|
970
|
+
this._roleMapState = roleMap;
|
|
971
|
+
let initialOrg = null;
|
|
972
|
+
try {
|
|
973
|
+
const persistedOrgString = localStorage.getItem("pace-core-selected-organisation");
|
|
974
|
+
if (persistedOrgString) {
|
|
975
|
+
const persistedOrg = JSON.parse(persistedOrgString);
|
|
976
|
+
if (persistedOrg.id && typeof persistedOrg.id === "string" && persistedOrg.id.trim() !== "") {
|
|
977
|
+
const validPersistedOrg = activeOrgs.find((org) => org.id === persistedOrg.id);
|
|
978
|
+
if (validPersistedOrg) {
|
|
979
|
+
initialOrg = validPersistedOrg;
|
|
980
|
+
} else {
|
|
981
|
+
logger.warn("OrganisationService", "Persisted organisation not found in active orgs, clearing cache");
|
|
982
|
+
localStorage.removeItem("pace-core-selected-organisation");
|
|
983
|
+
}
|
|
984
|
+
} else {
|
|
985
|
+
logger.warn("OrganisationService", "Invalid persisted organisation ID, clearing cache");
|
|
986
|
+
localStorage.removeItem("pace-core-selected-organisation");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
} catch (storageError) {
|
|
990
|
+
logger.warn("OrganisationService", "Failed to restore persisted organisation:", storageError);
|
|
991
|
+
localStorage.removeItem("pace-core-selected-organisation");
|
|
992
|
+
}
|
|
993
|
+
if (!initialOrg) {
|
|
994
|
+
const adminMembership = memberships.find((m) => m.role === "org_admin");
|
|
995
|
+
if (adminMembership) {
|
|
996
|
+
const foundOrg = organisations.find((org) => org.id === adminMembership.organisation_id);
|
|
997
|
+
if (foundOrg) {
|
|
998
|
+
initialOrg = foundOrg;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (!initialOrg) {
|
|
1003
|
+
initialOrg = activeOrgs[0];
|
|
1004
|
+
}
|
|
1005
|
+
if (!initialOrg) {
|
|
1006
|
+
throw new Error("No valid organisation found for user");
|
|
1007
|
+
}
|
|
1008
|
+
this._selectedOrganisation = initialOrg;
|
|
1009
|
+
localStorage.setItem("pace-core-selected-organisation", JSON.stringify(initialOrg));
|
|
1010
|
+
await this.setDatabaseOrganisationContext(initialOrg);
|
|
1011
|
+
this.retryCount = 0;
|
|
1012
|
+
this.hasFailedRef = false;
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
logger.error("OrganisationService", "Failed to load organisations:", err);
|
|
1015
|
+
this._error = err;
|
|
1016
|
+
this.retryCount = this.retryCount + 1;
|
|
1017
|
+
this.hasFailedRef = true;
|
|
1018
|
+
this.clearAllCachedData();
|
|
1019
|
+
} finally {
|
|
1020
|
+
this.isLoadingRef = false;
|
|
1021
|
+
this._isLoading = false;
|
|
1022
|
+
this.abortControllerRef = null;
|
|
1023
|
+
this.notify();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
clearAllCachedData() {
|
|
1027
|
+
localStorage.removeItem("pace-core-selected-organisation");
|
|
1028
|
+
localStorage.removeItem("pace-core-organisation-context");
|
|
1029
|
+
this._selectedOrganisation = null;
|
|
1030
|
+
this._organisations = [];
|
|
1031
|
+
this._userMemberships = [];
|
|
1032
|
+
this._roleMapState = /* @__PURE__ */ new Map();
|
|
1033
|
+
this.retryCount = 0;
|
|
1034
|
+
this._isContextReady = false;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// src/providers/services/OrganisationServiceProvider.tsx
|
|
1039
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1040
|
+
var OrganisationServiceContext = createContext2(null);
|
|
1041
|
+
function OrganisationServiceProvider({
|
|
1042
|
+
children,
|
|
1043
|
+
supabaseClient,
|
|
1044
|
+
user,
|
|
1045
|
+
session
|
|
1046
|
+
}) {
|
|
1047
|
+
const organisationServiceRef = useRef(null);
|
|
1048
|
+
if (!organisationServiceRef.current) {
|
|
1049
|
+
organisationServiceRef.current = new OrganisationService(supabaseClient, user, session);
|
|
1050
|
+
}
|
|
1051
|
+
const organisationService = organisationServiceRef.current;
|
|
1052
|
+
useEffect2(() => {
|
|
1053
|
+
organisationService.updateDependencies(user, session);
|
|
1054
|
+
let isMounted = true;
|
|
1055
|
+
organisationService.initialize().catch((error) => {
|
|
1056
|
+
if (isMounted) {
|
|
1057
|
+
logger.error("OrganisationServiceProvider", "Failed to initialize organisation service:", error);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
return () => {
|
|
1061
|
+
isMounted = false;
|
|
1062
|
+
};
|
|
1063
|
+
}, [organisationService, user, session]);
|
|
1064
|
+
useEffect2(() => {
|
|
1065
|
+
return () => {
|
|
1066
|
+
organisationService.cleanup();
|
|
1067
|
+
};
|
|
1068
|
+
}, [organisationService]);
|
|
1069
|
+
const contextValue = useMemo2(() => ({
|
|
1070
|
+
organisationService
|
|
1071
|
+
}), [organisationService]);
|
|
1072
|
+
return /* @__PURE__ */ jsx2(OrganisationServiceContext.Provider, { value: contextValue, children });
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/providers/services/EventServiceProvider.tsx
|
|
1076
|
+
import { createContext as createContext3, useMemo as useMemo3, useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
1077
|
+
|
|
1078
|
+
// src/services/EventService.ts
|
|
1079
|
+
var EventService = class extends BaseService {
|
|
1080
|
+
constructor(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId) {
|
|
1081
|
+
super();
|
|
1082
|
+
this.events = [];
|
|
1083
|
+
this.selectedEvent = null;
|
|
1084
|
+
this._isLoading = false;
|
|
1085
|
+
// Start as false to avoid blocking UI
|
|
1086
|
+
this.error = null;
|
|
1087
|
+
// Dependencies
|
|
1088
|
+
this.supabaseClient = null;
|
|
1089
|
+
this.user = null;
|
|
1090
|
+
this.session = null;
|
|
1091
|
+
this.appName = "";
|
|
1092
|
+
this.selectedOrganisation = null;
|
|
1093
|
+
this.setSelectedEventId = null;
|
|
1094
|
+
// Internal state management
|
|
1095
|
+
this.isInitializedRef = false;
|
|
1096
|
+
this.isFetchingRef = false;
|
|
1097
|
+
this.hasAutoSelectedRef = false;
|
|
1098
|
+
this.userClearedEventRef = false;
|
|
1099
|
+
this.supabaseClient = supabaseClient;
|
|
1100
|
+
this.user = user;
|
|
1101
|
+
this.session = session;
|
|
1102
|
+
this.appName = appName;
|
|
1103
|
+
this.selectedOrganisation = selectedOrganisation;
|
|
1104
|
+
this.setSelectedEventId = setSelectedEventId;
|
|
1105
|
+
}
|
|
1106
|
+
// Helper method to get user-scoped storage key
|
|
1107
|
+
getStorageKey(userId) {
|
|
1108
|
+
if (!userId) {
|
|
1109
|
+
return "pace-core-selected-event-no-user";
|
|
1110
|
+
}
|
|
1111
|
+
return `pace-core-selected-event-${userId}`;
|
|
1112
|
+
}
|
|
1113
|
+
// Update dependencies
|
|
1114
|
+
async updateDependencies(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId) {
|
|
1115
|
+
const previousOrgId = this.selectedOrganisation?.id;
|
|
1116
|
+
const newOrgId = selectedOrganisation?.id;
|
|
1117
|
+
const previousUserId = this.user?.id || null;
|
|
1118
|
+
const newUserId = user?.id || null;
|
|
1119
|
+
if (previousUserId !== newUserId) {
|
|
1120
|
+
if (previousUserId !== null) {
|
|
1121
|
+
await this.clearEventSelectionForUser(previousUserId);
|
|
1122
|
+
}
|
|
1123
|
+
if (newUserId === null) {
|
|
1124
|
+
this.selectedEvent = null;
|
|
1125
|
+
this.setSelectedEventId?.(null);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
this.supabaseClient = supabaseClient;
|
|
1129
|
+
this.user = user;
|
|
1130
|
+
this.session = session;
|
|
1131
|
+
this.appName = appName;
|
|
1132
|
+
this.selectedOrganisation = selectedOrganisation;
|
|
1133
|
+
this.setSelectedEventId = setSelectedEventId;
|
|
1134
|
+
if (previousOrgId !== newOrgId) {
|
|
1135
|
+
this.resetInitialization();
|
|
1136
|
+
this.isInitializedRef = false;
|
|
1137
|
+
this.isFetchingRef = false;
|
|
1138
|
+
if (previousOrgId !== null && newOrgId !== null && previousOrgId !== newOrgId) {
|
|
1139
|
+
this.events = [];
|
|
1140
|
+
this.selectedEvent = null;
|
|
1141
|
+
} else if (previousOrgId !== null && newOrgId === null) {
|
|
1142
|
+
this.events = [];
|
|
1143
|
+
this.selectedEvent = null;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
this.notify();
|
|
1147
|
+
}
|
|
1148
|
+
// Event state getters
|
|
1149
|
+
getEvents() {
|
|
1150
|
+
return [...this.events];
|
|
1151
|
+
}
|
|
1152
|
+
getSelectedEvent() {
|
|
1153
|
+
return this.selectedEvent;
|
|
1154
|
+
}
|
|
1155
|
+
isLoading() {
|
|
1156
|
+
return this._isLoading;
|
|
1157
|
+
}
|
|
1158
|
+
getError() {
|
|
1159
|
+
return this.error;
|
|
1160
|
+
}
|
|
1161
|
+
// Event methods
|
|
1162
|
+
setSelectedEvent(event) {
|
|
1163
|
+
if (event) {
|
|
1164
|
+
try {
|
|
1165
|
+
if (this.selectedOrganisation && event.organisation_id !== this.selectedOrganisation.id) {
|
|
1166
|
+
logger.error("EventService", "Event organisation_id does not match selected organisation", {
|
|
1167
|
+
eventOrganisationId: event.organisation_id,
|
|
1168
|
+
selectedOrganisationId: this.selectedOrganisation.id,
|
|
1169
|
+
eventName: event.event_name
|
|
1170
|
+
});
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
logger.error("EventService", "Error during event validation:", error);
|
|
1175
|
+
}
|
|
1176
|
+
this.selectedEvent = event;
|
|
1177
|
+
this.setSelectedEventId?.(event.event_id);
|
|
1178
|
+
this.persistEventSelection(event.event_id).catch((error) => {
|
|
1179
|
+
logger.warn("EventService", "Failed to persist event selection:", error);
|
|
1180
|
+
});
|
|
1181
|
+
this.userClearedEventRef = false;
|
|
1182
|
+
} else {
|
|
1183
|
+
this.selectedEvent = null;
|
|
1184
|
+
this.setSelectedEventId?.(null);
|
|
1185
|
+
this.clearEventSelection().catch((error) => {
|
|
1186
|
+
logger.warn("EventService", "Failed to clear event selection:", error);
|
|
1187
|
+
});
|
|
1188
|
+
this.hasAutoSelectedRef = false;
|
|
1189
|
+
this.userClearedEventRef = true;
|
|
1190
|
+
}
|
|
1191
|
+
this.notify();
|
|
1192
|
+
}
|
|
1193
|
+
async refreshEvents() {
|
|
1194
|
+
this.isInitializedRef = false;
|
|
1195
|
+
this.isFetchingRef = false;
|
|
1196
|
+
await this.fetchEvents();
|
|
1197
|
+
}
|
|
1198
|
+
async loadPersistedEvent(events) {
|
|
1199
|
+
try {
|
|
1200
|
+
const userId = this.user?.id || null;
|
|
1201
|
+
if (!userId) {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
const storageKey = this.getStorageKey(userId);
|
|
1205
|
+
const persistedEventId = await secureStorage.getItem(storageKey);
|
|
1206
|
+
if (persistedEventId && events.length > 0) {
|
|
1207
|
+
const persistedEvent = events.find((event) => event.event_id === persistedEventId);
|
|
1208
|
+
if (persistedEvent) {
|
|
1209
|
+
this.setSelectedEvent(persistedEvent);
|
|
1210
|
+
return true;
|
|
1211
|
+
} else {
|
|
1212
|
+
await secureStorage.removeItem(storageKey);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
logger.warn("EventService", "Failed to load persisted event:", error);
|
|
1217
|
+
}
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Restore persisted event after login screen has rendered
|
|
1222
|
+
* This should be called explicitly from login page component
|
|
1223
|
+
*
|
|
1224
|
+
* @returns Promise<boolean> - true if event was successfully restored, false otherwise
|
|
1225
|
+
*/
|
|
1226
|
+
async restorePersistedEvent() {
|
|
1227
|
+
if (this.events.length === 0) {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
return await this.loadPersistedEvent(this.events);
|
|
1231
|
+
}
|
|
1232
|
+
async persistEventSelection(eventId) {
|
|
1233
|
+
try {
|
|
1234
|
+
const userId = this.user?.id || null;
|
|
1235
|
+
const storageKey = this.getStorageKey(userId);
|
|
1236
|
+
await secureStorage.setItem(storageKey, eventId, { encrypt: true });
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
logger.warn("EventService", "Failed to persist event selection:", error);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
async clearEventSelection() {
|
|
1242
|
+
try {
|
|
1243
|
+
const userId = this.user?.id || null;
|
|
1244
|
+
const storageKey = this.getStorageKey(userId);
|
|
1245
|
+
await secureStorage.removeItem(storageKey);
|
|
1246
|
+
this.selectedEvent = null;
|
|
1247
|
+
this.setSelectedEventId?.(null);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
logger.warn("EventService", "Failed to clear event selection:", error);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Clear event selection for a specific user (used when user logs out or changes)
|
|
1254
|
+
*/
|
|
1255
|
+
async clearEventSelectionForUser(userId) {
|
|
1256
|
+
try {
|
|
1257
|
+
if (!userId) return;
|
|
1258
|
+
const storageKey = this.getStorageKey(userId);
|
|
1259
|
+
await secureStorage.removeItem(storageKey);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
logger.warn("EventService", "Failed to clear event selection for user:", error);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
autoSelectNextEvent(events) {
|
|
1265
|
+
const nextEvent = this.getNextEventByDate(events);
|
|
1266
|
+
if (nextEvent) {
|
|
1267
|
+
this.setSelectedEvent(nextEvent);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
// Lifecycle methods
|
|
1271
|
+
async initialize() {
|
|
1272
|
+
await super.initialize();
|
|
1273
|
+
}
|
|
1274
|
+
cleanup() {
|
|
1275
|
+
super.cleanup();
|
|
1276
|
+
}
|
|
1277
|
+
async doInitialize() {
|
|
1278
|
+
if (this.isInitializedRef) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
if (this.isFetchingRef) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
try {
|
|
1285
|
+
sessionStorage.removeItem("pace-core-selected-event");
|
|
1286
|
+
localStorage.removeItem("pace-core-selected-event");
|
|
1287
|
+
localStorage.removeItem("_sec_pace-core-selected-event");
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
logger.warn("EventService", "Failed to clean up old storage keys:", error);
|
|
1290
|
+
}
|
|
1291
|
+
if (!this.user || !this.selectedOrganisation) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
await this.fetchEvents(false);
|
|
1295
|
+
}
|
|
1296
|
+
doCleanup() {
|
|
1297
|
+
}
|
|
1298
|
+
async fetchEvents(skipLoadPersisted = false) {
|
|
1299
|
+
if (!this.user || !this.session || !this.supabaseClient || !this.appName || !this.selectedOrganisation) {
|
|
1300
|
+
this.notify();
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
this._isLoading = true;
|
|
1304
|
+
this.notify();
|
|
1305
|
+
if (this.isFetchingRef) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
this.isFetchingRef = true;
|
|
1309
|
+
let isMounted = true;
|
|
1310
|
+
try {
|
|
1311
|
+
const { data, error: rpcError } = await this.supabaseClient.rpc("data_user_events_get", {
|
|
1312
|
+
p_user_id: this.user.id,
|
|
1313
|
+
p_organisation_id: this.selectedOrganisation.id,
|
|
1314
|
+
p_app_name: this.appName
|
|
1315
|
+
});
|
|
1316
|
+
if (rpcError) {
|
|
1317
|
+
logger.error("EventService", "RPC error fetching events:", rpcError);
|
|
1318
|
+
throw new Error(rpcError.message || "Failed to fetch events");
|
|
1319
|
+
}
|
|
1320
|
+
if (isMounted) {
|
|
1321
|
+
const eventsData = data || [];
|
|
1322
|
+
const transformedEvents = eventsData.map((event) => ({
|
|
1323
|
+
id: event.event_id,
|
|
1324
|
+
event_id: event.event_id,
|
|
1325
|
+
event_name: event.event_name,
|
|
1326
|
+
event_code: event.event_code,
|
|
1327
|
+
event_date: event.event_date,
|
|
1328
|
+
event_venue: event.event_venue,
|
|
1329
|
+
event_participants: event.event_participants,
|
|
1330
|
+
event_colours: event.event_colours,
|
|
1331
|
+
event_logo: "",
|
|
1332
|
+
// No logo field in event table
|
|
1333
|
+
organisation_id: assertOrganisationId(event.organisation_id),
|
|
1334
|
+
is_visible: event.is_visible,
|
|
1335
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1336
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1337
|
+
}));
|
|
1338
|
+
this.events = transformedEvents;
|
|
1339
|
+
this.error = null;
|
|
1340
|
+
this.hasAutoSelectedRef = false;
|
|
1341
|
+
if (!skipLoadPersisted) {
|
|
1342
|
+
const persistedEventLoaded = await this.loadPersistedEvent(transformedEvents);
|
|
1343
|
+
if (!persistedEventLoaded && !this.userClearedEventRef) {
|
|
1344
|
+
const nextEvent = this.getNextEventByDate(transformedEvents);
|
|
1345
|
+
if (nextEvent) {
|
|
1346
|
+
this.hasAutoSelectedRef = true;
|
|
1347
|
+
this.setSelectedEvent(nextEvent);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
} else {
|
|
1351
|
+
if (!this.userClearedEventRef) {
|
|
1352
|
+
const nextEvent = this.getNextEventByDate(transformedEvents);
|
|
1353
|
+
if (nextEvent) {
|
|
1354
|
+
this.hasAutoSelectedRef = true;
|
|
1355
|
+
this.setSelectedEvent(nextEvent);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
logger.error("EventService", "Error fetching events:", err);
|
|
1362
|
+
const _error = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
1363
|
+
if (isMounted) {
|
|
1364
|
+
this.error = _error;
|
|
1365
|
+
this.events = [];
|
|
1366
|
+
}
|
|
1367
|
+
} finally {
|
|
1368
|
+
if (isMounted) {
|
|
1369
|
+
this._isLoading = false;
|
|
1370
|
+
}
|
|
1371
|
+
this.isFetchingRef = false;
|
|
1372
|
+
this.notify();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
getNextEventByDate(events) {
|
|
1376
|
+
const eventsToUse = events || this.events;
|
|
1377
|
+
if (!eventsToUse || eventsToUse.length === 0) {
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
const now = /* @__PURE__ */ new Date();
|
|
1381
|
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
1382
|
+
const futureEvents = eventsToUse.filter((event) => {
|
|
1383
|
+
if (!event.event_date) return false;
|
|
1384
|
+
const eventDate = new Date(event.event_date);
|
|
1385
|
+
const startOfEventDate = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate()).getTime();
|
|
1386
|
+
return startOfEventDate >= startOfToday;
|
|
1387
|
+
});
|
|
1388
|
+
if (futureEvents.length > 0) {
|
|
1389
|
+
const sortedFutureEvents = futureEvents.sort((a, b) => {
|
|
1390
|
+
const dateA = new Date(a.event_date);
|
|
1391
|
+
const dateB = new Date(b.event_date);
|
|
1392
|
+
return dateA.getTime() - dateB.getTime();
|
|
1393
|
+
});
|
|
1394
|
+
return sortedFutureEvents[0];
|
|
1395
|
+
}
|
|
1396
|
+
const pastEvents = eventsToUse.filter((event) => {
|
|
1397
|
+
if (!event.event_date) return false;
|
|
1398
|
+
const eventDate = new Date(event.event_date);
|
|
1399
|
+
const startOfEventDate = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate()).getTime();
|
|
1400
|
+
return startOfEventDate < startOfToday;
|
|
1401
|
+
});
|
|
1402
|
+
if (pastEvents.length > 0) {
|
|
1403
|
+
const sortedPastEvents = pastEvents.sort((a, b) => {
|
|
1404
|
+
const dateA = new Date(a.event_date);
|
|
1405
|
+
const dateB = new Date(b.event_date);
|
|
1406
|
+
return dateB.getTime() - dateA.getTime();
|
|
1407
|
+
});
|
|
1408
|
+
return sortedPastEvents[0];
|
|
1409
|
+
}
|
|
1410
|
+
return null;
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
// src/providers/services/EventServiceProvider.tsx
|
|
1415
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1416
|
+
var EventServiceContext = createContext3(null);
|
|
1417
|
+
function EventServiceProvider({
|
|
1418
|
+
children,
|
|
1419
|
+
supabaseClient,
|
|
1420
|
+
user,
|
|
1421
|
+
session,
|
|
1422
|
+
appName,
|
|
1423
|
+
selectedOrganisation,
|
|
1424
|
+
setSelectedEventId
|
|
1425
|
+
}) {
|
|
1426
|
+
const eventServiceRef = useRef2(null);
|
|
1427
|
+
if (!eventServiceRef.current) {
|
|
1428
|
+
eventServiceRef.current = new EventService(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId);
|
|
1429
|
+
}
|
|
1430
|
+
const eventService = eventServiceRef.current;
|
|
1431
|
+
useEffect3(() => {
|
|
1432
|
+
let isMounted = true;
|
|
1433
|
+
const updateAndInitialize = async () => {
|
|
1434
|
+
await eventService.updateDependencies(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId);
|
|
1435
|
+
if (!isMounted) return;
|
|
1436
|
+
await eventService.initialize().catch((error) => {
|
|
1437
|
+
if (isMounted) {
|
|
1438
|
+
logger.error("EventServiceProvider", "Failed to initialize event service:", error);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
};
|
|
1442
|
+
updateAndInitialize();
|
|
1443
|
+
return () => {
|
|
1444
|
+
isMounted = false;
|
|
1445
|
+
};
|
|
1446
|
+
}, [eventService, supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId]);
|
|
1447
|
+
useEffect3(() => {
|
|
1448
|
+
return () => {
|
|
1449
|
+
eventService.cleanup();
|
|
1450
|
+
};
|
|
1451
|
+
}, [eventService]);
|
|
1452
|
+
const contextValue = useMemo3(() => ({
|
|
1453
|
+
eventService
|
|
1454
|
+
}), [eventService]);
|
|
1455
|
+
return /* @__PURE__ */ jsx3(EventServiceContext.Provider, { value: contextValue, children });
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/providers/services/InactivityServiceProvider.tsx
|
|
1459
|
+
import { createContext as createContext4, useMemo as useMemo4, useEffect as useEffect4, useRef as useRef3 } from "react";
|
|
1460
|
+
|
|
1461
|
+
// src/services/InactivityService.ts
|
|
1462
|
+
var InactivityService = class extends BaseService {
|
|
1463
|
+
constructor(supabaseClient, user, session, idleTimeoutMs = 30 * 60 * 1e3, warnBeforeMs = 60 * 1e3, onIdleLogout) {
|
|
1464
|
+
super();
|
|
1465
|
+
this._showInactivityWarning = false;
|
|
1466
|
+
this._inactivityTimeRemaining = 0;
|
|
1467
|
+
this._isIdle = false;
|
|
1468
|
+
this._timeRemaining = 0;
|
|
1469
|
+
this._showWarning = false;
|
|
1470
|
+
this._isTracking = false;
|
|
1471
|
+
// Dependencies
|
|
1472
|
+
this.supabaseClient = null;
|
|
1473
|
+
this.user = null;
|
|
1474
|
+
this.session = null;
|
|
1475
|
+
this.idleTimeoutMs = 30 * 60 * 1e3;
|
|
1476
|
+
// 30 minutes
|
|
1477
|
+
this.warnBeforeMs = 60 * 1e3;
|
|
1478
|
+
// 60 seconds
|
|
1479
|
+
this.onIdleLogout = null;
|
|
1480
|
+
// Internal state management
|
|
1481
|
+
this.inactivityTracker = null;
|
|
1482
|
+
this.isInactivityEnabled = true;
|
|
1483
|
+
this.cleanupHandlers = null;
|
|
1484
|
+
this.supabaseClient = supabaseClient;
|
|
1485
|
+
this.user = user;
|
|
1486
|
+
this.session = session;
|
|
1487
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
1488
|
+
this.warnBeforeMs = warnBeforeMs;
|
|
1489
|
+
this.onIdleLogout = onIdleLogout;
|
|
1490
|
+
this._timeRemaining = idleTimeoutMs;
|
|
1491
|
+
}
|
|
1492
|
+
// Interface implementation
|
|
1493
|
+
isIdle() {
|
|
1494
|
+
return this._isIdle;
|
|
1495
|
+
}
|
|
1496
|
+
getTimeRemaining() {
|
|
1497
|
+
return this._timeRemaining;
|
|
1498
|
+
}
|
|
1499
|
+
isWarningShown() {
|
|
1500
|
+
return this._showWarning;
|
|
1501
|
+
}
|
|
1502
|
+
isTracking() {
|
|
1503
|
+
return this._isTracking;
|
|
1504
|
+
}
|
|
1505
|
+
getShowInactivityWarning() {
|
|
1506
|
+
return this._showInactivityWarning;
|
|
1507
|
+
}
|
|
1508
|
+
getInactivityTimeRemaining() {
|
|
1509
|
+
return this._inactivityTimeRemaining;
|
|
1510
|
+
}
|
|
1511
|
+
// Additional getter methods that tests expect
|
|
1512
|
+
getIsIdle() {
|
|
1513
|
+
return this._isIdle;
|
|
1514
|
+
}
|
|
1515
|
+
getIsTracking() {
|
|
1516
|
+
return this._isTracking;
|
|
1517
|
+
}
|
|
1518
|
+
getShowWarning() {
|
|
1519
|
+
return this._showWarning;
|
|
1520
|
+
}
|
|
1521
|
+
// Additional methods for testing
|
|
1522
|
+
setShowInactivityWarning(value) {
|
|
1523
|
+
this._showInactivityWarning = value;
|
|
1524
|
+
this.notify();
|
|
1525
|
+
}
|
|
1526
|
+
setInactivityTimeRemaining(value) {
|
|
1527
|
+
this._inactivityTimeRemaining = value;
|
|
1528
|
+
this.notify();
|
|
1529
|
+
}
|
|
1530
|
+
setIsIdle(value) {
|
|
1531
|
+
this._isIdle = value;
|
|
1532
|
+
this.notify();
|
|
1533
|
+
}
|
|
1534
|
+
setTimeRemaining(value) {
|
|
1535
|
+
this._timeRemaining = value;
|
|
1536
|
+
this.notify();
|
|
1537
|
+
}
|
|
1538
|
+
setShowWarning(value) {
|
|
1539
|
+
this._showWarning = value;
|
|
1540
|
+
this.notify();
|
|
1541
|
+
}
|
|
1542
|
+
setIsTracking(value) {
|
|
1543
|
+
this._isTracking = value;
|
|
1544
|
+
this.notify();
|
|
1545
|
+
}
|
|
1546
|
+
triggerWarning(timeRemaining) {
|
|
1547
|
+
this._showInactivityWarning = true;
|
|
1548
|
+
this._inactivityTimeRemaining = Math.ceil(timeRemaining / 1e3);
|
|
1549
|
+
this._showWarning = true;
|
|
1550
|
+
this.notify();
|
|
1551
|
+
}
|
|
1552
|
+
triggerIdle() {
|
|
1553
|
+
this._isIdle = true;
|
|
1554
|
+
this.handleIdleLogout();
|
|
1555
|
+
this.notify();
|
|
1556
|
+
}
|
|
1557
|
+
// Update dependencies
|
|
1558
|
+
updateDependencies(supabaseClient, user, session, idleTimeoutMs, warnBeforeMs, onIdleLogout) {
|
|
1559
|
+
this.supabaseClient = supabaseClient;
|
|
1560
|
+
this.user = user;
|
|
1561
|
+
this.session = session;
|
|
1562
|
+
if (idleTimeoutMs !== void 0) this.idleTimeoutMs = idleTimeoutMs;
|
|
1563
|
+
if (warnBeforeMs !== void 0) this.warnBeforeMs = warnBeforeMs;
|
|
1564
|
+
if (onIdleLogout !== void 0) this.onIdleLogout = onIdleLogout;
|
|
1565
|
+
this.notify();
|
|
1566
|
+
}
|
|
1567
|
+
// Inactivity methods
|
|
1568
|
+
resetActivity() {
|
|
1569
|
+
if (this.inactivityTracker) {
|
|
1570
|
+
this.inactivityTracker.resetActivity();
|
|
1571
|
+
}
|
|
1572
|
+
this._isIdle = false;
|
|
1573
|
+
this._showWarning = false;
|
|
1574
|
+
this._showInactivityWarning = false;
|
|
1575
|
+
this._inactivityTimeRemaining = 0;
|
|
1576
|
+
this._timeRemaining = this.idleTimeoutMs;
|
|
1577
|
+
this.notify();
|
|
1578
|
+
}
|
|
1579
|
+
startTracking() {
|
|
1580
|
+
if (this.inactivityTracker) {
|
|
1581
|
+
this.inactivityTracker.startTracking();
|
|
1582
|
+
}
|
|
1583
|
+
this._isTracking = true;
|
|
1584
|
+
this.notify();
|
|
1585
|
+
}
|
|
1586
|
+
stopTracking() {
|
|
1587
|
+
if (this.inactivityTracker) {
|
|
1588
|
+
this.inactivityTracker.stopTracking();
|
|
1589
|
+
}
|
|
1590
|
+
this._isTracking = false;
|
|
1591
|
+
this.notify();
|
|
1592
|
+
}
|
|
1593
|
+
async handleIdleLogout() {
|
|
1594
|
+
this._showInactivityWarning = false;
|
|
1595
|
+
this._inactivityTimeRemaining = 0;
|
|
1596
|
+
this.stopTracking();
|
|
1597
|
+
try {
|
|
1598
|
+
if (this.supabaseClient) {
|
|
1599
|
+
await this.supabaseClient.auth.signOut();
|
|
1600
|
+
}
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
logger.error("InactivityService", "Error during idle logout:", error);
|
|
1603
|
+
}
|
|
1604
|
+
this.onIdleLogout?.("inactivity");
|
|
1605
|
+
this.notify();
|
|
1606
|
+
}
|
|
1607
|
+
handleStaySignedIn() {
|
|
1608
|
+
this._showInactivityWarning = false;
|
|
1609
|
+
this._inactivityTimeRemaining = 0;
|
|
1610
|
+
this.resetActivity();
|
|
1611
|
+
this.notify();
|
|
1612
|
+
}
|
|
1613
|
+
async handleSignOutNow() {
|
|
1614
|
+
this._showInactivityWarning = false;
|
|
1615
|
+
this._inactivityTimeRemaining = 0;
|
|
1616
|
+
this.stopTracking();
|
|
1617
|
+
try {
|
|
1618
|
+
if (this.supabaseClient) {
|
|
1619
|
+
await this.supabaseClient.auth.signOut();
|
|
1620
|
+
}
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
logger.error("InactivityService", "Error during manual sign out:", error);
|
|
1623
|
+
}
|
|
1624
|
+
this.onIdleLogout?.("inactivity");
|
|
1625
|
+
this.notify();
|
|
1626
|
+
}
|
|
1627
|
+
// Lifecycle methods
|
|
1628
|
+
async initialize() {
|
|
1629
|
+
await super.initialize();
|
|
1630
|
+
await this.setupInactivityTracker();
|
|
1631
|
+
}
|
|
1632
|
+
cleanup() {
|
|
1633
|
+
if (this.cleanupHandlers) {
|
|
1634
|
+
this.cleanupHandlers();
|
|
1635
|
+
this.cleanupHandlers = null;
|
|
1636
|
+
}
|
|
1637
|
+
if (this.inactivityTracker) {
|
|
1638
|
+
this.inactivityTracker = null;
|
|
1639
|
+
}
|
|
1640
|
+
this._isTracking = false;
|
|
1641
|
+
this._isIdle = false;
|
|
1642
|
+
this._showWarning = false;
|
|
1643
|
+
this._showInactivityWarning = false;
|
|
1644
|
+
this._timeRemaining = 0;
|
|
1645
|
+
this._inactivityTimeRemaining = 0;
|
|
1646
|
+
super.cleanup();
|
|
1647
|
+
}
|
|
1648
|
+
async doInitialize() {
|
|
1649
|
+
if (typeof window !== "undefined") {
|
|
1650
|
+
const isProduction = import.meta.env.MODE === "production";
|
|
1651
|
+
if (isProduction) {
|
|
1652
|
+
logger.warn("InactivityService", "Inactivity feature enabled in production");
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
doCleanup() {
|
|
1657
|
+
}
|
|
1658
|
+
async setupInactivityTracker() {
|
|
1659
|
+
if (typeof window === "undefined") return;
|
|
1660
|
+
this.isInactivityEnabled = !!(this.user && this.session);
|
|
1661
|
+
if (!this.isInactivityEnabled) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
this.setupEventHandlers();
|
|
1665
|
+
}
|
|
1666
|
+
setupEventHandlers() {
|
|
1667
|
+
if (typeof window === "undefined") return;
|
|
1668
|
+
let idleTimer = null;
|
|
1669
|
+
let warningTimer = null;
|
|
1670
|
+
let lastActivity = Date.now();
|
|
1671
|
+
const resetTimers = () => {
|
|
1672
|
+
lastActivity = Date.now();
|
|
1673
|
+
if (idleTimer) {
|
|
1674
|
+
clearTimeout(idleTimer);
|
|
1675
|
+
idleTimer = null;
|
|
1676
|
+
}
|
|
1677
|
+
if (warningTimer) {
|
|
1678
|
+
clearTimeout(warningTimer);
|
|
1679
|
+
warningTimer = null;
|
|
1680
|
+
}
|
|
1681
|
+
this._showInactivityWarning = false;
|
|
1682
|
+
this._inactivityTimeRemaining = 0;
|
|
1683
|
+
this._isIdle = false;
|
|
1684
|
+
this._showWarning = false;
|
|
1685
|
+
this.notify();
|
|
1686
|
+
};
|
|
1687
|
+
const startIdleTimer = () => {
|
|
1688
|
+
if (idleTimer) {
|
|
1689
|
+
clearTimeout(idleTimer);
|
|
1690
|
+
}
|
|
1691
|
+
idleTimer = setTimeout(() => {
|
|
1692
|
+
this._isIdle = true;
|
|
1693
|
+
this._showWarning = true;
|
|
1694
|
+
this.notify();
|
|
1695
|
+
warningTimer = setTimeout(() => {
|
|
1696
|
+
this.handleIdleLogout();
|
|
1697
|
+
}, this.warnBeforeMs);
|
|
1698
|
+
}, this.idleTimeoutMs - this.warnBeforeMs);
|
|
1699
|
+
};
|
|
1700
|
+
const startWarningTimer = () => {
|
|
1701
|
+
if (warningTimer) {
|
|
1702
|
+
clearTimeout(warningTimer);
|
|
1703
|
+
}
|
|
1704
|
+
warningTimer = setTimeout(() => {
|
|
1705
|
+
this.handleIdleLogout();
|
|
1706
|
+
}, this.warnBeforeMs);
|
|
1707
|
+
};
|
|
1708
|
+
const activityEvents = ["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"];
|
|
1709
|
+
const handleActivity = () => {
|
|
1710
|
+
resetTimers();
|
|
1711
|
+
startIdleTimer();
|
|
1712
|
+
};
|
|
1713
|
+
activityEvents.forEach((event) => {
|
|
1714
|
+
document.addEventListener(event, handleActivity, true);
|
|
1715
|
+
});
|
|
1716
|
+
startIdleTimer();
|
|
1717
|
+
this.cleanupHandlers = () => {
|
|
1718
|
+
if (idleTimer) {
|
|
1719
|
+
clearTimeout(idleTimer);
|
|
1720
|
+
idleTimer = null;
|
|
1721
|
+
}
|
|
1722
|
+
if (warningTimer) {
|
|
1723
|
+
clearTimeout(warningTimer);
|
|
1724
|
+
warningTimer = null;
|
|
1725
|
+
}
|
|
1726
|
+
activityEvents.forEach((event) => {
|
|
1727
|
+
document.removeEventListener(event, handleActivity, true);
|
|
1728
|
+
});
|
|
1729
|
+
this._isTracking = false;
|
|
1730
|
+
this._isIdle = false;
|
|
1731
|
+
this._showWarning = false;
|
|
1732
|
+
this._timeRemaining = 0;
|
|
1733
|
+
this._showInactivityWarning = false;
|
|
1734
|
+
this._inactivityTimeRemaining = 0;
|
|
1735
|
+
};
|
|
1736
|
+
this._isTracking = true;
|
|
1737
|
+
this.notify();
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
// src/providers/services/InactivityServiceProvider.tsx
|
|
1742
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1743
|
+
var InactivityServiceContext = createContext4(null);
|
|
1744
|
+
function InactivityServiceProvider({
|
|
1745
|
+
children,
|
|
1746
|
+
supabaseClient,
|
|
1747
|
+
user,
|
|
1748
|
+
session,
|
|
1749
|
+
idleTimeoutMs = 30 * 60 * 1e3,
|
|
1750
|
+
warnBeforeMs = 60 * 1e3,
|
|
1751
|
+
onIdleLogout
|
|
1752
|
+
}) {
|
|
1753
|
+
const inactivityServiceRef = useRef3(null);
|
|
1754
|
+
if (!inactivityServiceRef.current) {
|
|
1755
|
+
inactivityServiceRef.current = new InactivityService(supabaseClient, user, session, idleTimeoutMs, warnBeforeMs, onIdleLogout);
|
|
1756
|
+
}
|
|
1757
|
+
const inactivityService = inactivityServiceRef.current;
|
|
1758
|
+
useEffect4(() => {
|
|
1759
|
+
inactivityService.updateDependencies(supabaseClient, user, session, idleTimeoutMs, warnBeforeMs, onIdleLogout);
|
|
1760
|
+
let isMounted = true;
|
|
1761
|
+
inactivityService.initialize().catch((error) => {
|
|
1762
|
+
if (isMounted) {
|
|
1763
|
+
logger.error("InactivityServiceProvider", "Failed to initialize inactivity service:", error);
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
return () => {
|
|
1767
|
+
isMounted = false;
|
|
1768
|
+
};
|
|
1769
|
+
}, [inactivityService, supabaseClient, user, session, idleTimeoutMs, warnBeforeMs, onIdleLogout]);
|
|
1770
|
+
useEffect4(() => {
|
|
1771
|
+
return () => {
|
|
1772
|
+
inactivityService.cleanup();
|
|
1773
|
+
};
|
|
1774
|
+
}, [inactivityService]);
|
|
1775
|
+
const contextValue = useMemo4(() => ({
|
|
1776
|
+
inactivityService
|
|
1777
|
+
}), [inactivityService]);
|
|
1778
|
+
return /* @__PURE__ */ jsx4(InactivityServiceContext.Provider, { value: contextValue, children });
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// src/hooks/services/useAuthService.ts
|
|
1782
|
+
import { useContext, useReducer, useEffect as useEffect5 } from "react";
|
|
1783
|
+
function useAuthService() {
|
|
1784
|
+
const context = useContext(AuthServiceContext);
|
|
1785
|
+
if (!context) {
|
|
1786
|
+
throw new Error("useAuthService must be used within AuthServiceProvider");
|
|
1787
|
+
}
|
|
1788
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
1789
|
+
useEffect5(() => {
|
|
1790
|
+
return context.authService.subscribe(() => forceUpdate());
|
|
1791
|
+
}, [context.authService]);
|
|
1792
|
+
return context.authService;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/hooks/services/useOrganisationService.ts
|
|
1796
|
+
import { useContext as useContext2, useReducer as useReducer2, useEffect as useEffect6 } from "react";
|
|
1797
|
+
function useOrganisationService() {
|
|
1798
|
+
const context = useContext2(OrganisationServiceContext);
|
|
1799
|
+
if (!context) {
|
|
1800
|
+
throw new Error("useOrganisationService must be used within OrganisationServiceProvider");
|
|
1801
|
+
}
|
|
1802
|
+
const [, forceUpdate] = useReducer2((x) => x + 1, 0);
|
|
1803
|
+
useEffect6(() => {
|
|
1804
|
+
return context.organisationService.subscribe(() => forceUpdate());
|
|
1805
|
+
}, [context.organisationService]);
|
|
1806
|
+
return context.organisationService;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// src/hooks/services/useEventService.ts
|
|
1810
|
+
import { useContext as useContext3, useReducer as useReducer3, useEffect as useEffect7 } from "react";
|
|
1811
|
+
function useEventService() {
|
|
1812
|
+
const context = useContext3(EventServiceContext);
|
|
1813
|
+
if (!context) {
|
|
1814
|
+
throw new Error("useEventService must be used within EventServiceProvider");
|
|
1815
|
+
}
|
|
1816
|
+
const [, forceUpdate] = useReducer3((x) => x + 1, 0);
|
|
1817
|
+
useEffect7(() => {
|
|
1818
|
+
return context.eventService.subscribe(() => forceUpdate());
|
|
1819
|
+
}, [context.eventService]);
|
|
1820
|
+
return context.eventService;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// src/hooks/services/useInactivityService.ts
|
|
1824
|
+
import { useContext as useContext4, useReducer as useReducer4, useEffect as useEffect8 } from "react";
|
|
1825
|
+
function useInactivityService() {
|
|
1826
|
+
const context = useContext4(InactivityServiceContext);
|
|
1827
|
+
if (!context) {
|
|
1828
|
+
throw new Error("useInactivityService must be used within InactivityServiceProvider");
|
|
1829
|
+
}
|
|
1830
|
+
const [, forceUpdate] = useReducer4((x) => x + 1, 0);
|
|
1831
|
+
useEffect8(() => {
|
|
1832
|
+
return context.inactivityService.subscribe(() => forceUpdate());
|
|
1833
|
+
}, [context.inactivityService]);
|
|
1834
|
+
return context.inactivityService;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// src/hooks/useSessionRestoration.ts
|
|
1838
|
+
import { useContext as useContext5, useMemo as useMemo5, useEffect as useEffect9, useState as useState2 } from "react";
|
|
1839
|
+
var log = createLogger("useSessionRestoration");
|
|
1840
|
+
var SESSION_RESTORATION_TIMEOUT_MS = 5e3;
|
|
1841
|
+
function useSessionRestoration() {
|
|
1842
|
+
const context = useContext5(AuthServiceContext);
|
|
1843
|
+
if (!context) {
|
|
1844
|
+
throw new Error("useSessionRestoration must be used within AuthServiceProvider");
|
|
1845
|
+
}
|
|
1846
|
+
const { sessionRestoration } = context;
|
|
1847
|
+
const [hasTimedOut, setHasTimedOut] = useState2(false);
|
|
1848
|
+
useEffect9(() => {
|
|
1849
|
+
let timeoutHandle = null;
|
|
1850
|
+
if (sessionRestoration.isRestoring && !sessionRestoration.restorationComplete && !sessionRestoration.restorationError) {
|
|
1851
|
+
setHasTimedOut(false);
|
|
1852
|
+
timeoutHandle = setTimeout(() => {
|
|
1853
|
+
log.warn("Session restoration timed out");
|
|
1854
|
+
setHasTimedOut(true);
|
|
1855
|
+
}, SESSION_RESTORATION_TIMEOUT_MS);
|
|
1856
|
+
} else {
|
|
1857
|
+
setHasTimedOut(false);
|
|
1858
|
+
}
|
|
1859
|
+
return () => {
|
|
1860
|
+
if (timeoutHandle) {
|
|
1861
|
+
clearTimeout(timeoutHandle);
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
}, [
|
|
1865
|
+
sessionRestoration.isRestoring,
|
|
1866
|
+
sessionRestoration.restorationComplete,
|
|
1867
|
+
sessionRestoration.restorationError
|
|
1868
|
+
]);
|
|
1869
|
+
return useMemo5(() => ({
|
|
1870
|
+
...sessionRestoration,
|
|
1871
|
+
hasTimedOut,
|
|
1872
|
+
timeoutMs: SESSION_RESTORATION_TIMEOUT_MS
|
|
1873
|
+
}), [sessionRestoration, hasTimedOut]);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// src/providers/services/UnifiedAuthProvider.tsx
|
|
1877
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1878
|
+
var UnifiedAuthContext = createContext5(void 0);
|
|
1879
|
+
var useUnifiedAuth = () => {
|
|
1880
|
+
const context = useContext6(UnifiedAuthContext);
|
|
1881
|
+
if (!context) {
|
|
1882
|
+
logger.error("useUnifiedAuth", "useUnifiedAuth must be used within a UnifiedAuthProvider");
|
|
1883
|
+
throw new Error("useUnifiedAuth must be used within a UnifiedAuthProvider");
|
|
1884
|
+
}
|
|
1885
|
+
return context;
|
|
1886
|
+
};
|
|
1887
|
+
function UnifiedAuthContextProvider({
|
|
1888
|
+
children,
|
|
1889
|
+
appName,
|
|
1890
|
+
appConfig = { requires_event: true },
|
|
1891
|
+
// Default to requiring events
|
|
1892
|
+
...props
|
|
1893
|
+
}) {
|
|
1894
|
+
const authService = useAuthService();
|
|
1895
|
+
const organisationService = useOrganisationService();
|
|
1896
|
+
const inactivityService = useInactivityService();
|
|
1897
|
+
const sessionRestorationState = useSessionRestoration();
|
|
1898
|
+
const {
|
|
1899
|
+
hasTimedOut: sessionRestorationTimedOut,
|
|
1900
|
+
timeoutMs: sessionRestorationTimeoutMs,
|
|
1901
|
+
isRestoring,
|
|
1902
|
+
restorationComplete,
|
|
1903
|
+
restorationError
|
|
1904
|
+
} = sessionRestorationState;
|
|
1905
|
+
const sessionRestoration = useMemo6(() => ({
|
|
1906
|
+
isRestoring,
|
|
1907
|
+
restorationComplete,
|
|
1908
|
+
restorationError
|
|
1909
|
+
}), [isRestoring, restorationComplete, restorationError]);
|
|
1910
|
+
let eventService;
|
|
1911
|
+
try {
|
|
1912
|
+
eventService = useEventService();
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
eventService = {
|
|
1915
|
+
getEvents: () => [],
|
|
1916
|
+
getSelectedEvent: () => null,
|
|
1917
|
+
isLoading: () => false,
|
|
1918
|
+
getError: () => null,
|
|
1919
|
+
setSelectedEvent: () => {
|
|
1920
|
+
},
|
|
1921
|
+
refreshEvents: async () => {
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
const currentUser = authService.getUser();
|
|
1926
|
+
const currentSession = authService.getSession();
|
|
1927
|
+
const isAuth = !!(currentUser && currentSession);
|
|
1928
|
+
const supabase = authService.getSupabaseClient();
|
|
1929
|
+
const [appId, setAppId] = useState3(void 0);
|
|
1930
|
+
const isResolvingAppIdRef = useRef4(false);
|
|
1931
|
+
useEffect10(() => {
|
|
1932
|
+
if (isAuth && currentUser?.id && supabase && appName && !appId && !isResolvingAppIdRef.current) {
|
|
1933
|
+
isResolvingAppIdRef.current = true;
|
|
1934
|
+
const userId = currentUser.id;
|
|
1935
|
+
const appNameValue = appName;
|
|
1936
|
+
import("./api-ROMBCNKU.js").then(async ({ resolveAppContext, setupRBAC }) => {
|
|
1937
|
+
try {
|
|
1938
|
+
setupRBAC(supabase);
|
|
1939
|
+
const result = await resolveAppContext({
|
|
1940
|
+
userId,
|
|
1941
|
+
appName: appNameValue
|
|
1942
|
+
});
|
|
1943
|
+
if (result?.appId) {
|
|
1944
|
+
setAppId(result.appId);
|
|
1945
|
+
logger.debug("UnifiedAuthProvider", "appId resolved on login", {
|
|
1946
|
+
appId: result.appId,
|
|
1947
|
+
appName: appNameValue
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
logger.error("UnifiedAuthProvider", "Failed to resolve appId on login", {
|
|
1952
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1953
|
+
appName: appNameValue,
|
|
1954
|
+
userId
|
|
1955
|
+
});
|
|
1956
|
+
} finally {
|
|
1957
|
+
isResolvingAppIdRef.current = false;
|
|
1958
|
+
}
|
|
1959
|
+
}).catch((importError) => {
|
|
1960
|
+
logger.error("UnifiedAuthProvider", "Failed to import RBAC API", importError);
|
|
1961
|
+
isResolvingAppIdRef.current = false;
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
if (!isAuth) {
|
|
1965
|
+
setAppId(void 0);
|
|
1966
|
+
}
|
|
1967
|
+
}, [isAuth, currentUser?.id, supabase, appName, appId]);
|
|
1968
|
+
const authLoading = authService.isLoading();
|
|
1969
|
+
const orgLoading = organisationService.isLoading();
|
|
1970
|
+
const eventLoading = eventService.isLoading();
|
|
1971
|
+
const restorationLoading = sessionRestoration.isRestoring && !sessionRestorationTimedOut && !sessionRestoration.restorationError;
|
|
1972
|
+
const totalLoading = restorationLoading || authLoading || orgLoading || eventLoading;
|
|
1973
|
+
const authError = authService.getError();
|
|
1974
|
+
const selectedOrganisation = organisationService.getSelectedOrganisation();
|
|
1975
|
+
const organisations = organisationService.getOrganisations();
|
|
1976
|
+
const userMemberships = organisationService.getUserMemberships();
|
|
1977
|
+
const organisationError = organisationService.getError();
|
|
1978
|
+
const hasValidOrganisationContext = organisationService.hasValidOrganisationContext();
|
|
1979
|
+
const isContextReady = organisationService.isContextReady();
|
|
1980
|
+
const events = eventService.getEvents();
|
|
1981
|
+
const selectedEvent = eventService.getSelectedEvent();
|
|
1982
|
+
const eventError = eventService.getError();
|
|
1983
|
+
const showInactivityWarning = inactivityService.getShowInactivityWarning();
|
|
1984
|
+
const inactivityTimeRemaining = inactivityService.getInactivityTimeRemaining();
|
|
1985
|
+
const isIdle = inactivityService.isIdle();
|
|
1986
|
+
const timeRemaining = inactivityService.getTimeRemaining();
|
|
1987
|
+
const showWarning = inactivityService.isWarningShown();
|
|
1988
|
+
const isTracking = inactivityService.isTracking();
|
|
1989
|
+
const hasErrors = !!(authError || organisationError || eventError || sessionRestoration.restorationError);
|
|
1990
|
+
const signIn = useCallback((email, password) => authService.signIn(email, password), [authService]);
|
|
1991
|
+
const signUp = useCallback((email, password) => authService.signUp(email, password), [authService]);
|
|
1992
|
+
const signOut = useCallback(() => authService.signOut(), [authService]);
|
|
1993
|
+
const resetPassword = useCallback((email) => authService.resetPassword(email), [authService]);
|
|
1994
|
+
const updatePassword = useCallback((password) => authService.updatePassword(password), [authService]);
|
|
1995
|
+
const refreshSession = useCallback(() => authService.refreshSession(), [authService]);
|
|
1996
|
+
const switchOrganisation = useCallback((orgId) => organisationService.switchOrganisation(orgId), [organisationService]);
|
|
1997
|
+
const getUserRole = useCallback((orgId) => organisationService.getUserRole(orgId), [organisationService]);
|
|
1998
|
+
const validateOrganisationAccess = useCallback((orgId) => organisationService.validateOrganisationAccess(orgId), [organisationService]);
|
|
1999
|
+
const refreshOrganisations = useCallback(() => organisationService.refreshOrganisations(), [organisationService]);
|
|
2000
|
+
const ensureOrganisationContext = useCallback(() => organisationService.ensureOrganisationContext(), [organisationService]);
|
|
2001
|
+
const isOrganisationSecure = useCallback(() => organisationService.isOrganisationSecure(), [organisationService]);
|
|
2002
|
+
const getPrimaryOrganisation = useCallback(() => organisationService.getPrimaryOrganisation(), [organisationService]);
|
|
2003
|
+
const setSelectedEvent = useCallback((event) => eventService.setSelectedEvent(event), [eventService]);
|
|
2004
|
+
const refreshEvents = useCallback(() => eventService.refreshEvents(), [eventService]);
|
|
2005
|
+
const resetActivity = useCallback(() => inactivityService.resetActivity(), [inactivityService]);
|
|
2006
|
+
const startTracking = useCallback(() => inactivityService.startTracking(), [inactivityService]);
|
|
2007
|
+
const stopTracking = useCallback(() => inactivityService.stopTracking(), [inactivityService]);
|
|
2008
|
+
const handleIdleLogout = useCallback(() => inactivityService.handleIdleLogout(), [inactivityService]);
|
|
2009
|
+
const handleStaySignedIn = useCallback(() => inactivityService.handleStaySignedIn(), [inactivityService]);
|
|
2010
|
+
const handleSignOutNow = useCallback(() => inactivityService.handleSignOutNow(), [inactivityService]);
|
|
2011
|
+
const prevStateRef = useRef4(null);
|
|
2012
|
+
const isDev = import.meta.env.DEV || import.meta.env.MODE === "development";
|
|
2013
|
+
if (isDev) {
|
|
2014
|
+
const currentState = {
|
|
2015
|
+
isAuthenticated: isAuth,
|
|
2016
|
+
userEmail: currentUser?.email,
|
|
2017
|
+
totalLoading
|
|
2018
|
+
};
|
|
2019
|
+
const prevState = prevStateRef.current;
|
|
2020
|
+
if (!prevState || prevState.isAuthenticated !== currentState.isAuthenticated || prevState.userEmail !== currentState.userEmail || prevState.totalLoading !== currentState.totalLoading) {
|
|
2021
|
+
prevStateRef.current = currentState;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
const contextValue = useMemo6(() => {
|
|
2025
|
+
return {
|
|
2026
|
+
// Auth state
|
|
2027
|
+
user: currentUser,
|
|
2028
|
+
session: currentSession,
|
|
2029
|
+
isAuthenticated: isAuth,
|
|
2030
|
+
authLoading,
|
|
2031
|
+
authError,
|
|
2032
|
+
supabase,
|
|
2033
|
+
// Auth methods
|
|
2034
|
+
signIn,
|
|
2035
|
+
signUp,
|
|
2036
|
+
signOut,
|
|
2037
|
+
resetPassword,
|
|
2038
|
+
updatePassword,
|
|
2039
|
+
refreshSession,
|
|
2040
|
+
// Organisation state
|
|
2041
|
+
selectedOrganisation,
|
|
2042
|
+
organisations,
|
|
2043
|
+
userMemberships,
|
|
2044
|
+
organisationLoading: orgLoading,
|
|
2045
|
+
organisationError,
|
|
2046
|
+
hasValidOrganisationContext,
|
|
2047
|
+
isContextReady,
|
|
2048
|
+
// Organisation methods
|
|
2049
|
+
switchOrganisation,
|
|
2050
|
+
getUserRole,
|
|
2051
|
+
validateOrganisationAccess,
|
|
2052
|
+
refreshOrganisations,
|
|
2053
|
+
ensureOrganisationContext,
|
|
2054
|
+
isOrganisationSecure,
|
|
2055
|
+
getPrimaryOrganisation,
|
|
2056
|
+
// Event state
|
|
2057
|
+
events,
|
|
2058
|
+
selectedEvent,
|
|
2059
|
+
eventLoading,
|
|
2060
|
+
eventError,
|
|
2061
|
+
// Event methods
|
|
2062
|
+
setSelectedEvent,
|
|
2063
|
+
refreshEvents,
|
|
2064
|
+
// Inactivity state
|
|
2065
|
+
showInactivityWarning,
|
|
2066
|
+
inactivityTimeRemaining,
|
|
2067
|
+
isIdle,
|
|
2068
|
+
timeRemaining,
|
|
2069
|
+
showWarning,
|
|
2070
|
+
isTracking,
|
|
2071
|
+
// Inactivity methods
|
|
2072
|
+
resetActivity,
|
|
2073
|
+
startTracking,
|
|
2074
|
+
stopTracking,
|
|
2075
|
+
handleIdleLogout,
|
|
2076
|
+
handleStaySignedIn,
|
|
2077
|
+
handleSignOutNow,
|
|
2078
|
+
// Additional unified properties
|
|
2079
|
+
appName,
|
|
2080
|
+
appId,
|
|
2081
|
+
// Resolved immediately on login
|
|
2082
|
+
appConfig,
|
|
2083
|
+
isLoading: totalLoading,
|
|
2084
|
+
hasErrors,
|
|
2085
|
+
sessionRestoration,
|
|
2086
|
+
sessionRestorationTimedOut,
|
|
2087
|
+
sessionRestorationTimeoutMs
|
|
2088
|
+
};
|
|
2089
|
+
}, [
|
|
2090
|
+
// All primitive values extracted from services
|
|
2091
|
+
// Note: Arrays/objects from services are stable references (same reference unless data changes)
|
|
2092
|
+
currentUser,
|
|
2093
|
+
currentSession,
|
|
2094
|
+
isAuth,
|
|
2095
|
+
authLoading,
|
|
2096
|
+
authError,
|
|
2097
|
+
supabase,
|
|
2098
|
+
selectedOrganisation,
|
|
2099
|
+
organisations,
|
|
2100
|
+
userMemberships,
|
|
2101
|
+
orgLoading,
|
|
2102
|
+
organisationError,
|
|
2103
|
+
hasValidOrganisationContext,
|
|
2104
|
+
isContextReady,
|
|
2105
|
+
events,
|
|
2106
|
+
selectedEvent,
|
|
2107
|
+
eventLoading,
|
|
2108
|
+
eventError,
|
|
2109
|
+
showInactivityWarning,
|
|
2110
|
+
inactivityTimeRemaining,
|
|
2111
|
+
isIdle,
|
|
2112
|
+
timeRemaining,
|
|
2113
|
+
showWarning,
|
|
2114
|
+
isTracking,
|
|
2115
|
+
totalLoading,
|
|
2116
|
+
hasErrors,
|
|
2117
|
+
appName,
|
|
2118
|
+
appId,
|
|
2119
|
+
appConfig,
|
|
2120
|
+
sessionRestoration,
|
|
2121
|
+
sessionRestorationTimedOut,
|
|
2122
|
+
sessionRestorationTimeoutMs,
|
|
2123
|
+
// Stable function references from useCallback (services are stable, so callbacks are too)
|
|
2124
|
+
signIn,
|
|
2125
|
+
signUp,
|
|
2126
|
+
signOut,
|
|
2127
|
+
resetPassword,
|
|
2128
|
+
updatePassword,
|
|
2129
|
+
refreshSession,
|
|
2130
|
+
switchOrganisation,
|
|
2131
|
+
getUserRole,
|
|
2132
|
+
validateOrganisationAccess,
|
|
2133
|
+
refreshOrganisations,
|
|
2134
|
+
ensureOrganisationContext,
|
|
2135
|
+
isOrganisationSecure,
|
|
2136
|
+
getPrimaryOrganisation,
|
|
2137
|
+
setSelectedEvent,
|
|
2138
|
+
refreshEvents,
|
|
2139
|
+
resetActivity,
|
|
2140
|
+
startTracking,
|
|
2141
|
+
stopTracking,
|
|
2142
|
+
handleIdleLogout,
|
|
2143
|
+
handleStaySignedIn,
|
|
2144
|
+
handleSignOutNow
|
|
2145
|
+
]);
|
|
2146
|
+
return /* @__PURE__ */ jsx5(UnifiedAuthContext.Provider, { value: contextValue, children });
|
|
2147
|
+
}
|
|
2148
|
+
function EventServiceProviderWrapper({
|
|
2149
|
+
children,
|
|
2150
|
+
supabaseClient,
|
|
2151
|
+
user,
|
|
2152
|
+
session,
|
|
2153
|
+
appName
|
|
2154
|
+
}) {
|
|
2155
|
+
const organisationService = useOrganisationService();
|
|
2156
|
+
const selectedOrganisation = organisationService.getSelectedOrganisation();
|
|
2157
|
+
return /* @__PURE__ */ jsx5(
|
|
2158
|
+
EventServiceProvider,
|
|
2159
|
+
{
|
|
2160
|
+
supabaseClient,
|
|
2161
|
+
user,
|
|
2162
|
+
session,
|
|
2163
|
+
appName,
|
|
2164
|
+
selectedOrganisation,
|
|
2165
|
+
setSelectedEventId: () => {
|
|
2166
|
+
},
|
|
2167
|
+
children
|
|
2168
|
+
}
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
function ServiceAwareProviders({
|
|
2172
|
+
children,
|
|
2173
|
+
supabaseClient,
|
|
2174
|
+
appName,
|
|
2175
|
+
appConfig,
|
|
2176
|
+
persistState,
|
|
2177
|
+
enablePersistence,
|
|
2178
|
+
requireOrganisationContext,
|
|
2179
|
+
idleTimeoutMs,
|
|
2180
|
+
warnBeforeMs,
|
|
2181
|
+
onIdleLogout,
|
|
2182
|
+
renderInactivityWarning,
|
|
2183
|
+
dangerouslyDisableInactivity
|
|
2184
|
+
}) {
|
|
2185
|
+
const authService = useAuthService();
|
|
2186
|
+
return /* @__PURE__ */ jsx5(
|
|
2187
|
+
OrganisationServiceProvider,
|
|
2188
|
+
{
|
|
2189
|
+
supabaseClient,
|
|
2190
|
+
user: authService.getUser(),
|
|
2191
|
+
session: authService.getSession(),
|
|
2192
|
+
children: /* @__PURE__ */ jsx5(
|
|
2193
|
+
EventServiceProviderWrapper,
|
|
2194
|
+
{
|
|
2195
|
+
supabaseClient,
|
|
2196
|
+
user: authService.getUser(),
|
|
2197
|
+
session: authService.getSession(),
|
|
2198
|
+
appName,
|
|
2199
|
+
children: /* @__PURE__ */ jsx5(
|
|
2200
|
+
InactivityServiceProvider,
|
|
2201
|
+
{
|
|
2202
|
+
supabaseClient,
|
|
2203
|
+
user: authService.getUser(),
|
|
2204
|
+
session: authService.getSession(),
|
|
2205
|
+
idleTimeoutMs,
|
|
2206
|
+
warnBeforeMs,
|
|
2207
|
+
onIdleLogout,
|
|
2208
|
+
children: /* @__PURE__ */ jsx5(
|
|
2209
|
+
UnifiedAuthContextProvider,
|
|
2210
|
+
{
|
|
2211
|
+
appName,
|
|
2212
|
+
appConfig,
|
|
2213
|
+
supabaseClient,
|
|
2214
|
+
persistState,
|
|
2215
|
+
enablePersistence,
|
|
2216
|
+
requireOrganisationContext,
|
|
2217
|
+
idleTimeoutMs,
|
|
2218
|
+
warnBeforeMs,
|
|
2219
|
+
onIdleLogout,
|
|
2220
|
+
renderInactivityWarning,
|
|
2221
|
+
dangerouslyDisableInactivity,
|
|
2222
|
+
children
|
|
2223
|
+
}
|
|
2224
|
+
)
|
|
2225
|
+
}
|
|
2226
|
+
)
|
|
2227
|
+
}
|
|
2228
|
+
)
|
|
2229
|
+
}
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
function UnifiedAuthProvider({
|
|
2233
|
+
children,
|
|
2234
|
+
supabaseClient,
|
|
2235
|
+
appName,
|
|
2236
|
+
appConfig = { requires_event: true },
|
|
2237
|
+
// Default to requiring events
|
|
2238
|
+
persistState = true,
|
|
2239
|
+
enablePersistence,
|
|
2240
|
+
requireOrganisationContext = true,
|
|
2241
|
+
idleTimeoutMs = 30 * 60 * 1e3,
|
|
2242
|
+
// 30 minutes
|
|
2243
|
+
warnBeforeMs = 60 * 1e3,
|
|
2244
|
+
// 60 seconds
|
|
2245
|
+
onIdleLogout,
|
|
2246
|
+
renderInactivityWarning,
|
|
2247
|
+
dangerouslyDisableInactivity = false
|
|
2248
|
+
}) {
|
|
2249
|
+
const clientRef = useRef4(supabaseClient);
|
|
2250
|
+
useEffect10(() => {
|
|
2251
|
+
if (clientRef.current !== supabaseClient) {
|
|
2252
|
+
logger.warn("UnifiedAuthProvider", "Supabase client reference changed - this may indicate multiple client instances are being created", {
|
|
2253
|
+
previousClient: clientRef.current,
|
|
2254
|
+
newClient: supabaseClient,
|
|
2255
|
+
note: 'Ensure you create the Supabase client once and reuse it. Creating multiple clients can cause performance issues and the "Multiple GoTrueClient instances" warning.'
|
|
2256
|
+
});
|
|
2257
|
+
clientRef.current = supabaseClient;
|
|
2258
|
+
}
|
|
2259
|
+
}, [supabaseClient]);
|
|
2260
|
+
return /* @__PURE__ */ jsx5(AuthServiceProvider, { supabaseClient, appName, children: /* @__PURE__ */ jsx5(
|
|
2261
|
+
ServiceAwareProviders,
|
|
2262
|
+
{
|
|
2263
|
+
supabaseClient,
|
|
2264
|
+
appName,
|
|
2265
|
+
appConfig,
|
|
2266
|
+
persistState,
|
|
2267
|
+
enablePersistence,
|
|
2268
|
+
requireOrganisationContext,
|
|
2269
|
+
idleTimeoutMs,
|
|
2270
|
+
warnBeforeMs,
|
|
2271
|
+
onIdleLogout,
|
|
2272
|
+
renderInactivityWarning,
|
|
2273
|
+
dangerouslyDisableInactivity,
|
|
2274
|
+
children
|
|
2275
|
+
}
|
|
2276
|
+
) });
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
export {
|
|
2280
|
+
AuthServiceContext,
|
|
2281
|
+
AuthServiceProvider,
|
|
2282
|
+
OrganisationServiceContext,
|
|
2283
|
+
OrganisationServiceProvider,
|
|
2284
|
+
EventServiceContext,
|
|
2285
|
+
EventServiceProvider,
|
|
2286
|
+
InactivityServiceContext,
|
|
2287
|
+
InactivityServiceProvider,
|
|
2288
|
+
useAuthService,
|
|
2289
|
+
useOrganisationService,
|
|
2290
|
+
useEventService,
|
|
2291
|
+
useInactivityService,
|
|
2292
|
+
useSessionRestoration,
|
|
2293
|
+
useUnifiedAuth,
|
|
2294
|
+
UnifiedAuthProvider
|
|
2295
|
+
};
|
|
2296
|
+
//# sourceMappingURL=chunk-FUEYYMX5.js.map
|