@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.
Files changed (209) hide show
  1. package/dist/{DataTable-K3RJRSOX.js → DataTable-GUFUNZ3N.js} +5 -5
  2. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-B8HaLe69.d.ts} +47 -17
  3. package/dist/{UnifiedAuthProvider-B76OWOAT.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
  4. package/dist/{chunk-FMTK4XNN.js → chunk-2UUZZJFT.js} +3 -3
  5. package/dist/{chunk-3IC5WCMO.js → chunk-3GOZZZYH.js} +3 -3
  6. package/dist/{chunk-ULX5FYEM.js → chunk-DDM4CCYT.js} +3 -3
  7. package/dist/{chunk-K2JGDXGU.js → chunk-E7UAOUMY.js} +2 -2
  8. package/dist/{chunk-T6ZJVI3A.js → chunk-IM4QE42D.js} +4 -4
  9. package/dist/{chunk-3NFNJOO7.js → chunk-MX64ZF6I.js} +4 -4
  10. package/dist/{chunk-C4OYJOV4.js → chunk-UCQSRW7Z.js} +829 -829
  11. package/dist/chunk-UCQSRW7Z.js.map +1 -0
  12. package/dist/{chunk-WK2Y6TGA.js → chunk-VGZZXKBR.js} +3 -3
  13. package/dist/chunk-VGZZXKBR.js.map +1 -0
  14. package/dist/{chunk-LBBUPSSC.js → chunk-YGPFYGA6.js} +3760 -3692
  15. package/dist/chunk-YGPFYGA6.js.map +1 -0
  16. package/dist/components.d.ts +1 -2
  17. package/dist/components.js +6 -10
  18. package/dist/components.js.map +1 -1
  19. package/dist/hooks.js +5 -5
  20. package/dist/index.d.ts +1 -2
  21. package/dist/index.js +9 -13
  22. package/dist/index.js.map +1 -1
  23. package/dist/providers.js +1 -1
  24. package/dist/rbac/index.js +5 -5
  25. package/dist/utils.js +1 -1
  26. package/docs/api/classes/ColumnFactory.md +1 -1
  27. package/docs/api/classes/ErrorBoundary.md +1 -1
  28. package/docs/api/classes/InvalidScopeError.md +1 -1
  29. package/docs/api/classes/Logger.md +1 -1
  30. package/docs/api/classes/MissingUserContextError.md +1 -1
  31. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  32. package/docs/api/classes/PermissionDeniedError.md +1 -1
  33. package/docs/api/classes/RBACAuditManager.md +1 -1
  34. package/docs/api/classes/RBACCache.md +1 -1
  35. package/docs/api/classes/RBACEngine.md +1 -1
  36. package/docs/api/classes/RBACError.md +1 -1
  37. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  38. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  39. package/docs/api/classes/StorageUtils.md +1 -1
  40. package/docs/api/enums/FileCategory.md +1 -1
  41. package/docs/api/enums/LogLevel.md +1 -1
  42. package/docs/api/enums/RBACErrorCode.md +1 -1
  43. package/docs/api/enums/RPCFunction.md +1 -1
  44. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  45. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  46. package/docs/api/interfaces/AggregateConfig.md +1 -1
  47. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  48. package/docs/api/interfaces/AvatarProps.md +128 -0
  49. package/docs/api/interfaces/BadgeProps.md +1 -1
  50. package/docs/api/interfaces/ButtonProps.md +1 -1
  51. package/docs/api/interfaces/CalendarProps.md +1 -1
  52. package/docs/api/interfaces/CardProps.md +1 -1
  53. package/docs/api/interfaces/ColorPalette.md +1 -1
  54. package/docs/api/interfaces/ColorShade.md +1 -1
  55. package/docs/api/interfaces/ComplianceResult.md +1 -1
  56. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  57. package/docs/api/interfaces/DataRecord.md +1 -1
  58. package/docs/api/interfaces/DataTableAction.md +1 -1
  59. package/docs/api/interfaces/DataTableColumn.md +1 -1
  60. package/docs/api/interfaces/DataTableProps.md +1 -1
  61. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  62. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  63. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  64. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  65. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  66. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  67. package/docs/api/interfaces/ExportColumn.md +1 -1
  68. package/docs/api/interfaces/ExportOptions.md +1 -1
  69. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  70. package/docs/api/interfaces/FileMetadata.md +1 -1
  71. package/docs/api/interfaces/FileReference.md +1 -1
  72. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  73. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  74. package/docs/api/interfaces/FileUploadProps.md +1 -1
  75. package/docs/api/interfaces/FooterProps.md +1 -1
  76. package/docs/api/interfaces/FormFieldProps.md +1 -1
  77. package/docs/api/interfaces/FormProps.md +1 -1
  78. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  79. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  80. package/docs/api/interfaces/InputProps.md +1 -1
  81. package/docs/api/interfaces/LabelProps.md +1 -1
  82. package/docs/api/interfaces/LoggerConfig.md +1 -1
  83. package/docs/api/interfaces/LoginFormProps.md +1 -1
  84. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  85. package/docs/api/interfaces/NavigationContextType.md +1 -1
  86. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  87. package/docs/api/interfaces/NavigationItem.md +1 -1
  88. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  90. package/docs/api/interfaces/Organisation.md +1 -1
  91. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  92. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  93. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  94. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  95. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  96. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  97. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  98. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  99. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  100. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  101. package/docs/api/interfaces/PaletteData.md +1 -1
  102. package/docs/api/interfaces/ParsedAddress.md +1 -1
  103. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  104. package/docs/api/interfaces/ProgressProps.md +1 -1
  105. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  106. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  107. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  109. package/docs/api/interfaces/QuickFix.md +1 -1
  110. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  111. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  112. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  113. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  114. package/docs/api/interfaces/RBACConfig.md +1 -1
  115. package/docs/api/interfaces/RBACContext.md +1 -1
  116. package/docs/api/interfaces/RBACLogger.md +1 -1
  117. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  118. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  119. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  120. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  121. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  122. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  123. package/docs/api/interfaces/RBACResult.md +1 -1
  124. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  125. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  126. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  127. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  128. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  129. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  130. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  131. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  132. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  133. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  134. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  135. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  136. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  137. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  138. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  139. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  140. package/docs/api/interfaces/RouteConfig.md +1 -1
  141. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  142. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  143. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  144. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  145. package/docs/api/interfaces/SetupIssue.md +1 -1
  146. package/docs/api/interfaces/StorageConfig.md +1 -1
  147. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  148. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  149. package/docs/api/interfaces/StorageListOptions.md +1 -1
  150. package/docs/api/interfaces/StorageListResult.md +1 -1
  151. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  152. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  153. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  154. package/docs/api/interfaces/StyleImport.md +1 -1
  155. package/docs/api/interfaces/SwitchProps.md +1 -1
  156. package/docs/api/interfaces/TabsContentProps.md +1 -1
  157. package/docs/api/interfaces/TabsListProps.md +1 -1
  158. package/docs/api/interfaces/TabsProps.md +1 -1
  159. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  160. package/docs/api/interfaces/TextareaProps.md +1 -1
  161. package/docs/api/interfaces/ToastActionElement.md +1 -1
  162. package/docs/api/interfaces/ToastProps.md +1 -1
  163. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  164. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  165. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  166. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  167. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  168. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  169. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  170. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  171. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  172. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  173. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  174. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  175. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  176. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  177. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  178. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  179. package/docs/api/interfaces/UserEventAccess.md +1 -1
  180. package/docs/api/interfaces/UserMenuProps.md +1 -1
  181. package/docs/api/interfaces/UserProfile.md +1 -1
  182. package/docs/api/modules.md +6 -45
  183. package/docs/api-reference/components.md +57 -22
  184. package/docs/getting-started/examples/README.md +2 -2
  185. package/docs/implementation-guides/public-pages.md +140 -1230
  186. package/docs/standards/05-security-standard.md +3 -1
  187. package/docs/standards/07-rbac-and-rls-standard.md +356 -0
  188. package/package.json +1 -2
  189. package/src/__tests__/public-recipe-view.test.ts +199 -0
  190. package/src/__tests__/rls-policies.test.ts +333 -0
  191. package/src/components/Avatar/Avatar.test.tsx +183 -209
  192. package/src/components/Avatar/Avatar.tsx +179 -53
  193. package/src/components/Avatar/index.ts +1 -1
  194. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  195. package/src/components/UserMenu/UserMenu.tsx +7 -5
  196. package/src/components/index.ts +2 -1
  197. package/src/index.ts +2 -1
  198. package/src/services/OrganisationService.ts +5 -4
  199. package/dist/chunk-C4OYJOV4.js.map +0 -1
  200. package/dist/chunk-LBBUPSSC.js.map +0 -1
  201. package/dist/chunk-WK2Y6TGA.js.map +0 -1
  202. /package/dist/{DataTable-K3RJRSOX.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
  203. /package/dist/{UnifiedAuthProvider-B76OWOAT.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
  204. /package/dist/{chunk-FMTK4XNN.js.map → chunk-2UUZZJFT.js.map} +0 -0
  205. /package/dist/{chunk-3IC5WCMO.js.map → chunk-3GOZZZYH.js.map} +0 -0
  206. /package/dist/{chunk-ULX5FYEM.js.map → chunk-DDM4CCYT.js.map} +0 -0
  207. /package/dist/{chunk-K2JGDXGU.js.map → chunk-E7UAOUMY.js.map} +0 -0
  208. /package/dist/{chunk-T6ZJVI3A.js.map → chunk-IM4QE42D.js.map} +0 -0
  209. /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-K2JGDXGU.js";
3
+ } from "./chunk-E7UAOUMY.js";
4
4
  import {
5
5
  useUnifiedAuth
6
- } from "./chunk-WK2Y6TGA.js";
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 log3 = createLogger("StorageHelpers");
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
- log3.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
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
- log3.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
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
- log3.error("Failed to create signed URL:", error);
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
- log3.error("Failed to create signed URL:", error);
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
- log3.error(`Failed to generate public URL for file ${file.id}:`, err);
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
- log3.error(`Failed to generate signed URL for file ${file.id}:`, err);
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
- const signedUrlResults = await Promise.all(signedUrlPromises);
1078
- for (const result of signedUrlResults) {
1079
- if (result.url) {
1080
- urlMap.set(result.id, result.url);
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
- if (uncachedFiles.length > 0) {
1085
- cleanupUrlCache();
1086
- }
1087
- return urlMap;
1088
- }
1089
- async function deleteFile(supabase, path, isPublic = false) {
1090
- try {
1091
- const bucketName = getBucketName(isPublic);
1092
- const { error } = await supabase.storage.from(bucketName).remove([path]);
1093
- if (error) {
1094
- return {
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
- return { success: true };
1100
- } catch (error) {
1101
- return {
1102
- success: false,
1103
- error: `Delete failed: ${error instanceof Error ? error.message : "Unknown error"}`
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
- const files = (data || []).map((item) => ({
1122
- name: item.name,
1123
- path: `${searchPath}${item.name}`,
1124
- size: item.metadata?.size || 0,
1125
- mimeType: item.metadata?.mimetype || "application/octet-stream",
1126
- lastModified: item.updated_at || item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1127
- metadata: {
1128
- mimeType: item.metadata?.mimetype || "application/octet-stream",
1129
- size: item.metadata?.size || 0,
1130
- orgId: options.orgId,
1131
- appName: options.appName,
1132
- uploadedBy: "unknown",
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
- async function downloadFile(supabase, path, isPublic = false) {
1148
- try {
1149
- const bucketName = getBucketName(isPublic);
1150
- const { data, error } = await supabase.storage.from(bucketName).download(path);
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
- async function archiveFile(supabase, path, options) {
1177
- try {
1178
- const bucketName = getBucketName(options.isPublic || false);
1179
- const archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
1180
- const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
1181
- if (copyError) {
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 useState3, useEffect as useEffect6, useCallback as useCallback3 } from "react";
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 log4 = createLogger("FileReferenceService");
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
- log4.error("Error creating file reference:", error);
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
- log4.error("Error getting file reference:", error);
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
- log4.error("Error getting file URL:", error);
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
- log4.error("Error getting signed URL:", error);
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
- log4.error("Error updating file reference:", error);
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
- log4.error("Error deleting file reference:", error);
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
- log4.error("Error listing file references:", error);
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
- log4.error("Error getting file count:", error);
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
- log4.error("Error getting file reference by ID:", error);
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
- log4.error("RPC ERROR getting files by category:", {
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
- log4.warn("File category mismatch in RPC response:", {
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
- log4.error("Error getting files by category:", error);
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] = useState3(null);
1617
- const [fileReference, setFileReference] = useState3(null);
1618
- const [fileReferences, setFileReferences] = useState3([]);
1619
- const [fileUrls, setFileUrls] = useState3(/* @__PURE__ */ new Map());
1620
- const [fileCount, setFileCount] = useState3(0);
1621
- const [isLoading, setIsLoading] = useState3(false);
1622
- const [error, setError] = useState3(null);
1623
- const fetchFiles = useCallback3(async () => {
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
- return;
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, fileRefs, {
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
- publicFileCache.set(cacheKey, {
2021
- data: {
2022
- fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
2023
- fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
2024
- fileReferences: fileRefs,
2025
- fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
2026
- const urlMap = /* @__PURE__ */ new Map();
2027
- for (const fileRef of fileRefs) {
2028
- const url = getPublicUrl(supabase, fileRef.file_path, true);
2029
- if (url) {
2030
- urlMap.set(fileRef.id, url);
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
- return urlMap;
2034
- })(),
2035
- fileCount: fileRefs.length
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("usePublicFileDisplay", "Error fetching files", {
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
- useEffect7(() => {
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
- }, [fetchFiles, table_name, record_id, organisation_id]);
2074
- const refetch = useCallback4(async () => {
2075
- if (!table_name || !record_id || !organisation_id) return;
2076
- if (enableCache) {
2077
- const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
2078
- publicFileCache.delete(cacheKey);
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 clearPublicFileDisplayCache() {
2094
- for (const [key] of publicFileCache) {
2095
- if (key.startsWith("public_file_")) {
2096
- publicFileCache.delete(key);
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
- function getPublicFileDisplayCacheStats() {
2101
- const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
2102
- return {
2103
- size: keys.length,
2104
- keys
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
- createFileReferenceService,
2144
- uploadFileWithReference,
2145
- usePublicFileDisplay,
2146
- clearPublicFileDisplayCache,
2147
- getPublicFileDisplayCacheStats
2140
+ ErrorBoundary,
2141
+ PublicPageContext,
2142
+ PublicPageProvider,
2143
+ usePublicPageContext,
2144
+ useIsPublicPage,
2145
+ useEventTheme,
2146
+ usePreventTabReload,
2147
+ useAppConfig
2148
2148
  };
2149
- //# sourceMappingURL=chunk-C4OYJOV4.js.map
2149
+ //# sourceMappingURL=chunk-UCQSRW7Z.js.map