@jmruthers/pace-core 0.5.101 → 0.5.103
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-DXELRJIX.js → DataTable-EEDFYMJP.js} +2 -2
- package/dist/{PublicLoadingSpinner-C2h8zg67.d.ts → PublicLoadingSpinner-48ewSMKK.d.ts} +22 -150
- package/dist/{chunk-2ZYHCFUO.js → chunk-5SGBVBRU.js} +2 -2
- package/dist/{chunk-EVVRUGQ2.js → chunk-62AVH7CM.js} +78 -55
- package/dist/{chunk-EVVRUGQ2.js.map → chunk-62AVH7CM.js.map} +1 -1
- package/dist/{chunk-A5DFMP3O.js → chunk-SZWCMVTQ.js} +135 -669
- package/dist/chunk-SZWCMVTQ.js.map +1 -0
- package/dist/{chunk-MKMKUCPF.js → chunk-X33A4WWI.js} +42 -141
- package/dist/chunk-X33A4WWI.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +3 -15
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +2 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/dist/types.js +3 -3
- package/dist/{usePublicRouteParams-BwMR2uub.d.ts → usePublicRouteParams-BiXgKiYa.d.ts} +1 -117
- 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 +2 -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/FileDisplayProps.md +77 -35
- 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 +11 -24
- 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/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.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 +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- 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 +29 -244
- package/docs/implementation-guides/file-reference-system.md +84 -21
- package/package.json +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +23 -13
- package/src/components/DataTable/hooks/useTableColumns.ts +36 -6
- package/src/components/FileDisplay/FileDisplay.test.tsx +1 -1
- package/src/components/FileDisplay/FileDisplay.tsx +189 -300
- package/src/components/PublicLayout/PublicPageHeader.tsx +15 -10
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +25 -35
- package/src/components/PublicLayout/index.ts +2 -5
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/index.ts +0 -2
- package/src/examples/PublicEventPage.tsx +17 -7
- package/src/examples/PublicPageApp.tsx +18 -8
- package/src/hooks/public/index.ts +2 -4
- package/src/hooks/useFileReference.ts +10 -1
- package/src/index.ts +0 -2
- package/src/utils/file-reference.ts +54 -9
- package/src/utils/storage/README.md +22 -20
- package/src/utils/storage/helpers.ts +12 -1
- package/dist/chunk-A5DFMP3O.js.map +0 -1
- package/dist/chunk-MKMKUCPF.js.map +0 -1
- package/docs/api/interfaces/EventLogoProps.md +0 -152
- package/docs/api/interfaces/UseEventLogoOptions.md +0 -74
- package/docs/api/interfaces/UseEventLogoReturn.md +0 -81
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
- package/src/components/PublicLayout/EventLogo.tsx +0 -474
- package/src/hooks/public/usePublicEventLogo.ts +0 -295
- package/src/hooks/useEventLogo.ts +0 -316
- /package/dist/{DataTable-DXELRJIX.js.map → DataTable-EEDFYMJP.js.map} +0 -0
- /package/dist/{chunk-2ZYHCFUO.js.map → chunk-5SGBVBRU.js.map} +0 -0
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef, useContext, useMemo } from 'react';
|
|
2
|
-
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
2
|
import { FileReference, FileCategory } from '../../types/file-reference';
|
|
4
|
-
import { useFileReferenceForRecord, useFileReference } from '../../hooks/useFileReference';
|
|
5
3
|
import { usePublicFileDisplay } from '../../hooks/public/usePublicFileDisplay';
|
|
6
4
|
import { useFileDisplay } from '../../hooks/useFileDisplay';
|
|
7
5
|
import { useFileUrl } from '../../hooks/useFileUrl';
|
|
8
|
-
import { getPublicUrl, getSignedUrl } from '../../utils/storage/helpers';
|
|
9
6
|
import { PublicPageContext, useIsPublicPage } from '../PublicLayout/PublicPageProvider';
|
|
10
7
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
11
8
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody, DialogFooter } from '../Dialog/Dialog';
|
|
12
9
|
import { Button } from '../Button/Button';
|
|
13
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Size classes for fallback display
|
|
13
|
+
*/
|
|
14
|
+
const fallbackSizeClasses = {
|
|
15
|
+
xs: 'h-4 w-4 text-xs',
|
|
16
|
+
sm: 'h-6 w-6 text-sm',
|
|
17
|
+
md: 'h-8 w-8 text-base',
|
|
18
|
+
lg: 'h-12 w-12 text-lg',
|
|
19
|
+
xl: 'h-16 w-16 text-xl',
|
|
20
|
+
'2xl': 'h-20 w-20 text-2xl'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default fallback text generator - extracts initials from file name
|
|
25
|
+
*/
|
|
26
|
+
function defaultGenerateFallbackText(fileName?: string): string {
|
|
27
|
+
if (!fileName) return 'FL';
|
|
28
|
+
|
|
29
|
+
// Extract initials from file name (without extension)
|
|
30
|
+
const baseName = fileName.replace(/\.[^/.]+$/, '');
|
|
31
|
+
const words = baseName.split(/[\s\-_]+/);
|
|
32
|
+
|
|
33
|
+
if (words.length === 0) return 'FL';
|
|
34
|
+
|
|
35
|
+
return words
|
|
36
|
+
.map(word => word.charAt(0).toUpperCase())
|
|
37
|
+
.join('')
|
|
38
|
+
.substring(0, 3); // Max 3 characters
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
export interface FileDisplayProps {
|
|
15
|
-
/** Supabase client instance. Optional when used in PublicPageProvider or UnifiedAuthProvider context */
|
|
16
|
-
supabase?: SupabaseClient;
|
|
17
42
|
table_name: string;
|
|
18
43
|
record_id: string;
|
|
19
44
|
organisation_id: string;
|
|
20
45
|
category?: FileCategory;
|
|
21
46
|
/** Display only a single file instead of all files. Uses first file (prefers images) from all files, without category filtering */
|
|
22
47
|
displayOnly?: boolean;
|
|
23
|
-
showUpload?: boolean;
|
|
24
48
|
showDelete?: boolean;
|
|
25
49
|
className?: string;
|
|
26
50
|
children?: React.ReactNode;
|
|
@@ -28,6 +52,14 @@ export interface FileDisplayProps {
|
|
|
28
52
|
loadingComponent?: React.ComponentType;
|
|
29
53
|
/** Custom error component to render when an error occurs */
|
|
30
54
|
errorComponent?: React.ComponentType<{ error: Error | string | null; retry?: () => void }>;
|
|
55
|
+
/** Whether to show fallback UI when no file is available or image fails to load */
|
|
56
|
+
showFallback?: boolean;
|
|
57
|
+
/** Custom function to generate fallback text from file name or other context */
|
|
58
|
+
generateFallbackText?: (fileName?: string) => string;
|
|
59
|
+
/** Explicit fallback text to display (overrides generateFallbackText) */
|
|
60
|
+
fallbackText?: string;
|
|
61
|
+
/** Size variant for fallback display (only applies when showFallback is true) */
|
|
62
|
+
fallbackSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
// Shared rendering logic for file display
|
|
@@ -46,11 +78,13 @@ interface FileDisplayContentProps {
|
|
|
46
78
|
children?: React.ReactNode;
|
|
47
79
|
onDelete?: () => Promise<void>;
|
|
48
80
|
clearError?: () => void;
|
|
49
|
-
supabase?: SupabaseClient;
|
|
50
81
|
organisation_id: string;
|
|
51
|
-
loadingUrls?: Set<string>;
|
|
52
82
|
loadingComponent?: React.ComponentType;
|
|
53
83
|
errorComponent?: React.ComponentType<{ error: Error | string | null; retry?: () => void }>;
|
|
84
|
+
showFallback?: boolean;
|
|
85
|
+
generateFallbackText?: (fileName?: string) => string;
|
|
86
|
+
fallbackText?: string;
|
|
87
|
+
fallbackSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
54
88
|
}
|
|
55
89
|
|
|
56
90
|
function FileDisplayContent({
|
|
@@ -68,19 +102,33 @@ function FileDisplayContent({
|
|
|
68
102
|
children,
|
|
69
103
|
onDelete,
|
|
70
104
|
clearError,
|
|
71
|
-
supabase,
|
|
72
105
|
organisation_id,
|
|
73
|
-
loadingUrls = new Set(),
|
|
74
106
|
loadingComponent: LoadingComponent,
|
|
75
|
-
errorComponent: ErrorComponent
|
|
107
|
+
errorComponent: ErrorComponent,
|
|
108
|
+
showFallback = false,
|
|
109
|
+
generateFallbackText = defaultGenerateFallbackText,
|
|
110
|
+
fallbackText,
|
|
111
|
+
fallbackSize = 'md'
|
|
76
112
|
}: FileDisplayContentProps) {
|
|
77
113
|
const [imageError, setImageError] = useState(false);
|
|
78
114
|
const [internalFileUrls, setInternalFileUrls] = useState<Map<string, string>>(new Map(fileUrls));
|
|
79
115
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
80
|
-
const loadedFilesRef = useRef<Set<string>>(new Set());
|
|
81
|
-
const loadingUrlsRef = useRef<Set<string>>(new Set(loadingUrls));
|
|
82
116
|
const fileReferencesRef = useRef<FileReference[]>([]);
|
|
83
117
|
|
|
118
|
+
// Compute fallback text
|
|
119
|
+
const computedFallbackText = useMemo(() => {
|
|
120
|
+
if (fallbackText) return fallbackText;
|
|
121
|
+
const fileName = fileReference?.file_metadata?.fileName;
|
|
122
|
+
return generateFallbackText(fileName);
|
|
123
|
+
}, [fallbackText, fileReference, generateFallbackText]);
|
|
124
|
+
|
|
125
|
+
// Compute fallback classes
|
|
126
|
+
const fallbackClasses = useMemo(() => {
|
|
127
|
+
const sizeClass = fallbackSizeClasses[fallbackSize];
|
|
128
|
+
const baseClasses = 'flex items-center justify-center bg-sec-100 text-sec-600 font-semibold rounded';
|
|
129
|
+
return `${baseClasses} ${sizeClass} ${className}`.trim();
|
|
130
|
+
}, [fallbackSize, className]);
|
|
131
|
+
|
|
84
132
|
// Sync fileUrls prop with internal state
|
|
85
133
|
useEffect(() => {
|
|
86
134
|
setInternalFileUrls(new Map(fileUrls));
|
|
@@ -93,64 +141,11 @@ function FileDisplayContent({
|
|
|
93
141
|
|
|
94
142
|
if (currentIds !== prevIds) {
|
|
95
143
|
fileReferencesRef.current = fileReferences;
|
|
96
|
-
// Reset
|
|
97
|
-
loadedFilesRef.current.clear();
|
|
144
|
+
// Reset internal URLs when file references change
|
|
98
145
|
setInternalFileUrls(new Map());
|
|
99
146
|
}
|
|
100
147
|
}, [fileReferences]);
|
|
101
148
|
|
|
102
|
-
// Fetch URLs for all file references (for multiple files view when not using context hooks)
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (!supabase || category || fileReferences.length === 0) return;
|
|
105
|
-
|
|
106
|
-
const loadFileUrls = async () => {
|
|
107
|
-
// Find files that need URLs loaded
|
|
108
|
-
const urlsToLoad = fileReferences.filter(fileRef => {
|
|
109
|
-
return !loadedFilesRef.current.has(fileRef.id) && !loadingUrlsRef.current.has(fileRef.id);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (urlsToLoad.length === 0) return;
|
|
113
|
-
|
|
114
|
-
// Mark files as loading (update both state and ref)
|
|
115
|
-
loadingUrlsRef.current = new Set([...loadingUrlsRef.current, ...urlsToLoad.map(f => f.id)]);
|
|
116
|
-
|
|
117
|
-
// Load URLs for files that need them
|
|
118
|
-
for (const fileRef of urlsToLoad) {
|
|
119
|
-
try {
|
|
120
|
-
let url: string | null = null;
|
|
121
|
-
|
|
122
|
-
if (fileRef.is_public) {
|
|
123
|
-
// Public files: generate public URL
|
|
124
|
-
url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
125
|
-
} else {
|
|
126
|
-
// Private files: generate signed URL
|
|
127
|
-
const signedUrlResult = await getSignedUrl(supabase, fileRef.file_path, {
|
|
128
|
-
appName: 'file-reference',
|
|
129
|
-
orgId: organisation_id,
|
|
130
|
-
expiresIn: 3600
|
|
131
|
-
});
|
|
132
|
-
url = signedUrlResult?.url || null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (url) {
|
|
136
|
-
setInternalFileUrls(prev => {
|
|
137
|
-
const updated = new Map(prev);
|
|
138
|
-
updated.set(fileRef.id, url!);
|
|
139
|
-
return updated;
|
|
140
|
-
});
|
|
141
|
-
loadedFilesRef.current.add(fileRef.id);
|
|
142
|
-
}
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error(`Failed to load URL for file ${fileRef.id}:`, error);
|
|
145
|
-
} finally {
|
|
146
|
-
loadingUrlsRef.current.delete(fileRef.id);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
loadFileUrls();
|
|
152
|
-
}, [category, fileReferences.map(f => f.id).join(','), supabase, organisation_id]);
|
|
153
|
-
|
|
154
149
|
const handleDeleteClick = () => {
|
|
155
150
|
setDeleteDialogOpen(true);
|
|
156
151
|
};
|
|
@@ -163,8 +158,28 @@ function FileDisplayContent({
|
|
|
163
158
|
setImageError(false);
|
|
164
159
|
};
|
|
165
160
|
|
|
166
|
-
const handleImageError = () => {
|
|
161
|
+
const handleImageError = (e?: React.SyntheticEvent<HTMLImageElement>) => {
|
|
167
162
|
setImageError(true);
|
|
163
|
+
|
|
164
|
+
// If fallback is enabled, show fallback UI when image fails to load
|
|
165
|
+
if (showFallback && e) {
|
|
166
|
+
const target = e.target as HTMLImageElement;
|
|
167
|
+
target.style.display = 'none';
|
|
168
|
+
|
|
169
|
+
// Check if fallback already exists
|
|
170
|
+
if (target.nextSibling && (target.nextSibling as HTMLElement).className.includes('bg-sec-100')) {
|
|
171
|
+
return; // Fallback already shown
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create fallback element
|
|
175
|
+
const fallback = document.createElement('div');
|
|
176
|
+
fallback.className = fallbackClasses;
|
|
177
|
+
fallback.textContent = computedFallbackText;
|
|
178
|
+
fallback.title = fileReference?.file_metadata?.fileName || 'File';
|
|
179
|
+
|
|
180
|
+
// Insert fallback after the image
|
|
181
|
+
target.parentNode?.insertBefore(fallback, target.nextSibling);
|
|
182
|
+
}
|
|
168
183
|
};
|
|
169
184
|
|
|
170
185
|
const getFileIcon = (fileType: string) => {
|
|
@@ -186,6 +201,37 @@ function FileDisplayContent({
|
|
|
186
201
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
187
202
|
};
|
|
188
203
|
|
|
204
|
+
// Show fallback immediately if enabled and we have no files (even during loading)
|
|
205
|
+
// This provides better UX by showing fallback UI instead of a spinner when we know there are no files
|
|
206
|
+
if (fileCount === 0 && !isLoading) {
|
|
207
|
+
// Show fallback if enabled
|
|
208
|
+
if (showFallback) {
|
|
209
|
+
return (
|
|
210
|
+
<div className={fallbackClasses} title="No file">
|
|
211
|
+
{computedFallbackText}
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div className={`text-sec-500 text-center p-4 ${className}`}>
|
|
219
|
+
No files found
|
|
220
|
+
{children}
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// During loading, show fallback if enabled (better UX than spinner for empty states)
|
|
226
|
+
if (isLoading && showFallback && fileCount === 0) {
|
|
227
|
+
return (
|
|
228
|
+
<div className={fallbackClasses} title="Loading...">
|
|
229
|
+
{computedFallbackText}
|
|
230
|
+
{children}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
189
235
|
if (isLoading) {
|
|
190
236
|
if (LoadingComponent) {
|
|
191
237
|
return <LoadingComponent />;
|
|
@@ -201,6 +247,16 @@ function FileDisplayContent({
|
|
|
201
247
|
if (ErrorComponent) {
|
|
202
248
|
return <ErrorComponent error={error} retry={clearError} />;
|
|
203
249
|
}
|
|
250
|
+
|
|
251
|
+
// Show fallback if enabled
|
|
252
|
+
if (showFallback) {
|
|
253
|
+
return (
|
|
254
|
+
<div className={fallbackClasses} title="File unavailable">
|
|
255
|
+
{computedFallbackText}
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
204
260
|
return (
|
|
205
261
|
<div className={`p-4 bg-acc-50 border border-acc-200 rounded-lg ${className}`}>
|
|
206
262
|
<div className="text-acc-600">
|
|
@@ -218,21 +274,21 @@ function FileDisplayContent({
|
|
|
218
274
|
);
|
|
219
275
|
}
|
|
220
276
|
|
|
221
|
-
if (fileCount === 0) {
|
|
222
|
-
return (
|
|
223
|
-
<div className={`text-sec-500 text-center p-4 ${className}`}>
|
|
224
|
-
No files found
|
|
225
|
-
{children}
|
|
226
|
-
</div>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
277
|
// Single file display (when category or displayOnly is specified)
|
|
231
278
|
if ((category || displayOnly) && fileReference) {
|
|
232
279
|
const isImage = fileReference.file_metadata.fileType?.startsWith('image/');
|
|
233
280
|
|
|
234
281
|
// Simplified image-only display when displayOnly is true and it's an image
|
|
235
|
-
if (displayOnly && isImage && !
|
|
282
|
+
if (displayOnly && isImage && !showDelete) {
|
|
283
|
+
// Show fallback if image error occurred and fallback is enabled
|
|
284
|
+
if (imageError && showFallback) {
|
|
285
|
+
return (
|
|
286
|
+
<div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
|
|
287
|
+
{computedFallbackText}
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
236
292
|
// Show loading skeleton if URL is not available yet
|
|
237
293
|
if (!fileUrl) {
|
|
238
294
|
return (
|
|
@@ -243,6 +299,7 @@ function FileDisplayContent({
|
|
|
243
299
|
</div>
|
|
244
300
|
);
|
|
245
301
|
}
|
|
302
|
+
|
|
246
303
|
return (
|
|
247
304
|
<img
|
|
248
305
|
src={fileUrl}
|
|
@@ -254,6 +311,15 @@ function FileDisplayContent({
|
|
|
254
311
|
}
|
|
255
312
|
|
|
256
313
|
// Standard single file display with wrapper
|
|
314
|
+
// For displayOnly mode, if fallback is enabled and there's no URL or image error, show fallback instead of folder icon
|
|
315
|
+
if (displayOnly && showFallback && (!fileUrl || imageError || !isImage)) {
|
|
316
|
+
return (
|
|
317
|
+
<div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
|
|
318
|
+
{computedFallbackText}
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
257
323
|
return (
|
|
258
324
|
<div className={`space-y-2 ${className}`}>
|
|
259
325
|
{isImage && fileUrl && !imageError ? (
|
|
@@ -295,6 +361,11 @@ function FileDisplayContent({
|
|
|
295
361
|
</>
|
|
296
362
|
)}
|
|
297
363
|
</div>
|
|
364
|
+
) : isImage && imageError && showFallback ? (
|
|
365
|
+
// Show fallback when image fails to load and fallback is enabled
|
|
366
|
+
<div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
|
|
367
|
+
{computedFallbackText}
|
|
368
|
+
</div>
|
|
298
369
|
) : (
|
|
299
370
|
<div className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
|
|
300
371
|
<span className="text-2xl">
|
|
@@ -352,16 +423,11 @@ function FileDisplayContent({
|
|
|
352
423
|
{fileReferences.map((fileRef) => {
|
|
353
424
|
const isImage = fileRef.file_metadata.fileType?.startsWith('image/');
|
|
354
425
|
const fileUrl = internalFileUrls.get(fileRef.id) || null;
|
|
355
|
-
const isLoadingUrl = loadingUrlsRef.current.has(fileRef.id);
|
|
356
426
|
const canDownload = !isImage && fileUrl;
|
|
357
427
|
|
|
358
428
|
return (
|
|
359
429
|
<div key={fileRef.id} className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
|
|
360
|
-
{
|
|
361
|
-
<div className="w-12 h-12 flex items-center justify-center bg-sec-100 rounded animate-pulse">
|
|
362
|
-
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-main-500"></div>
|
|
363
|
-
</div>
|
|
364
|
-
) : isImage && fileUrl ? (
|
|
430
|
+
{isImage && fileUrl ? (
|
|
365
431
|
<img
|
|
366
432
|
src={fileUrl}
|
|
367
433
|
alt={fileRef.file_metadata.fileName || 'File'}
|
|
@@ -413,181 +479,6 @@ function FileDisplayContent({
|
|
|
413
479
|
);
|
|
414
480
|
}
|
|
415
481
|
|
|
416
|
-
/**
|
|
417
|
-
* Internal component for backwards compatibility (when supabase is explicitly provided)
|
|
418
|
-
*/
|
|
419
|
-
function FileDisplayBackwardsCompat({
|
|
420
|
-
supabase,
|
|
421
|
-
table_name,
|
|
422
|
-
record_id,
|
|
423
|
-
organisation_id,
|
|
424
|
-
category,
|
|
425
|
-
displayOnly = false,
|
|
426
|
-
showUpload = false,
|
|
427
|
-
showDelete = false,
|
|
428
|
-
className = '',
|
|
429
|
-
children,
|
|
430
|
-
loadingComponent,
|
|
431
|
-
errorComponent
|
|
432
|
-
}: Required<Pick<FileDisplayProps, 'supabase'>> & Omit<FileDisplayProps, 'supabase'>) {
|
|
433
|
-
const {
|
|
434
|
-
isLoading: isLoadingForRecord,
|
|
435
|
-
error: errorForRecord,
|
|
436
|
-
fileUrl,
|
|
437
|
-
fileReference,
|
|
438
|
-
fileReferences,
|
|
439
|
-
fileCount,
|
|
440
|
-
loadFileReference,
|
|
441
|
-
loadFileUrl,
|
|
442
|
-
loadFileReferences,
|
|
443
|
-
loadFileCount,
|
|
444
|
-
deleteFile,
|
|
445
|
-
clearError
|
|
446
|
-
} = useFileReferenceForRecord(supabase, table_name, record_id, organisation_id);
|
|
447
|
-
|
|
448
|
-
// Use useFileReference to get getFilesByCategory when category is provided
|
|
449
|
-
const {
|
|
450
|
-
getFilesByCategory,
|
|
451
|
-
isLoading: isLoadingCategory,
|
|
452
|
-
error: errorCategory
|
|
453
|
-
} = useFileReference(supabase);
|
|
454
|
-
|
|
455
|
-
// Consolidated state for category mode
|
|
456
|
-
const [categoryFileReferences, setCategoryFileReferences] = useState<FileReference[]>([]);
|
|
457
|
-
const categoryFileReference = categoryFileReferences[0] || null;
|
|
458
|
-
const categoryFileUrlHook = useFileUrl(categoryFileReference, {
|
|
459
|
-
supabase,
|
|
460
|
-
organisation_id,
|
|
461
|
-
autoLoad: !!categoryFileReference
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
// Consolidated state for displayOnly mode
|
|
465
|
-
const [displayOnlyFileReference, setDisplayOnlyFileReference] = useState<FileReference | null>(null);
|
|
466
|
-
const displayOnlyFileUrlHook = useFileUrl(displayOnlyFileReference, {
|
|
467
|
-
supabase,
|
|
468
|
-
organisation_id,
|
|
469
|
-
autoLoad: !!displayOnlyFileReference
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// Load files by category when category is provided
|
|
473
|
-
useEffect(() => {
|
|
474
|
-
if (category) {
|
|
475
|
-
const loadCategoryFiles = async () => {
|
|
476
|
-
try {
|
|
477
|
-
const files = await getFilesByCategory(table_name, record_id, category, organisation_id);
|
|
478
|
-
setCategoryFileReferences(files);
|
|
479
|
-
|
|
480
|
-
// URL generation is handled by useFileUrl hook
|
|
481
|
-
if (files.length === 0) {
|
|
482
|
-
setCategoryFileReferences([]);
|
|
483
|
-
}
|
|
484
|
-
} catch (err) {
|
|
485
|
-
console.error('[FileDisplayBackwardsCompat] Error loading files by category:', err);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
loadCategoryFiles();
|
|
490
|
-
} else {
|
|
491
|
-
// Clear category files when no category
|
|
492
|
-
setCategoryFileReferences([]);
|
|
493
|
-
}
|
|
494
|
-
}, [category, table_name, record_id, organisation_id, supabase, getFilesByCategory]);
|
|
495
|
-
|
|
496
|
-
// Load file data on mount (when no category)
|
|
497
|
-
useEffect(() => {
|
|
498
|
-
if (!category) {
|
|
499
|
-
loadFileCount();
|
|
500
|
-
loadFileReferences();
|
|
501
|
-
}
|
|
502
|
-
}, [loadFileCount, loadFileReferences, category]);
|
|
503
|
-
|
|
504
|
-
// Load file URL when file reference is available (when no category)
|
|
505
|
-
useEffect(() => {
|
|
506
|
-
if (!category && fileReference) {
|
|
507
|
-
loadFileUrl();
|
|
508
|
-
}
|
|
509
|
-
}, [category, fileReference, loadFileUrl]);
|
|
510
|
-
|
|
511
|
-
// Handle displayOnly mode: select first file (prefer images) from all files
|
|
512
|
-
useEffect(() => {
|
|
513
|
-
if (displayOnly && !category && fileReferences.length > 0) {
|
|
514
|
-
// Prefer image files
|
|
515
|
-
const imageFiles = fileReferences.filter(f =>
|
|
516
|
-
f.file_metadata.fileType?.startsWith('image/')
|
|
517
|
-
);
|
|
518
|
-
const targetFile = imageFiles.length > 0 ? imageFiles[0] : fileReferences[0];
|
|
519
|
-
setDisplayOnlyFileReference(targetFile);
|
|
520
|
-
// URL generation is handled by useFileUrl hook
|
|
521
|
-
} else {
|
|
522
|
-
// Clear displayOnly files when not in displayOnly mode or when category is specified
|
|
523
|
-
setDisplayOnlyFileReference(null);
|
|
524
|
-
}
|
|
525
|
-
}, [displayOnly, category, fileReferences]);
|
|
526
|
-
|
|
527
|
-
const handleDelete = async () => {
|
|
528
|
-
await deleteFile(true);
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// Determine final file reference and URL based on mode
|
|
532
|
-
// Priority: category > displayOnly > default (all files)
|
|
533
|
-
let finalFileReference: FileReference | null = null;
|
|
534
|
-
let finalFileUrl: string | null = null;
|
|
535
|
-
let finalFileReferences: FileReference[] = [];
|
|
536
|
-
let finalFileCount = 0;
|
|
537
|
-
let finalIsLoading = false;
|
|
538
|
-
let finalError: string | Error | null = null;
|
|
539
|
-
|
|
540
|
-
if (category) {
|
|
541
|
-
// Category mode: use category-filtered files
|
|
542
|
-
finalFileReference = categoryFileReference;
|
|
543
|
-
finalFileUrl = categoryFileUrlHook.url;
|
|
544
|
-
finalFileReferences = categoryFileReferences;
|
|
545
|
-
finalFileCount = categoryFileReferences.length;
|
|
546
|
-
finalIsLoading = isLoadingCategory || categoryFileUrlHook.isLoading;
|
|
547
|
-
finalError = errorCategory || categoryFileUrlHook.error;
|
|
548
|
-
} else if (displayOnly) {
|
|
549
|
-
// DisplayOnly mode: use first file from all files (prefers images)
|
|
550
|
-
finalFileReference = displayOnlyFileReference;
|
|
551
|
-
finalFileUrl = displayOnlyFileUrlHook.url;
|
|
552
|
-
finalFileReferences = displayOnlyFileReference ? [displayOnlyFileReference] : [];
|
|
553
|
-
finalFileCount = displayOnlyFileReference ? 1 : 0;
|
|
554
|
-
finalIsLoading = isLoadingForRecord || displayOnlyFileUrlHook.isLoading;
|
|
555
|
-
finalError = errorForRecord || displayOnlyFileUrlHook.error;
|
|
556
|
-
} else {
|
|
557
|
-
// Default mode: show all files
|
|
558
|
-
finalFileReference = fileReference;
|
|
559
|
-
finalFileUrl = fileUrl;
|
|
560
|
-
finalFileReferences = fileReferences;
|
|
561
|
-
finalFileCount = fileCount;
|
|
562
|
-
finalIsLoading = isLoadingForRecord;
|
|
563
|
-
finalError = errorForRecord;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return (
|
|
567
|
-
<FileDisplayContent
|
|
568
|
-
isLoading={finalIsLoading}
|
|
569
|
-
error={finalError}
|
|
570
|
-
fileUrl={finalFileUrl}
|
|
571
|
-
fileReference={finalFileReference}
|
|
572
|
-
fileReferences={finalFileReferences}
|
|
573
|
-
fileUrls={new Map()} // Will be populated by FileDisplayContent's useEffect
|
|
574
|
-
fileCount={finalFileCount}
|
|
575
|
-
category={category}
|
|
576
|
-
displayOnly={displayOnly}
|
|
577
|
-
showDelete={showDelete}
|
|
578
|
-
className={className}
|
|
579
|
-
children={children}
|
|
580
|
-
onDelete={handleDelete}
|
|
581
|
-
clearError={clearError}
|
|
582
|
-
supabase={supabase}
|
|
583
|
-
organisation_id={organisation_id}
|
|
584
|
-
loadingUrls={new Set()}
|
|
585
|
-
loadingComponent={loadingComponent}
|
|
586
|
-
errorComponent={errorComponent}
|
|
587
|
-
/>
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
482
|
/**
|
|
592
483
|
* Internal component for public page context
|
|
593
484
|
* Uses PublicPageContext to get Supabase client
|
|
@@ -598,13 +489,16 @@ function FileDisplayPublic({
|
|
|
598
489
|
organisation_id,
|
|
599
490
|
category,
|
|
600
491
|
displayOnly = false,
|
|
601
|
-
showUpload = false,
|
|
602
492
|
showDelete = false,
|
|
603
493
|
className = '',
|
|
604
494
|
children,
|
|
605
495
|
loadingComponent,
|
|
606
|
-
errorComponent
|
|
607
|
-
|
|
496
|
+
errorComponent,
|
|
497
|
+
showFallback,
|
|
498
|
+
generateFallbackText,
|
|
499
|
+
fallbackText,
|
|
500
|
+
fallbackSize
|
|
501
|
+
}: FileDisplayProps) {
|
|
608
502
|
const publicPageContext = useContext(PublicPageContext);
|
|
609
503
|
const supabase = publicPageContext?.supabase ?? null;
|
|
610
504
|
|
|
@@ -633,9 +527,9 @@ function FileDisplayPublic({
|
|
|
633
527
|
{ supabase }
|
|
634
528
|
);
|
|
635
529
|
|
|
636
|
-
//
|
|
530
|
+
// Public context doesn't support delete operations
|
|
637
531
|
const handleDelete = async () => {
|
|
638
|
-
|
|
532
|
+
// Delete operations are not available in public context for security reasons
|
|
639
533
|
};
|
|
640
534
|
|
|
641
535
|
// Handle displayOnly mode: select first file (prefer images) from all files
|
|
@@ -673,10 +567,13 @@ function FileDisplayPublic({
|
|
|
673
567
|
className={className}
|
|
674
568
|
children={children}
|
|
675
569
|
onDelete={showDelete ? handleDelete : undefined}
|
|
676
|
-
supabase={supabase}
|
|
677
570
|
organisation_id={organisation_id}
|
|
678
571
|
loadingComponent={loadingComponent}
|
|
679
572
|
errorComponent={errorComponent}
|
|
573
|
+
showFallback={showFallback}
|
|
574
|
+
generateFallbackText={generateFallbackText}
|
|
575
|
+
fallbackText={fallbackText}
|
|
576
|
+
fallbackSize={fallbackSize}
|
|
680
577
|
/>
|
|
681
578
|
);
|
|
682
579
|
}
|
|
@@ -691,13 +588,16 @@ function FileDisplayAuthenticated({
|
|
|
691
588
|
organisation_id,
|
|
692
589
|
category,
|
|
693
590
|
displayOnly = false,
|
|
694
|
-
showUpload = false,
|
|
695
591
|
showDelete = false,
|
|
696
592
|
className = '',
|
|
697
593
|
children,
|
|
698
594
|
loadingComponent,
|
|
699
|
-
errorComponent
|
|
700
|
-
|
|
595
|
+
errorComponent,
|
|
596
|
+
showFallback,
|
|
597
|
+
generateFallbackText,
|
|
598
|
+
fallbackText,
|
|
599
|
+
fallbackSize
|
|
600
|
+
}: FileDisplayProps) {
|
|
701
601
|
const { supabase } = useUnifiedAuth();
|
|
702
602
|
|
|
703
603
|
if (!supabase) {
|
|
@@ -756,10 +656,9 @@ function FileDisplayAuthenticated({
|
|
|
756
656
|
}
|
|
757
657
|
}, [displayOnly, category, fileReferences, fileUrls]);
|
|
758
658
|
|
|
759
|
-
//
|
|
760
|
-
// For now, we'll show a warning
|
|
659
|
+
// Delete operation - implementation pending
|
|
761
660
|
const handleDelete = async () => {
|
|
762
|
-
|
|
661
|
+
// TODO: Implement delete via FileReferenceService when delete functionality is needed
|
|
763
662
|
};
|
|
764
663
|
|
|
765
664
|
// Determine final file reference and URL based on mode
|
|
@@ -798,10 +697,13 @@ function FileDisplayAuthenticated({
|
|
|
798
697
|
className={className}
|
|
799
698
|
children={children}
|
|
800
699
|
onDelete={showDelete ? handleDelete : undefined}
|
|
801
|
-
supabase={supabase}
|
|
802
700
|
organisation_id={organisation_id}
|
|
803
701
|
loadingComponent={loadingComponent}
|
|
804
702
|
errorComponent={errorComponent}
|
|
703
|
+
showFallback={showFallback}
|
|
704
|
+
generateFallbackText={generateFallbackText}
|
|
705
|
+
fallbackText={fallbackText}
|
|
706
|
+
fallbackSize={fallbackSize}
|
|
805
707
|
/>
|
|
806
708
|
);
|
|
807
709
|
}
|
|
@@ -812,8 +714,7 @@ function FileDisplayAuthenticated({
|
|
|
812
714
|
* This component is context-aware and automatically detects whether it's being used
|
|
813
715
|
* in a public or authenticated context. It fetches and displays files from storage.
|
|
814
716
|
*
|
|
815
|
-
*
|
|
816
|
-
* When `supabase` prop is not provided, it automatically detects context and uses:
|
|
717
|
+
* The component automatically detects context and uses:
|
|
817
718
|
* - PublicPageProvider context for public pages
|
|
818
719
|
* - UnifiedAuthProvider context for authenticated pages
|
|
819
720
|
*
|
|
@@ -823,39 +724,21 @@ function FileDisplayAuthenticated({
|
|
|
823
724
|
* @returns React element with file display
|
|
824
725
|
*/
|
|
825
726
|
export function FileDisplay({
|
|
826
|
-
supabase,
|
|
827
727
|
table_name,
|
|
828
728
|
record_id,
|
|
829
729
|
organisation_id,
|
|
830
730
|
category,
|
|
831
731
|
displayOnly = false,
|
|
832
|
-
showUpload = false,
|
|
833
732
|
showDelete = false,
|
|
834
733
|
className = '',
|
|
835
734
|
children,
|
|
836
735
|
loadingComponent,
|
|
837
|
-
errorComponent
|
|
736
|
+
errorComponent,
|
|
737
|
+
showFallback,
|
|
738
|
+
generateFallbackText,
|
|
739
|
+
fallbackText,
|
|
740
|
+
fallbackSize
|
|
838
741
|
}: FileDisplayProps) {
|
|
839
|
-
// If supabase is explicitly provided, use backwards compatible mode
|
|
840
|
-
if (supabase) {
|
|
841
|
-
return (
|
|
842
|
-
<FileDisplayBackwardsCompat
|
|
843
|
-
supabase={supabase}
|
|
844
|
-
table_name={table_name}
|
|
845
|
-
record_id={record_id}
|
|
846
|
-
organisation_id={organisation_id}
|
|
847
|
-
category={category}
|
|
848
|
-
displayOnly={displayOnly}
|
|
849
|
-
showUpload={showUpload}
|
|
850
|
-
showDelete={showDelete}
|
|
851
|
-
className={className}
|
|
852
|
-
children={children}
|
|
853
|
-
loadingComponent={loadingComponent}
|
|
854
|
-
errorComponent={errorComponent}
|
|
855
|
-
/>
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
742
|
// Check which context we're in and route to the appropriate component
|
|
860
743
|
const isPublicPage = useIsPublicPage();
|
|
861
744
|
|
|
@@ -868,17 +751,20 @@ export function FileDisplay({
|
|
|
868
751
|
organisation_id={organisation_id}
|
|
869
752
|
category={category}
|
|
870
753
|
displayOnly={displayOnly}
|
|
871
|
-
showUpload={showUpload}
|
|
872
754
|
showDelete={showDelete}
|
|
873
755
|
className={className}
|
|
874
756
|
children={children}
|
|
875
757
|
loadingComponent={loadingComponent}
|
|
876
758
|
errorComponent={errorComponent}
|
|
759
|
+
showFallback={showFallback}
|
|
760
|
+
generateFallbackText={generateFallbackText}
|
|
761
|
+
fallbackText={fallbackText}
|
|
762
|
+
fallbackSize={fallbackSize}
|
|
877
763
|
/>
|
|
878
764
|
);
|
|
879
765
|
}
|
|
880
766
|
|
|
881
|
-
// Otherwise,
|
|
767
|
+
// Otherwise, use the authenticated component
|
|
882
768
|
// It will show an error if not in UnifiedAuthProvider
|
|
883
769
|
return (
|
|
884
770
|
<FileDisplayAuthenticated
|
|
@@ -887,12 +773,15 @@ export function FileDisplay({
|
|
|
887
773
|
organisation_id={organisation_id}
|
|
888
774
|
category={category}
|
|
889
775
|
displayOnly={displayOnly}
|
|
890
|
-
showUpload={showUpload}
|
|
891
776
|
showDelete={showDelete}
|
|
892
777
|
className={className}
|
|
893
778
|
children={children}
|
|
894
779
|
loadingComponent={loadingComponent}
|
|
895
780
|
errorComponent={errorComponent}
|
|
781
|
+
showFallback={showFallback}
|
|
782
|
+
generateFallbackText={generateFallbackText}
|
|
783
|
+
fallbackText={fallbackText}
|
|
784
|
+
fallbackSize={fallbackSize}
|
|
896
785
|
/>
|
|
897
786
|
);
|
|
898
787
|
}
|