@jmruthers/pace-core 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/{DataTable-ZQDRE46Q.js → DataTable-BEMN72L5.js} +2 -2
  2. package/dist/{chunk-5H3C2SWM.js → chunk-4EIBJ6DF.js} +2 -2
  3. package/dist/{chunk-M4RW7PIP.js → chunk-SFGUMWEE.js} +105 -81
  4. package/dist/chunk-SFGUMWEE.js.map +1 -0
  5. package/dist/components.js +2 -2
  6. package/dist/index.js +2 -2
  7. package/dist/utils.js +1 -1
  8. package/docs/api/classes/ErrorBoundary.md +1 -1
  9. package/docs/api/classes/InvalidScopeError.md +1 -1
  10. package/docs/api/classes/MissingUserContextError.md +1 -1
  11. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  12. package/docs/api/classes/PermissionDeniedError.md +1 -1
  13. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  14. package/docs/api/classes/RBACAuditManager.md +1 -1
  15. package/docs/api/classes/RBACCache.md +1 -1
  16. package/docs/api/classes/RBACEngine.md +1 -1
  17. package/docs/api/classes/RBACError.md +1 -1
  18. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  19. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  20. package/docs/api/interfaces/AggregateConfig.md +1 -1
  21. package/docs/api/interfaces/ButtonProps.md +1 -1
  22. package/docs/api/interfaces/CardProps.md +1 -1
  23. package/docs/api/interfaces/ColorPalette.md +1 -1
  24. package/docs/api/interfaces/ColorShade.md +1 -1
  25. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  26. package/docs/api/interfaces/DataTableAction.md +1 -1
  27. package/docs/api/interfaces/DataTableColumn.md +1 -1
  28. package/docs/api/interfaces/DataTableProps.md +34 -34
  29. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  30. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  31. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  32. package/docs/api/interfaces/EventContextType.md +1 -1
  33. package/docs/api/interfaces/EventLogoProps.md +1 -1
  34. package/docs/api/interfaces/EventProviderProps.md +1 -1
  35. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  36. package/docs/api/interfaces/FileUploadProps.md +1 -1
  37. package/docs/api/interfaces/FooterProps.md +1 -1
  38. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  39. package/docs/api/interfaces/InputProps.md +1 -1
  40. package/docs/api/interfaces/LabelProps.md +1 -1
  41. package/docs/api/interfaces/LoginFormProps.md +1 -1
  42. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  43. package/docs/api/interfaces/NavigationContextType.md +1 -1
  44. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  45. package/docs/api/interfaces/NavigationItem.md +1 -1
  46. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  47. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  48. package/docs/api/interfaces/Organisation.md +1 -1
  49. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  50. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  51. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  52. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  53. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  54. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  55. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  56. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  57. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  58. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  59. package/docs/api/interfaces/PaletteData.md +1 -1
  60. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  61. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  62. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  63. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  64. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  65. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  66. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  67. package/docs/api/interfaces/RBACConfig.md +1 -1
  68. package/docs/api/interfaces/RBACContextType.md +1 -1
  69. package/docs/api/interfaces/RBACLogger.md +1 -1
  70. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  71. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  72. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  73. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  74. package/docs/api/interfaces/RouteConfig.md +1 -1
  75. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  76. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  77. package/docs/api/interfaces/StorageConfig.md +1 -1
  78. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  79. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  80. package/docs/api/interfaces/StorageListOptions.md +1 -1
  81. package/docs/api/interfaces/StorageListResult.md +1 -1
  82. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  83. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  84. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  85. package/docs/api/interfaces/StyleImport.md +1 -1
  86. package/docs/api/interfaces/ToastActionElement.md +1 -1
  87. package/docs/api/interfaces/ToastProps.md +1 -1
  88. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  89. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  90. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  91. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  92. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  93. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  94. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  95. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  96. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  97. package/docs/api/interfaces/UserEventAccess.md +1 -1
  98. package/docs/api/interfaces/UserMenuProps.md +1 -1
  99. package/docs/api/interfaces/UserProfile.md +1 -1
  100. package/docs/api/modules.md +3 -3
  101. package/docs/implementation-guides/data-tables.md +20 -0
  102. package/docs/quick-reference.md +9 -0
  103. package/docs/rbac/examples.md +4 -0
  104. package/package.json +1 -1
  105. package/src/__tests__/helpers/test-utils.tsx +147 -1
  106. package/src/components/DataTable/DataTable.tsx +20 -0
  107. package/src/components/DataTable/__tests__/DataTable.hooks.test 2.tsx +191 -0
  108. package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +191 -0
  109. package/src/components/DataTable/components/DataTableCore.tsx +164 -131
  110. package/src/hooks/__tests__/hooks.integration.test.tsx +575 -0
  111. package/src/hooks/__tests__/useApiFetch.unit.test.ts +115 -0
  112. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +133 -0
  113. package/src/hooks/__tests__/useDebounce.unit.test.ts +82 -0
  114. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +293 -0
  115. package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +385 -0
  116. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +286 -0
  117. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +838 -0
  118. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +104 -0
  119. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +633 -0
  120. package/src/hooks/__tests__/useRBAC.unit.test.ts +856 -0
  121. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +537 -0
  122. package/src/hooks/__tests__/useToast.unit.test.tsx +62 -0
  123. package/src/hooks/__tests__/useZodForm.unit.test.tsx +37 -0
  124. package/src/rbac/utils/__tests__/eventContext.test.ts +428 -0
  125. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +428 -0
  126. package/src/utils/__tests__/appConfig.unit.test.ts +55 -0
  127. package/src/utils/__tests__/audit.unit.test.ts +69 -0
  128. package/src/utils/__tests__/auth-utils.unit.test.ts +70 -0
  129. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +317 -0
  130. package/src/utils/__tests__/cn.unit.test.ts +34 -0
  131. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +503 -0
  132. package/src/utils/__tests__/dynamicUtils.unit.test.ts +322 -0
  133. package/src/utils/__tests__/formatDate.unit.test.ts +109 -0
  134. package/src/utils/__tests__/formatting.unit.test.ts +66 -0
  135. package/src/utils/__tests__/index.unit.test.ts +251 -0
  136. package/src/utils/__tests__/lazyLoad.unit.test.tsx +309 -0
  137. package/src/utils/__tests__/organisationContext.unit.test.ts +192 -0
  138. package/src/utils/__tests__/performanceBudgets.unit.test.ts +259 -0
  139. package/src/utils/__tests__/permissionTypes.unit.test.ts +250 -0
  140. package/src/utils/__tests__/permissionUtils.unit.test.ts +362 -0
  141. package/src/utils/__tests__/sanitization.unit.test.ts +346 -0
  142. package/src/utils/__tests__/schemaUtils.unit.test.ts +441 -0
  143. package/src/utils/__tests__/secureDataAccess.unit.test.ts +334 -0
  144. package/src/utils/__tests__/secureErrors.unit.test.ts +377 -0
  145. package/src/utils/__tests__/secureStorage.unit.test.ts +293 -0
  146. package/src/utils/__tests__/security.unit.test.ts +127 -0
  147. package/src/utils/__tests__/securityMonitor.unit.test.ts +280 -0
  148. package/src/utils/__tests__/sessionTracking.unit.test.ts +356 -0
  149. package/src/utils/__tests__/validation.unit.test.ts +84 -0
  150. package/src/utils/__tests__/validationUtils.unit.test.ts +571 -0
  151. package/src/validation/__tests__/common.unit.test.ts +101 -0
  152. package/src/validation/__tests__/csrf.unit.test.ts +302 -0
  153. package/src/validation/__tests__/passwordSchema.unit.test 2.ts +98 -0
  154. package/src/validation/__tests__/passwordSchema.unit.test.ts +98 -0
  155. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +466 -0
  156. package/dist/chunk-M4RW7PIP.js.map +0 -1
  157. /package/dist/{DataTable-ZQDRE46Q.js.map → DataTable-BEMN72L5.js.map} +0 -0
  158. /package/dist/{chunk-5H3C2SWM.js.map → chunk-4EIBJ6DF.js.map} +0 -0
@@ -171,66 +171,15 @@ function DataTableInternal<TData extends DataRecord>({
171
171
  }: DataTableCoreProps<TData>) {
172
172
 
173
173
  // ============================================================================
174
- // MANDATORY RBAC ENFORCEMENT
174
+ // ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
175
175
  // ============================================================================
176
176
 
177
- // MANDATORY: Get authenticated user
177
+ // MANDATORY: Get authenticated user - ALWAYS call this hook
178
178
  const authResult = useUnifiedAuth();
179
179
  const user = authResult.user;
180
180
 
181
- // MANDATORY: Every DataTable must have a user and RBAC config
182
- if (!user) {
183
- throw new Error('DataTable requires authenticated user for RBAC');
184
- }
185
-
186
- if (!rbac?.resource) {
187
- throw new Error('DataTable requires rbac.resource for permission checking');
188
- }
189
-
190
- const scope = {
191
- organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
192
- eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
193
- appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
194
- };
195
-
196
- // MANDATORY: Check all permissions upfront - ALWAYS enforce RBAC
197
- const permissions = {
198
- canRead: useCan(user.id, scope, `read:${rbac.resource}` as any, rbac.pageId),
199
- canCreate: useCan(user.id, scope, `create:${rbac.resource}` as any, rbac.pageId),
200
- canUpdate: useCan(user.id, scope, `update:${rbac.resource}` as any, rbac.pageId),
201
- canDelete: useCan(user.id, scope, `delete:${rbac.resource}` as any, rbac.pageId),
202
- canExport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
203
- canImport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
204
- };
205
-
206
- // MANDATORY: No data access without read permission
207
- if (!permissions.canRead.can) {
208
- return <AccessDeniedPage resource={rbac?.resource || 'test-resource'} operation="read" />;
209
- }
210
-
211
- // MANDATORY: Features are automatically filtered by permissions
212
- const secureFeatures: DataTableFeatureConfig = {
213
- ...features,
214
- creation: features.creation && permissions.canCreate.can,
215
- editing: features.editing && permissions.canUpdate.can,
216
- deletion: features.deletion && permissions.canDelete.can,
217
- deleteSelected: features.deleteSelected && permissions.canDelete.can,
218
- export: features.export && permissions.canExport.can,
219
- import: features.import && permissions.canImport.can,
220
- };
221
-
222
- // MANDATORY: Handlers are automatically secured
223
- const secureHandlers = {
224
- onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
225
- onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
226
- onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
227
- onImport: permissions.canImport.can ? onImport : undefined,
228
- onExport: permissions.canExport.can ? onExport : undefined,
229
- onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
230
- };
231
-
232
181
  // ============================================================================
233
- // TABLE STATE MANAGEMENT (Single State Source)
182
+ // TABLE STATE MANAGEMENT (Single State Source) - ALWAYS call these hooks
234
183
  // ============================================================================
235
184
 
236
185
  const [sorting, setSorting] = useState<SortingState>([]);
@@ -257,7 +206,7 @@ function DataTableInternal<TData extends DataRecord>({
257
206
  const [expanded, setExpanded] = useState<ExpandedState>({});
258
207
 
259
208
  // ============================================================================
260
- // PAGINATION STATE MANAGEMENT
209
+ // PAGINATION STATE MANAGEMENT - ALWAYS call these hooks
261
210
  // ============================================================================
262
211
 
263
212
  const [pagination, setPagination] = useState<PaginationState>({
@@ -266,7 +215,7 @@ function DataTableInternal<TData extends DataRecord>({
266
215
  });
267
216
 
268
217
  // ============================================================================
269
- // EDITING STATE MANAGEMENT
218
+ // EDITING STATE MANAGEMENT - ALWAYS call these hooks
270
219
  // ============================================================================
271
220
 
272
221
  // Use the centralized state management hook
@@ -284,19 +233,19 @@ function DataTableInternal<TData extends DataRecord>({
284
233
  const editingData = tableState.editingData;
285
234
 
286
235
  // ============================================================================
287
- // IMPORT MODAL STATE MANAGEMENT
236
+ // IMPORT MODAL STATE MANAGEMENT - ALWAYS call these hooks
288
237
  // ============================================================================
289
238
 
290
239
  const [showImportModal, setShowImportModal] = useState(false);
291
240
 
292
241
  // ============================================================================
293
- // FILTER ROW VISIBILITY STATE MANAGEMENT
242
+ // FILTER ROW VISIBILITY STATE MANAGEMENT - ALWAYS call these hooks
294
243
  // ============================================================================
295
244
 
296
245
  const [showFilterRow, setShowFilterRow] = useState(false);
297
246
 
298
247
  // ============================================================================
299
- // PERFORMANCE HOOK
248
+ // PERFORMANCE HOOK - ALWAYS call this hook
300
249
  // ============================================================================
301
250
 
302
251
  const performanceHook = useDataTablePerformance({
@@ -322,7 +271,7 @@ function DataTableInternal<TData extends DataRecord>({
322
271
  } = performanceHook;
323
272
 
324
273
  // ============================================================================
325
- // HIERARCHICAL DATA VALIDATION AND PROCESSING
274
+ // HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
326
275
  // ============================================================================
327
276
 
328
277
  const hierarchicalValidation = useMemo(() => {
@@ -340,7 +289,7 @@ function DataTableInternal<TData extends DataRecord>({
340
289
  }, [features.hierarchical, hierarchical?.enabled, hierarchicalValidation.isValid, data]);
341
290
 
342
291
  // ============================================================================
343
- // HIERARCHICAL STATE MANAGEMENT
292
+ // HIERARCHICAL STATE MANAGEMENT - ALWAYS call these hooks
344
293
  // ============================================================================
345
294
 
346
295
  const hierarchicalState = useHierarchicalState(
@@ -349,7 +298,7 @@ function DataTableInternal<TData extends DataRecord>({
349
298
  );
350
299
 
351
300
  // ============================================================================
352
- // COLUMN ORDER PERSISTENCE
301
+ // COLUMN ORDER PERSISTENCE - ALWAYS call these hooks
353
302
  // ============================================================================
354
303
 
355
304
  const {
@@ -363,7 +312,45 @@ function DataTableInternal<TData extends DataRecord>({
363
312
  });
364
313
 
365
314
  // ============================================================================
366
- // CONFIGURATION RESOLUTION
315
+ // RBAC PERMISSION CHECKS - ALWAYS call these hooks
316
+ // ============================================================================
317
+
318
+ // MANDATORY: Check all permissions upfront - ALWAYS enforce RBAC
319
+ const permissions = {
320
+ canRead: useCan(user?.id || '', {
321
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
322
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
323
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
324
+ }, `read:${rbac.resource}` as any, rbac.pageId),
325
+ canCreate: useCan(user?.id || '', {
326
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
327
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
328
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
329
+ }, `create:${rbac.resource}` as any, rbac.pageId),
330
+ canUpdate: useCan(user?.id || '', {
331
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
332
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
333
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
334
+ }, `update:${rbac.resource}` as any, rbac.pageId),
335
+ canDelete: useCan(user?.id || '', {
336
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
337
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
338
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
339
+ }, `delete:${rbac.resource}` as any, rbac.pageId),
340
+ canExport: useCan(user?.id || '', {
341
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
342
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
343
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
344
+ }, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
345
+ canImport: useCan(user?.id || '', {
346
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
347
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
348
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
349
+ }, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
350
+ };
351
+
352
+ // ============================================================================
353
+ // CONFIGURATION RESOLUTION - ALWAYS call these hooks
367
354
  // ============================================================================
368
355
 
369
356
  const finalPaginationMode = paginationMode || detectedMode;
@@ -393,7 +380,7 @@ function DataTableInternal<TData extends DataRecord>({
393
380
  const isLoading = externalIsLoading || performanceLoading;
394
381
 
395
382
  // ============================================================================
396
- // DATA PROCESSING
383
+ // DATA PROCESSING - ALWAYS call these hooks
397
384
  // ============================================================================
398
385
 
399
386
  // Data comparison utility to prevent unnecessary re-renders
@@ -459,7 +446,7 @@ function DataTableInternal<TData extends DataRecord>({
459
446
  }, [finalPaginationMode, serverData?.totalCount, data?.length]);
460
447
 
461
448
  // ============================================================================
462
- // HIERARCHICAL SORTING
449
+ // HIERARCHICAL SORTING - ALWAYS call these hooks
463
450
  // ============================================================================
464
451
 
465
452
  const sortedTableData = useMemo(() => {
@@ -479,9 +466,101 @@ function DataTableInternal<TData extends DataRecord>({
479
466
  const finalDataCount = finalPaginationMode === 'server' ? (serverData?.totalCount || 0) : finalTableData?.length || 0;
480
467
 
481
468
  // ============================================================================
482
- // ACTIONS PROCESSING
469
+ // ACTIONS PROCESSING - ALWAYS call these hooks
470
+ // ============================================================================
471
+
483
472
  // ============================================================================
473
+ // COLUMN PROCESSING - ALWAYS call these hooks
474
+ // ============================================================================
475
+
476
+ // ============================================================================
477
+ // TABLE CONFIGURATION - ALWAYS call these hooks
478
+ // ============================================================================
479
+
480
+ // ============================================================================
481
+ // SEARCH HANDLERS - ALWAYS call these hooks
482
+ // ============================================================================
483
+
484
+ const handleSearch = useCallback((value: string) => {
485
+ setSearchQuery(value);
486
+
487
+ if (features.pagination) {
488
+ setPagination(prev => ({ ...prev, pageIndex: 0 }));
489
+ }
490
+ }, [setSearchQuery, features.pagination]);
491
+
492
+ // ============================================================================
493
+ // SERVER-SIDE DATA FETCHING - ALWAYS call these hooks
494
+ // ============================================================================
495
+
496
+ const handleServerSideChange = useCallback(async () => {
497
+ if (finalPaginationMode !== 'server' || !serverSide) return;
498
+
499
+ const params: ServerSideParams = {
500
+ pageIndex: pagination.pageIndex,
501
+ pageSize: pagination.pageSize,
502
+ sorting,
503
+ columnFilters,
504
+ globalFilter: searchQuery,
505
+ grouping,
506
+ };
507
+
508
+ await fetchServerData(params);
509
+ }, [
510
+ finalPaginationMode,
511
+ serverSide,
512
+ pagination,
513
+ sorting,
514
+ columnFilters,
515
+ searchQuery,
516
+ grouping,
517
+ fetchServerData,
518
+ ]);
519
+
520
+ // ============================================================================
521
+ // EFFECTS - ALWAYS call these hooks
522
+ // ============================================================================
523
+
524
+ useEffect(() => {
525
+ if ((tableData?.length || 0) > 0 || finalPaginationMode === 'server') {
526
+ handleServerSideChange();
527
+ }
528
+ }, [handleServerSideChange, tableData?.length, finalPaginationMode]);
484
529
 
530
+ useEffect(() => {
531
+ return () => {
532
+ if (typeof cleanup === 'function') {
533
+ cleanup();
534
+ }
535
+ };
536
+ }, [cleanup]);
537
+
538
+ // ============================================================================
539
+ // RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
540
+ // ============================================================================
541
+
542
+ // MANDATORY: Features are automatically filtered by permissions
543
+ const secureFeatures: DataTableFeatureConfig = useMemo(() => ({
544
+ ...features,
545
+ creation: features.creation && permissions.canCreate.can,
546
+ editing: features.editing && permissions.canUpdate.can,
547
+ deletion: features.deletion && permissions.canDelete.can,
548
+ deleteSelected: features.deleteSelected && permissions.canDelete.can,
549
+ export: features.export && permissions.canExport.can,
550
+ import: features.import && permissions.canImport.can,
551
+ }), [features, permissions.canCreate.can, permissions.canUpdate.can, permissions.canDelete.can, permissions.canExport.can, permissions.canImport.can]);
552
+
553
+ // MANDATORY: Handlers are automatically secured
554
+ const secureHandlers = useMemo(() => ({
555
+ onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
556
+ onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
557
+ onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
558
+ onImport: permissions.canImport.can ? onImport : undefined,
559
+ onExport: permissions.canExport.can ? onExport : undefined,
560
+ onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
561
+ }), [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, onEditRow, onDeleteRow, onCreateRow, onImport, onExport, onDeleteSelected]);
562
+
563
+ // MANDATORY: Process actions with RBAC checks
485
564
  const effectiveActions = useMemo(() => {
486
565
  // Create a new array to avoid mutating the original
487
566
  const result = [...actions];
@@ -525,10 +604,7 @@ function DataTableInternal<TData extends DataRecord>({
525
604
  return result;
526
605
  }, [actions, secureFeatures, permissions, secureHandlers, getRowId, tableActions]);
527
606
 
528
- // ============================================================================
529
- // COLUMN PROCESSING
530
- // ============================================================================
531
-
607
+ // MANDATORY: Process columns with actions
532
608
  const enhancedColumns = useMemo(() => {
533
609
  // Create enhanced base columns
534
610
  const baseColumns: ColumnDef<TData>[] = [...columns].map(column => ({
@@ -629,16 +705,7 @@ function DataTableInternal<TData extends DataRecord>({
629
705
  return finalColumns;
630
706
  }, [columns, features, effectiveActions, columnOrder]);
631
707
 
632
- // ============================================================================
633
- // COLUMN WIDTHS
634
- // ============================================================================
635
-
636
-
637
- // ============================================================================
638
- // TABLE CONFIGURATION
639
- // ============================================================================
640
-
641
- // Memoize table configuration to prevent unnecessary re-creation
708
+ // MANDATORY: Memoize table configuration to prevent unnecessary re-creation
642
709
  const tableConfig = useMemo(() => ({
643
710
  data: finalTableData as TData[],
644
711
  columns: enhancedColumns,
@@ -706,62 +773,28 @@ function DataTableInternal<TData extends DataRecord>({
706
773
  const table = useReactTable(tableConfig);
707
774
 
708
775
  // ============================================================================
709
- // SEARCH HANDLERS
776
+ // RBAC VALIDATION AND EARLY RETURNS - AFTER ALL HOOKS
710
777
  // ============================================================================
711
778
 
712
- const handleSearch = useCallback((value: string) => {
713
- setSearchQuery(value);
714
-
715
- if (features.pagination) {
716
- setPagination(prev => ({ ...prev, pageIndex: 0 }));
717
- }
718
- }, [setSearchQuery, features.pagination]);
719
-
720
- // ============================================================================
721
- // SERVER-SIDE DATA FETCHING
722
- // ============================================================================
779
+ // MANDATORY: Every DataTable must have a user and RBAC config
780
+ if (!user) {
781
+ throw new Error('DataTable requires authenticated user for RBAC');
782
+ }
723
783
 
724
- const handleServerSideChange = useCallback(async () => {
725
- if (finalPaginationMode !== 'server' || !serverSide) return;
726
-
727
- const params: ServerSideParams = {
728
- pageIndex: pagination.pageIndex,
729
- pageSize: pagination.pageSize,
730
- sorting,
731
- columnFilters,
732
- globalFilter: searchQuery,
733
- grouping,
734
- };
735
-
736
- await fetchServerData(params);
737
- }, [
738
- finalPaginationMode,
739
- serverSide,
740
- pagination,
741
- sorting,
742
- columnFilters,
743
- searchQuery,
744
- grouping,
745
- fetchServerData,
746
- ]);
784
+ if (!rbac?.resource) {
785
+ throw new Error('DataTable requires rbac.resource for permission checking');
786
+ }
747
787
 
748
- // ============================================================================
749
- // EFFECTS
750
- // ============================================================================
751
-
752
- useEffect(() => {
753
- if ((tableData?.length || 0) > 0 || finalPaginationMode === 'server') {
754
- handleServerSideChange();
755
- }
756
- }, [handleServerSideChange, tableData?.length, finalPaginationMode]);
788
+ const scope = {
789
+ organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
790
+ eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
791
+ appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
792
+ };
757
793
 
758
- useEffect(() => {
759
- return () => {
760
- if (typeof cleanup === 'function') {
761
- cleanup();
762
- }
763
- };
764
- }, [cleanup]);
794
+ // MANDATORY: No data access without read permission
795
+ if (!permissions.canRead.can) {
796
+ return <AccessDeniedPage resource={rbac?.resource || 'test-resource'} operation="read" />;
797
+ }
765
798
 
766
799
  // ============================================================================
767
800
  // RENDER