@jmruthers/pace-core 0.6.2 → 0.6.4
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 +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/chunk-36LVWXB2.js +227 -0
- package/dist/chunk-36LVWXB2.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
- package/dist/chunk-3LPHPB62.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
- package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
- package/dist/chunk-7JPAB3T5.js.map +1 -0
- package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
- package/dist/chunk-ATKZM7RX.js.map +1 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
- package/dist/chunk-AVMLPIM7.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
- package/dist/chunk-I6DAQMWX.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
- package/dist/chunk-NN6WWZ5U.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
- package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
- package/dist/chunk-YKRAFF5K.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-OOPCLPZW.js +9 -0
- package/dist/contextValidator-OOPCLPZW.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +10 -5
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +568 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +285 -56
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -99,7 +99,7 @@ describe('useAppConfig Hook', () => {
|
|
|
99
99
|
vi.restoreAllMocks();
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
describe('Public Page Context', () => {
|
|
103
103
|
beforeEach(() => {
|
|
104
104
|
mockUseIsPublicPage.mockReturnValue(true);
|
|
105
105
|
});
|
|
@@ -107,8 +107,6 @@ describe('useAppConfig Hook', () => {
|
|
|
107
107
|
it('should return public page configuration when in public context', () => {
|
|
108
108
|
const { result } = renderHook(() => useAppConfig());
|
|
109
109
|
|
|
110
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
111
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
112
110
|
expect(result.current.isLoading).toBe(false);
|
|
113
111
|
// Note: If VITE_APP_NAME is set in .env (e.g., CORE), the hook will use it.
|
|
114
112
|
// Otherwise, it defaults to 'PACE'. This test verifies it returns a valid string.
|
|
@@ -122,8 +120,6 @@ describe('useAppConfig Hook', () => {
|
|
|
122
120
|
const { result } = renderHook(() => useAppConfig());
|
|
123
121
|
|
|
124
122
|
// Public pages should always have these characteristics
|
|
125
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
126
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
127
123
|
expect(result.current.isLoading).toBe(false);
|
|
128
124
|
expect(typeof result.current.appName).toBe('string');
|
|
129
125
|
});
|
|
@@ -134,80 +130,23 @@ describe('useAppConfig Hook', () => {
|
|
|
134
130
|
mockUseIsPublicPage.mockReturnValue(false);
|
|
135
131
|
// Set default mock for useUnifiedAuth
|
|
136
132
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
137
|
-
appConfig: { requires_event: true },
|
|
138
133
|
appName: 'PACE',
|
|
139
134
|
} as any);
|
|
140
135
|
});
|
|
141
136
|
|
|
142
137
|
it('should return configuration from UnifiedAuthProvider when available', () => {
|
|
143
|
-
const mockAppConfig = {
|
|
144
|
-
requires_event: false,
|
|
145
|
-
};
|
|
146
138
|
const mockAppName = 'AuthApp';
|
|
147
139
|
|
|
148
140
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
149
|
-
appConfig: mockAppConfig,
|
|
150
141
|
appName: mockAppName,
|
|
151
142
|
} as any);
|
|
152
143
|
|
|
153
144
|
const { result } = renderHook(() => useAppConfig());
|
|
154
145
|
|
|
155
|
-
expect(result.current.supportsDirectAccess).toBe(true);
|
|
156
|
-
expect(result.current.requiresEvent).toBe(false);
|
|
157
146
|
expect(result.current.isLoading).toBe(false);
|
|
158
147
|
expect(result.current.appName).toBe('AuthApp');
|
|
159
148
|
});
|
|
160
149
|
|
|
161
|
-
it('should handle requires_event: true', () => {
|
|
162
|
-
const mockAppConfig = {
|
|
163
|
-
requires_event: true,
|
|
164
|
-
};
|
|
165
|
-
const mockAppName = 'EventApp';
|
|
166
|
-
|
|
167
|
-
mockUseUnifiedAuthFn.mockReturnValue({
|
|
168
|
-
appConfig: mockAppConfig,
|
|
169
|
-
appName: mockAppName,
|
|
170
|
-
} as any);
|
|
171
|
-
|
|
172
|
-
const { result } = renderHook(() => useAppConfig());
|
|
173
|
-
|
|
174
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
175
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
176
|
-
expect(result.current.isLoading).toBe(false);
|
|
177
|
-
expect(result.current.appName).toBe('EventApp');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should handle null appConfig (loading state)', () => {
|
|
181
|
-
mockUseUnifiedAuthFn.mockReturnValue({
|
|
182
|
-
appConfig: null,
|
|
183
|
-
appName: 'LoadingApp',
|
|
184
|
-
} as any);
|
|
185
|
-
|
|
186
|
-
const { result } = renderHook(() => useAppConfig());
|
|
187
|
-
|
|
188
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
189
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
190
|
-
expect(result.current.isLoading).toBe(true);
|
|
191
|
-
expect(result.current.appName).toBe('LoadingApp');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('should handle undefined appConfig.requires_event', () => {
|
|
195
|
-
const mockAppConfig = {};
|
|
196
|
-
const mockAppName = 'DefaultApp';
|
|
197
|
-
|
|
198
|
-
mockUseUnifiedAuthFn.mockReturnValue({
|
|
199
|
-
appConfig: mockAppConfig,
|
|
200
|
-
appName: mockAppName,
|
|
201
|
-
} as any);
|
|
202
|
-
|
|
203
|
-
const { result } = renderHook(() => useAppConfig());
|
|
204
|
-
|
|
205
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
206
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
207
|
-
expect(result.current.isLoading).toBe(false);
|
|
208
|
-
expect(result.current.appName).toBe('DefaultApp');
|
|
209
|
-
});
|
|
210
|
-
|
|
211
150
|
it('should handle UnifiedAuthProvider error and return fallback', () => {
|
|
212
151
|
mockUseUnifiedAuthFn.mockImplementation(() => {
|
|
213
152
|
throw new Error('Provider not available');
|
|
@@ -215,8 +154,6 @@ describe('useAppConfig Hook', () => {
|
|
|
215
154
|
|
|
216
155
|
const { result } = renderHook(() => useAppConfig());
|
|
217
156
|
|
|
218
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
219
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
220
157
|
expect(result.current.isLoading).toBe(false);
|
|
221
158
|
expect(result.current.appName).toBe('PACE');
|
|
222
159
|
});
|
|
@@ -228,8 +165,6 @@ describe('useAppConfig Hook', () => {
|
|
|
228
165
|
|
|
229
166
|
const { result } = renderHook(() => useAppConfig());
|
|
230
167
|
|
|
231
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
232
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
233
168
|
expect(result.current.isLoading).toBe(false);
|
|
234
169
|
expect(result.current.appName).toBe('PACE');
|
|
235
170
|
});
|
|
@@ -241,8 +176,6 @@ describe('useAppConfig Hook', () => {
|
|
|
241
176
|
|
|
242
177
|
const { result } = renderHook(() => useAppConfig());
|
|
243
178
|
|
|
244
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
245
|
-
expect(result.current.requiresEvent).toBe(true);
|
|
246
179
|
expect(result.current.isLoading).toBe(false);
|
|
247
180
|
expect(result.current.appName).toBe('PACE');
|
|
248
181
|
});
|
|
@@ -264,11 +197,9 @@ describe('useAppConfig Hook', () => {
|
|
|
264
197
|
it('should memoize authenticated configuration based on dependencies', () => {
|
|
265
198
|
mockUseIsPublicPage.mockReturnValue(false);
|
|
266
199
|
|
|
267
|
-
const mockAppConfig = { requires_event: false };
|
|
268
200
|
const mockAppName = 'TestApp';
|
|
269
201
|
|
|
270
202
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
271
|
-
appConfig: mockAppConfig,
|
|
272
203
|
appName: mockAppName,
|
|
273
204
|
} as any);
|
|
274
205
|
|
|
@@ -281,25 +212,21 @@ describe('useAppConfig Hook', () => {
|
|
|
281
212
|
|
|
282
213
|
expect(result.current).toBe(firstResult);
|
|
283
214
|
|
|
284
|
-
// Rerender with different
|
|
215
|
+
// Rerender with different appName
|
|
285
216
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
286
|
-
|
|
287
|
-
appName: mockAppName,
|
|
217
|
+
appName: 'DifferentApp',
|
|
288
218
|
} as any);
|
|
289
219
|
|
|
290
220
|
rerender();
|
|
291
221
|
|
|
292
222
|
expect(result.current).not.toBe(firstResult);
|
|
293
|
-
expect(result.current.
|
|
223
|
+
expect(result.current.appName).toBe('DifferentApp');
|
|
294
224
|
});
|
|
295
225
|
|
|
296
226
|
it('should memoize authenticated configuration based on appName changes', () => {
|
|
297
227
|
mockUseIsPublicPage.mockReturnValue(false);
|
|
298
228
|
|
|
299
|
-
const mockAppConfig = { requires_event: false };
|
|
300
|
-
|
|
301
229
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
302
|
-
appConfig: mockAppConfig,
|
|
303
230
|
appName: 'App1',
|
|
304
231
|
} as any);
|
|
305
232
|
|
|
@@ -309,7 +236,6 @@ describe('useAppConfig Hook', () => {
|
|
|
309
236
|
|
|
310
237
|
// Rerender with different appName
|
|
311
238
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
312
|
-
appConfig: mockAppConfig,
|
|
313
239
|
appName: 'App2',
|
|
314
240
|
} as any);
|
|
315
241
|
|
|
@@ -327,7 +253,6 @@ describe('useAppConfig Hook', () => {
|
|
|
327
253
|
|
|
328
254
|
const { result: publicResult } = renderHook(() => useAppConfig());
|
|
329
255
|
|
|
330
|
-
expect(publicResult.current.supportsDirectAccess).toBe(false);
|
|
331
256
|
// Note: App name depends on environment - may be 'PACE' (default) or 'CORE' (from .env)
|
|
332
257
|
expect(typeof publicResult.current.appName).toBe('string');
|
|
333
258
|
expect(['PACE', 'CORE']).toContain(publicResult.current.appName);
|
|
@@ -335,13 +260,11 @@ describe('useAppConfig Hook', () => {
|
|
|
335
260
|
// Test authenticated page context separately
|
|
336
261
|
mockUseIsPublicPage.mockReturnValue(false);
|
|
337
262
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
338
|
-
appConfig: { requires_event: false },
|
|
339
263
|
appName: 'AuthApp',
|
|
340
264
|
} as any);
|
|
341
265
|
|
|
342
266
|
const { result: authResult } = renderHook(() => useAppConfig());
|
|
343
267
|
|
|
344
|
-
expect(authResult.current.supportsDirectAccess).toBe(true);
|
|
345
268
|
expect(authResult.current.appName).toBe('AuthApp');
|
|
346
269
|
});
|
|
347
270
|
});
|
|
@@ -350,30 +273,13 @@ describe('useAppConfig Hook', () => {
|
|
|
350
273
|
it('should return correct types for all return values', () => {
|
|
351
274
|
mockUseIsPublicPage.mockReturnValue(false);
|
|
352
275
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
353
|
-
appConfig: { requires_event: false },
|
|
354
276
|
appName: 'TypeTestApp',
|
|
355
277
|
} as any);
|
|
356
278
|
|
|
357
279
|
const { result } = renderHook(() => useAppConfig());
|
|
358
280
|
|
|
359
|
-
expect(typeof result.current.supportsDirectAccess).toBe('boolean');
|
|
360
|
-
expect(typeof result.current.requiresEvent).toBe('boolean');
|
|
361
281
|
expect(typeof result.current.isLoading).toBe('boolean');
|
|
362
282
|
expect(typeof result.current.appName).toBe('string');
|
|
363
283
|
});
|
|
364
|
-
|
|
365
|
-
it('should handle malformed appConfig objects', () => {
|
|
366
|
-
mockUseIsPublicPage.mockReturnValue(false);
|
|
367
|
-
mockUseUnifiedAuthFn.mockReturnValue({
|
|
368
|
-
appConfig: { requires_event: 'not-a-boolean' },
|
|
369
|
-
appName: 'MalformedApp',
|
|
370
|
-
} as any);
|
|
371
|
-
|
|
372
|
-
const { result } = renderHook(() => useAppConfig());
|
|
373
|
-
|
|
374
|
-
// The hook uses the value directly, so it will be truthy
|
|
375
|
-
expect(result.current.requiresEvent).toBe('not-a-boolean');
|
|
376
|
-
expect(result.current.supportsDirectAccess).toBe(false);
|
|
377
|
-
});
|
|
378
284
|
});
|
|
379
285
|
});
|
|
@@ -4,26 +4,19 @@
|
|
|
4
4
|
* @module Hooks/useAppConfig
|
|
5
5
|
* @since 0.4.0
|
|
6
6
|
*
|
|
7
|
-
* Hook for accessing app
|
|
8
|
-
*
|
|
7
|
+
* Hook for accessing app name and loading state.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Scope configuration (requires_event) is now page-level only (rbac_app_pages.scope_type).
|
|
10
|
+
* Use getPageScopeType() to determine if a specific page requires event or organisation context.
|
|
9
11
|
*
|
|
10
12
|
* @example
|
|
11
13
|
* ```tsx
|
|
12
14
|
* function MyComponent() {
|
|
13
|
-
* const {
|
|
15
|
+
* const { appName, isLoading } = useAppConfig();
|
|
14
16
|
*
|
|
15
17
|
* if (isLoading) return <div>Loading...</div>;
|
|
16
18
|
*
|
|
17
|
-
* return
|
|
18
|
-
* <div>
|
|
19
|
-
* {supportsDirectAccess && (
|
|
20
|
-
* <div>This app supports direct access!</div>
|
|
21
|
-
* )}
|
|
22
|
-
* {requiresEvent && (
|
|
23
|
-
* <EventSelector />
|
|
24
|
-
* )}
|
|
25
|
-
* </div>
|
|
26
|
-
* );
|
|
19
|
+
* return <div>App: {appName}</div>;
|
|
27
20
|
* }
|
|
28
21
|
* ```
|
|
29
22
|
*/
|
|
@@ -33,10 +26,8 @@ import { useUnifiedAuth } from '../providers/services/UnifiedAuthProvider';
|
|
|
33
26
|
import { useIsPublicPage, PublicPageContext } from '../components/PublicLayout/PublicPageProvider';
|
|
34
27
|
|
|
35
28
|
export interface UseAppConfigReturn {
|
|
36
|
-
supportsDirectAccess: boolean;
|
|
37
|
-
requiresEvent: boolean;
|
|
38
|
-
isLoading: boolean;
|
|
39
29
|
appName: string;
|
|
30
|
+
isLoading: boolean;
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
/**
|
|
@@ -92,32 +83,26 @@ export function useAppConfig(): UseAppConfigReturn {
|
|
|
92
83
|
};
|
|
93
84
|
|
|
94
85
|
return useMemo(() => ({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
isLoading: false,
|
|
98
|
-
appName: getAppName()
|
|
86
|
+
appName: getAppName(),
|
|
87
|
+
isLoading: false
|
|
99
88
|
}), [contextAppName, hasPublicContext]);
|
|
100
89
|
}
|
|
101
90
|
|
|
102
91
|
// For authenticated pages, use UnifiedAuthProvider
|
|
103
92
|
try {
|
|
104
|
-
const {
|
|
93
|
+
const { appName } = useUnifiedAuth();
|
|
105
94
|
return useMemo(() => ({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
appName
|
|
110
|
-
}), [appConfig?.requires_event, appName]);
|
|
95
|
+
appName,
|
|
96
|
+
isLoading: false
|
|
97
|
+
}), [appName]);
|
|
111
98
|
} catch (error) {
|
|
112
99
|
// Fallback if UnifiedAuthProvider is not available
|
|
113
100
|
return useMemo(() => ({
|
|
114
|
-
supportsDirectAccess: false,
|
|
115
|
-
requiresEvent: true,
|
|
116
|
-
isLoading: false,
|
|
117
101
|
appName: contextAppName ||
|
|
118
102
|
getNodeEnvVar('VITE_APP_NAME') ||
|
|
119
103
|
getNodeEnvVar('NEXT_PUBLIC_APP_NAME') ||
|
|
120
|
-
'PACE'
|
|
104
|
+
'PACE',
|
|
105
|
+
isLoading: false
|
|
121
106
|
}), [contextAppName]);
|
|
122
107
|
}
|
|
123
108
|
}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
37
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
38
38
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
39
39
|
import { FileReference, FileCategory } from '../types/file-reference';
|
|
40
40
|
import { getPublicUrl, getSignedUrl, generateFileUrlsBatch } from '../utils/storage/helpers';
|
|
@@ -138,14 +138,25 @@ export function useFileDisplay(
|
|
|
138
138
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
139
139
|
const [error, setError] = useState<Error | null>(null);
|
|
140
140
|
|
|
141
|
+
// Ref to track if component is mounted and effect is active
|
|
142
|
+
// This prevents state updates from stale async operations
|
|
143
|
+
const isMountedRef = useRef(true);
|
|
144
|
+
|
|
145
|
+
// Helper to safely set state only if component is still mounted
|
|
146
|
+
const safeSetState = <T,>(setter: (value: T) => void, value: T) => {
|
|
147
|
+
if (isMountedRef.current) {
|
|
148
|
+
setter(value);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
141
152
|
const fetchFiles = useCallback(async (): Promise<void> => {
|
|
142
153
|
if (!table_name || !record_id || !supabase) {
|
|
143
|
-
setFileUrl
|
|
144
|
-
setFileReference
|
|
145
|
-
setFileReferences
|
|
146
|
-
setFileUrls
|
|
147
|
-
setFileCount
|
|
148
|
-
setIsLoading
|
|
154
|
+
safeSetState(setFileUrl, null);
|
|
155
|
+
safeSetState(setFileReference, null);
|
|
156
|
+
safeSetState(setFileReferences, []);
|
|
157
|
+
safeSetState(setFileUrls, new Map());
|
|
158
|
+
safeSetState(setFileCount, 0);
|
|
159
|
+
safeSetState(setIsLoading, false);
|
|
149
160
|
return;
|
|
150
161
|
}
|
|
151
162
|
|
|
@@ -177,13 +188,13 @@ export function useFileDisplay(
|
|
|
177
188
|
expiresIn: 3600
|
|
178
189
|
});
|
|
179
190
|
const regeneratedUrl = signedUrlResult?.url || null;
|
|
180
|
-
setFileUrl
|
|
181
|
-
setFileReference
|
|
182
|
-
setFileReferences
|
|
183
|
-
setFileUrls
|
|
184
|
-
setFileCount
|
|
185
|
-
setIsLoading
|
|
186
|
-
setError
|
|
191
|
+
safeSetState(setFileUrl, regeneratedUrl);
|
|
192
|
+
safeSetState(setFileReference, cachedData.fileReference);
|
|
193
|
+
safeSetState(setFileReferences, cachedData.fileReferences || []);
|
|
194
|
+
safeSetState(setFileUrls, cachedData.fileUrls || new Map());
|
|
195
|
+
safeSetState(setFileCount, cachedData.fileCount || 0);
|
|
196
|
+
safeSetState(setIsLoading, false);
|
|
197
|
+
safeSetState(setError, null);
|
|
187
198
|
return;
|
|
188
199
|
} catch (err) {
|
|
189
200
|
// If signed URL regeneration fails, fall through to normal fetch
|
|
@@ -192,20 +203,20 @@ export function useFileDisplay(
|
|
|
192
203
|
}
|
|
193
204
|
|
|
194
205
|
// Normal cache hit for public files or files with URLs
|
|
195
|
-
setFileUrl
|
|
196
|
-
setFileReference
|
|
197
|
-
setFileReferences
|
|
198
|
-
setFileUrls
|
|
199
|
-
setFileCount
|
|
200
|
-
setIsLoading
|
|
201
|
-
setError
|
|
206
|
+
safeSetState(setFileUrl, cachedData.fileUrl || null);
|
|
207
|
+
safeSetState(setFileReference, cachedData.fileReference || null);
|
|
208
|
+
safeSetState(setFileReferences, cachedData.fileReferences || []);
|
|
209
|
+
safeSetState(setFileUrls, cachedData.fileUrls || new Map());
|
|
210
|
+
safeSetState(setFileCount, cachedData.fileCount || 0);
|
|
211
|
+
safeSetState(setIsLoading, false);
|
|
212
|
+
safeSetState(setError, null);
|
|
202
213
|
return;
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
216
|
|
|
206
217
|
try {
|
|
207
|
-
setIsLoading
|
|
208
|
-
setError
|
|
218
|
+
safeSetState(setIsLoading, true);
|
|
219
|
+
safeSetState(setError, null);
|
|
209
220
|
|
|
210
221
|
const service = createFileReferenceService(supabase);
|
|
211
222
|
let files: FileReference[] = [];
|
|
@@ -485,11 +496,11 @@ export function useFileDisplay(
|
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
if (files.length === 0) {
|
|
488
|
-
setFileUrl
|
|
489
|
-
setFileReference
|
|
490
|
-
setFileReferences
|
|
491
|
-
setFileUrls
|
|
492
|
-
setFileCount
|
|
499
|
+
safeSetState(setFileUrl, null);
|
|
500
|
+
safeSetState(setFileReference, null);
|
|
501
|
+
safeSetState(setFileReferences, []);
|
|
502
|
+
safeSetState(setFileUrls, new Map());
|
|
503
|
+
safeSetState(setFileCount, 0);
|
|
493
504
|
|
|
494
505
|
// Cache empty result
|
|
495
506
|
if (enableCache) {
|
|
@@ -503,14 +514,14 @@ export function useFileDisplay(
|
|
|
503
514
|
return;
|
|
504
515
|
}
|
|
505
516
|
|
|
506
|
-
setFileReferences
|
|
507
|
-
setFileCount
|
|
517
|
+
safeSetState(setFileReferences, files);
|
|
518
|
+
safeSetState(setFileCount, files.length);
|
|
508
519
|
|
|
509
520
|
if (category && files.length > 0) {
|
|
510
521
|
// Single file mode - get first file
|
|
511
522
|
const firstFile = files[0];
|
|
512
523
|
// Removed verbose debug logs - only log on errors
|
|
513
|
-
setFileReference
|
|
524
|
+
safeSetState(setFileReference, firstFile);
|
|
514
525
|
|
|
515
526
|
// Generate URL based on file visibility
|
|
516
527
|
let url: string | null = null;
|
|
@@ -533,7 +544,7 @@ export function useFileDisplay(
|
|
|
533
544
|
});
|
|
534
545
|
}
|
|
535
546
|
}
|
|
536
|
-
setFileUrl
|
|
547
|
+
safeSetState(setFileUrl, url);
|
|
537
548
|
} else {
|
|
538
549
|
// Multiple files mode - generate URLs for all files in batch
|
|
539
550
|
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
@@ -542,9 +553,9 @@ export function useFileDisplay(
|
|
|
542
553
|
userId: organisation_id ? undefined : record_id,
|
|
543
554
|
expiresIn: 3600
|
|
544
555
|
});
|
|
545
|
-
setFileUrls
|
|
546
|
-
setFileReference
|
|
547
|
-
setFileUrl
|
|
556
|
+
safeSetState(setFileUrls, urlMap);
|
|
557
|
+
safeSetState(setFileReference, null);
|
|
558
|
+
safeSetState(setFileUrl, null);
|
|
548
559
|
}
|
|
549
560
|
|
|
550
561
|
// Cache the result
|
|
@@ -588,30 +599,46 @@ export function useFileDisplay(
|
|
|
588
599
|
} catch (err) {
|
|
589
600
|
logger.error('useFileDisplay', 'Error fetching files:', err);
|
|
590
601
|
const error = err instanceof Error ? err : new Error('Unknown error occurred');
|
|
591
|
-
setError
|
|
592
|
-
setFileUrl
|
|
593
|
-
setFileReference
|
|
594
|
-
setFileReferences
|
|
595
|
-
setFileUrls
|
|
596
|
-
setFileCount
|
|
602
|
+
safeSetState(setError, error);
|
|
603
|
+
safeSetState(setFileUrl, null);
|
|
604
|
+
safeSetState(setFileReference, null);
|
|
605
|
+
safeSetState(setFileReferences, []);
|
|
606
|
+
safeSetState(setFileUrls, new Map());
|
|
607
|
+
safeSetState(setFileCount, 0);
|
|
597
608
|
} finally {
|
|
598
|
-
setIsLoading
|
|
609
|
+
safeSetState(setIsLoading, false);
|
|
599
610
|
}
|
|
600
611
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
601
612
|
|
|
602
613
|
// Fetch files when parameters change
|
|
603
614
|
useEffect(() => {
|
|
615
|
+
// Mark as mounted when effect runs
|
|
616
|
+
isMountedRef.current = true;
|
|
617
|
+
|
|
604
618
|
if (table_name && record_id && supabase) {
|
|
605
|
-
fetchFiles
|
|
619
|
+
// Call fetchFiles - it will check isMountedRef before setting state
|
|
620
|
+
fetchFiles().catch((err) => {
|
|
621
|
+
// Only set error if component is still mounted
|
|
622
|
+
if (isMountedRef.current) {
|
|
623
|
+
safeSetState(setError, err instanceof Error ? err : new Error('Unknown error'));
|
|
624
|
+
}
|
|
625
|
+
});
|
|
606
626
|
} else {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
627
|
+
// Only update state if component is still mounted
|
|
628
|
+
safeSetState(setFileUrl, null);
|
|
629
|
+
safeSetState(setFileReference, null);
|
|
630
|
+
safeSetState(setFileReferences, []);
|
|
631
|
+
safeSetState(setFileUrls, new Map());
|
|
632
|
+
safeSetState(setFileCount, 0);
|
|
633
|
+
safeSetState(setIsLoading, false);
|
|
634
|
+
safeSetState(setError, null);
|
|
614
635
|
}
|
|
636
|
+
|
|
637
|
+
// Cleanup: mark as unmounted when dependencies change or component unmounts
|
|
638
|
+
// This prevents state updates from stale requests
|
|
639
|
+
return () => {
|
|
640
|
+
isMountedRef.current = false;
|
|
641
|
+
};
|
|
615
642
|
// fetchFiles is memoized; we only need to re-run when parameters change
|
|
616
643
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
617
644
|
}, [table_name, record_id, organisation_id, supabase]);
|
package/src/index.ts
CHANGED
|
@@ -230,11 +230,10 @@ export { LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner';
|
|
|
230
230
|
export { SessionRestorationLoader } from './components/SessionRestorationLoader/SessionRestorationLoader';
|
|
231
231
|
export type { SessionRestorationLoaderProps } from './components/SessionRestorationLoader/SessionRestorationLoader';
|
|
232
232
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
export { OrganisationSelector } from './components/OrganisationSelector/OrganisationSelector';
|
|
233
|
+
// CONTEXT SELECTION
|
|
234
|
+
// Unified context selector (shows all accessible orgs and events)
|
|
235
|
+
export { ContextSelector } from './components/ContextSelector/ContextSelector';
|
|
236
|
+
export type { ContextSelectorProps } from './components/ContextSelector/ContextSelector';
|
|
238
237
|
export { useOrganisationPermissions } from './hooks/useOrganisationPermissions';
|
|
239
238
|
export { useOrganisationSecurity } from './hooks/useOrganisationSecurity';
|
|
240
239
|
export { createSecureDataAccess } from './utils/security/secureDataAccess';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides authentication service instance to React components.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { createContext, useMemo, useEffect, useState } from 'react';
|
|
11
|
+
import React, { createContext, useMemo, useEffect, useState, useRef } from 'react';
|
|
12
12
|
import { type SupabaseClient } from '@supabase/supabase-js';
|
|
13
13
|
import { AuthService } from '../../services/AuthService';
|
|
14
14
|
import type { SessionRestorationState } from '../../types/auth';
|
|
@@ -47,11 +47,21 @@ export interface AuthServiceProviderProps {
|
|
|
47
47
|
* @returns The auth service provider
|
|
48
48
|
*/
|
|
49
49
|
export function AuthServiceProvider({ children, supabaseClient, appName }: AuthServiceProviderProps) {
|
|
50
|
-
// Create service instance with
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// Create service instance once with useRef to avoid recreation on dependency changes
|
|
51
|
+
const authServiceRef = useRef<AuthService | null>(null);
|
|
52
|
+
|
|
53
|
+
if (!authServiceRef.current) {
|
|
54
|
+
authServiceRef.current = new AuthService(supabaseClient, appName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const authService = authServiceRef.current;
|
|
58
|
+
|
|
59
|
+
// Update dependencies when they change
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
// Note: AuthService doesn't have an updateDependencies method yet,
|
|
62
|
+
// but we should ensure the client is current if it's used directly
|
|
63
|
+
// This is safer than recreation
|
|
64
|
+
}, [authService, supabaseClient, appName]);
|
|
55
65
|
|
|
56
66
|
const [sessionRestoration, setSessionRestoration] = useState<SessionRestorationState>(
|
|
57
67
|
() => authService.getSessionRestorationState()
|
|
@@ -71,7 +81,7 @@ export function AuthServiceProvider({ children, supabaseClient, appName }: AuthS
|
|
|
71
81
|
|
|
72
82
|
// Initialize service on mount
|
|
73
83
|
useEffect(() => {
|
|
74
|
-
authService.initialize().catch(error => {
|
|
84
|
+
authService.initialize().catch((error: unknown) => {
|
|
75
85
|
logger.error('AuthServiceProvider', 'Failed to initialize auth service:', error);
|
|
76
86
|
});
|
|
77
87
|
|
|
@@ -73,7 +73,16 @@ export function EventServiceProvider({
|
|
|
73
73
|
useEffect(() => {
|
|
74
74
|
let isMounted = true;
|
|
75
75
|
|
|
76
|
+
logger.debug('EventServiceProvider', 'useEffect triggered', {
|
|
77
|
+
hasUser: !!user,
|
|
78
|
+
userId: user?.id,
|
|
79
|
+
hasSession: !!session,
|
|
80
|
+
appName,
|
|
81
|
+
isInitializing: initializingRef.current
|
|
82
|
+
});
|
|
83
|
+
|
|
76
84
|
if (initializingRef.current) {
|
|
85
|
+
logger.debug('EventServiceProvider', 'Skipping - already initializing');
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
79
88
|
|
|
@@ -81,17 +90,36 @@ export function EventServiceProvider({
|
|
|
81
90
|
initializingRef.current = true;
|
|
82
91
|
|
|
83
92
|
try {
|
|
93
|
+
logger.debug('EventServiceProvider', 'Updating dependencies and initializing', {
|
|
94
|
+
hasUser: !!user,
|
|
95
|
+
userId: user?.id,
|
|
96
|
+
hasSession: !!session,
|
|
97
|
+
appName,
|
|
98
|
+
hasSelectedOrganisation: !!selectedOrganisation
|
|
99
|
+
});
|
|
100
|
+
|
|
84
101
|
// Update dependencies (now async to handle user change cleanup)
|
|
85
102
|
await eventService.updateDependencies(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId);
|
|
86
103
|
|
|
87
104
|
if (!isMounted) return;
|
|
88
105
|
|
|
89
106
|
// Re-initialize service when dependencies change
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
107
|
+
// Only initialize if we have a user (required for fetching events)
|
|
108
|
+
if (user && session && supabaseClient && appName) {
|
|
109
|
+
logger.debug('EventServiceProvider', 'Initializing event service');
|
|
110
|
+
await eventService.initialize().catch(error => {
|
|
111
|
+
if (isMounted) {
|
|
112
|
+
logger.error('EventServiceProvider', 'Failed to initialize event service:', error);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
logger.debug('EventServiceProvider', 'Skipping initialization - missing required dependencies', {
|
|
117
|
+
hasUser: !!user,
|
|
118
|
+
hasSession: !!session,
|
|
119
|
+
hasSupabaseClient: !!supabaseClient,
|
|
120
|
+
appName
|
|
121
|
+
});
|
|
122
|
+
}
|
|
95
123
|
} finally {
|
|
96
124
|
initializingRef.current = false;
|
|
97
125
|
}
|