@jmruthers/pace-core 0.5.93 → 0.5.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-HC5S4RKB.js → DataTable-CHX2EFO3.js} +6 -6
- package/dist/{PublicLoadingSpinner-n74JgA9h.d.ts → PublicLoadingSpinner-BWUD6bLU.d.ts} +24 -3
- package/dist/{UnifiedAuthProvider-ZM7VUC45.js → UnifiedAuthProvider-H7RI4KYD.js} +3 -3
- package/dist/{chunk-AZ2QJYKU.js → chunk-2KLAOD4M.js} +3 -3
- package/dist/{chunk-HW5BGOWB.js → chunk-2ZYHCFUO.js} +4 -4
- package/dist/{chunk-AAM57AEU.js → chunk-5RYPBJYL.js} +16 -19
- package/dist/chunk-5RYPBJYL.js.map +1 -0
- package/dist/{chunk-XIBSVWJW.js → chunk-7TQDRDSM.js} +5 -5
- package/dist/{chunk-GP3HU6WS.js → chunk-G7UUVEAP.js} +3 -3
- package/dist/{chunk-M52CQP5W.js → chunk-MKMKUCPF.js} +762 -12
- package/dist/chunk-MKMKUCPF.js.map +1 -0
- package/dist/{chunk-OXFOS62D.js → chunk-MVNOAHOP.js} +2 -2
- package/dist/{chunk-SVMPR5IV.js → chunk-O6GASC4Q.js} +874 -784
- package/dist/chunk-O6GASC4Q.js.map +1 -0
- package/dist/{chunk-AYC2P377.js → chunk-ORACUZ7H.js} +2 -2
- package/dist/{chunk-TZXYSZT3.js → chunk-PRM6EYO3.js} +298 -238
- package/dist/{chunk-TZXYSZT3.js.map → chunk-PRM6EYO3.js.map} +1 -1
- package/dist/{chunk-6WFM22A4.js → chunk-ZGCVJ7WW.js} +2 -2
- package/dist/components.d.ts +1 -1
- package/dist/components.js +8 -8
- package/dist/hooks.d.ts +94 -3
- package/dist/hooks.js +20 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/{usePublicRouteParams-BlgwXweB.d.ts → usePublicRouteParams-BwMR2uub.d.ts} +93 -1
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +26 -11
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +24 -11
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UseEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +47 -0
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +120 -0
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +102 -16
- package/docs/implementation-guides/file-reference-system.md +15 -0
- package/docs/implementation-guides/file-upload-storage.md +16 -0
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +9 -7
- package/src/components/DataTable/components/DataTableCore.tsx +8 -1
- package/src/components/DataTable/components/EditableRow.tsx +62 -22
- package/src/components/DataTable/components/UnifiedTableBody.tsx +25 -101
- package/src/components/FileDisplay/FileDisplay.test.tsx +263 -39
- package/src/components/FileDisplay/FileDisplay.tsx +605 -83
- package/src/components/PublicLayout/PublicPageHeader.tsx +15 -8
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +71 -28
- package/src/components/Select/Select.test.tsx +83 -6
- package/src/components/Select/Select.tsx +236 -16
- package/src/examples/CorrectPublicPageImplementation.tsx +16 -13
- package/src/examples/PublicEventPage.tsx +9 -6
- package/src/examples/PublicPageApp.tsx +9 -6
- package/src/examples/PublicPageUsageExample.tsx +9 -7
- package/src/hooks/index.ts +4 -0
- package/src/hooks/public/index.ts +2 -0
- package/src/hooks/public/usePublicFileDisplay.ts +355 -0
- package/src/hooks/useFileDisplay.ts +370 -0
- package/src/services/AuthService.ts +19 -22
- package/dist/chunk-AAM57AEU.js.map +0 -1
- package/dist/chunk-M52CQP5W.js.map +0 -1
- package/dist/chunk-SVMPR5IV.js.map +0 -1
- /package/dist/{DataTable-HC5S4RKB.js.map → DataTable-CHX2EFO3.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ZM7VUC45.js.map → UnifiedAuthProvider-H7RI4KYD.js.map} +0 -0
- /package/dist/{chunk-AZ2QJYKU.js.map → chunk-2KLAOD4M.js.map} +0 -0
- /package/dist/{chunk-HW5BGOWB.js.map → chunk-2ZYHCFUO.js.map} +0 -0
- /package/dist/{chunk-XIBSVWJW.js.map → chunk-7TQDRDSM.js.map} +0 -0
- /package/dist/{chunk-GP3HU6WS.js.map → chunk-G7UUVEAP.js.map} +0 -0
- /package/dist/{chunk-OXFOS62D.js.map → chunk-MVNOAHOP.js.map} +0 -0
- /package/dist/{chunk-AYC2P377.js.map → chunk-ORACUZ7H.js.map} +0 -0
- /package/dist/{chunk-6WFM22A4.js.map → chunk-ZGCVJ7WW.js.map} +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public File Display Hook
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/Public
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* A React hook for accessing file references in public contexts without authentication.
|
|
8
|
+
* Provides file URLs and metadata for public pages.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - No authentication required
|
|
12
|
+
* - Only returns public files (is_public = true)
|
|
13
|
+
* - Caching for performance
|
|
14
|
+
* - Error handling and loading states
|
|
15
|
+
* - TypeScript support
|
|
16
|
+
* - Supports both single file (category) and multiple files
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { usePublicFileDisplay } from '@jmruthers/pace-core';
|
|
21
|
+
*
|
|
22
|
+
* function PublicFileView() {
|
|
23
|
+
* const { fileUrl, fileReference, isLoading, error } = usePublicFileDisplay(
|
|
24
|
+
* 'event',
|
|
25
|
+
* eventId,
|
|
26
|
+
* organisationId,
|
|
27
|
+
* FileCategory.EVENT_LOGOS,
|
|
28
|
+
* { supabase }
|
|
29
|
+
* );
|
|
30
|
+
*
|
|
31
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
32
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
33
|
+
*
|
|
34
|
+
* return fileUrl ? <img src={fileUrl} alt="File" /> : null;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
40
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
41
|
+
import type { Database } from '../../types/database';
|
|
42
|
+
import { FileReference, FileCategory } from '../../types/file-reference';
|
|
43
|
+
import { getPublicUrl } from '../../utils/storage/helpers';
|
|
44
|
+
|
|
45
|
+
// Simple in-memory cache for public file data
|
|
46
|
+
const publicFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();
|
|
47
|
+
|
|
48
|
+
export interface UsePublicFileDisplayReturn {
|
|
49
|
+
/** Single file URL if category is provided and file found, null otherwise */
|
|
50
|
+
fileUrl: string | null;
|
|
51
|
+
/** Single file reference if category is provided and file found, null otherwise */
|
|
52
|
+
fileReference: FileReference | null;
|
|
53
|
+
/** Array of all file references for the record (when category not provided or for multiple files) */
|
|
54
|
+
fileReferences: FileReference[];
|
|
55
|
+
/** Map of file IDs to URLs for multiple files */
|
|
56
|
+
fileUrls: Map<string, string>;
|
|
57
|
+
/** Total count of files for the record */
|
|
58
|
+
fileCount: number;
|
|
59
|
+
/** Whether the data is currently loading */
|
|
60
|
+
isLoading: boolean;
|
|
61
|
+
/** Any error that occurred during loading */
|
|
62
|
+
error: Error | null;
|
|
63
|
+
/** Function to manually refetch the data */
|
|
64
|
+
refetch: () => Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface UsePublicFileDisplayOptions {
|
|
68
|
+
/** Cache TTL in milliseconds (default: 30 minutes) */
|
|
69
|
+
cacheTtl?: number;
|
|
70
|
+
/** Whether to enable caching (default: true) */
|
|
71
|
+
enableCache?: boolean;
|
|
72
|
+
/** Supabase client instance (required) */
|
|
73
|
+
supabase: SupabaseClient<Database>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Hook for accessing public file references
|
|
78
|
+
*
|
|
79
|
+
* This hook provides access to file references without requiring
|
|
80
|
+
* authentication. It only returns public files and generates public URLs.
|
|
81
|
+
*
|
|
82
|
+
* @param table_name - The table name containing the file reference
|
|
83
|
+
* @param record_id - The record ID that owns the file(s)
|
|
84
|
+
* @param organisation_id - The organisation ID for storage path
|
|
85
|
+
* @param category - Optional file category to filter by (for single file mode)
|
|
86
|
+
* @param options - Configuration options for caching and behavior
|
|
87
|
+
* @returns Object containing file data, loading state, error, and refetch function
|
|
88
|
+
*/
|
|
89
|
+
export function usePublicFileDisplay(
|
|
90
|
+
table_name: string | undefined,
|
|
91
|
+
record_id: string | undefined,
|
|
92
|
+
organisation_id: string | undefined,
|
|
93
|
+
category: FileCategory | undefined,
|
|
94
|
+
options: UsePublicFileDisplayOptions
|
|
95
|
+
): UsePublicFileDisplayReturn {
|
|
96
|
+
const {
|
|
97
|
+
cacheTtl = 30 * 60 * 1000, // 30 minutes
|
|
98
|
+
enableCache = true,
|
|
99
|
+
supabase
|
|
100
|
+
} = options;
|
|
101
|
+
|
|
102
|
+
const [fileUrl, setFileUrl] = useState<string | null>(null);
|
|
103
|
+
const [fileReference, setFileReference] = useState<FileReference | null>(null);
|
|
104
|
+
const [fileReferences, setFileReferences] = useState<FileReference[]>([]);
|
|
105
|
+
const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());
|
|
106
|
+
const [fileCount, setFileCount] = useState<number>(0);
|
|
107
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
108
|
+
const [error, setError] = useState<Error | null>(null);
|
|
109
|
+
|
|
110
|
+
const fetchFiles = useCallback(async (): Promise<void> => {
|
|
111
|
+
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
112
|
+
setFileUrl(null);
|
|
113
|
+
setFileReference(null);
|
|
114
|
+
setFileReferences([]);
|
|
115
|
+
setFileUrls(new Map());
|
|
116
|
+
setFileCount(0);
|
|
117
|
+
setIsLoading(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validate UUID format for organisationId to prevent database errors
|
|
122
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
123
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
124
|
+
console.warn('[usePublicFileDisplay] Invalid organisationId format (not a valid UUID):', organisation_id);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check cache first
|
|
128
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || 'all'}`;
|
|
129
|
+
if (enableCache) {
|
|
130
|
+
const cached = publicFileCache.get(cacheKey);
|
|
131
|
+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
132
|
+
const cachedData = cached.data;
|
|
133
|
+
setFileUrl(cachedData.fileUrl || null);
|
|
134
|
+
setFileReference(cachedData.fileReference || null);
|
|
135
|
+
setFileReferences(cachedData.fileReferences || []);
|
|
136
|
+
setFileUrls(cachedData.fileUrls || new Map());
|
|
137
|
+
setFileCount(cachedData.fileCount || 0);
|
|
138
|
+
setIsLoading(false);
|
|
139
|
+
setError(null);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
setIsLoading(true);
|
|
146
|
+
setError(null);
|
|
147
|
+
|
|
148
|
+
let files: any[] = [];
|
|
149
|
+
|
|
150
|
+
if (category) {
|
|
151
|
+
// Single file mode - use RPC to get files by category
|
|
152
|
+
const { data, error: rpcError } = await (supabase as any)
|
|
153
|
+
.rpc('data_file_reference_by_category_list', {
|
|
154
|
+
p_table_name: table_name,
|
|
155
|
+
p_record_id: record_id,
|
|
156
|
+
p_category: category,
|
|
157
|
+
p_organisation_id: organisation_id
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (rpcError) {
|
|
161
|
+
throw new Error(rpcError.message || 'Failed to fetch file reference');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
files = data || [];
|
|
165
|
+
} else {
|
|
166
|
+
// Multiple files mode - use RPC to get all files
|
|
167
|
+
const { data: fileIds, error: rpcError } = await (supabase as any)
|
|
168
|
+
.rpc('data_file_reference_list', {
|
|
169
|
+
p_table_name: table_name,
|
|
170
|
+
p_record_id: record_id,
|
|
171
|
+
p_organisation_id: organisation_id
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (rpcError) {
|
|
175
|
+
throw new Error(rpcError.message || 'Failed to fetch file references');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!fileIds || fileIds.length === 0) {
|
|
179
|
+
files = [];
|
|
180
|
+
} else {
|
|
181
|
+
// Fetch full file reference data for each ID, but only public files
|
|
182
|
+
const ids = fileIds.map((item: any) => item.id);
|
|
183
|
+
const { data: fullData, error: fetchError } = await supabase
|
|
184
|
+
.from('file_references')
|
|
185
|
+
.select('*')
|
|
186
|
+
.in('id', ids)
|
|
187
|
+
.eq('is_public', true); // Only public files in public context
|
|
188
|
+
|
|
189
|
+
if (fetchError) {
|
|
190
|
+
throw new Error(fetchError.message || 'Failed to fetch file references');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
files = fullData || [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Ensure all files are public (category RPC might return both)
|
|
198
|
+
const publicFiles = files.filter((f: any) => f.is_public === true);
|
|
199
|
+
|
|
200
|
+
if (publicFiles.length === 0) {
|
|
201
|
+
setFileUrl(null);
|
|
202
|
+
setFileReference(null);
|
|
203
|
+
setFileReferences([]);
|
|
204
|
+
setFileUrls(new Map());
|
|
205
|
+
setFileCount(0);
|
|
206
|
+
|
|
207
|
+
// Cache empty result
|
|
208
|
+
if (enableCache) {
|
|
209
|
+
publicFileCache.set(cacheKey, {
|
|
210
|
+
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
ttl: cacheTtl
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Convert to FileReference format
|
|
219
|
+
const fileRefs: FileReference[] = publicFiles.map((f: any) => ({
|
|
220
|
+
id: f.id,
|
|
221
|
+
table_name: f.table_name,
|
|
222
|
+
record_id: f.record_id,
|
|
223
|
+
file_path: f.file_path,
|
|
224
|
+
file_metadata: f.file_metadata || {},
|
|
225
|
+
organisation_id: f.organisation_id,
|
|
226
|
+
app_id: f.app_id,
|
|
227
|
+
is_public: f.is_public ?? true,
|
|
228
|
+
created_at: f.created_at,
|
|
229
|
+
updated_at: f.updated_at
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
setFileReferences(fileRefs);
|
|
233
|
+
setFileCount(fileRefs.length);
|
|
234
|
+
|
|
235
|
+
if (category && fileRefs.length > 0) {
|
|
236
|
+
// Single file mode - get first file
|
|
237
|
+
const firstFile = fileRefs[0];
|
|
238
|
+
setFileReference(firstFile);
|
|
239
|
+
|
|
240
|
+
// Generate public URL
|
|
241
|
+
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
242
|
+
setFileUrl(url);
|
|
243
|
+
} else {
|
|
244
|
+
// Multiple files mode - generate URLs for all files
|
|
245
|
+
const urlMap = new Map<string, string>();
|
|
246
|
+
for (const fileRef of fileRefs) {
|
|
247
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
248
|
+
if (url) {
|
|
249
|
+
urlMap.set(fileRef.id, url);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
setFileUrls(urlMap);
|
|
253
|
+
setFileReference(null);
|
|
254
|
+
setFileUrl(null);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Cache the result
|
|
258
|
+
if (enableCache) {
|
|
259
|
+
publicFileCache.set(cacheKey, {
|
|
260
|
+
data: {
|
|
261
|
+
fileUrl: category ? (fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null) : null,
|
|
262
|
+
fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
|
|
263
|
+
fileReferences: fileRefs,
|
|
264
|
+
fileUrls: category ? new Map() : (() => {
|
|
265
|
+
const urlMap = new Map<string, string>();
|
|
266
|
+
for (const fileRef of fileRefs) {
|
|
267
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
268
|
+
if (url) {
|
|
269
|
+
urlMap.set(fileRef.id, url);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return urlMap;
|
|
273
|
+
})(),
|
|
274
|
+
fileCount: fileRefs.length
|
|
275
|
+
},
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
ttl: cacheTtl
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
} catch (err) {
|
|
282
|
+
console.error('[usePublicFileDisplay] Error fetching files:', err);
|
|
283
|
+
const error = err instanceof Error ? err : new Error('Unknown error occurred');
|
|
284
|
+
setError(error);
|
|
285
|
+
setFileUrl(null);
|
|
286
|
+
setFileReference(null);
|
|
287
|
+
setFileReferences([]);
|
|
288
|
+
setFileUrls(new Map());
|
|
289
|
+
setFileCount(0);
|
|
290
|
+
} finally {
|
|
291
|
+
setIsLoading(false);
|
|
292
|
+
}
|
|
293
|
+
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
294
|
+
|
|
295
|
+
// Fetch files when parameters change
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
if (table_name && record_id && organisation_id) {
|
|
298
|
+
fetchFiles();
|
|
299
|
+
} else {
|
|
300
|
+
setFileUrl(null);
|
|
301
|
+
setFileReference(null);
|
|
302
|
+
setFileReferences([]);
|
|
303
|
+
setFileUrls(new Map());
|
|
304
|
+
setFileCount(0);
|
|
305
|
+
setIsLoading(false);
|
|
306
|
+
setError(null);
|
|
307
|
+
}
|
|
308
|
+
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
309
|
+
|
|
310
|
+
const refetch = useCallback(async (): Promise<void> => {
|
|
311
|
+
if (!table_name || !record_id || !organisation_id) return;
|
|
312
|
+
|
|
313
|
+
// Clear cache for this file
|
|
314
|
+
if (enableCache) {
|
|
315
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || 'all'}`;
|
|
316
|
+
publicFileCache.delete(cacheKey);
|
|
317
|
+
}
|
|
318
|
+
await fetchFiles();
|
|
319
|
+
}, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
fileUrl,
|
|
323
|
+
fileReference,
|
|
324
|
+
fileReferences,
|
|
325
|
+
fileUrls,
|
|
326
|
+
fileCount,
|
|
327
|
+
isLoading,
|
|
328
|
+
error,
|
|
329
|
+
refetch
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Clear all cached public file data
|
|
335
|
+
* Useful for testing or when you need to force refresh all data
|
|
336
|
+
*/
|
|
337
|
+
export function clearPublicFileDisplayCache(): void {
|
|
338
|
+
for (const [key] of publicFileCache) {
|
|
339
|
+
if (key.startsWith('public_file_')) {
|
|
340
|
+
publicFileCache.delete(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get cache statistics for debugging
|
|
347
|
+
*/
|
|
348
|
+
export function getPublicFileDisplayCacheStats(): { size: number; keys: string[] } {
|
|
349
|
+
const keys = Array.from(publicFileCache.keys()).filter(key => key.startsWith('public_file_'));
|
|
350
|
+
return {
|
|
351
|
+
size: keys.length,
|
|
352
|
+
keys
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|