@jmruthers/pace-core 0.5.126 → 0.5.128

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 (180) hide show
  1. package/dist/{DataTable-6FN7XDXA.js → DataTable-3Z5HLOWF.js} +6 -6
  2. package/dist/{PublicLoadingSpinner-CaoRbHvJ.d.ts → PublicLoadingSpinner-CUAnTvcg.d.ts} +41 -21
  3. package/dist/{UnifiedAuthProvider-6C47WIML.js → UnifiedAuthProvider-CQDZRJIS.js} +3 -3
  4. package/dist/{chunk-QXGLU2O5.js → chunk-27MGXDD6.js} +282 -147
  5. package/dist/chunk-27MGXDD6.js.map +1 -0
  6. package/dist/{chunk-ZBLK676C.js → chunk-3CG5L6RN.js} +1 -19
  7. package/dist/chunk-3CG5L6RN.js.map +1 -0
  8. package/dist/{chunk-35ZDPMBM.js → chunk-BYXRHAIF.js} +3 -3
  9. package/dist/{chunk-IJOZZOGT.js → chunk-CQZU6TFE.js} +5 -5
  10. package/dist/{chunk-C43QIDN3.js → chunk-CTJRBUX2.js} +2 -2
  11. package/dist/{chunk-R4CRQUJJ.js → chunk-ENE3AB75.js} +463 -453
  12. package/dist/chunk-ENE3AB75.js.map +1 -0
  13. package/dist/{chunk-ESJTIADP.js → chunk-F64FFPOZ.js} +5 -15
  14. package/dist/{chunk-ESJTIADP.js.map → chunk-F64FFPOZ.js.map} +1 -1
  15. package/dist/{chunk-4MXVZVNS.js → chunk-TGIY2AR2.js} +2 -2
  16. package/dist/{chunk-XN6GWKMV.js → chunk-VZ5OR6HD.js} +161 -14
  17. package/dist/chunk-VZ5OR6HD.js.map +1 -0
  18. package/dist/{chunk-QWNJCQXZ.js → chunk-ZV77RZMU.js} +2 -2
  19. package/dist/{chunk-NZGLXZGP.js → chunk-ZYZCRSBD.js} +3 -54
  20. package/dist/chunk-ZYZCRSBD.js.map +1 -0
  21. package/dist/components.d.ts +1 -1
  22. package/dist/components.js +9 -9
  23. package/dist/hooks.js +7 -7
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +12 -12
  26. package/dist/providers.js +2 -2
  27. package/dist/rbac/index.js +7 -7
  28. package/dist/utils.d.ts +1 -1
  29. package/dist/utils.js +1 -1
  30. package/docs/api/classes/ColumnFactory.md +1 -1
  31. package/docs/api/classes/ErrorBoundary.md +1 -1
  32. package/docs/api/classes/InvalidScopeError.md +1 -1
  33. package/docs/api/classes/MissingUserContextError.md +1 -1
  34. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  35. package/docs/api/classes/PermissionDeniedError.md +1 -1
  36. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  37. package/docs/api/classes/RBACAuditManager.md +1 -1
  38. package/docs/api/classes/RBACCache.md +1 -1
  39. package/docs/api/classes/RBACEngine.md +1 -1
  40. package/docs/api/classes/RBACError.md +1 -1
  41. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  42. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  43. package/docs/api/classes/StorageUtils.md +1 -1
  44. package/docs/api/enums/FileCategory.md +1 -1
  45. package/docs/api/interfaces/AggregateConfig.md +1 -1
  46. package/docs/api/interfaces/ButtonProps.md +1 -1
  47. package/docs/api/interfaces/CardProps.md +1 -1
  48. package/docs/api/interfaces/ColorPalette.md +1 -1
  49. package/docs/api/interfaces/ColorShade.md +1 -1
  50. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  51. package/docs/api/interfaces/DataRecord.md +1 -1
  52. package/docs/api/interfaces/DataTableAction.md +1 -1
  53. package/docs/api/interfaces/DataTableColumn.md +1 -1
  54. package/docs/api/interfaces/DataTableProps.md +1 -1
  55. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  56. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  57. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  58. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  59. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  60. package/docs/api/interfaces/FileMetadata.md +1 -1
  61. package/docs/api/interfaces/FileReference.md +1 -1
  62. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  63. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  64. package/docs/api/interfaces/FileUploadProps.md +1 -1
  65. package/docs/api/interfaces/FooterProps.md +1 -1
  66. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  67. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  68. package/docs/api/interfaces/InputProps.md +1 -1
  69. package/docs/api/interfaces/LabelProps.md +1 -1
  70. package/docs/api/interfaces/LoginFormProps.md +1 -1
  71. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  72. package/docs/api/interfaces/NavigationContextType.md +1 -1
  73. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  74. package/docs/api/interfaces/NavigationItem.md +1 -1
  75. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  76. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  77. package/docs/api/interfaces/Organisation.md +1 -1
  78. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  79. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  80. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  81. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  82. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  83. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  84. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  85. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  86. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  87. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  88. package/docs/api/interfaces/PaletteData.md +1 -1
  89. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  90. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  92. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  93. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -62
  96. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  97. package/docs/api/interfaces/RBACConfig.md +1 -1
  98. package/docs/api/interfaces/RBACLogger.md +1 -1
  99. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  100. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  101. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  102. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  103. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  104. package/docs/api/interfaces/RouteConfig.md +1 -1
  105. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  106. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  107. package/docs/api/interfaces/StorageConfig.md +1 -1
  108. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  109. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  110. package/docs/api/interfaces/StorageListOptions.md +1 -1
  111. package/docs/api/interfaces/StorageListResult.md +1 -1
  112. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  113. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  114. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  115. package/docs/api/interfaces/StyleImport.md +1 -1
  116. package/docs/api/interfaces/SwitchProps.md +1 -1
  117. package/docs/api/interfaces/ToastActionElement.md +1 -1
  118. package/docs/api/interfaces/ToastProps.md +1 -1
  119. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  120. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  121. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  122. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  124. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  125. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  126. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  127. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  128. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  129. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  130. package/docs/api/interfaces/UserEventAccess.md +1 -1
  131. package/docs/api/interfaces/UserMenuProps.md +1 -1
  132. package/docs/api/interfaces/UserProfile.md +1 -1
  133. package/docs/api/modules.md +53 -28
  134. package/docs/api-reference/components.md +24 -0
  135. package/docs/api-reference/types.md +28 -0
  136. package/docs/architecture/rpc-function-standards.md +39 -5
  137. package/docs/implementation-guides/data-tables.md +55 -10
  138. package/docs/implementation-guides/permission-enforcement.md +4 -0
  139. package/docs/rbac/super-admin-guide.md +43 -5
  140. package/package.json +1 -1
  141. package/src/components/Button/Button.tsx +1 -1
  142. package/src/components/DataTable/__tests__/DataTable.export.test.tsx +702 -0
  143. package/src/components/DataTable/components/DataTableCore.tsx +55 -36
  144. package/src/components/DataTable/components/ImportModal.tsx +134 -2
  145. package/src/components/DataTable/index.ts +3 -1
  146. package/src/components/DataTable/types.ts +68 -0
  147. package/src/components/Dialog/Dialog.tsx +0 -13
  148. package/src/components/FileDisplay/FileDisplay.tsx +76 -0
  149. package/src/components/Header/Header.tsx +5 -0
  150. package/src/components/PaceAppLayout/PaceAppLayout.tsx +72 -50
  151. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +81 -1
  152. package/src/components/PublicLayout/PublicPageFooter.tsx +1 -1
  153. package/src/components/PublicLayout/PublicPageHeader.tsx +69 -128
  154. package/src/components/PublicLayout/PublicPageLayout.tsx +4 -4
  155. package/src/components/PublicLayout/PublicPageProvider.tsx +12 -3
  156. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
  157. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +3 -18
  158. package/src/hooks/__tests__/useAppConfig.unit.test.ts +3 -1
  159. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +11 -5
  160. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +8 -7
  161. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +41 -46
  162. package/src/hooks/public/usePublicFileDisplay.ts +176 -7
  163. package/src/hooks/public/usePublicRouteParams.ts +0 -12
  164. package/src/hooks/useAppConfig.ts +15 -6
  165. package/src/hooks/usePermissionCache.test.ts +12 -4
  166. package/src/hooks/usePermissionCache.ts +3 -19
  167. package/src/hooks/useSecureDataAccess.ts +0 -63
  168. package/src/services/EventService.ts +0 -19
  169. package/dist/chunk-NZGLXZGP.js.map +0 -1
  170. package/dist/chunk-QXGLU2O5.js.map +0 -1
  171. package/dist/chunk-R4CRQUJJ.js.map +0 -1
  172. package/dist/chunk-XN6GWKMV.js.map +0 -1
  173. package/dist/chunk-ZBLK676C.js.map +0 -1
  174. /package/dist/{DataTable-6FN7XDXA.js.map → DataTable-3Z5HLOWF.js.map} +0 -0
  175. /package/dist/{UnifiedAuthProvider-6C47WIML.js.map → UnifiedAuthProvider-CQDZRJIS.js.map} +0 -0
  176. /package/dist/{chunk-35ZDPMBM.js.map → chunk-BYXRHAIF.js.map} +0 -0
  177. /package/dist/{chunk-IJOZZOGT.js.map → chunk-CQZU6TFE.js.map} +0 -0
  178. /package/dist/{chunk-C43QIDN3.js.map → chunk-CTJRBUX2.js.map} +0 -0
  179. /package/dist/{chunk-4MXVZVNS.js.map → chunk-TGIY2AR2.js.map} +0 -0
  180. /package/dist/{chunk-QWNJCQXZ.js.map → chunk-ZV77RZMU.js.map} +0 -0
@@ -20,9 +20,11 @@
20
20
  * ```tsx
21
21
  * import { PublicPageProvider } from '@jmruthers/pace-core';
22
22
  *
23
+ * const APP_NAME = 'CORE';
24
+ *
23
25
  * function PublicApp() {
24
26
  * return (
25
- * <PublicPageProvider>
27
+ * <PublicPageProvider appName={APP_NAME}>
26
28
  * <Routes>
27
29
  * <Route path="/events/:eventCode/recipe-grid-report" element={<PublicRecipePage />} />
28
30
  * </Routes>
@@ -40,6 +42,7 @@ import { PublicErrorBoundary } from './PublicErrorBoundary';
40
42
  interface PublicPageContextType {
41
43
  isPublicPage: true;
42
44
  supabase: ReturnType<typeof createClient<Database>> | null;
45
+ appName: string | null;
43
46
  environment: {
44
47
  supabaseUrl: string | null;
45
48
  supabaseKey: string | null;
@@ -50,6 +53,8 @@ export const PublicPageContext = createContext<PublicPageContextType | undefined
50
53
 
51
54
  export interface PublicPageProviderProps {
52
55
  children: ReactNode;
56
+ /** Application name for logo display and branding. Should match the APP_NAME constant used in App.tsx */
57
+ appName?: string;
53
58
  }
54
59
 
55
60
  /**
@@ -60,8 +65,9 @@ export interface PublicPageProviderProps {
60
65
  * - Provides environment variables for public data access
61
66
  * - Includes error boundary for graceful error handling
62
67
  * - Is completely separate from the main app context
68
+ * - Provides appName for consistent logo display
63
69
  */
64
- export function PublicPageProvider({ children }: PublicPageProviderProps) {
70
+ export function PublicPageProvider({ children, appName }: PublicPageProviderProps) {
65
71
  // Get environment variables for public data access
66
72
  // Handle both Vite (import.meta.env) and Node.js (process.env) environments
67
73
  const getEnvVar = (key: string): string | undefined => {
@@ -90,12 +96,15 @@ export function PublicPageProvider({ children }: PublicPageProviderProps) {
90
96
  console.warn('[PublicPageProvider] Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment.');
91
97
  return null;
92
98
  }
93
- return createClient<Database>(supabaseUrl, supabaseKey);
99
+ const client = createClient<Database>(supabaseUrl, supabaseKey);
100
+ console.log('[PublicPageProvider] Supabase client created successfully for public pages');
101
+ return client;
94
102
  }, [supabaseUrl, supabaseKey]);
95
103
 
96
104
  const contextValue: PublicPageContextType = {
97
105
  isPublicPage: true,
98
106
  supabase,
107
+ appName: appName || null,
99
108
  environment: {
100
109
  supabaseUrl,
101
110
  supabaseKey
@@ -188,7 +188,7 @@ describe('[component] PublicPageFooter', () => {
188
188
  const { container } = render(<PublicPageFooter event={mockEvent} />);
189
189
 
190
190
  const footer = container.firstChild as HTMLElement;
191
- expect(footer).toHaveClass('mt-8', 'py-6', 'flex', 'justify-center', 'border-t', 'border-border', 'bg-main-100');
191
+ expect(footer).toHaveClass('mt-8', 'py-6', 'flex', 'justify-center');
192
192
  });
193
193
 
194
194
  it('has proper section structure', () => {
@@ -155,21 +155,6 @@ describe('[component] PublicPageHeader', () => {
155
155
  expect(screen.queryByAltText('Test App')).not.toBeInTheDocument();
156
156
  });
157
157
 
158
- it('shows custom app logo when provided', () => {
159
- const customAppLogo = <div data-testid="custom-app-logo">Custom App Logo</div>;
160
-
161
- render(
162
- <PublicPageHeader
163
- event={mockEvent}
164
- eventCode="EVENT123"
165
- customAppLogo={customAppLogo}
166
- />
167
- );
168
-
169
- expect(screen.getByTestId('custom-app-logo')).toBeInTheDocument();
170
- expect(screen.queryByAltText('Test App')).not.toBeInTheDocument();
171
- });
172
-
173
158
  it('shows event logo by default', () => {
174
159
  render(
175
160
  <PublicPageHeader event={mockEvent} eventCode="EVENT123" />
@@ -283,7 +268,7 @@ describe('[component] PublicPageHeader', () => {
283
268
  );
284
269
 
285
270
  const header = container.firstChild as HTMLElement;
286
- expect(header).toHaveClass('bg-background', 'border-b', 'border-sec-200');
271
+ expect(header).toHaveClass('w-full', 'grid', 'grid-cols-[auto_1fr_auto]', 'place-items-center', 'gap-2');
287
272
  });
288
273
 
289
274
  it('has proper container structure', () => {
@@ -291,9 +276,9 @@ describe('[component] PublicPageHeader', () => {
291
276
  <PublicPageHeader event={mockEvent} eventCode="EVENT123" />
292
277
  );
293
278
 
294
- // The header itself has the container classes, not a nested div
279
+ // The header itself has the container classes with responsive padding
295
280
  const header = container.firstChild as HTMLElement;
296
- expect(header).toHaveClass('px-4', 'w-[min(var(--app-width),100%)]', 'mx-auto');
281
+ expect(header).toHaveClass('w-full', 'px-[max(0rem,calc((100vw-var(--app-width))/2-0.5rem))]');
297
282
  });
298
283
 
299
284
  it('has proper logo row structure', () => {
@@ -13,8 +13,10 @@ vi.mock('../../providers/UnifiedAuthProvider', () => {
13
13
  };
14
14
  });
15
15
 
16
- vi.mock('../../components/PublicLayout/PublicPageProvider', () => {
16
+ vi.mock('../../components/PublicLayout/PublicPageProvider', async (importOriginal) => {
17
+ const actual = await importOriginal<typeof import('../../components/PublicLayout/PublicPageProvider')>();
17
18
  return {
19
+ ...actual,
18
20
  useIsPublicPage: vi.fn(),
19
21
  };
20
22
  });
@@ -570,11 +570,17 @@ describe('usePermissionCache', () => {
570
570
  wrapper: TestWrapper
571
571
  });
572
572
 
573
- await result.current.checkPermission('read', 'dashboard');
574
-
575
- expect(consoleSpy).toHaveBeenCalledWith(
576
- expect.stringContaining('[PermissionCache] read:dashboard = true (fresh)')
577
- );
573
+ // Verify the actual behavior: permission check works correctly
574
+ const hasPermission = await result.current.checkPermission('read', 'dashboard');
575
+
576
+ // Test the actual functionality: permission is checked and result is returned
577
+ expect(hasPermission).toBe(true);
578
+ // Verify caching is working: second call should use cache
579
+ const hasPermissionCached = await result.current.checkPermission('read', 'dashboard');
580
+ expect(hasPermissionCached).toBe(true);
581
+ // Verify debug info shows cache hits
582
+ const debugInfo = result.current.getDebugInfo();
583
+ expect(debugInfo.cacheHits).toBeGreaterThan(0);
578
584
 
579
585
  consoleSpy.mockRestore();
580
586
  });
@@ -304,14 +304,15 @@ describe('usePublicRouteParams', () => {
304
304
  refetch: vi.fn()
305
305
  });
306
306
 
307
- renderHook(() => usePublicRouteParams({ fetchEventData: true }));
307
+ // Verify the actual behavior: route params are extracted correctly
308
+ const { result } = renderHook(() => usePublicRouteParams({ fetchEventData: true }));
308
309
 
309
- expect(consoleSpy).toHaveBeenCalledWith('[usePublicRouteParams] Public route accessed:', {
310
- eventCode: 'test-event',
311
- eventId: '123',
312
- eventName: 'Test Event',
313
- path: '/public/event/test-event'
314
- });
310
+ // Test the actual functionality: event data is fetched and returned
311
+ expect(result.current.eventCode).toBe('test-event');
312
+ expect(result.current.eventId).toBe('123');
313
+ expect(result.current.event).toEqual(mockEvent);
314
+ expect(result.current.isLoading).toBe(false);
315
+ expect(result.current.error).toBeNull();
315
316
 
316
317
  consoleSpy.mockRestore();
317
318
  });
@@ -505,17 +505,15 @@ describe('useSecureDataAccess', () => {
505
505
 
506
506
  const { result } = renderHook(() => useSecureDataAccess());
507
507
 
508
- await result.current.secureQuery('test_table', '*', { status: 'active' });
508
+ // Verify the actual behavior: secure query works correctly
509
+ const queryResult = await result.current.secureQuery('test_table', '*', { status: 'active' });
509
510
 
510
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Executing secure query:', {
511
- table: 'test_table',
512
- organisationId: 'org-123',
513
- filters: { status: 'active' }
514
- });
515
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Query successful:', {
516
- table: 'test_table',
517
- resultCount: 0
518
- });
511
+ // Test the actual functionality: query executes and returns results
512
+ expect(queryResult).toEqual([]);
513
+ // Verify security: organisation context is used
514
+ expect(result.current.getCurrentOrganisationId()).toBe('org-123');
515
+ // Verify the query was executed
516
+ expect(mockSupabase.from).toHaveBeenCalledWith('test_table');
519
517
 
520
518
  consoleSpy.mockRestore();
521
519
  });
@@ -535,16 +533,14 @@ describe('useSecureDataAccess', () => {
535
533
 
536
534
  const { result } = renderHook(() => useSecureDataAccess());
537
535
 
538
- await result.current.secureInsert('test_table', { name: 'Test' });
536
+ // Verify the actual behavior: secure insert works correctly
537
+ const insertResult = await result.current.secureInsert('test_table', { name: 'Test' });
539
538
 
540
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Executing secure insert:', {
541
- table: 'test_table',
542
- organisationId: 'org-123'
543
- });
544
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Insert successful:', {
545
- table: 'test_table',
546
- id: 1
547
- });
539
+ // Test the actual functionality: insert executes and returns result
540
+ expect(insertResult).toEqual({ id: 1 });
541
+ // Verify security: organisation_id is automatically added
542
+ expect(mockSupabase.from).toHaveBeenCalledWith('test_table');
543
+ // Verify the operation completed successfully (organisation_id is added internally)
548
544
 
549
545
  consoleSpy.mockRestore();
550
546
  });
@@ -556,7 +552,7 @@ describe('useSecureDataAccess', () => {
556
552
  update: vi.fn().mockReturnValue({
557
553
  eq: vi.fn().mockReturnValue({
558
554
  eq: vi.fn().mockReturnValue({
559
- select: vi.fn().mockResolvedValue({ data: [{ id: 1 }], error: null })
555
+ select: vi.fn().mockResolvedValue({ data: [{ id: 1, name: 'Updated' }], error: null })
560
556
  })
561
557
  })
562
558
  })
@@ -566,17 +562,14 @@ describe('useSecureDataAccess', () => {
566
562
 
567
563
  const { result } = renderHook(() => useSecureDataAccess());
568
564
 
569
- await result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 });
565
+ // Verify the actual behavior: secure update works correctly
566
+ const updateResult = await result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 });
570
567
 
571
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Executing secure update:', {
572
- table: 'event',
573
- organisationId: 'org-123',
574
- filters: { id: 1 }
575
- });
576
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Update successful:', {
577
- table: 'event',
578
- updatedCount: 1
579
- });
568
+ // Test the actual functionality: update executes and returns results
569
+ expect(updateResult).toEqual([{ id: 1, name: 'Updated' }]);
570
+ // Verify security: organisation filter is applied
571
+ expect(mockSupabase.from).toHaveBeenCalledWith('event');
572
+ // Verify the operation completed successfully (organisation filter is applied internally)
580
573
 
581
574
  consoleSpy.mockRestore();
582
575
  });
@@ -596,16 +589,13 @@ describe('useSecureDataAccess', () => {
596
589
 
597
590
  const { result } = renderHook(() => useSecureDataAccess());
598
591
 
592
+ // Verify the actual behavior: secure delete works correctly
599
593
  await result.current.secureDelete('test_table', { id: 1 });
600
594
 
601
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Executing secure delete:', {
602
- table: 'test_table',
603
- organisationId: 'org-123',
604
- filters: { id: 1 }
605
- });
606
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Delete successful:', {
607
- table: 'test_table'
608
- });
595
+ // Test the actual functionality: delete executes without error
596
+ expect(mockSupabase.from).toHaveBeenCalledWith('test_table');
597
+ // Verify security: organisation filter is applied (operation completed without error)
598
+ // The delete operation succeeded, which means organisation context was validated
609
599
 
610
600
  consoleSpy.mockRestore();
611
601
  });
@@ -619,15 +609,20 @@ describe('useSecureDataAccess', () => {
619
609
 
620
610
  const { result } = renderHook(() => useSecureDataAccess());
621
611
 
622
- await result.current.secureRpc('test_function', { param1: 'value1' });
612
+ // Verify the actual behavior: secure RPC works correctly
613
+ const rpcResult = await result.current.secureRpc('test_function', { param1: 'value1' });
623
614
 
624
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] Executing secure RPC:', {
625
- functionName: 'test_function',
626
- organisationId: 'org-123'
627
- });
628
- expect(consoleSpy).toHaveBeenCalledWith('[useSecureDataAccess] RPC successful:', {
629
- functionName: 'test_function'
630
- });
615
+ // Test the actual functionality: RPC executes and returns result
616
+ expect(rpcResult).toEqual({ result: 'success' });
617
+ // Verify security: organisation_id is included in RPC params
618
+ expect(mockSupabase.rpc).toHaveBeenCalledWith(
619
+ 'test_function',
620
+ expect.objectContaining({
621
+ organisation_id: 'org-123'
622
+ })
623
+ );
624
+ // Verify RPC was called correctly
625
+ expect(mockSupabase.rpc).toHaveBeenCalledTimes(1);
631
626
 
632
627
  consoleSpy.mockRestore();
633
628
  });
@@ -151,21 +151,70 @@ export function usePublicFileDisplay(
151
151
  // Category is stored in file_metadata JSONB field, not a direct column
152
152
  if (category) {
153
153
  // Single file mode - use RPC to get files by category
154
+ const rpcParams = {
155
+ p_table_name: table_name,
156
+ p_record_id: record_id,
157
+ p_category: category,
158
+ p_organisation_id: organisation_id
159
+ };
160
+
161
+ // Step 1: Log RPC call parameters with full context
162
+ console.log('[usePublicFileDisplay] RPC Call Parameters:', {
163
+ function: 'data_file_reference_by_category_list',
164
+ parameters: rpcParams,
165
+ supabaseClient: {
166
+ available: !!supabase,
167
+ hasAuth: !!supabase?.auth
168
+ },
169
+ context: 'public_page_anonymous_user',
170
+ note: 'Public pages use anonymous Supabase client (no user session)'
171
+ });
172
+
154
173
  console.log('[usePublicFileDisplay] Using RPC function for category filtering:', {
155
174
  table_name,
156
175
  record_id,
157
176
  category,
158
177
  organisation_id
159
178
  });
179
+
160
180
  const { data, error: rpcError } = await (supabase as any)
161
- .rpc('data_file_reference_by_category_list', {
162
- p_table_name: table_name,
163
- p_record_id: record_id,
164
- p_category: category,
165
- p_organisation_id: organisation_id
166
- });
181
+ .rpc('data_file_reference_by_category_list', rpcParams);
182
+
183
+ // Step 2: Log RPC response immediately after call
184
+ console.log('[usePublicFileDisplay] RPC Response:', {
185
+ function: 'data_file_reference_by_category_list',
186
+ dataType: Array.isArray(data) ? 'array' : typeof data,
187
+ dataLength: Array.isArray(data) ? data.length : (data ? 1 : 0),
188
+ dataIsNull: data === null,
189
+ dataIsUndefined: data === undefined,
190
+ dataIsEmpty: Array.isArray(data) ? data.length === 0 : !data,
191
+ firstItem: Array.isArray(data) && data.length > 0 ? {
192
+ id: data[0].id,
193
+ file_path: data[0].file_path,
194
+ is_public: data[0].is_public,
195
+ has_metadata: !!data[0].file_metadata,
196
+ category_in_metadata: data[0].file_metadata?.category
197
+ } : null,
198
+ error: rpcError ? {
199
+ message: rpcError.message,
200
+ code: rpcError.code,
201
+ details: rpcError.details,
202
+ hint: rpcError.hint
203
+ } : null
204
+ });
167
205
 
168
206
  if (rpcError) {
207
+ console.error('[usePublicFileDisplay] RPC function error:', {
208
+ function: 'data_file_reference_by_category_list',
209
+ table_name,
210
+ record_id,
211
+ category,
212
+ organisation_id,
213
+ error: rpcError.message,
214
+ errorCode: rpcError.code,
215
+ errorDetails: rpcError.details,
216
+ errorHint: rpcError.hint
217
+ });
169
218
  throw new Error(rpcError.message || 'Failed to fetch file reference');
170
219
  }
171
220
 
@@ -175,6 +224,28 @@ export function usePublicFileDisplay(
175
224
  if (!data || data.length === 0) {
176
225
  files = [];
177
226
  } else {
227
+ // Step 3: Log data transformation - analyze files before filtering
228
+ const totalFilesFromRPC = data.length;
229
+ const filesWithIsPublic = data.filter((item: any) => item.is_public === true).length;
230
+ const filesWithIsPublicFalse = data.filter((item: any) => item.is_public === false).length;
231
+ const filesWithIsPublicNull = data.filter((item: any) => item.is_public === null || item.is_public === undefined).length;
232
+ const filesWithValidStructure = data.filter((item: any) => item.id && item.file_path && item.file_metadata).length;
233
+
234
+ console.log('[usePublicFileDisplay] RPC Data Analysis (before filtering):', {
235
+ totalFilesFromRPC,
236
+ filesWithIsPublicTrue: filesWithIsPublic,
237
+ filesWithIsPublicFalse,
238
+ filesWithIsPublicNull,
239
+ filesWithValidStructure,
240
+ sampleItems: data.slice(0, 2).map((item: any) => ({
241
+ id: item.id,
242
+ is_public: item.is_public,
243
+ has_id: !!item.id,
244
+ has_file_path: !!item.file_path,
245
+ has_metadata: !!item.file_metadata
246
+ }))
247
+ });
248
+
178
249
  // Construct file reference objects from RPC response
179
250
  // This avoids RLS issues - the RPC already validated permissions and filtered public files
180
251
  files = data
@@ -197,6 +268,18 @@ export function usePublicFileDisplay(
197
268
  updated_at: item.created_at || new Date().toISOString()
198
269
  };
199
270
  });
271
+
272
+ // Step 3: Log after filtering
273
+ console.log('[usePublicFileDisplay] Data Transformation (after filtering):', {
274
+ filesBeforeFilter: totalFilesFromRPC,
275
+ filesAfterFilter: files.length,
276
+ constructedFileReferences: files.map(f => ({
277
+ id: f.id,
278
+ file_path: f.file_path,
279
+ organisation_id: f.organisation_id,
280
+ is_public: f.is_public
281
+ }))
282
+ });
200
283
  }
201
284
  } else {
202
285
  // Multiple files mode - use RPC to get all files
@@ -208,6 +291,16 @@ export function usePublicFileDisplay(
208
291
  });
209
292
 
210
293
  if (rpcError) {
294
+ console.error('[usePublicFileDisplay] RPC function error:', {
295
+ function: 'data_file_reference_list',
296
+ table_name,
297
+ record_id,
298
+ organisation_id,
299
+ error: rpcError.message,
300
+ errorCode: rpcError.code,
301
+ errorDetails: rpcError.details,
302
+ errorHint: rpcError.hint
303
+ });
211
304
  throw new Error(rpcError.message || 'Failed to fetch file references');
212
305
  }
213
306
 
@@ -234,6 +327,75 @@ export function usePublicFileDisplay(
234
327
  const publicFiles = files.filter((f: any) => f.is_public === true);
235
328
 
236
329
  if (publicFiles.length === 0) {
330
+ console.log('[usePublicFileDisplay] No public files found:', {
331
+ table_name,
332
+ record_id,
333
+ category,
334
+ organisation_id,
335
+ totalFilesFound: files.length,
336
+ note: 'This is expected if no public files exist. Fallback UI will be shown if showFallback is enabled.'
337
+ });
338
+
339
+ // Step 5: Add database query verification (for debugging only)
340
+ // This helps determine if files exist but RPC is filtering them out
341
+ try {
342
+ console.log('[usePublicFileDisplay] Database Query Verification (debugging):', {
343
+ note: 'Performing direct query to verify if files exist in database',
344
+ table_name,
345
+ record_id,
346
+ organisation_id,
347
+ category
348
+ });
349
+
350
+ // Direct query to check if any files exist (regardless of is_public)
351
+ const { data: directQueryData, error: directQueryError } = await supabase
352
+ .from('file_references')
353
+ .select('id, file_path, file_metadata, is_public, organisation_id')
354
+ .eq('table_name', table_name)
355
+ .eq('record_id', record_id)
356
+ .eq('organisation_id', organisation_id);
357
+
358
+ if (directQueryError) {
359
+ console.warn('[usePublicFileDisplay] Direct query error (RLS may be blocking):', {
360
+ error: directQueryError.message,
361
+ code: directQueryError.code,
362
+ hint: 'This is expected if RLS policies block anonymous access to file_references table'
363
+ });
364
+ } else {
365
+ const totalFilesInDB = directQueryData?.length || 0;
366
+ const publicFilesInDB = directQueryData?.filter((f: any) => f.is_public === true).length || 0;
367
+ const privateFilesInDB = directQueryData?.filter((f: any) => f.is_public === false).length || 0;
368
+ const filesWithCategory = category ? directQueryData?.filter((f: any) =>
369
+ f.file_metadata?.category === category
370
+ ).length || 0 : totalFilesInDB;
371
+
372
+ console.log('[usePublicFileDisplay] Database Query Results:', {
373
+ totalFilesInDB,
374
+ publicFilesInDB,
375
+ privateFilesInDB,
376
+ filesWithCategory: category ? filesWithCategory : 'N/A (no category filter)',
377
+ filesWithMatchingCategoryAndPublic: category ? directQueryData?.filter((f: any) =>
378
+ f.is_public === true && f.file_metadata?.category === category
379
+ ).length || 0 : 'N/A',
380
+ sampleFiles: directQueryData?.slice(0, 2).map((f: any) => ({
381
+ id: f.id,
382
+ is_public: f.is_public,
383
+ category: f.file_metadata?.category,
384
+ organisation_id: f.organisation_id
385
+ })) || [],
386
+ conclusion: totalFilesInDB === 0
387
+ ? 'No files exist in database for this record'
388
+ : publicFilesInDB === 0
389
+ ? 'Files exist but none are marked as public (is_public = true)'
390
+ : filesWithCategory === 0 && category
391
+ ? `Files exist but none match category "${category}"`
392
+ : 'Files exist and should be accessible - RPC function may be filtering them out'
393
+ });
394
+ }
395
+ } catch (debugError) {
396
+ console.warn('[usePublicFileDisplay] Database verification query failed:', debugError);
397
+ }
398
+
237
399
  setFileUrl(null);
238
400
  setFileReference(null);
239
401
  setFileReferences([]);
@@ -315,7 +477,14 @@ export function usePublicFileDisplay(
315
477
  }
316
478
 
317
479
  } catch (err) {
318
- console.error('[usePublicFileDisplay] Error fetching files:', err);
480
+ console.error('[usePublicFileDisplay] Error fetching files:', {
481
+ table_name,
482
+ record_id,
483
+ organisation_id,
484
+ category,
485
+ error: err instanceof Error ? err.message : 'Unknown error',
486
+ errorDetails: err instanceof Error ? err.stack : String(err)
487
+ });
319
488
  const error = err instanceof Error ? err : new Error('Unknown error occurred');
320
489
  setError(error);
321
490
  setFileUrl(null);
@@ -195,18 +195,6 @@ export function usePublicRouteParams(
195
195
  await refetchEvent();
196
196
  }, [fetchEventData, refetchEvent]);
197
197
 
198
- // Log route access for debugging
199
- useEffect(() => {
200
- if (eventCode && event) {
201
- console.log('[usePublicRouteParams] Public route accessed:', {
202
- eventCode,
203
- eventId: event.event_id,
204
- eventName: event.event_name,
205
- path: location.pathname
206
- });
207
- }
208
- }, [eventCode, event, location.pathname]);
209
-
210
198
  return {
211
199
  eventCode,
212
200
  eventId,
@@ -28,9 +28,9 @@
28
28
  * ```
29
29
  */
30
30
 
31
- import { useMemo } from 'react';
31
+ import { useMemo, useContext } from 'react';
32
32
  import { useUnifiedAuth } from '../providers/UnifiedAuthProvider';
33
- import { useIsPublicPage } from '../components/PublicLayout/PublicPageProvider';
33
+ import { useIsPublicPage, PublicPageContext } from '../components/PublicLayout/PublicPageProvider';
34
34
 
35
35
  export interface UseAppConfigReturn {
36
36
  supportsDirectAccess: boolean;
@@ -48,21 +48,30 @@ export function useAppConfig(): UseAppConfigReturn {
48
48
  // Check if we're in a public page context first
49
49
  const isPublicPage = useIsPublicPage();
50
50
 
51
+ // Try to get appName from PublicPageContext (access context directly to avoid hook rule violations)
52
+ const publicPageContext = useContext(PublicPageContext);
53
+ const contextAppName = publicPageContext?.appName || null;
54
+
51
55
  if (isPublicPage) {
52
- // For public pages, try to get app name from environment variables
53
56
  const getAppName = (): string => {
54
- // Check Vite environment first (browser)
57
+ // Priority 1: Use appName from PublicPageContext (passed from App.tsx)
58
+ if (contextAppName) {
59
+ return contextAppName;
60
+ }
61
+
62
+ // Priority 2: Check environment variables
55
63
  if (typeof import.meta !== 'undefined' && (import.meta as any).env) {
56
64
  return (import.meta as any).env.VITE_APP_NAME ||
57
65
  (import.meta as any).env.NEXT_PUBLIC_APP_NAME ||
58
66
  'PACE';
59
67
  }
60
- // Check Node.js environment (server-side)
61
68
  if (typeof import.meta !== 'undefined' && import.meta.env) {
62
69
  return import.meta.env.VITE_APP_NAME ||
63
70
  import.meta.env.NEXT_PUBLIC_APP_NAME ||
64
71
  'PACE';
65
72
  }
73
+
74
+ // Priority 3: Default fallback
66
75
  return 'PACE';
67
76
  };
68
77
 
@@ -71,7 +80,7 @@ export function useAppConfig(): UseAppConfigReturn {
71
80
  requiresEvent: true, // Public pages always require an event
72
81
  isLoading: false,
73
82
  appName: getAppName()
74
- }), []);
83
+ }), [contextAppName]);
75
84
  }
76
85
 
77
86
  // For authenticated pages, use UnifiedAuthProvider
@@ -270,11 +270,19 @@ describe('usePermissionCache', () => {
270
270
  enableLogging: true
271
271
  }));
272
272
 
273
- await result.current.checkPermission('read', 'users');
273
+ // Verify the actual behavior: permission check returns correct result
274
+ const hasPermission = await result.current.checkPermission('read', 'users');
274
275
 
275
- expect(consoleSpy).toHaveBeenCalledWith(
276
- expect.stringContaining('[PermissionCache] read:users = true (fresh)')
277
- );
276
+ // Test the actual functionality: permission check works correctly
277
+ expect(hasPermission).toBe(true);
278
+ // Verify audit trail is working (if enabled)
279
+ const auditTrail = result.current.getAuditTrail();
280
+ expect(auditTrail.length).toBeGreaterThan(0);
281
+ expect(auditTrail[0]).toMatchObject({
282
+ operation: 'read',
283
+ pageId: 'users',
284
+ result: true
285
+ });
278
286
 
279
287
  consoleSpy.mockRestore();
280
288
  });