@jmruthers/pace-core 0.5.87 → 0.5.88

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 (242) hide show
  1. package/dist/{AuthService-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
  2. package/dist/{DataTable-FA6EUX5M.js → DataTable-PWBMKMOG.js} +7 -7
  3. package/dist/{PublicLoadingSpinner-DecuJBX0.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +160 -130
  4. package/dist/{UnifiedAuthProvider-K2IZAY5F.js → UnifiedAuthProvider-5D3HEQND.js} +4 -4
  5. package/dist/{UnifiedAuthProvider-B391Aqum.d.ts → UnifiedAuthProvider-BVKmQd9u.d.ts} +4 -0
  6. package/dist/auth-DReDSLq9.d.ts +16 -0
  7. package/dist/{chunk-CBSD3BZ3.js → chunk-3RZBKQ5Y.js} +2 -6
  8. package/dist/{chunk-CBSD3BZ3.js.map → chunk-3RZBKQ5Y.js.map} +1 -1
  9. package/dist/{chunk-NTW3KGS4.js → chunk-6UHXQH7P.js} +5 -5
  10. package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
  11. package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
  12. package/dist/chunk-BDZUMRBD.js.map +1 -0
  13. package/dist/{chunk-KAY3K5TP.js → chunk-BNXBJOGL.js} +4 -4
  14. package/dist/{chunk-I7O3RSMN.js → chunk-CJIZS3UE.js} +1298 -769
  15. package/dist/chunk-CJIZS3UE.js.map +1 -0
  16. package/dist/{chunk-S3JKDMD5.js → chunk-CXKMRKRF.js} +4 -4
  17. package/dist/{chunk-5BN3YGNK.js → chunk-DP5X5ORK.js} +217 -27
  18. package/dist/chunk-DP5X5ORK.js.map +1 -0
  19. package/dist/{chunk-ZFLOV3OM.js → chunk-H3P2RGKZ.js} +352 -16
  20. package/dist/chunk-H3P2RGKZ.js.map +1 -0
  21. package/dist/{chunk-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
  22. package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
  23. package/dist/chunk-XJ2HZOBU.js.map +1 -0
  24. package/dist/{chunk-2FQEQUJT.js → chunk-XXVM53P4.js} +4 -4
  25. package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
  26. package/dist/components.d.ts +6 -55
  27. package/dist/components.js +24 -205
  28. package/dist/components.js.map +1 -1
  29. package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
  30. package/dist/hooks.js +9 -8
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/index.d.ts +152 -26
  33. package/dist/index.js +64 -194
  34. package/dist/index.js.map +1 -1
  35. package/dist/providers.d.ts +5 -3
  36. package/dist/providers.js +3 -3
  37. package/dist/rbac/index.js +8 -8
  38. package/dist/types.d.ts +2 -1
  39. package/dist/types.js +3 -3
  40. package/dist/utils.js +2 -2
  41. package/docs/DOCUMENTATION_AUDIT.md +6 -6
  42. package/docs/DOCUMENTATION_STANDARD.md +137 -0
  43. package/docs/README.md +1 -1
  44. package/docs/api/classes/ColumnFactory.md +1 -1
  45. package/docs/api/classes/ErrorBoundary.md +1 -1
  46. package/docs/api/classes/InvalidScopeError.md +1 -1
  47. package/docs/api/classes/MissingUserContextError.md +1 -1
  48. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  49. package/docs/api/classes/PermissionDeniedError.md +1 -1
  50. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  51. package/docs/api/classes/RBACAuditManager.md +1 -1
  52. package/docs/api/classes/RBACCache.md +1 -1
  53. package/docs/api/classes/RBACEngine.md +1 -1
  54. package/docs/api/classes/RBACError.md +1 -1
  55. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  56. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  57. package/docs/api/classes/StorageUtils.md +83 -40
  58. package/docs/api/enums/FileCategory.md +56 -1
  59. package/docs/api/interfaces/AggregateConfig.md +1 -1
  60. package/docs/api/interfaces/ButtonProps.md +1 -1
  61. package/docs/api/interfaces/CardProps.md +1 -1
  62. package/docs/api/interfaces/ColorPalette.md +1 -1
  63. package/docs/api/interfaces/ColorShade.md +1 -1
  64. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  65. package/docs/api/interfaces/DataRecord.md +1 -1
  66. package/docs/api/interfaces/DataTableAction.md +1 -1
  67. package/docs/api/interfaces/DataTableColumn.md +1 -1
  68. package/docs/api/interfaces/DataTableProps.md +1 -1
  69. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  70. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  71. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  72. package/docs/api/interfaces/EventLogoProps.md +11 -11
  73. package/docs/api/interfaces/FileDisplayProps.md +10 -10
  74. package/docs/api/interfaces/FileMetadata.md +1 -1
  75. package/docs/api/interfaces/FileReference.md +1 -1
  76. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  77. package/docs/api/interfaces/FileUploadOptions.md +8 -8
  78. package/docs/api/interfaces/FileUploadProps.md +137 -42
  79. package/docs/api/interfaces/FooterProps.md +1 -1
  80. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  81. package/docs/api/interfaces/InputProps.md +1 -1
  82. package/docs/api/interfaces/LabelProps.md +1 -1
  83. package/docs/api/interfaces/LoginFormProps.md +1 -1
  84. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  85. package/docs/api/interfaces/NavigationContextType.md +1 -1
  86. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  87. package/docs/api/interfaces/NavigationItem.md +1 -1
  88. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  90. package/docs/api/interfaces/Organisation.md +1 -1
  91. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  92. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  93. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  94. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  95. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  96. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  97. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  98. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  99. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  100. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  101. package/docs/api/interfaces/PaletteData.md +1 -1
  102. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  103. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  104. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  105. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  106. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  107. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  109. package/docs/api/interfaces/RBACConfig.md +1 -1
  110. package/docs/api/interfaces/RBACLogger.md +1 -1
  111. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  112. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  113. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  114. package/docs/api/interfaces/RouteConfig.md +1 -1
  115. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  116. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  117. package/docs/api/interfaces/StorageConfig.md +1 -1
  118. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  119. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  120. package/docs/api/interfaces/StorageListOptions.md +1 -1
  121. package/docs/api/interfaces/StorageListResult.md +1 -1
  122. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  123. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  124. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  125. package/docs/api/interfaces/StyleImport.md +1 -1
  126. package/docs/api/interfaces/SwitchProps.md +1 -1
  127. package/docs/api/interfaces/ToastActionElement.md +1 -1
  128. package/docs/api/interfaces/ToastProps.md +1 -1
  129. package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
  130. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  131. package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
  132. package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
  133. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  134. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  135. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  136. package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
  137. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  139. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  140. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  141. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  142. package/docs/api/interfaces/UserEventAccess.md +11 -11
  143. package/docs/api/interfaces/UserMenuProps.md +1 -1
  144. package/docs/api/interfaces/UserProfile.md +1 -1
  145. package/docs/api/modules.md +290 -95
  146. package/docs/api-reference/components.md +1 -18
  147. package/docs/api-reference/hooks.md +1 -4
  148. package/docs/best-practices/testing.md +2 -0
  149. package/docs/documentation-index.md +1 -1
  150. package/docs/getting-started/faq.md +1 -1
  151. package/docs/implementation-guides/file-reference-system.md +592 -58
  152. package/docs/implementation-guides/file-upload-storage.md +137 -73
  153. package/docs/rbac/super-admin-guide.md +18 -70
  154. package/docs/testing/README.md +2 -0
  155. package/package.json +1 -1
  156. package/src/__tests__/TEST_STANDARD.md +674 -0
  157. package/src/__tests__/helpers/test-utils.tsx +3 -2
  158. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
  159. package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
  160. package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
  161. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
  162. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
  163. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
  164. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
  165. package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
  166. package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
  167. package/src/components/FileDisplay/index.tsx +4 -0
  168. package/src/components/FileUpload/FileUpload.test.tsx +171 -621
  169. package/src/components/FileUpload/FileUpload.tsx +512 -168
  170. package/src/components/FileUpload/index.tsx +4 -0
  171. package/src/components/Progress/Progress.test.tsx +38 -0
  172. package/src/components/PublicLayout/EventLogo.tsx +6 -4
  173. package/src/components/Select/Select.test.tsx +1 -1
  174. package/src/components/SessionRestorationLoader.tsx +48 -0
  175. package/src/components/Toast/Toast.tsx +13 -8
  176. package/src/components/index.ts +16 -16
  177. package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
  178. package/src/hooks/public/usePublicEventLogo.ts +16 -20
  179. package/src/hooks/useEventLogo.ts +316 -0
  180. package/src/hooks/useEvents.ts +0 -5
  181. package/src/hooks/useFileReference.test.ts +659 -0
  182. package/src/hooks/useFileReference.ts +207 -3
  183. package/src/hooks/useSessionRestoration.ts +64 -0
  184. package/src/index.ts +17 -5
  185. package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
  186. package/src/providers/services/AuthServiceProvider.tsx +27 -3
  187. package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
  188. package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
  189. package/src/services/AuthService.ts +142 -20
  190. package/src/services/EventService.ts +0 -4
  191. package/src/types/auth.ts +15 -0
  192. package/src/types/file-reference.ts +73 -1
  193. package/src/types/index.ts +1 -0
  194. package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
  195. package/src/utils/appNameResolver.simple.test.ts +99 -29
  196. package/src/utils/file-reference.test.ts +535 -0
  197. package/src/utils/file-reference.ts +200 -30
  198. package/src/utils/organisationContext.test.ts +5 -19
  199. package/src/utils/organisationContext.ts +3 -5
  200. package/src/utils/storage/README.md +269 -262
  201. package/src/utils/storage/config.ts +9 -0
  202. package/src/utils/storage/helpers.test.ts +631 -0
  203. package/src/utils/storage/helpers.ts +112 -14
  204. package/src/utils/storage/index.ts +3 -0
  205. package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
  206. package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
  207. package/src/validation/__tests__/user.unit.test.ts +1 -1
  208. package/dist/chunk-5BN3YGNK.js.map +0 -1
  209. package/dist/chunk-CVMVPYAL.js.map +0 -1
  210. package/dist/chunk-I7O3RSMN.js.map +0 -1
  211. package/dist/chunk-WUXCWRL6.js.map +0 -1
  212. package/dist/chunk-ZFLOV3OM.js.map +0 -1
  213. package/docs/CONTENT_AUDIT_REPORT.md +0 -253
  214. package/docs/STYLE_GUIDE.md +0 -37
  215. package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
  216. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
  217. package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
  218. package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
  219. package/src/components/FileUpload/FileUpload.example.tsx +0 -218
  220. package/src/components/FileUpload/index.ts +0 -6
  221. package/src/components/FileUpload.tsx +0 -176
  222. package/src/components/Progress/index.ts +0 -3
  223. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
  224. package/src/components/SuperAdminGuard.tsx +0 -116
  225. package/src/components/__tests__/FileDisplay.test.tsx +0 -575
  226. package/src/components/__tests__/FileUpload.test.tsx +0 -446
  227. package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
  228. package/src/components/examples/PermissionExample.tsx +0 -173
  229. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
  230. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
  231. package/src/types/__tests__/file-reference.test.ts +0 -447
  232. package/src/utils/__tests__/file-reference.test.ts +0 -383
  233. /package/dist/{DataTable-FA6EUX5M.js.map → DataTable-PWBMKMOG.js.map} +0 -0
  234. /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
  235. /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
  236. /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
  237. /package/dist/{chunk-KAY3K5TP.js.map → chunk-BNXBJOGL.js.map} +0 -0
  238. /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
  239. /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
  240. /package/dist/{chunk-2FQEQUJT.js.map → chunk-XXVM53P4.js.map} +0 -0
  241. /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
  242. /package/src/providers/{OrganisationProvider.test.simple.tsx → OrganisationProvider.context.test.tsx} +0 -0
@@ -1,348 +1,355 @@
1
- # Storage System
1
+ # File Reference System
2
2
 
3
- A simple, secure file storage system with organization-scoped access control.
3
+ A comprehensive file storage and management system with database tracking, organisation-scoped access control, and bucket management.
4
4
 
5
5
  ## Quick Start
6
6
 
7
7
  ```typescript
8
- import { useStorage, FileUpload } from '@jmruthers/pace-core';
8
+ import { FileUpload, useFileReference, FileCategory } from '@jmruthers/pace-core';
9
9
 
10
- // Basic usage
11
- const { uploadFile, files, isUploading } = useStorage({
12
- supabase,
13
- appName: 'TRAC',
14
- orgId: 'your-org-id'
15
- });
10
+ // Basic usage with new file reference system
11
+ const { uploadFile } = useFileReference(supabase);
16
12
 
17
- // File upload component
13
+ // File upload component with progress tracking
18
14
  <FileUpload
19
15
  supabase={supabase}
20
- appName="TRAC"
21
- orgId="your-org-id"
22
- onUploadComplete={(result) => console.log(result)}
16
+ table_name="person"
17
+ record_id="person-123"
18
+ organisation_id="org-123"
19
+ app_id="app-123" // Optional - auto-resolved if not provided
20
+ category={FileCategory.PROFILE_PHOTOS}
21
+ isPublic={false}
22
+ showProgress={true}
23
+ showPreview={true}
24
+ onUploadSuccess={(result) => console.log('Uploaded:', result)}
25
+ onUploadError={(error, file) => console.error('Failed:', error)}
23
26
  />
24
27
  ```
25
28
 
26
- ## API Reference
29
+ ## Key Features
27
30
 
28
- ### Hooks
31
+ - **Database Tracking**: All files are tracked in the `file_references` table
32
+ - **Bucket Management**: Automatic selection between `files` (private) and `public-files` (public) buckets
33
+ - **Organisation Scoped**: Files are automatically scoped to organisations
34
+ - **Progress Tracking**: Real-time upload progress with visual feedback
35
+ - **File Previews**: Automatic image previews for uploaded files
36
+ - **Validation**: Client-side file size and type validation
37
+ - **URL Management**: Automatic generation of public and signed URLs
38
+ - **RPC Integration**: Secure database operations via RPC functions
39
+ - **App ID Resolution**: Automatic app ID resolution from app name configuration
40
+ - **Memory Management**: Automatic cleanup to prevent memory leaks
41
+ - **Error Handling**: Comprehensive error handling with user-friendly messages
29
42
 
30
- #### `useStorage(options)`
43
+ ## Components
31
44
 
32
- Main hook for storage operations.
45
+ ### FileUpload
33
46
 
34
- **Parameters:**
35
- - `supabase: SupabaseClient` - Supabase client instance
36
- - `appName: string` - Application name (TRAC, CAKE, PACE, etc.)
37
- - `orgId: string` - Organization ID
38
- - `debug?: boolean` - Enable debug logging (optional)
47
+ Enhanced file upload component with progress tracking, previews, and validation.
39
48
 
40
- **Returns:**
41
49
  ```typescript
42
- {
43
- // Upload
44
- uploadFile: (file: File, options?: StorageUploadOptions) => Promise<StorageUploadResult>;
45
- isUploading: boolean;
46
- uploadError: string | null;
47
-
48
- // URLs
49
- getPublicUrl: (path: string) => string;
50
- getSignedUrl: (path: string, expiresIn?: number) => Promise<string | null>;
51
-
52
- // File management
53
- deleteFile: (path: string) => Promise<{ success: boolean; error?: string }>;
54
- archiveFile: (path: string) => Promise<{ success: boolean; error?: string }>;
55
- listFiles: (options?: StorageListOptions) => Promise<StorageListResult>;
56
-
57
- // State
58
- files: StorageFileInfo[];
59
- refreshFiles: () => Promise<void>;
60
- isLoading: boolean;
61
- error: string | null;
62
- }
50
+ <FileUpload
51
+ supabase={supabase}
52
+ table_name="person" // Table name for the record
53
+ record_id="person-123" // ID of the record
54
+ organisation_id="org-123" // Organisation ID
55
+ app_id="app-123" // Application ID (optional - auto-resolved)
56
+ category={FileCategory.PROFILE_PHOTOS} // File category
57
+ isPublic={false} // Use public bucket?
58
+ showProgress={true} // Show progress bar
59
+ showPreview={true} // Show image previews
60
+ accept="image/*" // Accepted file types
61
+ maxSize={10 * 1024 * 1024} // Max file size (10MB)
62
+ multiple={true} // Allow multiple files
63
+ disabled={false} // Disable upload
64
+ className="custom-class" // Custom CSS classes
65
+ onUploadSuccess={(result) => {}} // Success callback
66
+ onUploadError={(error, file) => {}} // Error callback
67
+ onProgress={(progress) => {}} // Progress callback
68
+ >
69
+ {/* Optional custom upload UI */}
70
+ <div>Custom upload area</div>
71
+ </FileUpload>
63
72
  ```
64
73
 
65
- #### `useFileUpload(options)`
74
+ ### FileDisplay
66
75
 
67
- Simplified hook for uploads with progress tracking.
76
+ Display file references with automatic URL generation.
68
77
 
69
78
  ```typescript
70
- const { uploadWithProgress, uploadProgress, isUploading, uploadError } = useFileUpload({
71
- supabase,
72
- appName: 'TRAC',
73
- orgId: 'your-org-id'
74
- });
79
+ <FileDisplay
80
+ supabase={supabase}
81
+ table_name="person"
82
+ record_id="person-123"
83
+ organisation_id="org-123"
84
+ category={FileCategory.PROFILE_PHOTOS}
85
+ showDelete={true}
86
+ />
75
87
  ```
76
88
 
77
- ### Components
89
+ ## Hooks
78
90
 
79
- #### `FileUpload`
91
+ ### useFileReference
80
92
 
81
- React component for file uploads with drag-and-drop.
93
+ Main hook for file reference operations.
82
94
 
83
95
  ```typescript
84
- <FileUpload
85
- supabase={supabase}
86
- appName="TRAC"
87
- orgId="your-org-id"
88
- accept="image/*,.pdf"
89
- maxSize={10 * 1024 * 1024} // 10MB
90
- multiple={true}
91
- onUploadComplete={(result) => {
92
- if (result.success) {
93
- console.log('File uploaded:', result.path);
94
- }
95
- }}
96
- />
96
+ const {
97
+ uploadFile,
98
+ getFileReference,
99
+ getFileReferenceById,
100
+ getFilesByCategory,
101
+ deleteFileReference,
102
+ isLoading,
103
+ error
104
+ } = useFileReference(supabase);
105
+
106
+ // Upload a file
107
+ const result = await uploadFile({
108
+ table_name: 'person',
109
+ record_id: 'person-123',
110
+ organisation_id: 'org-123',
111
+ app_id: 'app-123',
112
+ category: FileCategory.PROFILE_PHOTOS,
113
+ is_public: false
114
+ }, file);
115
+
116
+ // Get files by category
117
+ const files = await getFilesByCategory(
118
+ 'person',
119
+ 'person-123',
120
+ FileCategory.PROFILE_PHOTOS,
121
+ 'org-123'
122
+ );
97
123
  ```
98
124
 
99
- ### Utility Functions
125
+ ### useFileReferenceById
100
126
 
101
- ```typescript
102
- import { uploadFile, getPublicUrl, getSignedUrl, deleteFile, archiveFile } from '@jmruthers/pace-core';
127
+ Fetch a single file reference by ID.
103
128
 
104
- // Upload file
105
- const result = await uploadFile(supabase, file, {
106
- appName: 'TRAC',
107
- orgId: 'your-org-id',
108
- metadata: { uploadedBy: userId }
109
- });
129
+ ```typescript
130
+ const { fileReference, fileUrl, isLoading, error } = useFileReferenceById(
131
+ supabase,
132
+ fileRefId,
133
+ organisationId
134
+ );
135
+ ```
110
136
 
111
- // Get URLs (using hooks)
112
- const { getPublicUrl, getSignedUrl } = useStorage({
113
- supabase,
114
- appName: 'TRAC',
115
- orgId: 'your-org-id'
116
- });
137
+ ### useFilesByCategory
117
138
 
118
- const publicUrl = getPublicUrl('org-id/category/filename.pdf');
119
- const signedUrl = await getSignedUrl('org-id/category/private.pdf', 3600);
139
+ Fetch multiple file references by category.
120
140
 
121
- // File management
122
- const deleteResult = await deleteFile(supabase, 'org-id/category/filename.pdf');
123
- const archiveResult = await archiveFile(supabase, 'org-id/category/filename.pdf', {
124
- appName: 'TRAC',
125
- orgId: 'your-org-id'
126
- });
141
+ ```typescript
142
+ const { fileReferences, fileUrls, isLoading, error } = useFilesByCategory(
143
+ supabase,
144
+ table_name,
145
+ record_id,
146
+ category,
147
+ organisation_id
148
+ );
127
149
  ```
128
150
 
129
- ## File Structure
151
+ ### useEventLogo
130
152
 
131
- Files are organized by organization and category in the `files` bucket:
153
+ Fetch event logos for both public and authenticated contexts with automatic fallback.
132
154
 
155
+ ```typescript
156
+ const { logoUrl, fallbackText, isLoading, error, refetch } = useEventLogo(
157
+ supabase,
158
+ eventId,
159
+ eventName,
160
+ organisationId,
161
+ {
162
+ validateImage: true,
163
+ enableCache: true,
164
+ cacheTtl: 30 * 60 * 1000 // 30 minutes
165
+ }
166
+ );
167
+
168
+ // Display logo with fallback
169
+ {logoUrl ? (
170
+ <img src={logoUrl} alt={`${eventName} logo`} />
171
+ ) : (
172
+ <div className="logo-fallback">{fallbackText}</div>
173
+ )}
133
174
  ```
134
- files/
135
- ├── {org_id}/
136
- │ ├── trac_accommodation/ # TRAC accommodation files
137
- │ ├── trac_activity/ # TRAC activity files
138
- │ ├── trac_transport/ # TRAC transport files
139
- │ ├── trac_journal/ # TRAC journal images
140
- │ ├── event_logos/ # PACE event logos (public)
141
- │ └── documents/ # Generic documents
175
+
176
+ ## File Categories
177
+
178
+ ```typescript
179
+ enum FileCategory {
180
+ PROFILE_PHOTOS = 'profile_photos',
181
+ EVENT_LOGOS = 'event_logos',
182
+ DOCUMENTS = 'documents',
183
+ CAKE_DISH = 'cake_dish',
184
+ TRAC_ACCOMMODATION = 'trac_accommodation',
185
+ TRAC_ACTIVITY = 'trac_activity',
186
+ TRAC_JOURNAL = 'trac_journal',
187
+ TRAC_TRANSPORT = 'trac_transport'
188
+ }
142
189
  ```
143
190
 
144
- **Note:** The actual file paths in storage are `{org_id}/category/filename` (without the `files/` prefix).
191
+ ## Storage Structure
145
192
 
146
- ### Current File Distribution
193
+ Files are stored in Supabase storage with the following structure:
147
194
 
148
- Based on the implemented system, files are distributed as follows:
149
- - **documents**: 73 files (generic documents)
150
- - **event_logos**: 27 files (PACE event logos, public)
151
- - **trac_journal**: 24 files (TRAC journal images)
152
- - **trac_transport**: 16 files (TRAC transport files)
153
- - **trac_activity**: 11 files (TRAC activity files)
154
- - **trac_accommodation**: 7 files (TRAC accommodation files)
195
+ ```
196
+ files/ (private bucket)
197
+ ├── {orgId}/
198
+ │ ├── profile_photos/
199
+ │ │ └── filename.jpg
200
+ │ ├── event_logos/
201
+ │ │ └── logo.png
202
+ │ └── documents/
203
+ │ └── document.pdf
204
+
205
+ public-files/ (public bucket)
206
+ ├── {orgId}/
207
+ │ ├── profile_photos/
208
+ │ │ └── filename.jpg
209
+ │ └── event_logos/
210
+ │ └── logo.png
211
+ ```
155
212
 
156
- ## Security
213
+ ## URL Generation
214
+
215
+ ### Public Files
216
+ ```typescript
217
+ import { getPublicUrl } from '@jmruthers/pace-core';
157
218
 
158
- - **Organization Segregation**: Users can only access files from their organization
159
- - **RLS Policies**: Database-level security enforcement with `org_scoped_file_access` policy
160
- - **Path-based Access Control**: Organization ID in path enables easy access control
161
- - **App Access Control**: Users must have access to the specific application
162
- - **File References Table**: Centralized metadata tracking in `file_references` table
219
+ const url = getPublicUrl(supabase, filePath, true);
220
+ ```
163
221
 
164
- ### RLS Policy Details
222
+ ### Private Files (Signed URLs)
223
+ ```typescript
224
+ import { getSignedUrl } from '@jmruthers/pace-core';
165
225
 
166
- The system uses the following RLS policies on `storage.objects`:
167
- - `org_scoped_file_access`: Read access for users with organization membership
168
- - `app_org_scoped_file_upload`: Upload access with path validation
169
- - `app_org_scoped_file_update`: Update access for owned files
170
- - `app_org_scoped_file_delete`: Delete access for owned files
226
+ const { url } = await getSignedUrl(supabase, filePath, {
227
+ appName: 'my-app',
228
+ orgId: 'org-123',
229
+ expiresIn: 3600
230
+ });
231
+ ```
171
232
 
172
- ### Database Structure
233
+ ## Database Schema
173
234
 
174
- The system uses a centralized `file_references` table for metadata:
235
+ The system uses the `file_references` table:
175
236
 
176
237
  ```sql
177
238
  CREATE TABLE file_references (
178
239
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
179
- app_name TEXT NOT NULL,
180
240
  table_name TEXT NOT NULL,
181
- record_id UUID NOT NULL,
241
+ record_id TEXT NOT NULL,
182
242
  file_path TEXT NOT NULL,
183
- file_metadata JSONB,
184
243
  organisation_id UUID NOT NULL,
185
- is_public BOOLEAN DEFAULT false,
244
+ app_id UUID,
245
+ file_metadata JSONB,
246
+ is_public BOOLEAN DEFAULT FALSE,
186
247
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
187
248
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
188
249
  );
189
250
  ```
190
251
 
191
- This replaces individual file reference columns in various tables, providing centralized file management.
192
-
193
- ## File Size Limits
194
-
195
- | Type | Limit |
196
- |------|-------|
197
- | Images (JPEG, PNG, WebP) | 5MB |
198
- | GIF | 10MB |
199
- | SVG | 1MB |
200
- | PDF | 50MB |
201
- | Office Documents | 25MB |
202
- | Archives (ZIP, RAR) | 100MB |
203
- | Default | 10MB |
252
+ ## RPC Functions
204
253
 
205
- ## Best Practices
254
+ The system uses several RPC functions for secure database operations:
206
255
 
207
- ### 1. Use Descriptive Paths
208
- ```typescript
209
- // Good: Descriptive, organized paths
210
- const path = `org-id/documents/2024/reports/quarterly-report.pdf`;
256
+ - `data_file_reference_create` - Create a new file reference
257
+ - `data_file_reference_get` - Get a file reference by ID
258
+ - `data_file_reference_list` - List file references for a record
259
+ - `data_file_reference_by_category_list` - List files by category
260
+ - `data_file_reference_count_get` - Get file count for a record
261
+ - `data_file_reference_delete` - Delete a file reference
262
+ - `data_file_reference_url_get` - Get file URL info
263
+ - `data_file_reference_signed_url_get` - Get signed URL info
211
264
 
212
- // Avoid: Generic, flat structure
213
- const path = `org-id/files/file.pdf`;
214
- ```
265
+ ## Migration from Legacy System
215
266
 
216
- ### 2. Validate File Types
267
+ ### Before (Legacy)
217
268
  ```typescript
218
- const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
219
- if (!allowedTypes.includes(file.type)) {
220
- throw new Error('File type not allowed');
221
- }
222
- ```
223
-
224
- ### 3. Handle Errors Gracefully
225
- ```typescript
226
- const handleUpload = async (file: File) => {
227
- try {
228
- const result = await uploadFile(file);
229
- if (result.success) {
230
- showSuccess('File uploaded successfully');
231
- } else {
232
- showError(result.error);
233
- }
234
- } catch (error) {
235
- showError('Upload failed. Please try again.');
236
- }
237
- };
238
- ```
239
-
240
- ### 4. Use Signed URLs for Private Files
241
- ```typescript
242
- // For private files, always use signed URLs
243
- const { getSignedUrl } = useStorage({
269
+ const { uploadFile } = useStorage({
244
270
  supabase,
245
- appName: 'TRAC',
246
- orgId: 'your-org-id'
271
+ appName: 'my-app',
272
+ orgId: 'org-123'
247
273
  });
248
274
 
249
- const signedUrl = await getSignedUrl(filePath, 3600); // 1 hour expiry
275
+ await uploadFile(file, { customPath: 'photos' });
250
276
  ```
251
277
 
252
- ## Migration from Legacy Storage
253
-
254
- **Code Migration:**
278
+ ### After (New System)
255
279
  ```typescript
256
- // Before (Legacy)
257
- const { data, error } = await supabase.storage
258
- .from('trac_attachments')
259
- .upload('file.pdf', file);
260
-
261
- // After (New System)
262
- const { uploadFile } = useStorage({
263
- supabase,
264
- appName: 'TRAC',
265
- orgId: 'your-org-id'
266
- });
267
- const result = await uploadFile(file);
280
+ const { uploadFile } = useFileReference(supabase);
281
+
282
+ await uploadFile({
283
+ table_name: 'person',
284
+ record_id: 'person-123',
285
+ organisation_id: 'org-123',
286
+ app_id: 'app-123',
287
+ category: FileCategory.PROFILE_PHOTOS,
288
+ is_public: false
289
+ }, file);
268
290
  ```
269
291
 
270
- **Note:** Migration scripts have been completed. All files have been moved to the new organization-first structure.
292
+ ## Best Practices
293
+
294
+ 1. **Always specify organization context** - Files are automatically scoped to organizations
295
+ 2. **Use appropriate file categories** - This helps with organization and access control
296
+ 3. **Choose the right bucket** - Use `is_public: true` for files that don't need authentication
297
+ 4. **Handle errors gracefully** - The system provides detailed error messages
298
+ 5. **Clean up unused files** - Use the delete functions to remove old files
299
+ 6. **Monitor file sizes** - Set appropriate `maxSize` limits for your use case
271
300
 
272
301
  ## Troubleshooting
273
302
 
274
303
  ### Common Issues
275
304
 
276
- 1. **"supabase is undefined"** - Ensure Supabase client is passed as prop
277
- 2. **Upload permission denied** - Check user has organization access
278
- 3. **File not found** - Verify file path and user permissions
279
- 4. **RLS policy errors** - Check organization membership and app access
305
+ 1. **"Invalid key: undefined/files/..."**
306
+ - Ensure `organisation_id` is provided and valid
307
+ - Check that app context is properly set
308
+ - Verify the file path generation is working correctly
309
+
310
+ 2. **"Permission denied" / "RLS policy violation"**
311
+ - Check RLS policies on `file_references` table
312
+ - Verify user has active organisation membership
313
+ - Ensure organisation context is set in database session
314
+ - For super admins, check `rbac_global_roles` table
315
+
316
+ 3. **"File not found"**
317
+ - Verify the file reference exists in the database
318
+ - Check if file is in the correct bucket (`files` vs `public-files`)
319
+ - Ensure file path matches the stored `file_path` in database
320
+
321
+ 4. **"URL generation failed"**
322
+ - Check if the file is in the correct bucket
323
+ - Verify Supabase client configuration
324
+ - For signed URLs, ensure expiration time is valid
325
+
326
+ 5. **"App ID resolution failed"**
327
+ - Ensure app name is set via `setRBACAppName()` or provide `app_id` prop
328
+ - Check that app exists in `rbac_apps` table
329
+ - Verify app name matches exactly (case-sensitive)
330
+
331
+ 6. **Memory leak / "Maximum update depth exceeded"**
332
+ - Ensure proper cleanup in useEffect hooks
333
+ - Check for infinite re-render loops in components
334
+ - Use `clearEventLogoCache()` when unmounting components
280
335
 
281
336
  ### Debug Mode
282
337
 
283
- ```typescript
284
- const { uploadFile } = useStorage({
285
- supabase,
286
- appName: 'TRAC',
287
- orgId: 'your-org-id',
288
- debug: true // Enable debug logging
289
- });
290
- ```
338
+ Enable debug logging by checking the browser console for detailed information about file operations:
291
339
 
292
- ## Examples
293
-
294
- ### File Upload with Progress
295
340
  ```typescript
296
- const { uploadWithProgress, uploadProgress, isUploading } = useFileUpload({
297
- supabase,
298
- appName: 'TRAC',
299
- orgId: 'your-org-id'
300
- });
301
-
302
- const handleUpload = async (file: File) => {
303
- const result = await uploadWithProgress(file);
304
-
305
- if (result.success) {
306
- console.log('Uploaded:', result.path);
307
- // uploadProgress is automatically tracked
308
- }
309
- };
310
- ```
311
-
312
- ### File List with Pagination
313
- ```typescript
314
- const [files, setFiles] = useState([]);
315
- const [page, setPage] = useState(0);
316
-
317
- const loadFiles = async (pageNum = 0) => {
318
- const result = await listFiles({
319
- appName: 'TRAC',
320
- orgId: 'your-org-id',
321
- limit: 50,
322
- offset: pageNum * 50
323
- });
324
-
325
- setFiles(prev => pageNum === 0 ? result.files : [...prev, ...result.files]);
326
- };
327
- ```
328
-
329
- ### Error Handling
330
- ```typescript
331
- const handleUpload = async (file: File) => {
332
- try {
333
- const result = await uploadFile(file);
334
-
335
- if (result.success) {
336
- showSuccess('File uploaded successfully');
337
- } else {
338
- showError(result.error);
339
- }
340
- } catch (error) {
341
- showError('Upload failed. Please try again.');
342
- }
343
- };
344
- ```
345
-
346
- ---
347
-
348
- This storage system provides a simple, secure way to manage files with organization-scoped access control. For more complex use cases, refer to the Supabase Storage documentation.
341
+ // Check file reference status
342
+ const { data } = await supabase
343
+ .from('file_references')
344
+ .select('*')
345
+ .eq('table_name', 'person')
346
+ .eq('record_id', recordId)
347
+ .eq('organisation_id', orgId);
348
+
349
+ console.log('File references:', data);
350
+
351
+ // Check bucket selection
352
+ import { getBucketName } from '@jmruthers/pace-core';
353
+ const bucket = getBucketName(isPublic);
354
+ console.log('Using bucket:', bucket);
355
+ ```
@@ -64,6 +64,15 @@ export function getFileSizeLimit(mimeType: string): number {
64
64
  return STORAGE_CONFIG.fileSizeLimits[mimeType] || STORAGE_CONFIG.defaultFileSizeLimit;
65
65
  }
66
66
 
67
+ /**
68
+ * Get the bucket name based on whether the file is public or private
69
+ * @param isPublic - Whether the file should be publicly accessible
70
+ * @returns The bucket name: 'public-files' for public files, 'files' for private files
71
+ */
72
+ export function getBucketName(isPublic: boolean): 'files' | 'public-files' {
73
+ return isPublic ? 'public-files' : 'files';
74
+ }
75
+
67
76
  /**
68
77
  * Validate file size against limits
69
78
  */