@jmruthers/pace-core 0.5.187 → 0.5.189
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-K3RJRSOX.js → DataTable-GUFUNZ3N.js} +5 -5
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-B8HaLe69.d.ts} +47 -17
- package/dist/{UnifiedAuthProvider-B76OWOAT.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
- package/dist/{chunk-FMTK4XNN.js → chunk-2UUZZJFT.js} +3 -3
- package/dist/{chunk-3IC5WCMO.js → chunk-3GOZZZYH.js} +3 -3
- package/dist/{chunk-ULX5FYEM.js → chunk-DDM4CCYT.js} +3 -3
- package/dist/{chunk-K2JGDXGU.js → chunk-E7UAOUMY.js} +2 -2
- package/dist/{chunk-T6ZJVI3A.js → chunk-IM4QE42D.js} +4 -4
- package/dist/{chunk-3NFNJOO7.js → chunk-MX64ZF6I.js} +4 -4
- package/dist/{chunk-C4OYJOV4.js → chunk-UCQSRW7Z.js} +829 -829
- package/dist/chunk-UCQSRW7Z.js.map +1 -0
- package/dist/{chunk-WK2Y6TGA.js → chunk-VGZZXKBR.js} +3 -3
- package/dist/chunk-VGZZXKBR.js.map +1 -0
- package/dist/{chunk-LBBUPSSC.js → chunk-YGPFYGA6.js} +3760 -3692
- package/dist/chunk-YGPFYGA6.js.map +1 -0
- package/dist/components.d.ts +1 -2
- package/dist/components.js +6 -10
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.js +5 -5
- 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/Logger.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/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/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.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/ComplianceResult.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- 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/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/LoggerConfig.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/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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 +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/UseResourcePermissionsOptions.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 +6 -45
- package/docs/api-reference/components.md +57 -22
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/implementation-guides/public-pages.md +140 -1230
- package/docs/standards/05-security-standard.md +3 -1
- package/docs/standards/07-rbac-and-rls-standard.md +356 -0
- package/package.json +1 -2
- package/src/__tests__/public-recipe-view.test.ts +199 -0
- package/src/__tests__/rls-policies.test.ts +333 -0
- package/src/components/Avatar/Avatar.test.tsx +183 -209
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +7 -5
- package/src/components/index.ts +2 -1
- package/src/index.ts +2 -1
- package/src/services/OrganisationService.ts +5 -4
- package/dist/chunk-C4OYJOV4.js.map +0 -1
- package/dist/chunk-LBBUPSSC.js.map +0 -1
- package/dist/chunk-WK2Y6TGA.js.map +0 -1
- /package/dist/{DataTable-K3RJRSOX.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-B76OWOAT.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
- /package/dist/{chunk-FMTK4XNN.js.map → chunk-2UUZZJFT.js.map} +0 -0
- /package/dist/{chunk-3IC5WCMO.js.map → chunk-3GOZZZYH.js.map} +0 -0
- /package/dist/{chunk-ULX5FYEM.js.map → chunk-DDM4CCYT.js.map} +0 -0
- /package/dist/{chunk-K2JGDXGU.js.map → chunk-E7UAOUMY.js.map} +0 -0
- /package/dist/{chunk-T6ZJVI3A.js.map → chunk-IM4QE42D.js.map} +0 -0
- /package/dist/{chunk-3NFNJOO7.js.map → chunk-MX64ZF6I.js.map} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-E7UAOUMY.js";
|
|
4
4
|
import {
|
|
5
5
|
useUnifiedAuth
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VGZZXKBR.js";
|
|
7
7
|
import {
|
|
8
8
|
assertAppId
|
|
9
9
|
} from "./chunk-QXHPKYJV.js";
|
|
@@ -371,337 +371,6 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
|
371
371
|
};
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
// src/hooks/useEventTheme.ts
|
|
375
|
-
import { useEffect as useEffect4 } from "react";
|
|
376
|
-
import { useLocation } from "react-router-dom";
|
|
377
|
-
var log2 = createLogger("useEventTheme");
|
|
378
|
-
function useEventTheme(event) {
|
|
379
|
-
const location = useLocation();
|
|
380
|
-
let selectedEvent;
|
|
381
|
-
try {
|
|
382
|
-
if (event === void 0) {
|
|
383
|
-
const eventsContext = useEvents();
|
|
384
|
-
selectedEvent = eventsContext.selectedEvent;
|
|
385
|
-
} else {
|
|
386
|
-
selectedEvent = event;
|
|
387
|
-
}
|
|
388
|
-
} catch (error) {
|
|
389
|
-
if (event !== void 0) {
|
|
390
|
-
selectedEvent = event;
|
|
391
|
-
} else {
|
|
392
|
-
selectedEvent = null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
useEffect4(() => {
|
|
396
|
-
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
397
|
-
if (isOnLoginRoute) {
|
|
398
|
-
clearPalette();
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (!selectedEvent) {
|
|
402
|
-
clearPalette();
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
const eventColours = selectedEvent.event_colours;
|
|
406
|
-
const normalized = parseAndNormalizeEventColours(eventColours);
|
|
407
|
-
if (!normalized) {
|
|
408
|
-
clearPalette();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
applyPalette(normalized);
|
|
413
|
-
} catch (error) {
|
|
414
|
-
log2.error("Failed to apply event palette:", error);
|
|
415
|
-
}
|
|
416
|
-
return () => {
|
|
417
|
-
};
|
|
418
|
-
}, [selectedEvent, location.pathname]);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// src/hooks/usePreventTabReload.ts
|
|
422
|
-
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
423
|
-
function usePreventTabReload(options = {}) {
|
|
424
|
-
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
425
|
-
const isRestoringFromCacheRef = useRef3(false);
|
|
426
|
-
const gracePeriodTimeoutRef = useRef3(null);
|
|
427
|
-
useEffect5(() => {
|
|
428
|
-
if (!enabled || typeof window === "undefined") return;
|
|
429
|
-
const handlePageShow = (event) => {
|
|
430
|
-
if (event.persisted) {
|
|
431
|
-
isRestoringFromCacheRef.current = true;
|
|
432
|
-
if (gracePeriodTimeoutRef.current) {
|
|
433
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
434
|
-
}
|
|
435
|
-
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
436
|
-
isRestoringFromCacheRef.current = false;
|
|
437
|
-
}, gracePeriodMs);
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
const handleVisibilityChange = () => {
|
|
441
|
-
if (!document.hidden) {
|
|
442
|
-
isRestoringFromCacheRef.current = true;
|
|
443
|
-
if (gracePeriodTimeoutRef.current) {
|
|
444
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
445
|
-
}
|
|
446
|
-
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
447
|
-
isRestoringFromCacheRef.current = false;
|
|
448
|
-
}, gracePeriodMs);
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
window.addEventListener("pageshow", handlePageShow);
|
|
452
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
453
|
-
return () => {
|
|
454
|
-
window.removeEventListener("pageshow", handlePageShow);
|
|
455
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
456
|
-
if (gracePeriodTimeoutRef.current) {
|
|
457
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
}, [enabled, gracePeriodMs]);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
464
|
-
import { Component } from "react";
|
|
465
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
466
|
-
var ErrorBoundary = class extends Component {
|
|
467
|
-
constructor(props) {
|
|
468
|
-
super(props);
|
|
469
|
-
this.retryTimeoutId = null;
|
|
470
|
-
this.reportError = (errorId, componentName) => {
|
|
471
|
-
if (import.meta.env.MODE === "production") {
|
|
472
|
-
logger.warn("ErrorBoundary", "Error reporting would be triggered in production:", { errorId, componentName });
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
this.handleRetry = () => {
|
|
476
|
-
const { maxRetries = 3 } = this.props;
|
|
477
|
-
const { retryCount } = this.state;
|
|
478
|
-
if (retryCount < maxRetries) {
|
|
479
|
-
logger.debug("ErrorBoundary", `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
|
|
480
|
-
this.setState((prevState) => ({
|
|
481
|
-
hasError: false,
|
|
482
|
-
error: void 0,
|
|
483
|
-
errorInfo: void 0,
|
|
484
|
-
errorId: void 0,
|
|
485
|
-
retryCount: prevState.retryCount + 1
|
|
486
|
-
}));
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
this.state = {
|
|
490
|
-
hasError: false,
|
|
491
|
-
retryCount: 0
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
static getDerivedStateFromError(error) {
|
|
495
|
-
const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
496
|
-
return {
|
|
497
|
-
hasError: true,
|
|
498
|
-
error,
|
|
499
|
-
errorId
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
componentDidCatch(error, errorInfo) {
|
|
503
|
-
const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
|
|
504
|
-
const errorId = this.state.errorId;
|
|
505
|
-
this.setState({ errorInfo });
|
|
506
|
-
logger.error("ErrorBoundary", `[${componentName}] Caught error ${errorId}:`, error, errorInfo);
|
|
507
|
-
performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
|
|
508
|
-
componentName,
|
|
509
|
-
errorId,
|
|
510
|
-
errorMessage: error.message,
|
|
511
|
-
stack: error.stack?.substring(0, 200)
|
|
512
|
-
// Truncated stack trace
|
|
513
|
-
});
|
|
514
|
-
if (enableReporting) {
|
|
515
|
-
this.reportError(errorId, componentName);
|
|
516
|
-
}
|
|
517
|
-
if (onError) {
|
|
518
|
-
onError(error, errorInfo, errorId);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
componentWillUnmount() {
|
|
522
|
-
if (this.retryTimeoutId) {
|
|
523
|
-
clearTimeout(this.retryTimeoutId);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
render() {
|
|
527
|
-
if (this.state.hasError) {
|
|
528
|
-
const {
|
|
529
|
-
componentName = "Component",
|
|
530
|
-
fallback,
|
|
531
|
-
enableRetry = true,
|
|
532
|
-
maxRetries = 3
|
|
533
|
-
} = this.props;
|
|
534
|
-
const { retryCount, errorId } = this.state;
|
|
535
|
-
if (fallback) {
|
|
536
|
-
return fallback;
|
|
537
|
-
}
|
|
538
|
-
return /* @__PURE__ */ jsx(
|
|
539
|
-
"div",
|
|
540
|
-
{
|
|
541
|
-
role: "alert",
|
|
542
|
-
className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
|
|
543
|
-
"data-error-boundary": errorId,
|
|
544
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
545
|
-
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
|
|
546
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
547
|
-
/* @__PURE__ */ jsxs("h3", { className: "text-destructive", children: [
|
|
548
|
-
"Error in ",
|
|
549
|
-
componentName
|
|
550
|
-
] }),
|
|
551
|
-
/* @__PURE__ */ jsx("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
|
|
552
|
-
enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-4", children: [
|
|
553
|
-
/* @__PURE__ */ jsxs(
|
|
554
|
-
"button",
|
|
555
|
-
{
|
|
556
|
-
onClick: this.handleRetry,
|
|
557
|
-
className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
|
|
558
|
-
children: [
|
|
559
|
-
"Retry (",
|
|
560
|
-
retryCount + 1,
|
|
561
|
-
"/",
|
|
562
|
-
maxRetries,
|
|
563
|
-
")"
|
|
564
|
-
]
|
|
565
|
-
}
|
|
566
|
-
),
|
|
567
|
-
/* @__PURE__ */ jsx(
|
|
568
|
-
"button",
|
|
569
|
-
{
|
|
570
|
-
onClick: () => window.location.reload(),
|
|
571
|
-
className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
|
|
572
|
-
children: "Reload Page"
|
|
573
|
-
}
|
|
574
|
-
)
|
|
575
|
-
] }),
|
|
576
|
-
retryCount >= maxRetries && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
|
|
577
|
-
/* @__PURE__ */ jsx("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
|
|
578
|
-
/* @__PURE__ */ jsx(
|
|
579
|
-
"button",
|
|
580
|
-
{
|
|
581
|
-
onClick: () => window.location.reload(),
|
|
582
|
-
className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
|
|
583
|
-
children: "Reload Page"
|
|
584
|
-
}
|
|
585
|
-
)
|
|
586
|
-
] }),
|
|
587
|
-
import.meta.env.MODE === "development" && this.state.error && /* @__PURE__ */ jsxs("details", { className: "text-sm text-destructive/70", children: [
|
|
588
|
-
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium mb-2", children: "Error Details (Development)" }),
|
|
589
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-destructive/5 p-3 rounded border", children: [
|
|
590
|
-
/* @__PURE__ */ jsxs("p", { className: "font-mono", children: [
|
|
591
|
-
"Error ID: ",
|
|
592
|
-
errorId
|
|
593
|
-
] }),
|
|
594
|
-
/* @__PURE__ */ jsxs("pre", { className: "whitespace-pre-wrap text-xs overflow-auto max-h-32", children: [
|
|
595
|
-
this.state.error.toString(),
|
|
596
|
-
this.state.errorInfo?.componentStack
|
|
597
|
-
] })
|
|
598
|
-
] })
|
|
599
|
-
] })
|
|
600
|
-
] })
|
|
601
|
-
] })
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
return this.props.children;
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
610
|
-
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
611
|
-
import { createClient } from "@supabase/supabase-js";
|
|
612
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
613
|
-
var PublicPageContext = createContext(void 0);
|
|
614
|
-
function PublicPageProvider({ children, appName }) {
|
|
615
|
-
const getEnvVar = (key) => {
|
|
616
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
617
|
-
const env = import.meta.env;
|
|
618
|
-
return env[key];
|
|
619
|
-
}
|
|
620
|
-
if (typeof process !== "undefined" && process.env) {
|
|
621
|
-
return process.env[key];
|
|
622
|
-
}
|
|
623
|
-
return void 0;
|
|
624
|
-
};
|
|
625
|
-
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
626
|
-
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
627
|
-
const supabase = useMemo2(() => {
|
|
628
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
629
|
-
logger.warn("PublicPageProvider", "Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment. Use publishable key if anon key is disabled.");
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
632
|
-
const client = createClient(supabaseUrl, supabaseKey);
|
|
633
|
-
logger.info("PublicPageProvider", "Supabase client created successfully for public pages");
|
|
634
|
-
return client;
|
|
635
|
-
}, [supabaseUrl, supabaseKey]);
|
|
636
|
-
const contextValue = {
|
|
637
|
-
isPublicPage: true,
|
|
638
|
-
supabase,
|
|
639
|
-
appName: appName || null,
|
|
640
|
-
environment: {
|
|
641
|
-
supabaseUrl,
|
|
642
|
-
supabaseKey
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
return /* @__PURE__ */ jsx2(PublicPageContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(ErrorBoundary, { componentName: "PublicPageProvider", children }) });
|
|
646
|
-
}
|
|
647
|
-
function usePublicPageContext() {
|
|
648
|
-
const context = useContext(PublicPageContext);
|
|
649
|
-
if (!context) {
|
|
650
|
-
throw new Error("usePublicPageContext must be used within a PublicPageProvider");
|
|
651
|
-
}
|
|
652
|
-
return context;
|
|
653
|
-
}
|
|
654
|
-
function useIsPublicPage() {
|
|
655
|
-
const context = useContext(PublicPageContext);
|
|
656
|
-
return context !== void 0;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// src/hooks/useAppConfig.ts
|
|
660
|
-
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
661
|
-
function useAppConfig() {
|
|
662
|
-
const isPublicPage = useIsPublicPage();
|
|
663
|
-
const publicPageContext = useContext2(PublicPageContext);
|
|
664
|
-
const contextAppName = publicPageContext?.appName || null;
|
|
665
|
-
if (isPublicPage) {
|
|
666
|
-
const getAppName = () => {
|
|
667
|
-
if (contextAppName) {
|
|
668
|
-
return contextAppName;
|
|
669
|
-
}
|
|
670
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
671
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
672
|
-
}
|
|
673
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
674
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
675
|
-
}
|
|
676
|
-
return "PACE";
|
|
677
|
-
};
|
|
678
|
-
return useMemo3(() => ({
|
|
679
|
-
supportsDirectAccess: false,
|
|
680
|
-
// Public pages don't support direct access
|
|
681
|
-
requiresEvent: true,
|
|
682
|
-
// Public pages always require an event
|
|
683
|
-
isLoading: false,
|
|
684
|
-
appName: getAppName()
|
|
685
|
-
}), [contextAppName]);
|
|
686
|
-
}
|
|
687
|
-
try {
|
|
688
|
-
const { appConfig, appName } = useUnifiedAuth();
|
|
689
|
-
return useMemo3(() => ({
|
|
690
|
-
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
691
|
-
requiresEvent: appConfig?.requires_event ?? true,
|
|
692
|
-
isLoading: appConfig === null,
|
|
693
|
-
appName
|
|
694
|
-
}), [appConfig?.requires_event, appName]);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
return useMemo3(() => ({
|
|
697
|
-
supportsDirectAccess: false,
|
|
698
|
-
requiresEvent: true,
|
|
699
|
-
isLoading: false,
|
|
700
|
-
appName: "PACE"
|
|
701
|
-
}), []);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
374
|
// src/utils/storage/config.ts
|
|
706
375
|
var FILE_SIZE_LIMITS = {
|
|
707
376
|
// Images
|
|
@@ -772,7 +441,7 @@ function validateFileSize(file) {
|
|
|
772
441
|
}
|
|
773
442
|
|
|
774
443
|
// src/utils/storage/helpers.ts
|
|
775
|
-
var
|
|
444
|
+
var log2 = createLogger("StorageHelpers");
|
|
776
445
|
function generateFilePath(options, fileName) {
|
|
777
446
|
const { orgId, isPublic = false, customPath } = options;
|
|
778
447
|
if (!orgId) {
|
|
@@ -867,12 +536,12 @@ async function ensureFolderExists(supabase, folderPath, bucketName) {
|
|
|
867
536
|
contentType: "text/plain"
|
|
868
537
|
});
|
|
869
538
|
if (uploadError) {
|
|
870
|
-
|
|
539
|
+
log2.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
|
|
871
540
|
return true;
|
|
872
541
|
}
|
|
873
542
|
return true;
|
|
874
543
|
} catch (error) {
|
|
875
|
-
|
|
544
|
+
log2.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
876
545
|
return true;
|
|
877
546
|
}
|
|
878
547
|
}
|
|
@@ -979,7 +648,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
979
648
|
const bucketName = getBucketName(false);
|
|
980
649
|
const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
981
650
|
if (error) {
|
|
982
|
-
|
|
651
|
+
log2.error("Failed to create signed URL:", error);
|
|
983
652
|
return null;
|
|
984
653
|
}
|
|
985
654
|
return {
|
|
@@ -987,7 +656,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
987
656
|
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
988
657
|
};
|
|
989
658
|
} catch (error) {
|
|
990
|
-
|
|
659
|
+
log2.error("Failed to create signed URL:", error);
|
|
991
660
|
return null;
|
|
992
661
|
}
|
|
993
662
|
}
|
|
@@ -1049,7 +718,7 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
|
1049
718
|
});
|
|
1050
719
|
}
|
|
1051
720
|
} catch (err) {
|
|
1052
|
-
|
|
721
|
+
log2.error(`Failed to generate public URL for file ${file.id}:`, err);
|
|
1053
722
|
}
|
|
1054
723
|
}
|
|
1055
724
|
if (privateFiles.length > 0) {
|
|
@@ -1068,140 +737,397 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
|
1068
737
|
expiresAt: now + ttl
|
|
1069
738
|
});
|
|
1070
739
|
}
|
|
1071
|
-
return { id: file.id, url };
|
|
1072
|
-
} catch (err) {
|
|
1073
|
-
|
|
1074
|
-
return { id: file.id, url: null };
|
|
740
|
+
return { id: file.id, url };
|
|
741
|
+
} catch (err) {
|
|
742
|
+
log2.error(`Failed to generate signed URL for file ${file.id}:`, err);
|
|
743
|
+
return { id: file.id, url: null };
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
const signedUrlResults = await Promise.all(signedUrlPromises);
|
|
747
|
+
for (const result of signedUrlResults) {
|
|
748
|
+
if (result.url) {
|
|
749
|
+
urlMap.set(result.id, result.url);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (uncachedFiles.length > 0) {
|
|
754
|
+
cleanupUrlCache();
|
|
755
|
+
}
|
|
756
|
+
return urlMap;
|
|
757
|
+
}
|
|
758
|
+
async function deleteFile(supabase, path, isPublic = false) {
|
|
759
|
+
try {
|
|
760
|
+
const bucketName = getBucketName(isPublic);
|
|
761
|
+
const { error } = await supabase.storage.from(bucketName).remove([path]);
|
|
762
|
+
if (error) {
|
|
763
|
+
return {
|
|
764
|
+
success: false,
|
|
765
|
+
error: `Delete failed: ${error.message}`
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return { success: true };
|
|
769
|
+
} catch (error) {
|
|
770
|
+
return {
|
|
771
|
+
success: false,
|
|
772
|
+
error: `Delete failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async function listFiles(supabase, options) {
|
|
777
|
+
try {
|
|
778
|
+
const bucketName = getBucketName(options.isPublic || false);
|
|
779
|
+
const pathPrefix = `${options.orgId}/`;
|
|
780
|
+
const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
|
|
781
|
+
const { data, error } = await supabase.storage.from(bucketName).list(searchPath, {
|
|
782
|
+
limit: options.limit || 100,
|
|
783
|
+
offset: options.offset || 0,
|
|
784
|
+
sortBy: { column: "created_at", order: "desc" }
|
|
785
|
+
});
|
|
786
|
+
if (error) {
|
|
787
|
+
log2.error("Failed to list files:", error);
|
|
788
|
+
return { files: [], totalCount: 0, hasMore: false };
|
|
789
|
+
}
|
|
790
|
+
const files = (data || []).map((item) => ({
|
|
791
|
+
name: item.name,
|
|
792
|
+
path: `${searchPath}${item.name}`,
|
|
793
|
+
size: item.metadata?.size || 0,
|
|
794
|
+
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
795
|
+
lastModified: item.updated_at || item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
796
|
+
metadata: {
|
|
797
|
+
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
798
|
+
size: item.metadata?.size || 0,
|
|
799
|
+
orgId: options.orgId,
|
|
800
|
+
appName: options.appName,
|
|
801
|
+
uploadedBy: "unknown",
|
|
802
|
+
uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
803
|
+
isPublic: options.isPublic || false
|
|
804
|
+
}
|
|
805
|
+
}));
|
|
806
|
+
return {
|
|
807
|
+
files,
|
|
808
|
+
totalCount: files.length,
|
|
809
|
+
hasMore: files.length >= (options.limit || 100)
|
|
810
|
+
};
|
|
811
|
+
} catch (error) {
|
|
812
|
+
log2.error("Failed to list files:", error);
|
|
813
|
+
return { files: [], totalCount: 0, hasMore: false };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function downloadFile(supabase, path, isPublic = false) {
|
|
817
|
+
try {
|
|
818
|
+
const bucketName = getBucketName(isPublic);
|
|
819
|
+
const { data, error } = await supabase.storage.from(bucketName).download(path);
|
|
820
|
+
if (error) {
|
|
821
|
+
log2.error("Failed to download file:", error);
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
if (!data) {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
const fileName = path.split("/").pop() || "download";
|
|
828
|
+
const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), {
|
|
829
|
+
search: fileName
|
|
830
|
+
});
|
|
831
|
+
const metadata = fileInfo?.[0]?.metadata || {};
|
|
832
|
+
return {
|
|
833
|
+
blob: data,
|
|
834
|
+
metadata: {
|
|
835
|
+
name: fileName,
|
|
836
|
+
size: metadata.size || data.size,
|
|
837
|
+
type: metadata.mimetype || "application/octet-stream"
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
} catch (error) {
|
|
841
|
+
log2.error("Failed to download file:", error);
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function archiveFile(supabase, path, options) {
|
|
846
|
+
try {
|
|
847
|
+
const bucketName = getBucketName(options.isPublic || false);
|
|
848
|
+
const archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
|
|
849
|
+
const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
|
|
850
|
+
if (copyError) {
|
|
851
|
+
return {
|
|
852
|
+
success: false,
|
|
853
|
+
error: `Archive failed: ${copyError.message}`
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
const deleteResult = await deleteFile(supabase, path, options.isPublic || false);
|
|
857
|
+
if (!deleteResult.success) {
|
|
858
|
+
return deleteResult;
|
|
859
|
+
}
|
|
860
|
+
return { success: true };
|
|
861
|
+
} catch (error) {
|
|
862
|
+
return {
|
|
863
|
+
success: false,
|
|
864
|
+
error: `Archive failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/hooks/public/usePublicFileDisplay.ts
|
|
870
|
+
import { useState as useState3, useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
871
|
+
var publicFileCache = /* @__PURE__ */ new Map();
|
|
872
|
+
function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
873
|
+
const {
|
|
874
|
+
cacheTtl = 30 * 60 * 1e3,
|
|
875
|
+
// 30 minutes
|
|
876
|
+
enableCache = true,
|
|
877
|
+
supabase
|
|
878
|
+
} = options;
|
|
879
|
+
const [fileUrl, setFileUrl] = useState3(null);
|
|
880
|
+
const [fileReference, setFileReference] = useState3(null);
|
|
881
|
+
const [fileReferences, setFileReferences] = useState3([]);
|
|
882
|
+
const [fileUrls, setFileUrls] = useState3(/* @__PURE__ */ new Map());
|
|
883
|
+
const [fileCount, setFileCount] = useState3(0);
|
|
884
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
885
|
+
const [error, setError] = useState3(null);
|
|
886
|
+
const fetchFiles = useCallback3(async () => {
|
|
887
|
+
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
888
|
+
setFileUrl(null);
|
|
889
|
+
setFileReference(null);
|
|
890
|
+
setFileReferences([]);
|
|
891
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
892
|
+
setFileCount(0);
|
|
893
|
+
setIsLoading(false);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
897
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
898
|
+
logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
|
|
899
|
+
}
|
|
900
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
901
|
+
if (enableCache) {
|
|
902
|
+
const cached = publicFileCache.get(cacheKey);
|
|
903
|
+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
904
|
+
const cachedData = cached.data;
|
|
905
|
+
setFileUrl(cachedData.fileUrl || null);
|
|
906
|
+
setFileReference(cachedData.fileReference || null);
|
|
907
|
+
setFileReferences(cachedData.fileReferences || []);
|
|
908
|
+
setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
909
|
+
setFileCount(cachedData.fileCount || 0);
|
|
910
|
+
setIsLoading(false);
|
|
911
|
+
setError(null);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
setIsLoading(true);
|
|
917
|
+
setError(null);
|
|
918
|
+
let files = [];
|
|
919
|
+
if (category) {
|
|
920
|
+
const rpcParams = {
|
|
921
|
+
p_table_name: table_name,
|
|
922
|
+
p_record_id: record_id,
|
|
923
|
+
p_category: category,
|
|
924
|
+
p_organisation_id: organisation_id
|
|
925
|
+
};
|
|
926
|
+
const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
927
|
+
if (rpcError) {
|
|
928
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
929
|
+
function: "data_file_reference_by_category_list",
|
|
930
|
+
table_name,
|
|
931
|
+
record_id,
|
|
932
|
+
category,
|
|
933
|
+
organisation_id,
|
|
934
|
+
error: rpcError.message,
|
|
935
|
+
errorCode: rpcError.code,
|
|
936
|
+
errorDetails: rpcError.details,
|
|
937
|
+
errorHint: rpcError.hint
|
|
938
|
+
});
|
|
939
|
+
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
940
|
+
}
|
|
941
|
+
if (!data || data.length === 0) {
|
|
942
|
+
files = [];
|
|
943
|
+
} else {
|
|
944
|
+
files = data.filter((item) => {
|
|
945
|
+
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
946
|
+
}).map((item) => {
|
|
947
|
+
return {
|
|
948
|
+
id: item.id,
|
|
949
|
+
table_name,
|
|
950
|
+
record_id,
|
|
951
|
+
file_path: item.file_path,
|
|
952
|
+
file_metadata: item.file_metadata || {},
|
|
953
|
+
organisation_id,
|
|
954
|
+
app_id: item.file_metadata?.app_id || null,
|
|
955
|
+
is_public: true,
|
|
956
|
+
// RPC already filtered for public files
|
|
957
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
958
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
959
|
+
};
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
964
|
+
p_table_name: table_name,
|
|
965
|
+
p_record_id: record_id,
|
|
966
|
+
p_organisation_id: organisation_id
|
|
967
|
+
});
|
|
968
|
+
if (rpcError) {
|
|
969
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
970
|
+
function: "data_file_reference_list",
|
|
971
|
+
table_name,
|
|
972
|
+
record_id,
|
|
973
|
+
organisation_id,
|
|
974
|
+
error: rpcError.message,
|
|
975
|
+
errorCode: rpcError.code,
|
|
976
|
+
errorDetails: rpcError.details,
|
|
977
|
+
errorHint: rpcError.hint
|
|
978
|
+
});
|
|
979
|
+
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
980
|
+
}
|
|
981
|
+
if (!fileIds || fileIds.length === 0) {
|
|
982
|
+
files = [];
|
|
983
|
+
} else {
|
|
984
|
+
const ids = fileIds.map((item) => item.id);
|
|
985
|
+
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
986
|
+
if (fetchError) {
|
|
987
|
+
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
988
|
+
}
|
|
989
|
+
files = fullData || [];
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const publicFiles = files.filter((f) => f.is_public === true);
|
|
993
|
+
if (publicFiles.length === 0) {
|
|
994
|
+
setFileUrl(null);
|
|
995
|
+
setFileReference(null);
|
|
996
|
+
setFileReferences([]);
|
|
997
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
998
|
+
setFileCount(0);
|
|
999
|
+
if (enableCache) {
|
|
1000
|
+
publicFileCache.set(cacheKey, {
|
|
1001
|
+
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
1002
|
+
timestamp: Date.now(),
|
|
1003
|
+
ttl: cacheTtl
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1075
1007
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1008
|
+
const fileRefs = publicFiles.map((f) => ({
|
|
1009
|
+
id: f.id,
|
|
1010
|
+
table_name: f.table_name,
|
|
1011
|
+
record_id: f.record_id,
|
|
1012
|
+
file_path: f.file_path,
|
|
1013
|
+
file_metadata: f.file_metadata || {},
|
|
1014
|
+
organisation_id: f.organisation_id,
|
|
1015
|
+
app_id: f.app_id,
|
|
1016
|
+
is_public: f.is_public ?? true,
|
|
1017
|
+
created_at: f.created_at,
|
|
1018
|
+
updated_at: f.updated_at
|
|
1019
|
+
}));
|
|
1020
|
+
setFileReferences(fileRefs);
|
|
1021
|
+
setFileCount(fileRefs.length);
|
|
1022
|
+
if (category && fileRefs.length > 0) {
|
|
1023
|
+
const firstFile = fileRefs[0];
|
|
1024
|
+
setFileReference(firstFile);
|
|
1025
|
+
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1026
|
+
setFileUrl(url);
|
|
1027
|
+
} else {
|
|
1028
|
+
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
1029
|
+
appName: "pace-core",
|
|
1030
|
+
orgId: organisation_id,
|
|
1031
|
+
expiresIn: 3600
|
|
1032
|
+
});
|
|
1033
|
+
setFileUrls(urlMap);
|
|
1034
|
+
setFileReference(null);
|
|
1035
|
+
setFileUrl(null);
|
|
1036
|
+
}
|
|
1037
|
+
if (enableCache) {
|
|
1038
|
+
publicFileCache.set(cacheKey, {
|
|
1039
|
+
data: {
|
|
1040
|
+
fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
|
|
1041
|
+
fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
|
|
1042
|
+
fileReferences: fileRefs,
|
|
1043
|
+
fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
|
|
1044
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
1045
|
+
for (const fileRef of fileRefs) {
|
|
1046
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1047
|
+
if (url) {
|
|
1048
|
+
urlMap.set(fileRef.id, url);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return urlMap;
|
|
1052
|
+
})(),
|
|
1053
|
+
fileCount: fileRefs.length
|
|
1054
|
+
},
|
|
1055
|
+
timestamp: Date.now(),
|
|
1056
|
+
ttl: cacheTtl
|
|
1057
|
+
});
|
|
1081
1058
|
}
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
logger.error("usePublicFileDisplay", "Error fetching files", {
|
|
1061
|
+
table_name,
|
|
1062
|
+
record_id,
|
|
1063
|
+
organisation_id,
|
|
1064
|
+
category,
|
|
1065
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
1066
|
+
errorDetails: err instanceof Error ? err.stack : String(err)
|
|
1067
|
+
});
|
|
1068
|
+
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
1069
|
+
setError(error2);
|
|
1070
|
+
setFileUrl(null);
|
|
1071
|
+
setFileReference(null);
|
|
1072
|
+
setFileReferences([]);
|
|
1073
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1074
|
+
setFileCount(0);
|
|
1075
|
+
} finally {
|
|
1076
|
+
setIsLoading(false);
|
|
1082
1077
|
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
success: false,
|
|
1096
|
-
error: `Delete failed: ${error.message}`
|
|
1097
|
-
};
|
|
1078
|
+
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1079
|
+
useEffect4(() => {
|
|
1080
|
+
if (table_name && record_id && organisation_id) {
|
|
1081
|
+
fetchFiles();
|
|
1082
|
+
} else {
|
|
1083
|
+
setFileUrl(null);
|
|
1084
|
+
setFileReference(null);
|
|
1085
|
+
setFileReferences([]);
|
|
1086
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1087
|
+
setFileCount(0);
|
|
1088
|
+
setIsLoading(false);
|
|
1089
|
+
setError(null);
|
|
1098
1090
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
return
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
async function listFiles(supabase, options) {
|
|
1108
|
-
try {
|
|
1109
|
-
const bucketName = getBucketName(options.isPublic || false);
|
|
1110
|
-
const pathPrefix = `${options.orgId}/`;
|
|
1111
|
-
const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
|
|
1112
|
-
const { data, error } = await supabase.storage.from(bucketName).list(searchPath, {
|
|
1113
|
-
limit: options.limit || 100,
|
|
1114
|
-
offset: options.offset || 0,
|
|
1115
|
-
sortBy: { column: "created_at", order: "desc" }
|
|
1116
|
-
});
|
|
1117
|
-
if (error) {
|
|
1118
|
-
log3.error("Failed to list files:", error);
|
|
1119
|
-
return { files: [], totalCount: 0, hasMore: false };
|
|
1091
|
+
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
1092
|
+
const refetch = useCallback3(async () => {
|
|
1093
|
+
if (!table_name || !record_id || !organisation_id) return;
|
|
1094
|
+
if (enableCache) {
|
|
1095
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1096
|
+
publicFileCache.delete(cacheKey);
|
|
1120
1097
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1134
|
-
isPublic: options.isPublic || false
|
|
1135
|
-
}
|
|
1136
|
-
}));
|
|
1137
|
-
return {
|
|
1138
|
-
files,
|
|
1139
|
-
totalCount: files.length,
|
|
1140
|
-
hasMore: files.length >= (options.limit || 100)
|
|
1141
|
-
};
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
log3.error("Failed to list files:", error);
|
|
1144
|
-
return { files: [], totalCount: 0, hasMore: false };
|
|
1145
|
-
}
|
|
1098
|
+
await fetchFiles();
|
|
1099
|
+
}, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
|
|
1100
|
+
return {
|
|
1101
|
+
fileUrl,
|
|
1102
|
+
fileReference,
|
|
1103
|
+
fileReferences,
|
|
1104
|
+
fileUrls,
|
|
1105
|
+
fileCount,
|
|
1106
|
+
isLoading,
|
|
1107
|
+
error,
|
|
1108
|
+
refetch
|
|
1109
|
+
};
|
|
1146
1110
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
if (error) {
|
|
1152
|
-
log3.error("Failed to download file:", error);
|
|
1153
|
-
return null;
|
|
1154
|
-
}
|
|
1155
|
-
if (!data) {
|
|
1156
|
-
return null;
|
|
1111
|
+
function clearPublicFileDisplayCache() {
|
|
1112
|
+
for (const [key] of publicFileCache) {
|
|
1113
|
+
if (key.startsWith("public_file_")) {
|
|
1114
|
+
publicFileCache.delete(key);
|
|
1157
1115
|
}
|
|
1158
|
-
const fileName = path.split("/").pop() || "download";
|
|
1159
|
-
const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), {
|
|
1160
|
-
search: fileName
|
|
1161
|
-
});
|
|
1162
|
-
const metadata = fileInfo?.[0]?.metadata || {};
|
|
1163
|
-
return {
|
|
1164
|
-
blob: data,
|
|
1165
|
-
metadata: {
|
|
1166
|
-
name: fileName,
|
|
1167
|
-
size: metadata.size || data.size,
|
|
1168
|
-
type: metadata.mimetype || "application/octet-stream"
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
log3.error("Failed to download file:", error);
|
|
1173
|
-
return null;
|
|
1174
1116
|
}
|
|
1175
1117
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return {
|
|
1183
|
-
success: false,
|
|
1184
|
-
error: `Archive failed: ${copyError.message}`
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
const deleteResult = await deleteFile(supabase, path, options.isPublic || false);
|
|
1188
|
-
if (!deleteResult.success) {
|
|
1189
|
-
return deleteResult;
|
|
1190
|
-
}
|
|
1191
|
-
return { success: true };
|
|
1192
|
-
} catch (error) {
|
|
1193
|
-
return {
|
|
1194
|
-
success: false,
|
|
1195
|
-
error: `Archive failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1118
|
+
function getPublicFileDisplayCacheStats() {
|
|
1119
|
+
const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
|
|
1120
|
+
return {
|
|
1121
|
+
size: keys.length,
|
|
1122
|
+
keys
|
|
1123
|
+
};
|
|
1198
1124
|
}
|
|
1199
1125
|
|
|
1200
1126
|
// src/hooks/useFileDisplay.ts
|
|
1201
|
-
import { useState as
|
|
1127
|
+
import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
1202
1128
|
|
|
1203
1129
|
// src/utils/file-reference/index.ts
|
|
1204
|
-
var
|
|
1130
|
+
var log3 = createLogger("FileReferenceService");
|
|
1205
1131
|
var FileReferenceServiceImpl = class {
|
|
1206
1132
|
constructor(supabase) {
|
|
1207
1133
|
this.supabase = supabase;
|
|
@@ -1295,7 +1221,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1295
1221
|
);
|
|
1296
1222
|
return fileRef;
|
|
1297
1223
|
} catch (error) {
|
|
1298
|
-
|
|
1224
|
+
log3.error("Error creating file reference:", error);
|
|
1299
1225
|
throw error;
|
|
1300
1226
|
}
|
|
1301
1227
|
}
|
|
@@ -1310,7 +1236,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1310
1236
|
}
|
|
1311
1237
|
return data;
|
|
1312
1238
|
} catch (error) {
|
|
1313
|
-
|
|
1239
|
+
log3.error("Error getting file reference:", error);
|
|
1314
1240
|
throw error;
|
|
1315
1241
|
}
|
|
1316
1242
|
}
|
|
@@ -1334,7 +1260,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1334
1260
|
return await this.getSignedUrl(table_name, record_id, organisation_id);
|
|
1335
1261
|
}
|
|
1336
1262
|
} catch (error) {
|
|
1337
|
-
|
|
1263
|
+
log3.error("Error getting file URL:", error);
|
|
1338
1264
|
throw error;
|
|
1339
1265
|
}
|
|
1340
1266
|
}
|
|
@@ -1359,7 +1285,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1359
1285
|
});
|
|
1360
1286
|
return signedUrlResult?.url || null;
|
|
1361
1287
|
} catch (error) {
|
|
1362
|
-
|
|
1288
|
+
log3.error("Error getting signed URL:", error);
|
|
1363
1289
|
throw error;
|
|
1364
1290
|
}
|
|
1365
1291
|
}
|
|
@@ -1371,7 +1297,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1371
1297
|
}
|
|
1372
1298
|
return data;
|
|
1373
1299
|
} catch (error) {
|
|
1374
|
-
|
|
1300
|
+
log3.error("Error updating file reference:", error);
|
|
1375
1301
|
throw error;
|
|
1376
1302
|
}
|
|
1377
1303
|
}
|
|
@@ -1392,7 +1318,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1392
1318
|
}
|
|
1393
1319
|
return true;
|
|
1394
1320
|
} catch (error) {
|
|
1395
|
-
|
|
1321
|
+
log3.error("Error deleting file reference:", error);
|
|
1396
1322
|
throw error;
|
|
1397
1323
|
}
|
|
1398
1324
|
}
|
|
@@ -1434,7 +1360,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1434
1360
|
});
|
|
1435
1361
|
return fileReferences;
|
|
1436
1362
|
} catch (error) {
|
|
1437
|
-
|
|
1363
|
+
log3.error("Error listing file references:", error);
|
|
1438
1364
|
throw error;
|
|
1439
1365
|
}
|
|
1440
1366
|
}
|
|
@@ -1450,7 +1376,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1450
1376
|
}
|
|
1451
1377
|
return data || 0;
|
|
1452
1378
|
} catch (error) {
|
|
1453
|
-
|
|
1379
|
+
log3.error("Error getting file count:", error);
|
|
1454
1380
|
throw error;
|
|
1455
1381
|
}
|
|
1456
1382
|
}
|
|
@@ -1468,7 +1394,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1468
1394
|
}
|
|
1469
1395
|
return data[0];
|
|
1470
1396
|
} catch (error) {
|
|
1471
|
-
|
|
1397
|
+
log3.error("Error getting file reference by ID:", error);
|
|
1472
1398
|
throw error;
|
|
1473
1399
|
}
|
|
1474
1400
|
}
|
|
@@ -1481,7 +1407,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1481
1407
|
p_organisation_id: organisation_id
|
|
1482
1408
|
});
|
|
1483
1409
|
if (error) {
|
|
1484
|
-
|
|
1410
|
+
log3.error("RPC ERROR getting files by category:", {
|
|
1485
1411
|
error,
|
|
1486
1412
|
errorCode: error.code,
|
|
1487
1413
|
errorMessage: error.message,
|
|
@@ -1501,7 +1427,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1501
1427
|
const fileCategory = item.file_metadata?.category;
|
|
1502
1428
|
const matches = fileCategory === category;
|
|
1503
1429
|
if (!matches) {
|
|
1504
|
-
|
|
1430
|
+
log3.warn("File category mismatch in RPC response:", {
|
|
1505
1431
|
fileId: item.id,
|
|
1506
1432
|
expectedCategory: category,
|
|
1507
1433
|
actualCategory: fileCategory
|
|
@@ -1534,7 +1460,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1534
1460
|
});
|
|
1535
1461
|
return fileReferences;
|
|
1536
1462
|
} catch (error) {
|
|
1537
|
-
|
|
1463
|
+
log3.error("Error getting files by category:", error);
|
|
1538
1464
|
throw error;
|
|
1539
1465
|
}
|
|
1540
1466
|
}
|
|
@@ -1613,14 +1539,14 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1613
1539
|
enableCache = true,
|
|
1614
1540
|
supabase
|
|
1615
1541
|
} = options;
|
|
1616
|
-
const [fileUrl, setFileUrl] =
|
|
1617
|
-
const [fileReference, setFileReference] =
|
|
1618
|
-
const [fileReferences, setFileReferences] =
|
|
1619
|
-
const [fileUrls, setFileUrls] =
|
|
1620
|
-
const [fileCount, setFileCount] =
|
|
1621
|
-
const [isLoading, setIsLoading] =
|
|
1622
|
-
const [error, setError] =
|
|
1623
|
-
const fetchFiles =
|
|
1542
|
+
const [fileUrl, setFileUrl] = useState4(null);
|
|
1543
|
+
const [fileReference, setFileReference] = useState4(null);
|
|
1544
|
+
const [fileReferences, setFileReferences] = useState4([]);
|
|
1545
|
+
const [fileUrls, setFileUrls] = useState4(/* @__PURE__ */ new Map());
|
|
1546
|
+
const [fileCount, setFileCount] = useState4(0);
|
|
1547
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1548
|
+
const [error, setError] = useState4(null);
|
|
1549
|
+
const fetchFiles = useCallback4(async () => {
|
|
1624
1550
|
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1625
1551
|
setFileUrl(null);
|
|
1626
1552
|
setFileReference(null);
|
|
@@ -1732,282 +1658,13 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1732
1658
|
orgId: organisation_id,
|
|
1733
1659
|
expiresIn: 3600
|
|
1734
1660
|
});
|
|
1735
|
-
url = signedUrlResult?.url || null;
|
|
1736
|
-
logger.debug("useFileDisplay", "Generated signed URL:", url ? "URL generated" : "URL generation failed");
|
|
1737
|
-
}
|
|
1738
|
-
logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
|
|
1739
|
-
setFileUrl(url);
|
|
1740
|
-
} else {
|
|
1741
|
-
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1742
|
-
appName: "pace-core",
|
|
1743
|
-
orgId: organisation_id,
|
|
1744
|
-
expiresIn: 3600
|
|
1745
|
-
});
|
|
1746
|
-
setFileUrls(urlMap);
|
|
1747
|
-
setFileReference(null);
|
|
1748
|
-
setFileUrl(null);
|
|
1749
|
-
}
|
|
1750
|
-
if (enableCache) {
|
|
1751
|
-
let cacheData = {
|
|
1752
|
-
fileReference: category && files.length > 0 ? files[0] : null,
|
|
1753
|
-
fileReferences: files,
|
|
1754
|
-
fileUrls: /* @__PURE__ */ new Map(),
|
|
1755
|
-
fileCount: files.length
|
|
1756
|
-
};
|
|
1757
|
-
if (category && files.length > 0) {
|
|
1758
|
-
const firstFile = files[0];
|
|
1759
|
-
let url = null;
|
|
1760
|
-
if (firstFile.is_public) {
|
|
1761
|
-
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1762
|
-
}
|
|
1763
|
-
cacheData.fileUrl = url;
|
|
1764
|
-
} else {
|
|
1765
|
-
const urlMap = /* @__PURE__ */ new Map();
|
|
1766
|
-
for (const fileRef of files) {
|
|
1767
|
-
if (fileRef.is_public) {
|
|
1768
|
-
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1769
|
-
if (url) {
|
|
1770
|
-
urlMap.set(fileRef.id, url);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
cacheData.fileUrls = urlMap;
|
|
1775
|
-
}
|
|
1776
|
-
authenticatedFileCache.set(cacheKey, {
|
|
1777
|
-
data: cacheData,
|
|
1778
|
-
timestamp: Date.now(),
|
|
1779
|
-
ttl: cacheTtl
|
|
1780
|
-
});
|
|
1781
|
-
cleanupCache();
|
|
1782
|
-
}
|
|
1783
|
-
} catch (err) {
|
|
1784
|
-
logger.error("useFileDisplay", "Error fetching files:", err);
|
|
1785
|
-
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
1786
|
-
setError(error2);
|
|
1787
|
-
setFileUrl(null);
|
|
1788
|
-
setFileReference(null);
|
|
1789
|
-
setFileReferences([]);
|
|
1790
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
1791
|
-
setFileCount(0);
|
|
1792
|
-
} finally {
|
|
1793
|
-
setIsLoading(false);
|
|
1794
|
-
}
|
|
1795
|
-
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1796
|
-
useEffect6(() => {
|
|
1797
|
-
if (table_name && record_id && organisation_id && supabase) {
|
|
1798
|
-
fetchFiles();
|
|
1799
|
-
} else {
|
|
1800
|
-
setFileUrl(null);
|
|
1801
|
-
setFileReference(null);
|
|
1802
|
-
setFileReferences([]);
|
|
1803
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
1804
|
-
setFileCount(0);
|
|
1805
|
-
setIsLoading(false);
|
|
1806
|
-
setError(null);
|
|
1807
|
-
}
|
|
1808
|
-
}, [fetchFiles, table_name, record_id, organisation_id, supabase]);
|
|
1809
|
-
const refetch = useCallback3(async () => {
|
|
1810
|
-
if (!table_name || !record_id || !organisation_id || !supabase) return;
|
|
1811
|
-
if (enableCache) {
|
|
1812
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1813
|
-
authenticatedFileCache.delete(cacheKey);
|
|
1814
|
-
}
|
|
1815
|
-
await fetchFiles();
|
|
1816
|
-
}, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);
|
|
1817
|
-
return {
|
|
1818
|
-
fileUrl,
|
|
1819
|
-
fileReference,
|
|
1820
|
-
fileReferences,
|
|
1821
|
-
fileUrls,
|
|
1822
|
-
fileCount,
|
|
1823
|
-
isLoading,
|
|
1824
|
-
error,
|
|
1825
|
-
refetch
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
|
-
function clearFileDisplayCache() {
|
|
1829
|
-
for (const [key] of authenticatedFileCache) {
|
|
1830
|
-
if (key.startsWith("file_")) {
|
|
1831
|
-
authenticatedFileCache.delete(key);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
function getFileDisplayCacheStats() {
|
|
1836
|
-
const keys = Array.from(authenticatedFileCache.keys()).filter((key) => key.startsWith("file_"));
|
|
1837
|
-
return {
|
|
1838
|
-
size: keys.length,
|
|
1839
|
-
keys
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
|
|
1843
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1844
|
-
authenticatedFileCache.delete(cacheKey);
|
|
1845
|
-
if (category) {
|
|
1846
|
-
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id}_all`;
|
|
1847
|
-
authenticatedFileCache.delete(allCategoryKey);
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
// src/hooks/public/usePublicFileDisplay.ts
|
|
1852
|
-
import { useState as useState4, useEffect as useEffect7, useCallback as useCallback4 } from "react";
|
|
1853
|
-
var publicFileCache = /* @__PURE__ */ new Map();
|
|
1854
|
-
function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
1855
|
-
const {
|
|
1856
|
-
cacheTtl = 30 * 60 * 1e3,
|
|
1857
|
-
// 30 minutes
|
|
1858
|
-
enableCache = true,
|
|
1859
|
-
supabase
|
|
1860
|
-
} = options;
|
|
1861
|
-
const [fileUrl, setFileUrl] = useState4(null);
|
|
1862
|
-
const [fileReference, setFileReference] = useState4(null);
|
|
1863
|
-
const [fileReferences, setFileReferences] = useState4([]);
|
|
1864
|
-
const [fileUrls, setFileUrls] = useState4(/* @__PURE__ */ new Map());
|
|
1865
|
-
const [fileCount, setFileCount] = useState4(0);
|
|
1866
|
-
const [isLoading, setIsLoading] = useState4(false);
|
|
1867
|
-
const [error, setError] = useState4(null);
|
|
1868
|
-
const fetchFiles = useCallback4(async () => {
|
|
1869
|
-
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1870
|
-
setFileUrl(null);
|
|
1871
|
-
setFileReference(null);
|
|
1872
|
-
setFileReferences([]);
|
|
1873
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
1874
|
-
setFileCount(0);
|
|
1875
|
-
setIsLoading(false);
|
|
1876
|
-
return;
|
|
1877
|
-
}
|
|
1878
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1879
|
-
if (!uuidRegex.test(organisation_id)) {
|
|
1880
|
-
logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
|
|
1881
|
-
}
|
|
1882
|
-
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1883
|
-
if (enableCache) {
|
|
1884
|
-
const cached = publicFileCache.get(cacheKey);
|
|
1885
|
-
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
1886
|
-
const cachedData = cached.data;
|
|
1887
|
-
setFileUrl(cachedData.fileUrl || null);
|
|
1888
|
-
setFileReference(cachedData.fileReference || null);
|
|
1889
|
-
setFileReferences(cachedData.fileReferences || []);
|
|
1890
|
-
setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1891
|
-
setFileCount(cachedData.fileCount || 0);
|
|
1892
|
-
setIsLoading(false);
|
|
1893
|
-
setError(null);
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
try {
|
|
1898
|
-
setIsLoading(true);
|
|
1899
|
-
setError(null);
|
|
1900
|
-
let files = [];
|
|
1901
|
-
if (category) {
|
|
1902
|
-
const rpcParams = {
|
|
1903
|
-
p_table_name: table_name,
|
|
1904
|
-
p_record_id: record_id,
|
|
1905
|
-
p_category: category,
|
|
1906
|
-
p_organisation_id: organisation_id
|
|
1907
|
-
};
|
|
1908
|
-
const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
1909
|
-
if (rpcError) {
|
|
1910
|
-
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1911
|
-
function: "data_file_reference_by_category_list",
|
|
1912
|
-
table_name,
|
|
1913
|
-
record_id,
|
|
1914
|
-
category,
|
|
1915
|
-
organisation_id,
|
|
1916
|
-
error: rpcError.message,
|
|
1917
|
-
errorCode: rpcError.code,
|
|
1918
|
-
errorDetails: rpcError.details,
|
|
1919
|
-
errorHint: rpcError.hint
|
|
1920
|
-
});
|
|
1921
|
-
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
1922
|
-
}
|
|
1923
|
-
if (!data || data.length === 0) {
|
|
1924
|
-
files = [];
|
|
1925
|
-
} else {
|
|
1926
|
-
files = data.filter((item) => {
|
|
1927
|
-
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
1928
|
-
}).map((item) => {
|
|
1929
|
-
return {
|
|
1930
|
-
id: item.id,
|
|
1931
|
-
table_name,
|
|
1932
|
-
record_id,
|
|
1933
|
-
file_path: item.file_path,
|
|
1934
|
-
file_metadata: item.file_metadata || {},
|
|
1935
|
-
organisation_id,
|
|
1936
|
-
app_id: item.file_metadata?.app_id || null,
|
|
1937
|
-
is_public: true,
|
|
1938
|
-
// RPC already filtered for public files
|
|
1939
|
-
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1940
|
-
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1941
|
-
};
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
} else {
|
|
1945
|
-
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1946
|
-
p_table_name: table_name,
|
|
1947
|
-
p_record_id: record_id,
|
|
1948
|
-
p_organisation_id: organisation_id
|
|
1949
|
-
});
|
|
1950
|
-
if (rpcError) {
|
|
1951
|
-
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1952
|
-
function: "data_file_reference_list",
|
|
1953
|
-
table_name,
|
|
1954
|
-
record_id,
|
|
1955
|
-
organisation_id,
|
|
1956
|
-
error: rpcError.message,
|
|
1957
|
-
errorCode: rpcError.code,
|
|
1958
|
-
errorDetails: rpcError.details,
|
|
1959
|
-
errorHint: rpcError.hint
|
|
1960
|
-
});
|
|
1961
|
-
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1962
|
-
}
|
|
1963
|
-
if (!fileIds || fileIds.length === 0) {
|
|
1964
|
-
files = [];
|
|
1965
|
-
} else {
|
|
1966
|
-
const ids = fileIds.map((item) => item.id);
|
|
1967
|
-
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
1968
|
-
if (fetchError) {
|
|
1969
|
-
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1970
|
-
}
|
|
1971
|
-
files = fullData || [];
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
const publicFiles = files.filter((f) => f.is_public === true);
|
|
1975
|
-
if (publicFiles.length === 0) {
|
|
1976
|
-
setFileUrl(null);
|
|
1977
|
-
setFileReference(null);
|
|
1978
|
-
setFileReferences([]);
|
|
1979
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
1980
|
-
setFileCount(0);
|
|
1981
|
-
if (enableCache) {
|
|
1982
|
-
publicFileCache.set(cacheKey, {
|
|
1983
|
-
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
1984
|
-
timestamp: Date.now(),
|
|
1985
|
-
ttl: cacheTtl
|
|
1986
|
-
});
|
|
1661
|
+
url = signedUrlResult?.url || null;
|
|
1662
|
+
logger.debug("useFileDisplay", "Generated signed URL:", url ? "URL generated" : "URL generation failed");
|
|
1987
1663
|
}
|
|
1988
|
-
|
|
1989
|
-
}
|
|
1990
|
-
const fileRefs = publicFiles.map((f) => ({
|
|
1991
|
-
id: f.id,
|
|
1992
|
-
table_name: f.table_name,
|
|
1993
|
-
record_id: f.record_id,
|
|
1994
|
-
file_path: f.file_path,
|
|
1995
|
-
file_metadata: f.file_metadata || {},
|
|
1996
|
-
organisation_id: f.organisation_id,
|
|
1997
|
-
app_id: f.app_id,
|
|
1998
|
-
is_public: f.is_public ?? true,
|
|
1999
|
-
created_at: f.created_at,
|
|
2000
|
-
updated_at: f.updated_at
|
|
2001
|
-
}));
|
|
2002
|
-
setFileReferences(fileRefs);
|
|
2003
|
-
setFileCount(fileRefs.length);
|
|
2004
|
-
if (category && fileRefs.length > 0) {
|
|
2005
|
-
const firstFile = fileRefs[0];
|
|
2006
|
-
setFileReference(firstFile);
|
|
2007
|
-
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1664
|
+
logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
|
|
2008
1665
|
setFileUrl(url);
|
|
2009
1666
|
} else {
|
|
2010
|
-
const urlMap = await generateFileUrlsBatch(supabase,
|
|
1667
|
+
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
2011
1668
|
appName: "pace-core",
|
|
2012
1669
|
orgId: organisation_id,
|
|
2013
1670
|
expiresIn: 3600
|
|
@@ -2017,36 +1674,40 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
2017
1674
|
setFileUrl(null);
|
|
2018
1675
|
}
|
|
2019
1676
|
if (enableCache) {
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
1677
|
+
let cacheData = {
|
|
1678
|
+
fileReference: category && files.length > 0 ? files[0] : null,
|
|
1679
|
+
fileReferences: files,
|
|
1680
|
+
fileUrls: /* @__PURE__ */ new Map(),
|
|
1681
|
+
fileCount: files.length
|
|
1682
|
+
};
|
|
1683
|
+
if (category && files.length > 0) {
|
|
1684
|
+
const firstFile = files[0];
|
|
1685
|
+
let url = null;
|
|
1686
|
+
if (firstFile.is_public) {
|
|
1687
|
+
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1688
|
+
}
|
|
1689
|
+
cacheData.fileUrl = url;
|
|
1690
|
+
} else {
|
|
1691
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
1692
|
+
for (const fileRef of files) {
|
|
1693
|
+
if (fileRef.is_public) {
|
|
1694
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1695
|
+
if (url) {
|
|
1696
|
+
urlMap.set(fileRef.id, url);
|
|
2032
1697
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
cacheData.fileUrls = urlMap;
|
|
1701
|
+
}
|
|
1702
|
+
authenticatedFileCache.set(cacheKey, {
|
|
1703
|
+
data: cacheData,
|
|
2037
1704
|
timestamp: Date.now(),
|
|
2038
1705
|
ttl: cacheTtl
|
|
2039
1706
|
});
|
|
1707
|
+
cleanupCache();
|
|
2040
1708
|
}
|
|
2041
1709
|
} catch (err) {
|
|
2042
|
-
logger.error("
|
|
2043
|
-
table_name,
|
|
2044
|
-
record_id,
|
|
2045
|
-
organisation_id,
|
|
2046
|
-
category,
|
|
2047
|
-
error: err instanceof Error ? err.message : "Unknown error",
|
|
2048
|
-
errorDetails: err instanceof Error ? err.stack : String(err)
|
|
2049
|
-
});
|
|
1710
|
+
logger.error("useFileDisplay", "Error fetching files:", err);
|
|
2050
1711
|
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
2051
1712
|
setError(error2);
|
|
2052
1713
|
setFileUrl(null);
|
|
@@ -2057,52 +1718,391 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
2057
1718
|
} finally {
|
|
2058
1719
|
setIsLoading(false);
|
|
2059
1720
|
}
|
|
2060
|
-
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
2061
|
-
|
|
2062
|
-
if (table_name && record_id && organisation_id) {
|
|
2063
|
-
fetchFiles();
|
|
2064
|
-
} else {
|
|
2065
|
-
setFileUrl(null);
|
|
2066
|
-
setFileReference(null);
|
|
2067
|
-
setFileReferences([]);
|
|
2068
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
2069
|
-
setFileCount(0);
|
|
2070
|
-
setIsLoading(false);
|
|
2071
|
-
setError(null);
|
|
1721
|
+
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1722
|
+
useEffect5(() => {
|
|
1723
|
+
if (table_name && record_id && organisation_id && supabase) {
|
|
1724
|
+
fetchFiles();
|
|
1725
|
+
} else {
|
|
1726
|
+
setFileUrl(null);
|
|
1727
|
+
setFileReference(null);
|
|
1728
|
+
setFileReferences([]);
|
|
1729
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1730
|
+
setFileCount(0);
|
|
1731
|
+
setIsLoading(false);
|
|
1732
|
+
setError(null);
|
|
1733
|
+
}
|
|
1734
|
+
}, [fetchFiles, table_name, record_id, organisation_id, supabase]);
|
|
1735
|
+
const refetch = useCallback4(async () => {
|
|
1736
|
+
if (!table_name || !record_id || !organisation_id || !supabase) return;
|
|
1737
|
+
if (enableCache) {
|
|
1738
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1739
|
+
authenticatedFileCache.delete(cacheKey);
|
|
1740
|
+
}
|
|
1741
|
+
await fetchFiles();
|
|
1742
|
+
}, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);
|
|
1743
|
+
return {
|
|
1744
|
+
fileUrl,
|
|
1745
|
+
fileReference,
|
|
1746
|
+
fileReferences,
|
|
1747
|
+
fileUrls,
|
|
1748
|
+
fileCount,
|
|
1749
|
+
isLoading,
|
|
1750
|
+
error,
|
|
1751
|
+
refetch
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
function clearFileDisplayCache() {
|
|
1755
|
+
for (const [key] of authenticatedFileCache) {
|
|
1756
|
+
if (key.startsWith("file_")) {
|
|
1757
|
+
authenticatedFileCache.delete(key);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function getFileDisplayCacheStats() {
|
|
1762
|
+
const keys = Array.from(authenticatedFileCache.keys()).filter((key) => key.startsWith("file_"));
|
|
1763
|
+
return {
|
|
1764
|
+
size: keys.length,
|
|
1765
|
+
keys
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
|
|
1769
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1770
|
+
authenticatedFileCache.delete(cacheKey);
|
|
1771
|
+
if (category) {
|
|
1772
|
+
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id}_all`;
|
|
1773
|
+
authenticatedFileCache.delete(allCategoryKey);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
1778
|
+
import { Component } from "react";
|
|
1779
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1780
|
+
var ErrorBoundary = class extends Component {
|
|
1781
|
+
constructor(props) {
|
|
1782
|
+
super(props);
|
|
1783
|
+
this.retryTimeoutId = null;
|
|
1784
|
+
this.reportError = (errorId, componentName) => {
|
|
1785
|
+
if (import.meta.env.MODE === "production") {
|
|
1786
|
+
logger.warn("ErrorBoundary", "Error reporting would be triggered in production:", { errorId, componentName });
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
this.handleRetry = () => {
|
|
1790
|
+
const { maxRetries = 3 } = this.props;
|
|
1791
|
+
const { retryCount } = this.state;
|
|
1792
|
+
if (retryCount < maxRetries) {
|
|
1793
|
+
logger.debug("ErrorBoundary", `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1794
|
+
this.setState((prevState) => ({
|
|
1795
|
+
hasError: false,
|
|
1796
|
+
error: void 0,
|
|
1797
|
+
errorInfo: void 0,
|
|
1798
|
+
errorId: void 0,
|
|
1799
|
+
retryCount: prevState.retryCount + 1
|
|
1800
|
+
}));
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
this.state = {
|
|
1804
|
+
hasError: false,
|
|
1805
|
+
retryCount: 0
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
static getDerivedStateFromError(error) {
|
|
1809
|
+
const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1810
|
+
return {
|
|
1811
|
+
hasError: true,
|
|
1812
|
+
error,
|
|
1813
|
+
errorId
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
componentDidCatch(error, errorInfo) {
|
|
1817
|
+
const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
|
|
1818
|
+
const errorId = this.state.errorId;
|
|
1819
|
+
this.setState({ errorInfo });
|
|
1820
|
+
logger.error("ErrorBoundary", `[${componentName}] Caught error ${errorId}:`, error, errorInfo);
|
|
1821
|
+
performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
|
|
1822
|
+
componentName,
|
|
1823
|
+
errorId,
|
|
1824
|
+
errorMessage: error.message,
|
|
1825
|
+
stack: error.stack?.substring(0, 200)
|
|
1826
|
+
// Truncated stack trace
|
|
1827
|
+
});
|
|
1828
|
+
if (enableReporting) {
|
|
1829
|
+
this.reportError(errorId, componentName);
|
|
1830
|
+
}
|
|
1831
|
+
if (onError) {
|
|
1832
|
+
onError(error, errorInfo, errorId);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
componentWillUnmount() {
|
|
1836
|
+
if (this.retryTimeoutId) {
|
|
1837
|
+
clearTimeout(this.retryTimeoutId);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
render() {
|
|
1841
|
+
if (this.state.hasError) {
|
|
1842
|
+
const {
|
|
1843
|
+
componentName = "Component",
|
|
1844
|
+
fallback,
|
|
1845
|
+
enableRetry = true,
|
|
1846
|
+
maxRetries = 3
|
|
1847
|
+
} = this.props;
|
|
1848
|
+
const { retryCount, errorId } = this.state;
|
|
1849
|
+
if (fallback) {
|
|
1850
|
+
return fallback;
|
|
1851
|
+
}
|
|
1852
|
+
return /* @__PURE__ */ jsx(
|
|
1853
|
+
"div",
|
|
1854
|
+
{
|
|
1855
|
+
role: "alert",
|
|
1856
|
+
className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
|
|
1857
|
+
"data-error-boundary": errorId,
|
|
1858
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1859
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
|
|
1860
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1861
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-destructive", children: [
|
|
1862
|
+
"Error in ",
|
|
1863
|
+
componentName
|
|
1864
|
+
] }),
|
|
1865
|
+
/* @__PURE__ */ jsx("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
|
|
1866
|
+
enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-4", children: [
|
|
1867
|
+
/* @__PURE__ */ jsxs(
|
|
1868
|
+
"button",
|
|
1869
|
+
{
|
|
1870
|
+
onClick: this.handleRetry,
|
|
1871
|
+
className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
|
|
1872
|
+
children: [
|
|
1873
|
+
"Retry (",
|
|
1874
|
+
retryCount + 1,
|
|
1875
|
+
"/",
|
|
1876
|
+
maxRetries,
|
|
1877
|
+
")"
|
|
1878
|
+
]
|
|
1879
|
+
}
|
|
1880
|
+
),
|
|
1881
|
+
/* @__PURE__ */ jsx(
|
|
1882
|
+
"button",
|
|
1883
|
+
{
|
|
1884
|
+
onClick: () => window.location.reload(),
|
|
1885
|
+
className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
|
|
1886
|
+
children: "Reload Page"
|
|
1887
|
+
}
|
|
1888
|
+
)
|
|
1889
|
+
] }),
|
|
1890
|
+
retryCount >= maxRetries && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
|
|
1891
|
+
/* @__PURE__ */ jsx("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
|
|
1892
|
+
/* @__PURE__ */ jsx(
|
|
1893
|
+
"button",
|
|
1894
|
+
{
|
|
1895
|
+
onClick: () => window.location.reload(),
|
|
1896
|
+
className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
|
|
1897
|
+
children: "Reload Page"
|
|
1898
|
+
}
|
|
1899
|
+
)
|
|
1900
|
+
] }),
|
|
1901
|
+
import.meta.env.MODE === "development" && this.state.error && /* @__PURE__ */ jsxs("details", { className: "text-sm text-destructive/70", children: [
|
|
1902
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium mb-2", children: "Error Details (Development)" }),
|
|
1903
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-destructive/5 p-3 rounded border", children: [
|
|
1904
|
+
/* @__PURE__ */ jsxs("p", { className: "font-mono", children: [
|
|
1905
|
+
"Error ID: ",
|
|
1906
|
+
errorId
|
|
1907
|
+
] }),
|
|
1908
|
+
/* @__PURE__ */ jsxs("pre", { className: "whitespace-pre-wrap text-xs overflow-auto max-h-32", children: [
|
|
1909
|
+
this.state.error.toString(),
|
|
1910
|
+
this.state.errorInfo?.componentStack
|
|
1911
|
+
] })
|
|
1912
|
+
] })
|
|
1913
|
+
] })
|
|
1914
|
+
] })
|
|
1915
|
+
] })
|
|
1916
|
+
}
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
return this.props.children;
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
1924
|
+
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
1925
|
+
import { createClient } from "@supabase/supabase-js";
|
|
1926
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1927
|
+
var PublicPageContext = createContext(void 0);
|
|
1928
|
+
function PublicPageProvider({ children, appName }) {
|
|
1929
|
+
const getEnvVar = (key) => {
|
|
1930
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
1931
|
+
const env = import.meta.env;
|
|
1932
|
+
return env[key];
|
|
2072
1933
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
1934
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1935
|
+
return process.env[key];
|
|
1936
|
+
}
|
|
1937
|
+
return void 0;
|
|
1938
|
+
};
|
|
1939
|
+
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
1940
|
+
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
1941
|
+
const supabase = useMemo2(() => {
|
|
1942
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
1943
|
+
logger.warn("PublicPageProvider", "Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment. Use publishable key if anon key is disabled.");
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
const client = createClient(supabaseUrl, supabaseKey);
|
|
1947
|
+
logger.info("PublicPageProvider", "Supabase client created successfully for public pages");
|
|
1948
|
+
return client;
|
|
1949
|
+
}, [supabaseUrl, supabaseKey]);
|
|
1950
|
+
const contextValue = {
|
|
1951
|
+
isPublicPage: true,
|
|
1952
|
+
supabase,
|
|
1953
|
+
appName: appName || null,
|
|
1954
|
+
environment: {
|
|
1955
|
+
supabaseUrl,
|
|
1956
|
+
supabaseKey
|
|
2079
1957
|
}
|
|
2080
|
-
await fetchFiles();
|
|
2081
|
-
}, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
|
|
2082
|
-
return {
|
|
2083
|
-
fileUrl,
|
|
2084
|
-
fileReference,
|
|
2085
|
-
fileReferences,
|
|
2086
|
-
fileUrls,
|
|
2087
|
-
fileCount,
|
|
2088
|
-
isLoading,
|
|
2089
|
-
error,
|
|
2090
|
-
refetch
|
|
2091
1958
|
};
|
|
1959
|
+
return /* @__PURE__ */ jsx2(PublicPageContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(ErrorBoundary, { componentName: "PublicPageProvider", children }) });
|
|
2092
1960
|
}
|
|
2093
|
-
function
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
1961
|
+
function usePublicPageContext() {
|
|
1962
|
+
const context = useContext(PublicPageContext);
|
|
1963
|
+
if (!context) {
|
|
1964
|
+
throw new Error("usePublicPageContext must be used within a PublicPageProvider");
|
|
1965
|
+
}
|
|
1966
|
+
return context;
|
|
1967
|
+
}
|
|
1968
|
+
function useIsPublicPage() {
|
|
1969
|
+
const context = useContext(PublicPageContext);
|
|
1970
|
+
return context !== void 0;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// src/hooks/useEventTheme.ts
|
|
1974
|
+
import { useEffect as useEffect6 } from "react";
|
|
1975
|
+
import { useLocation } from "react-router-dom";
|
|
1976
|
+
var log4 = createLogger("useEventTheme");
|
|
1977
|
+
function useEventTheme(event) {
|
|
1978
|
+
const location = useLocation();
|
|
1979
|
+
let selectedEvent;
|
|
1980
|
+
try {
|
|
1981
|
+
if (event === void 0) {
|
|
1982
|
+
const eventsContext = useEvents();
|
|
1983
|
+
selectedEvent = eventsContext.selectedEvent;
|
|
1984
|
+
} else {
|
|
1985
|
+
selectedEvent = event;
|
|
1986
|
+
}
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
if (event !== void 0) {
|
|
1989
|
+
selectedEvent = event;
|
|
1990
|
+
} else {
|
|
1991
|
+
selectedEvent = null;
|
|
2097
1992
|
}
|
|
2098
1993
|
}
|
|
1994
|
+
useEffect6(() => {
|
|
1995
|
+
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
1996
|
+
if (isOnLoginRoute) {
|
|
1997
|
+
clearPalette();
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (!selectedEvent) {
|
|
2001
|
+
clearPalette();
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const eventColours = selectedEvent.event_colours;
|
|
2005
|
+
const normalized = parseAndNormalizeEventColours(eventColours);
|
|
2006
|
+
if (!normalized) {
|
|
2007
|
+
clearPalette();
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
try {
|
|
2011
|
+
applyPalette(normalized);
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
log4.error("Failed to apply event palette:", error);
|
|
2014
|
+
}
|
|
2015
|
+
return () => {
|
|
2016
|
+
};
|
|
2017
|
+
}, [selectedEvent, location.pathname]);
|
|
2099
2018
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2019
|
+
|
|
2020
|
+
// src/hooks/usePreventTabReload.ts
|
|
2021
|
+
import { useEffect as useEffect7, useRef as useRef3 } from "react";
|
|
2022
|
+
function usePreventTabReload(options = {}) {
|
|
2023
|
+
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
2024
|
+
const isRestoringFromCacheRef = useRef3(false);
|
|
2025
|
+
const gracePeriodTimeoutRef = useRef3(null);
|
|
2026
|
+
useEffect7(() => {
|
|
2027
|
+
if (!enabled || typeof window === "undefined") return;
|
|
2028
|
+
const handlePageShow = (event) => {
|
|
2029
|
+
if (event.persisted) {
|
|
2030
|
+
isRestoringFromCacheRef.current = true;
|
|
2031
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2032
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
2033
|
+
}
|
|
2034
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
2035
|
+
isRestoringFromCacheRef.current = false;
|
|
2036
|
+
}, gracePeriodMs);
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
const handleVisibilityChange = () => {
|
|
2040
|
+
if (!document.hidden) {
|
|
2041
|
+
isRestoringFromCacheRef.current = true;
|
|
2042
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2043
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
2044
|
+
}
|
|
2045
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
2046
|
+
isRestoringFromCacheRef.current = false;
|
|
2047
|
+
}, gracePeriodMs);
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
2051
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
2052
|
+
return () => {
|
|
2053
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
2054
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
2055
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2056
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
}, [enabled, gracePeriodMs]);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// src/hooks/useAppConfig.ts
|
|
2063
|
+
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
2064
|
+
function useAppConfig() {
|
|
2065
|
+
const isPublicPage = useIsPublicPage();
|
|
2066
|
+
const publicPageContext = useContext2(PublicPageContext);
|
|
2067
|
+
const contextAppName = publicPageContext?.appName || null;
|
|
2068
|
+
if (isPublicPage) {
|
|
2069
|
+
const getAppName = () => {
|
|
2070
|
+
if (contextAppName) {
|
|
2071
|
+
return contextAppName;
|
|
2072
|
+
}
|
|
2073
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
2074
|
+
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
2075
|
+
}
|
|
2076
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
2077
|
+
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
2078
|
+
}
|
|
2079
|
+
return "PACE";
|
|
2080
|
+
};
|
|
2081
|
+
return useMemo3(() => ({
|
|
2082
|
+
supportsDirectAccess: false,
|
|
2083
|
+
// Public pages don't support direct access
|
|
2084
|
+
requiresEvent: true,
|
|
2085
|
+
// Public pages always require an event
|
|
2086
|
+
isLoading: false,
|
|
2087
|
+
appName: getAppName()
|
|
2088
|
+
}), [contextAppName]);
|
|
2089
|
+
}
|
|
2090
|
+
try {
|
|
2091
|
+
const { appConfig, appName } = useUnifiedAuth();
|
|
2092
|
+
return useMemo3(() => ({
|
|
2093
|
+
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
2094
|
+
requiresEvent: appConfig?.requires_event ?? true,
|
|
2095
|
+
isLoading: appConfig === null,
|
|
2096
|
+
appName
|
|
2097
|
+
}), [appConfig?.requires_event, appName]);
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
return useMemo3(() => ({
|
|
2100
|
+
supportsDirectAccess: false,
|
|
2101
|
+
requiresEvent: true,
|
|
2102
|
+
isLoading: false,
|
|
2103
|
+
appName: "PACE"
|
|
2104
|
+
}), []);
|
|
2105
|
+
}
|
|
2106
2106
|
}
|
|
2107
2107
|
|
|
2108
2108
|
export {
|
|
@@ -2110,14 +2110,6 @@ export {
|
|
|
2110
2110
|
useQueryCache,
|
|
2111
2111
|
queryCacheHelpers,
|
|
2112
2112
|
useAddressAutocomplete,
|
|
2113
|
-
useEventTheme,
|
|
2114
|
-
usePreventTabReload,
|
|
2115
|
-
ErrorBoundary,
|
|
2116
|
-
PublicPageContext,
|
|
2117
|
-
PublicPageProvider,
|
|
2118
|
-
usePublicPageContext,
|
|
2119
|
-
useIsPublicPage,
|
|
2120
|
-
useAppConfig,
|
|
2121
2113
|
FILE_SIZE_LIMITS,
|
|
2122
2114
|
DEFAULT_FILE_SIZE_LIMIT,
|
|
2123
2115
|
APP_PATH_MAPPING,
|
|
@@ -2136,14 +2128,22 @@ export {
|
|
|
2136
2128
|
listFiles,
|
|
2137
2129
|
downloadFile,
|
|
2138
2130
|
archiveFile,
|
|
2131
|
+
usePublicFileDisplay,
|
|
2132
|
+
clearPublicFileDisplayCache,
|
|
2133
|
+
getPublicFileDisplayCacheStats,
|
|
2134
|
+
createFileReferenceService,
|
|
2135
|
+
uploadFileWithReference,
|
|
2139
2136
|
useFileDisplay,
|
|
2140
2137
|
clearFileDisplayCache,
|
|
2141
2138
|
getFileDisplayCacheStats,
|
|
2142
2139
|
invalidateFileDisplayCache,
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2140
|
+
ErrorBoundary,
|
|
2141
|
+
PublicPageContext,
|
|
2142
|
+
PublicPageProvider,
|
|
2143
|
+
usePublicPageContext,
|
|
2144
|
+
useIsPublicPage,
|
|
2145
|
+
useEventTheme,
|
|
2146
|
+
usePreventTabReload,
|
|
2147
|
+
useAppConfig
|
|
2148
2148
|
};
|
|
2149
|
-
//# sourceMappingURL=chunk-
|
|
2149
|
+
//# sourceMappingURL=chunk-UCQSRW7Z.js.map
|