@jmruthers/pace-core 0.6.7 → 0.6.9

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 (117) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/audit-tool/00-dependencies.cjs +215 -9
  3. package/audit-tool/audits/02-project-structure.cjs +41 -53
  4. package/audit-tool/audits/03-architecture.cjs +34 -6
  5. package/audit-tool/audits/06-security-rbac.cjs +10 -0
  6. package/audit-tool/audits/07-api-tech-stack.cjs +55 -1
  7. package/audit-tool/index.cjs +23 -19
  8. package/audit-tool/utils/report-utils.cjs +141 -2
  9. package/dist/{DataTable-7PMH7XN7.js → DataTable-SOAFXIWY.js} +5 -5
  10. package/dist/{PublicPageProvider-DlsCaR5v.d.ts → PublicPageProvider-CIGSujI2.d.ts} +14 -8
  11. package/dist/{UnifiedAuthProvider-ZT6TIGM7.js → UnifiedAuthProvider-7SNDOWYD.js} +2 -2
  12. package/dist/{api-Y4MQWOFW.js → api-7P7DI652.js} +1 -1
  13. package/dist/{chunk-L4XMVJKY.js → chunk-4DDCYDQ3.js} +8 -7
  14. package/dist/{chunk-JGWDVX64.js → chunk-5HNSDQWH.js} +125 -55
  15. package/dist/{chunk-ZKAWKYT4.js → chunk-5W2A3DRC.js} +2 -1
  16. package/dist/{chunk-IUBRCBSY.js → chunk-C7ZQ5O4C.js} +11 -5
  17. package/dist/{chunk-VBCS3DUA.js → chunk-EF2UGZWY.js} +3 -3
  18. package/dist/{chunk-BM4CQ5P3.js → chunk-GS5672WG.js} +6 -6
  19. package/dist/{chunk-Q7Q7V5NV.js → chunk-J2U36LHD.js} +72 -9
  20. package/dist/{chunk-ZFYPMX46.js → chunk-LX6U42O3.js} +1 -1
  21. package/dist/{chunk-5X4QLXRG.js → chunk-MPBLMWVR.js} +5 -3
  22. package/dist/{chunk-6F3IILHI.js → chunk-S6ZQKDY6.js} +1 -1
  23. package/dist/{chunk-FTCRZOG2.js → chunk-T5CVK4R3.js} +5 -5
  24. package/dist/{chunk-GHYHJTYV.js → chunk-Z2FNRKF3.js} +13 -13
  25. package/dist/components.d.ts +1 -1
  26. package/dist/components.js +12 -12
  27. package/dist/{database.generated-CcnC_DRc.d.ts → database.generated-DT8JTZiP.d.ts} +12 -12
  28. package/dist/eslint-rules/rules/04-code-quality.cjs +66 -10
  29. package/dist/eslint-rules/rules/06-security-rbac.cjs +8 -3
  30. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +190 -68
  31. package/dist/{functions-DHebl8-F.d.ts → functions-lBy5L2ry.d.ts} +1 -1
  32. package/dist/hooks.d.ts +3 -3
  33. package/dist/hooks.js +7 -7
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.js +16 -16
  36. package/dist/providers.js +2 -2
  37. package/dist/rbac/index.d.ts +2 -2
  38. package/dist/rbac/index.js +6 -6
  39. package/dist/theming/runtime.d.ts +48 -1
  40. package/dist/theming/runtime.js +1 -1
  41. package/dist/{timezone-BZe_eUxx.d.ts → timezone-0AyangqX.d.ts} +1 -1
  42. package/dist/types.d.ts +3 -3
  43. package/dist/{usePublicRouteParams-MamNgwqe.d.ts → usePublicRouteParams-DQLrDqDb.d.ts} +1 -1
  44. package/dist/utils.d.ts +3 -3
  45. package/dist/utils.js +3 -3
  46. package/docs/api/modules.md +64 -15
  47. package/docs/api-reference/rpc-functions.md +3 -3
  48. package/docs/getting-started/dependencies.md +23 -0
  49. package/docs/implementation-guides/app-layout.md +1 -1
  50. package/docs/implementation-guides/data-tables.md +67 -1
  51. package/docs/standards/1-pace-core-compliance-standards.md +38 -1
  52. package/eslint-config-pace-core.cjs +30 -11
  53. package/package.json +45 -15
  54. package/scripts/eslint-audit.cjs +123 -0
  55. package/scripts/install-eslint-config.cjs +67 -2
  56. package/scripts/validate-dependencies.cjs +248 -0
  57. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +20 -8
  58. package/src/__tests__/templates/accessibility.test.template.tsx +1 -0
  59. package/src/components/AddressField/AddressField.tsx +26 -1
  60. package/src/components/Alert/Alert.test.tsx +86 -22
  61. package/src/components/Alert/Alert.tsx +19 -11
  62. package/src/components/Badge/Badge.tsx +1 -1
  63. package/src/components/Checkbox/Checkbox.test.tsx +2 -1
  64. package/src/components/ContextSelector/ContextSelector.tsx +39 -41
  65. package/src/components/DataTable/DataTable.tsx +1 -19
  66. package/src/components/DataTable/__tests__/DataTable.select-label-display.test.tsx +483 -0
  67. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +6 -10
  68. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +18 -9
  69. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +3 -2
  70. package/src/components/DataTable/components/EmptyState.tsx +1 -1
  71. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +1 -1
  72. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +3 -3
  73. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +33 -29
  74. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
  75. package/src/components/DataTable/hooks/useTableColumns.ts +23 -1
  76. package/src/components/DataTable/utils/__tests__/selectFieldUtils.test.ts +207 -0
  77. package/src/components/DataTable/utils/index.ts +1 -0
  78. package/src/components/DataTable/utils/selectFieldUtils.ts +134 -0
  79. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -2
  80. package/src/components/FileUpload/FileUpload.test.tsx +22 -31
  81. package/src/components/FileUpload/FileUpload.tsx +29 -0
  82. package/src/components/NavigationMenu/NavigationMenu.test.tsx +48 -12
  83. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +9 -9
  84. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +30 -30
  85. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -4
  86. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +7 -1
  87. package/src/components/UserMenu/UserMenu.tsx +3 -5
  88. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +8 -5
  89. package/src/hooks/__tests__/useFileUrl.unit.test.ts +4 -0
  90. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +3 -3
  91. package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +45 -8
  92. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +22 -2
  93. package/src/hooks/public/usePublicRouteParams.ts +8 -4
  94. package/src/hooks/useAddressAutocomplete.test.ts +18 -18
  95. package/src/hooks/useEventTheme.ts +5 -1
  96. package/src/hooks/useFileUrl.ts +52 -8
  97. package/src/hooks/useOrganisationSecurity.test.ts +2 -1
  98. package/src/providers/__tests__/ProviderLifecycle.test.tsx +1 -1
  99. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +15 -6
  100. package/src/rbac/__tests__/rbac-functions.test.ts +3 -3
  101. package/src/rbac/api.test.ts +104 -0
  102. package/src/rbac/engine.ts +1 -1
  103. package/src/rbac/hooks/useCan.test.ts +2 -2
  104. package/src/rbac/secureClient.ts +1 -1
  105. package/src/rbac/types/functions.ts +1 -1
  106. package/src/theming/__tests__/parseEventColours.test.ts +117 -8
  107. package/src/theming/parseEventColours.ts +56 -2
  108. package/src/types/database.generated.ts +9 -9
  109. package/src/types/supabase.ts +2 -3
  110. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +9 -9
  111. package/src/utils/file-reference/__tests__/file-reference.test.ts +4 -0
  112. package/src/utils/formatting/formatDate.test.ts +3 -2
  113. package/src/utils/formatting/formatDateTime.test.ts +2 -2
  114. package/src/utils/google-places/googlePlacesUtils.test.ts +36 -24
  115. package/src/utils/storage/__tests__/helpers.unit.test.ts +19 -12
  116. package/src/utils/storage/helpers.test.ts +69 -3
  117. package/src/utils/supabase/createBaseClient.ts +25 -7
@@ -83,13 +83,127 @@ function formatStandardSection(standard, standardName, issues, standardDocPath)
83
83
  return section;
84
84
  }
85
85
 
86
+ /**
87
+ * Format dependency audit section for markdown report
88
+ * @param {Object} dependencyResult - Dependency audit result
89
+ * @returns {string} - Formatted markdown section
90
+ */
91
+ function formatDependencyAuditSection(dependencyResult) {
92
+ if (dependencyResult.error) {
93
+ return `\n### ❌ Dependency Audit\n\n**Error:** ${dependencyResult.error}\n\n`;
94
+ }
95
+
96
+ const issues = dependencyResult.issues || {};
97
+ const includedDeps = issues.includedDeps || [];
98
+ const missingRequired = issues.missingRequired || [];
99
+ const missingOptional = issues.missingOptional || [];
100
+ const versionIssues = issues.versionIssues || [];
101
+ const wrongLocation = issues.wrongLocation || [];
102
+ const missingDevDeps = issues.missingDevDeps || [];
103
+ const devVersionIssues = issues.devVersionIssues || [];
104
+
105
+ const totalIssues = includedDeps.length + missingRequired.length + versionIssues.length + wrongLocation.length + missingDevDeps.length + devVersionIssues.length;
106
+
107
+ if (totalIssues === 0 && missingOptional.length === 0) {
108
+ return `\n### ✅ Dependency Audit\n\nNo issues found.\n`;
109
+ }
110
+
111
+ let section = `\n### ${totalIssues > 0 ? '❌' : '⚠️'} Dependency Audit\n\n`;
112
+ section += `**Reference:** [Dependencies Guide](https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/getting-started/dependencies.md)\n\n`;
113
+
114
+ if (totalIssues > 0) {
115
+ section += `**Issues Found:** ${totalIssues}\n\n`;
116
+ }
117
+
118
+ // Included dependencies (errors)
119
+ if (includedDeps.length > 0) {
120
+ section += `#### ❌ Included Dependencies (MUST REMOVE) - ${includedDeps.length} issue${includedDeps.length > 1 ? 's' : ''}\n\n`;
121
+ section += `These packages are already included in pace-core and should NOT be installed in your app:\n\n`;
122
+ includedDeps.forEach(issue => {
123
+ section += `- **${issue.package}**@${issue.installed} (in ${issue.location})\n`;
124
+ section += ` - Should NOT be installed (already included in pace-core)\n`;
125
+ section += ` - Fix: \`npm uninstall ${issue.package}\`\n\n`;
126
+ });
127
+ }
128
+
129
+ // Missing required dependencies (errors)
130
+ if (missingRequired.length > 0) {
131
+ section += `#### ❌ Missing Required Dependencies - ${missingRequired.length} issue${missingRequired.length > 1 ? 's' : ''}\n\n`;
132
+ section += `These packages are required for pace-core to function:\n\n`;
133
+ missingRequired.forEach(issue => {
134
+ section += `- **${issue.package}** (required: ${issue.required})\n`;
135
+ section += ` - Fix: \`npm install ${issue.package}@${issue.required}\`\n\n`;
136
+ });
137
+ }
138
+
139
+ // Version issues (errors)
140
+ if (versionIssues.length > 0) {
141
+ section += `#### ⚠️ Version Issues - ${versionIssues.length} issue${versionIssues.length > 1 ? 's' : ''}\n\n`;
142
+ section += `These packages have incorrect version ranges:\n\n`;
143
+ versionIssues.forEach(issue => {
144
+ section += `- **${issue.package}**\n`;
145
+ section += ` - Installed: ${issue.installed}\n`;
146
+ section += ` - Required: ${issue.required}\n`;
147
+ section += ` - Fix: \`npm install ${issue.package}@${issue.required}\`\n\n`;
148
+ });
149
+ }
150
+
151
+ // Wrong location (warnings)
152
+ if (wrongLocation.length > 0) {
153
+ section += `#### ⚠️ Wrong Location - ${wrongLocation.length} issue${wrongLocation.length > 1 ? 's' : ''}\n\n`;
154
+ section += `These packages are in the wrong location in package.json:\n\n`;
155
+ wrongLocation.forEach(issue => {
156
+ section += `- **${issue.package}**\n`;
157
+ section += ` - Currently in: ${issue.current}\n`;
158
+ section += ` - Should be in: ${issue.shouldBe}\n`;
159
+ section += ` - Fix: \`npm uninstall ${issue.package} && npm install -D ${issue.package}\`\n\n`;
160
+ });
161
+ }
162
+
163
+ // Missing optional dependencies (info)
164
+ if (missingOptional.length > 0) {
165
+ section += `#### ℹ️ Missing Optional Dependencies - ${missingOptional.length} package${missingOptional.length > 1 ? 's' : ''}\n\n`;
166
+ section += `These packages are optional and only needed if you use specific features:\n\n`;
167
+ missingOptional.forEach(issue => {
168
+ section += `- **${issue.package}** (required: ${issue.required})\n`;
169
+ section += ` - Install only if you use this feature\n`;
170
+ section += ` - Fix: \`npm install ${issue.package}@${issue.required}\`\n\n`;
171
+ });
172
+ }
173
+
174
+ // Missing dev dependencies (errors)
175
+ if (missingDevDeps.length > 0) {
176
+ section += `#### ❌ Missing Required Dev Dependencies - ${missingDevDeps.length} issue${missingDevDeps.length > 1 ? 's' : ''}\n\n`;
177
+ section += `These dev dependencies are required for a compatible development environment:\n\n`;
178
+ missingDevDeps.forEach(issue => {
179
+ section += `- **${issue.package}** (required: ${issue.required})\n`;
180
+ section += ` - Fix: \`npm install -D ${issue.package}@${issue.required}\`\n\n`;
181
+ });
182
+ }
183
+
184
+ // Dev version issues (errors)
185
+ if (devVersionIssues.length > 0) {
186
+ section += `#### ⚠️ Dev Dependency Version Issues - ${devVersionIssues.length} issue${devVersionIssues.length > 1 ? 's' : ''}\n\n`;
187
+ section += `These dev dependencies have incorrect version ranges:\n\n`;
188
+ devVersionIssues.forEach(issue => {
189
+ section += `- **${issue.package}** (in ${issue.location})\n`;
190
+ section += ` - Installed: ${issue.installed}\n`;
191
+ section += ` - Required: ${issue.required}\n`;
192
+ section += ` - Fix: \`npm install -D ${issue.package}@${issue.required}\`\n\n`;
193
+ });
194
+ }
195
+
196
+ return section;
197
+ }
198
+
86
199
  /**
87
200
  * Generate markdown report from audit results
88
- * @param {Object} results - Audit results object
201
+ * @param {Object} results - Audit results object (standard audits)
89
202
  * @param {string} consumingAppPath - Path to consuming app
203
+ * @param {Object} dependencyResult - Dependency audit result (optional)
90
204
  * @returns {string} - Complete markdown report
91
205
  */
92
- function generateMarkdownReport(results, consumingAppPath) {
206
+ function generateMarkdownReport(results, consumingAppPath, dependencyResult = null) {
93
207
  const now = new Date();
94
208
  const timestamp = now.toISOString();
95
209
 
@@ -141,10 +255,29 @@ function generateMarkdownReport(results, consumingAppPath) {
141
255
  }
142
256
  });
143
257
 
258
+ // Count dependency audit issues
259
+ let dependencyIssues = 0;
260
+ if (dependencyResult && !dependencyResult.error) {
261
+ const depIssues = dependencyResult.issues || {};
262
+ dependencyIssues = (depIssues.includedDeps?.length || 0) +
263
+ (depIssues.missingRequired?.length || 0) +
264
+ (depIssues.versionIssues?.length || 0) +
265
+ (depIssues.wrongLocation?.length || 0) +
266
+ (depIssues.missingDevDeps?.length || 0) +
267
+ (depIssues.devVersionIssues?.length || 0);
268
+ totalIssues += dependencyIssues;
269
+ }
270
+
144
271
  report += `**Total Issues:** ${totalIssues}\n\n`;
145
272
  report += `| Standard | Issues |\n`;
146
273
  report += `|----------|--------|\n`;
147
274
 
275
+ // Add dependency audit to summary table first
276
+ if (dependencyResult) {
277
+ const depIcon = dependencyIssues === 0 ? '✅' : '❌';
278
+ report += `| ${depIcon} Dependency Audit | ${dependencyIssues} |\n`;
279
+ }
280
+
148
281
  Object.entries(standardCounts).forEach(([standard, count]) => {
149
282
  const icon = count === 0 ? '✅' : '❌';
150
283
  report += `| ${icon} ${standardNames[standard]} | ${count} |\n`;
@@ -153,6 +286,11 @@ function generateMarkdownReport(results, consumingAppPath) {
153
286
  report += `\n---\n\n`;
154
287
  report += `## Detailed Results\n\n`;
155
288
 
289
+ // Add dependency audit section first
290
+ if (dependencyResult) {
291
+ report += formatDependencyAuditSection(dependencyResult);
292
+ }
293
+
156
294
  // Generate sections for each standard
157
295
  Object.keys(standardNames).forEach(standard => {
158
296
  const standardResult = results[standard];
@@ -236,6 +374,7 @@ function generateSummary(results) {
236
374
  module.exports = {
237
375
  formatIssue,
238
376
  formatStandardSection,
377
+ formatDependencyAuditSection,
239
378
  generateMarkdownReport,
240
379
  generateSummary,
241
380
  };
@@ -1,10 +1,10 @@
1
- export { ActionButtons, BulkOperationsDropdown, ColumnFactory, ColumnVisibilityDropdown, DataTable, DataTableCore, DataTableErrorBoundary, DataTableModals, DataTableToolbar, EditableRow, EmptyState, EnhancedPaginationControls, GroupingDropdown, ImportModal, LoadingState, PaginationControls, SortIndicator, UnifiedTableBody, announce, announceBulkOperation, announceFilterChange, announceLoadingState, announcePaginationChange, announceSearchResults, announceSelectionChange, announceSortChange, average, calculateAllDepths, calculateAllIndentation, calculateIndentation, calculateOptimalPageSize, cleanupLiveRegion, count, createHierarchicalStructure, defaultDataTableFeatures, exportToCSV, exportToCSVWithTableRows, generateCSVContent, getAriaSortState, getAriaSortValue, getCellRenderer, getColumnHeaderText, getHierarchicalSortConfig, getPageSizeOptions, getPaginationBinding, getRowDepth, getRowDescription, getRowIdSafe, getSortButtonLabel, groupHierarchicalData, hasValidRowId, initializeLiveRegion, isHierarchicalSortableColumn, max, min, normalizeDataTableFeatures, shouldShowColumnForRow, sortHierarchicalDataByStructure, sortHierarchicalDataWithSorting, sum, validateHierarchicalData, validatePaginationConfig } from './chunk-Q7Q7V5NV.js';
2
- import './chunk-BM4CQ5P3.js';
1
+ export { ActionButtons, BulkOperationsDropdown, ColumnFactory, ColumnVisibilityDropdown, DataTable, DataTableCore, DataTableErrorBoundary, DataTableModals, DataTableToolbar, EditableRow, EmptyState, EnhancedPaginationControls, GroupingDropdown, ImportModal, LoadingState, PaginationControls, SortIndicator, UnifiedTableBody, announce, announceBulkOperation, announceFilterChange, announceLoadingState, announcePaginationChange, announceSearchResults, announceSelectionChange, announceSortChange, average, calculateAllDepths, calculateAllIndentation, calculateIndentation, calculateOptimalPageSize, cleanupLiveRegion, count, createHierarchicalStructure, defaultDataTableFeatures, exportToCSV, exportToCSVWithTableRows, findSelectLabel, generateCSVContent, getAriaSortState, getAriaSortValue, getCellRenderer, getColumnHeaderText, getHierarchicalSortConfig, getPageSizeOptions, getPaginationBinding, getRowDepth, getRowDescription, getRowIdSafe, getSortButtonLabel, groupHierarchicalData, hasValidRowId, initializeLiveRegion, isHierarchicalSortableColumn, max, min, normalizeDataTableFeatures, shouldShowColumnForRow, sortHierarchicalDataByStructure, sortHierarchicalDataWithSorting, sum, validateHierarchicalData, validatePaginationConfig } from './chunk-J2U36LHD.js';
2
+ import './chunk-GS5672WG.js';
3
3
  export { CircuitBreaker, DEFAULT_FALLBACK_CONFIG, DataChunkManager, DataTableError, DataTableErrorType, ErrorRecoveryManager, MemoryMonitor, SearchIndex, VisibilityTracker, chunkData, debounce, determinePaginationMode, getOptimalPageSizeOptions, safeExecute, throttle, useDataTablePerformance } from './chunk-S7DKJPLT.js';
4
- import './chunk-VBCS3DUA.js';
4
+ import './chunk-EF2UGZWY.js';
5
5
  import './chunk-C7NSAPTL.js';
6
- import './chunk-FTCRZOG2.js';
7
- import './chunk-ZFYPMX46.js';
6
+ import './chunk-T5CVK4R3.js';
7
+ import './chunk-LX6U42O3.js';
8
8
  import './chunk-AHU7G2R5.js';
9
9
  import './chunk-4SXLQIZO.js';
10
10
  import './chunk-A3W6LW53.js';
@@ -473,27 +473,32 @@ declare namespace Textarea {
473
473
  * Features:
474
474
  * - Multiple visual variants (default, destructive, inline)
475
475
  * - Title and description support
476
- * - Semantic HTML: renders as `<aside>` element
477
- * - ARIA role="alert" for accessibility
476
+ * - Semantic HTML: renders as `<p>` element with `role="alert"` (default) or custom role
478
477
  * - Keyboard and screen reader accessible
479
478
  * - Composable with icons and actions
480
479
  * - Inline variant for lightweight text formatting
481
480
  *
482
481
  * @example
483
482
  * ```tsx
484
- * // Basic alert (renders as <aside> with <h5> title and <p> description)
483
+ * // Basic alert (renders as <p role="alert"> with <h5> title and <p> description)
485
484
  * <Alert>
486
485
  * <AlertTitle>Success</AlertTitle>
487
486
  * <AlertDescription>Your changes have been saved.</AlertDescription>
488
487
  * </Alert>
489
488
  *
490
- * // Destructive alert with icon (renders as <aside> with <h5> title and <p> description)
489
+ * // Destructive alert with icon (renders as <p role="alert"> with <h5> title and <p> description)
491
490
  * <Alert variant="destructive">
492
491
  * <ErrorIcon />
493
492
  * <AlertTitle>Error</AlertTitle>
494
493
  * <AlertDescription>Something went wrong.</AlertDescription>
495
494
  * </Alert>
496
495
  *
496
+ * // Status message (renders as <p role="status"> for informational messages)
497
+ * <Alert role="status" aria-live="polite">
498
+ * <AlertTitle>No data available</AlertTitle>
499
+ * <AlertDescription>Get started by adding your first entry.</AlertDescription>
500
+ * </Alert>
501
+ *
497
502
  * // Inline alert (renders as React.Fragment with <strong> title and <span> description)
498
503
  * <Alert variant="inline">
499
504
  * <AlertTitle>Note:</AlertTitle>
@@ -502,15 +507,16 @@ declare namespace Textarea {
502
507
  * ```
503
508
  *
504
509
  * @accessibility
505
- * - Uses semantic HTML: `<aside>` element for better semantic meaning
506
- * - Uses role="alert" for screen reader announcement
510
+ * - Uses semantic HTML: `<p>` element with `role="alert"` (default) for screen reader announcements
511
+ * - Can be customized with `role="status"` for informational messages
507
512
  * - Title and description are semantically structured
508
513
  * - Supports keyboard navigation and focus
509
514
  */
510
515
 
511
- declare const Alert: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLElement> & {
516
+ declare const Alert: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLParagraphElement> & {
512
517
  variant?: "default" | "destructive" | "inline";
513
- } & React$1.RefAttributes<HTMLElement>>;
518
+ role?: string;
519
+ } & React$1.RefAttributes<HTMLParagraphElement>>;
514
520
  declare const AlertTitle: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLHeadingElement> & React$1.RefAttributes<HTMLHeadingElement>>;
515
521
  declare const AlertDescription: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLParagraphElement> & React$1.RefAttributes<HTMLParagraphElement>>;
516
522
 
@@ -1,5 +1,5 @@
1
- export { UnifiedAuthContext, UnifiedAuthProvider, useUnifiedAuth } from './chunk-FTCRZOG2.js';
2
- import './chunk-ZFYPMX46.js';
1
+ export { UnifiedAuthContext, UnifiedAuthProvider, useUnifiedAuth } from './chunk-T5CVK4R3.js';
2
+ import './chunk-LX6U42O3.js';
3
3
  import './chunk-AHU7G2R5.js';
4
4
  import './chunk-4SXLQIZO.js';
5
5
  import './chunk-HF6O3O37.js';
@@ -1,4 +1,4 @@
1
- export { OrganisationContextRequiredError, clearCache, getAccessLevel, getPageScopeType, getPermissionMap, getRoleContext, hasAllPermissions, hasAnyPermission, invalidateAppCache, invalidateEventCache, invalidateOrganisationCache, invalidateUserCache, isEventAdmin, isOrganisationAdmin, isPermitted, isPermittedCached, isRBACInitialized, isSuperAdmin, resolveAppContext, setupRBAC } from './chunk-ZFYPMX46.js';
1
+ export { OrganisationContextRequiredError, clearCache, getAccessLevel, getPageScopeType, getPermissionMap, getRoleContext, hasAllPermissions, hasAnyPermission, invalidateAppCache, invalidateEventCache, invalidateOrganisationCache, invalidateUserCache, isEventAdmin, isOrganisationAdmin, isPermitted, isPermittedCached, isRBACInitialized, isSuperAdmin, resolveAppContext, setupRBAC } from './chunk-LX6U42O3.js';
2
2
  import './chunk-AHU7G2R5.js';
3
3
  import './chunk-TTRFSOKR.js';
4
4
  import './chunk-3RG5ZIWI.js';
@@ -1,6 +1,6 @@
1
- import { generateFilePath, generateUniqueFileName, extractFileMetadata, uploadFile, getPublicUrl, getSignedUrl, deleteFile, downloadFile, listFiles, archiveFile } from './chunk-5X4QLXRG.js';
2
- import { useOrganisationSecurity, usePublicPageContext } from './chunk-VBCS3DUA.js';
3
- import { useOrganisations } from './chunk-FTCRZOG2.js';
1
+ import { generateFilePath, generateUniqueFileName, extractFileMetadata, uploadFile, getPublicUrl, getSignedUrl, deleteFile, downloadFile, listFiles, archiveFile } from './chunk-MPBLMWVR.js';
2
+ import { useOrganisationSecurity, usePublicPageContext } from './chunk-EF2UGZWY.js';
3
+ import { useOrganisations } from './chunk-T5CVK4R3.js';
4
4
  import { assertOrganisationId } from './chunk-4SXLQIZO.js';
5
5
  import { createLogger, logger } from './chunk-TTRFSOKR.js';
6
6
  import { useForm } from 'react-hook-form';
@@ -481,13 +481,14 @@ function usePublicRouteParams(options = {}) {
481
481
  }, [fetchEventData, eventLoading]);
482
482
  const finalError = useMemo(() => {
483
483
  if (error) return error;
484
- if (eventError) return eventError;
484
+ if (!validateEventCode) return null;
485
+ if (fetchEventData && eventError) return eventError;
485
486
  return null;
486
- }, [error, eventError]);
487
+ }, [error, eventError, fetchEventData, validateEventCode]);
487
488
  const eventId = useMemo(() => {
488
- if (!event) return null;
489
+ if (!fetchEventData || !event) return null;
489
490
  return event.event_id || event.id;
490
- }, [event]);
491
+ }, [fetchEventData, event]);
491
492
  const refetch = useCallback(async () => {
492
493
  if (!fetchEventData) return;
493
494
  await refetchEvent();
@@ -1,12 +1,12 @@
1
- import { AccessDenied } from './chunk-6F3IILHI.js';
2
- import { Input, Dialog, DialogContent, DialogHeader, DialogBody, DialogFooter, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Alert, AlertDescription, Label, SelectLabel, SelectSeparator, DialogTitle, deriveFormKey, useSessionDraft, filterSensitiveFields, isSensitiveField, SelectGroup, AlertTitle, Progress } from './chunk-Q7Q7V5NV.js';
3
- import { Button, useRBAC, useResolvedScope, usePermissions, useEvents, useCan } from './chunk-BM4CQ5P3.js';
4
- import { useAddressAutocomplete, createFileReferenceService, uploadFileWithReference, usePublicFileDisplay, useFileDisplay, getPublicUrl, getSignedUrl, generateFileUrlsBatch, useEventTheme, usePreventTabReload } from './chunk-5X4QLXRG.js';
5
- import { clearPalette } from './chunk-ZKAWKYT4.js';
1
+ import { AccessDenied } from './chunk-S6ZQKDY6.js';
2
+ import { Input, Dialog, DialogContent, DialogHeader, DialogBody, DialogFooter, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Alert, AlertDescription, Label, SelectLabel, SelectSeparator, DialogTitle, deriveFormKey, useSessionDraft, filterSensitiveFields, isSensitiveField, SelectGroup, AlertTitle, Progress } from './chunk-J2U36LHD.js';
3
+ import { Button, useRBAC, useResolvedScope, usePermissions, useEvents, useCan } from './chunk-GS5672WG.js';
4
+ import { useAddressAutocomplete, createFileReferenceService, uploadFileWithReference, usePublicFileDisplay, useFileDisplay, getPublicUrl, getSignedUrl, generateFileUrlsBatch, useEventTheme, usePreventTabReload } from './chunk-MPBLMWVR.js';
5
+ import { clearPalette } from './chunk-5W2A3DRC.js';
6
6
  import { useToast } from './chunk-S7DKJPLT.js';
7
- import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, useIsPublicPage, PublicPageContext, useAppConfig, ErrorBoundary } from './chunk-VBCS3DUA.js';
8
- import { useUnifiedAuth, useOrganisations, UnifiedAuthContext, EventServiceContext, useSessionRestoration } from './chunk-FTCRZOG2.js';
9
- import { EventContextRequiredError, OrganisationContextRequiredError, isSuperAdmin } from './chunk-ZFYPMX46.js';
7
+ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, useIsPublicPage, PublicPageContext, useAppConfig, ErrorBoundary } from './chunk-EF2UGZWY.js';
8
+ import { useUnifiedAuth, useOrganisations, UnifiedAuthContext, EventServiceContext, useSessionRestoration } from './chunk-T5CVK4R3.js';
9
+ import { EventContextRequiredError, OrganisationContextRequiredError, isSuperAdmin } from './chunk-LX6U42O3.js';
10
10
  import { assertAppId } from './chunk-4SXLQIZO.js';
11
11
  import { getAppId } from './chunk-FEJLJNWA.js';
12
12
  import { LoadingSpinner } from './chunk-A3W6LW53.js';
@@ -16,7 +16,7 @@ import { createLogger, logger } from './chunk-TTRFSOKR.js';
16
16
  import * as React6 from 'react';
17
17
  import React6__default, { useState, useRef, useMemo, useCallback, useEffect, useContext } from 'react';
18
18
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
19
- import { FileText, ExternalLink, X, ChevronDown, KeyRound, LogOut, AlertCircle, RefreshCw, Calendar as Calendar$1, Building2, File, Check } from 'lucide-react';
19
+ import { FileText, ExternalLink, X, ChevronDown, KeyRound, LogOut, Calendar as Calendar$1, Building2, AlertCircle, RefreshCw, File, Check } from 'lucide-react';
20
20
  import * as SwitchPrimitive from '@radix-ui/react-switch';
21
21
  import * as TabsPrimitive from '@radix-ui/react-tabs';
22
22
  import { useDayPicker, DayPicker } from 'react-day-picker';
@@ -53,6 +53,7 @@ var AddressField = React6.forwardRef(
53
53
  const inputRef = React6.useRef(null);
54
54
  const suggestionsRef = React6.useRef(null);
55
55
  const containerRef = React6.useRef(null);
56
+ const blurTimeoutRef = React6.useRef(null);
56
57
  const value = controlledValue !== void 0 ? controlledValue : internalValue;
57
58
  const { suggestions, isLoading, error: autocompleteError, selectAddress, clearSuggestions } = useAddressAutocomplete(apiKey, value, {
58
59
  autocompleteOptions,
@@ -145,16 +146,33 @@ var AddressField = React6.forwardRef(
145
146
  }, [suggestions, value]);
146
147
  const handleBlur = React6.useCallback(
147
148
  (e) => {
148
- setTimeout(() => {
149
+ if (blurTimeoutRef.current) {
150
+ clearTimeout(blurTimeoutRef.current);
151
+ blurTimeoutRef.current = null;
152
+ }
153
+ blurTimeoutRef.current = setTimeout(() => {
154
+ if (typeof window === "undefined" || typeof document === "undefined") {
155
+ blurTimeoutRef.current = null;
156
+ return;
157
+ }
149
158
  if (!containerRef.current?.contains(document.activeElement)) {
150
159
  setInputFocused(false);
151
160
  setIsOpen(false);
152
161
  setSelectedIndex(-1);
153
162
  }
163
+ blurTimeoutRef.current = null;
154
164
  }, 200);
155
165
  },
156
166
  []
157
167
  );
168
+ React6.useEffect(() => {
169
+ return () => {
170
+ if (blurTimeoutRef.current) {
171
+ clearTimeout(blurTimeoutRef.current);
172
+ blurTimeoutRef.current = null;
173
+ }
174
+ };
175
+ }, []);
158
176
  React6.useEffect(() => {
159
177
  const handleClickOutside = (event) => {
160
178
  if (containerRef.current && !containerRef.current.contains(event.target)) {
@@ -282,20 +300,28 @@ function useFileUrl(fileReference, options) {
282
300
  const [isLoading, setIsLoading] = useState(false);
283
301
  const [error, setError] = useState(null);
284
302
  const fileReferenceIdRef = useRef(null);
303
+ const isLoadingRef = useRef(false);
304
+ const urlRef = useRef(null);
305
+ useEffect(() => {
306
+ isLoadingRef.current = isLoading;
307
+ urlRef.current = url;
308
+ }, [isLoading, url]);
285
309
  const loadUrl = useCallback(async () => {
286
310
  if (!fileReference) {
287
311
  setUrl(null);
288
312
  setIsLoading(false);
289
313
  setError(null);
314
+ fileReferenceIdRef.current = null;
290
315
  return;
291
316
  }
292
317
  if (!supabase) {
293
318
  setUrl(null);
294
319
  setIsLoading(false);
295
320
  setError(new Error("Supabase client is required for URL generation"));
321
+ fileReferenceIdRef.current = null;
296
322
  return;
297
323
  }
298
- if (isLoading || url && fileReferenceIdRef.current === fileReference.id) {
324
+ if (isLoadingRef.current || urlRef.current && fileReferenceIdRef.current === fileReference.id) {
299
325
  return;
300
326
  }
301
327
  setIsLoading(true);
@@ -323,24 +349,49 @@ function useFileUrl(fileReference, options) {
323
349
  } finally {
324
350
  setIsLoading(false);
325
351
  }
326
- }, [fileReference, supabase, organisation_id, isLoading, url]);
352
+ }, [fileReference, supabase, organisation_id]);
327
353
  const clear = useCallback(() => {
328
354
  setUrl(null);
329
355
  setError(null);
330
356
  setIsLoading(false);
331
357
  fileReferenceIdRef.current = null;
358
+ urlRef.current = null;
332
359
  }, []);
333
360
  useEffect(() => {
334
- if (autoLoad) {
335
- if (fileReferenceIdRef.current !== fileReference?.id) {
361
+ if (!autoLoad) {
362
+ return;
363
+ }
364
+ const currentFileId = fileReference?.id ?? null;
365
+ const previousFileId = fileReferenceIdRef.current;
366
+ if (!fileReference) {
367
+ if (previousFileId !== null) {
336
368
  setUrl(null);
337
369
  setError(null);
370
+ fileReferenceIdRef.current = null;
371
+ urlRef.current = null;
338
372
  }
339
- if (fileReference && !url && !isLoading) {
373
+ return;
374
+ }
375
+ if (previousFileId !== null && previousFileId !== currentFileId) {
376
+ setUrl(null);
377
+ setError(null);
378
+ fileReferenceIdRef.current = null;
379
+ urlRef.current = null;
380
+ loadUrl();
381
+ return;
382
+ }
383
+ if (previousFileId === null && currentFileId !== null) {
384
+ fileReferenceIdRef.current = currentFileId;
385
+ if (!isLoading && !url) {
340
386
  loadUrl();
341
387
  }
388
+ } else if (previousFileId === currentFileId && !url && !isLoading) {
389
+ if (fileReferenceIdRef.current !== currentFileId) {
390
+ fileReferenceIdRef.current = currentFileId;
391
+ }
392
+ loadUrl();
342
393
  }
343
- }, [fileReference?.id, autoLoad, loadUrl, url, isLoading]);
394
+ }, [fileReference, autoLoad, loadUrl, url, isLoading]);
344
395
  return {
345
396
  url,
346
397
  isLoading,
@@ -2604,7 +2655,6 @@ function ContextSelector({
2604
2655
  error: eventError,
2605
2656
  refreshEvents
2606
2657
  } = useEvents();
2607
- const { isSuperAdmin: isSuperAdmin2 } = useRBAC();
2608
2658
  const isLoading = showOrganisations && orgLoading || showEvents && eventLoading;
2609
2659
  const hasError = showOrganisations && orgError || showEvents && eventError;
2610
2660
  const hasItems = showOrganisations && (organisations?.length || 0) > 0 || showEvents && (events?.length || 0) > 0;
@@ -2617,6 +2667,36 @@ function ContextSelector({
2617
2667
  }
2618
2668
  return "";
2619
2669
  }, [showOrganisations, showEvents, selectedOrganisation?.id, selectedEvent]);
2670
+ const displayValue = useMemo(() => {
2671
+ if (showEvents && selectedEvent) {
2672
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2673
+ /* @__PURE__ */ jsx(Calendar$1, { className: "inline-block size-4 mr-2" }),
2674
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedEvent.event_name })
2675
+ ] });
2676
+ }
2677
+ if (showOrganisations && selectedOrganisation) {
2678
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2679
+ /* @__PURE__ */ jsx(Building2, { className: "inline-block size-4 mr-2" }),
2680
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOrganisation.display_name })
2681
+ ] });
2682
+ }
2683
+ return null;
2684
+ }, [showOrganisations, showEvents, selectedOrganisation, selectedEvent]);
2685
+ const effectivePlaceholder = useMemo(() => {
2686
+ if (placeholder !== "Select organisation or event") {
2687
+ return placeholder;
2688
+ }
2689
+ if (showOrganisations && showEvents) {
2690
+ return "Select organisation or event";
2691
+ }
2692
+ if (showOrganisations) {
2693
+ return "Select organisation";
2694
+ }
2695
+ if (showEvents) {
2696
+ return "Select event";
2697
+ }
2698
+ return placeholder;
2699
+ }, [placeholder, showOrganisations, showEvents]);
2620
2700
  const handleValueChange = (value) => {
2621
2701
  if (disabled || isLoading) return;
2622
2702
  if (value.startsWith("org:") && showOrganisations) {
@@ -2704,36 +2784,6 @@ function ContextSelector({
2704
2784
  }
2705
2785
  return null;
2706
2786
  }
2707
- const displayValue = useMemo(() => {
2708
- if (showEvents && selectedEvent) {
2709
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2710
- /* @__PURE__ */ jsx(Calendar$1, { className: "inline-block size-4 mr-2" }),
2711
- /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedEvent.event_name })
2712
- ] });
2713
- }
2714
- if (showOrganisations && selectedOrganisation) {
2715
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2716
- /* @__PURE__ */ jsx(Building2, { className: "inline-block size-4 mr-2" }),
2717
- /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOrganisation.display_name })
2718
- ] });
2719
- }
2720
- return null;
2721
- }, [showOrganisations, showEvents, selectedOrganisation, selectedEvent]);
2722
- const effectivePlaceholder = useMemo(() => {
2723
- if (placeholder !== "Select organisation or event") {
2724
- return placeholder;
2725
- }
2726
- if (showOrganisations && showEvents) {
2727
- return "Select organisation or event";
2728
- }
2729
- if (showOrganisations) {
2730
- return "Select organisation";
2731
- }
2732
- if (showEvents) {
2733
- return "Select event";
2734
- }
2735
- return placeholder;
2736
- }, [placeholder, showOrganisations, showEvents]);
2737
2787
  return /* @__PURE__ */ jsxs(
2738
2788
  Select,
2739
2789
  {
@@ -2911,11 +2961,11 @@ var UserMenu = React6__default.memo(function UserMenu2({
2911
2961
  /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
2912
2962
  ] }) }),
2913
2963
  /* @__PURE__ */ jsxs(SelectContent, { children: [
2914
- /* @__PURE__ */ jsx(SelectLabel, { className: "font-normal", children: /* @__PURE__ */ jsxs("li", { className: "pt-2", children: [
2964
+ /* @__PURE__ */ jsxs(SelectLabel, { className: "font-normal pt-2", children: [
2915
2965
  userInfo.displayName,
2916
2966
  /* @__PURE__ */ jsx("br", {}),
2917
2967
  /* @__PURE__ */ jsx("small", { children: userInfo.email })
2918
- ] }) }),
2968
+ ] }),
2919
2969
  /* @__PURE__ */ jsx(SelectSeparator, {}),
2920
2970
  /* @__PURE__ */ jsxs(
2921
2971
  SelectItem,
@@ -2994,7 +3044,7 @@ function useNavigationFiltering({
2994
3044
  }
2995
3045
  const userId2 = authContext.user.id;
2996
3046
  const appName = authContext.appName;
2997
- import('./api-Y4MQWOFW.js').then(({ resolveAppContext }) => {
3047
+ import('./api-7P7DI652.js').then(({ resolveAppContext }) => {
2998
3048
  resolveAppContext({
2999
3049
  userId: userId2,
3000
3050
  appName
@@ -3783,7 +3833,7 @@ function PaceAppLayout({
3783
3833
  return;
3784
3834
  }
3785
3835
  try {
3786
- const { isSuperAdmin: checkSuperAdminDynamic } = await import('./api-Y4MQWOFW.js');
3836
+ const { isSuperAdmin: checkSuperAdminDynamic } = await import('./api-7P7DI652.js');
3787
3837
  const isSuper = await checkSuperAdminDynamic(user.id);
3788
3838
  if (isSuper) {
3789
3839
  if (isMounted) {
@@ -3797,7 +3847,7 @@ function PaceAppLayout({
3797
3847
  }
3798
3848
  }
3799
3849
  try {
3800
- const { getPermissionMap } = await import('./api-Y4MQWOFW.js');
3850
+ const { getPermissionMap } = await import('./api-7P7DI652.js');
3801
3851
  const permissionScope = {
3802
3852
  organisationId: currentScope.organisationId,
3803
3853
  eventId: currentScope.eventId,
@@ -3808,7 +3858,7 @@ function PaceAppLayout({
3808
3858
  userId: user.id,
3809
3859
  scope: permissionScope
3810
3860
  });
3811
- const { getPageScopeType } = await import('./api-Y4MQWOFW.js');
3861
+ const { getPageScopeType } = await import('./api-7P7DI652.js');
3812
3862
  const effectiveAppId = currentScope.appId || resolvedAppId;
3813
3863
  const effectiveAppName = appName;
3814
3864
  const hasEventContext = !!currentScope.eventId;
@@ -3917,7 +3967,7 @@ function PaceAppLayout({
3917
3967
  let hasAccess = true;
3918
3968
  if (currentRoute.pageId && currentRoute.permissions && currentRoute.permissions.length > 0) {
3919
3969
  try {
3920
- const { isPermittedCached } = await import('./api-Y4MQWOFW.js');
3970
+ const { isPermittedCached } = await import('./api-7P7DI652.js');
3921
3971
  const hasPagePermission = await isPermittedCached({
3922
3972
  userId: user?.id || "",
3923
3973
  scope,
@@ -3933,7 +3983,7 @@ function PaceAppLayout({
3933
3983
  }
3934
3984
  }
3935
3985
  if (hasAccess && currentRoute.roles && currentRoute.roles.length > 0 && user?.id) {
3936
- const { useUnifiedAuth: useUnifiedAuth2 } = await import('./UnifiedAuthProvider-ZT6TIGM7.js');
3986
+ const { useUnifiedAuth: useUnifiedAuth2 } = await import('./UnifiedAuthProvider-7SNDOWYD.js');
3937
3987
  hasAccess = true;
3938
3988
  }
3939
3989
  if (!isMounted) return;
@@ -4424,7 +4474,16 @@ function FileUpload({
4424
4474
  const [isResolvingAppId, setIsResolvingAppId] = useState(!app_id);
4425
4475
  const [appIdError, setAppIdError] = useState(null);
4426
4476
  const fileInputRef = useRef(null);
4477
+ const progressIntervalsRef = useRef(/* @__PURE__ */ new Map());
4427
4478
  const { uploadFile, isLoading, error } = useFileReference(supabase);
4479
+ useEffect(() => {
4480
+ return () => {
4481
+ progressIntervalsRef.current.forEach((interval) => {
4482
+ clearInterval(interval);
4483
+ });
4484
+ progressIntervalsRef.current.clear();
4485
+ };
4486
+ }, []);
4428
4487
  useEffect(() => {
4429
4488
  if (app_id) {
4430
4489
  setResolvedAppId(app_id);
@@ -4579,11 +4638,16 @@ function FileUpload({
4579
4638
  return updated;
4580
4639
  });
4581
4640
  }, 200);
4641
+ progressIntervalsRef.current.set(fileId, progressInterval);
4582
4642
  if (!resolvedAppId) {
4643
+ clearInterval(progressInterval);
4644
+ progressIntervalsRef.current.delete(fileId);
4583
4645
  const errorMsg = appIdError || "App ID not available. Please provide app_id prop or set app name.";
4584
4646
  throw new Error(errorMsg);
4585
4647
  }
4586
4648
  if (!pageContext) {
4649
+ clearInterval(progressInterval);
4650
+ progressIntervalsRef.current.delete(fileId);
4587
4651
  const errorMsg = "pageContext is required for file upload. This is used for permission checks.";
4588
4652
  throw new Error(errorMsg);
4589
4653
  }
@@ -4601,6 +4665,7 @@ function FileUpload({
4601
4665
  is_public: isPublic
4602
4666
  }, file);
4603
4667
  clearInterval(progressInterval);
4668
+ progressIntervalsRef.current.delete(fileId);
4604
4669
  if (result) {
4605
4670
  setUploadStates((prev) => {
4606
4671
  const updated = new Map(prev);
@@ -4656,6 +4721,11 @@ function FileUpload({
4656
4721
  onUploadError?.("Upload failed", file);
4657
4722
  }
4658
4723
  } catch (err) {
4724
+ const interval = progressIntervalsRef.current.get(fileId);
4725
+ if (interval) {
4726
+ clearInterval(interval);
4727
+ progressIntervalsRef.current.delete(fileId);
4728
+ }
4659
4729
  const errorMessage = err instanceof Error ? err.message : "Upload failed";
4660
4730
  setUploadStates((prev) => {
4661
4731
  const updated = new Map(prev);