@jmruthers/pace-core 0.5.121 → 0.5.124

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 (255) hide show
  1. package/dist/{AuthService-D4646R4b.d.ts → AuthService-DYuQPJj6.d.ts} +0 -9
  2. package/dist/{DataTable-DGZDJUYM.js → DataTable-OKDYRW2S.js} +7 -8
  3. package/dist/{PublicLoadingSpinner-DgDWTFqn.d.ts → PublicLoadingSpinner-CaoRbHvJ.d.ts} +30 -4
  4. package/dist/{UnifiedAuthProvider-UACKFATV.js → UnifiedAuthProvider-6C47WIML.js} +3 -4
  5. package/dist/{chunk-D6BOFXYR.js → chunk-35ZDPMBM.js} +3 -3
  6. package/dist/{chunk-CGURJ27Z.js → chunk-4MXVZVNS.js} +2 -2
  7. package/dist/{chunk-ZYJ6O5CA.js → chunk-C43QIDN3.js} +2 -2
  8. package/dist/{chunk-VKOCWWVY.js → chunk-CX5M4ZAG.js} +1 -6
  9. package/dist/{chunk-VKOCWWVY.js 3.map → chunk-CX5M4ZAG.js.map} +1 -1
  10. package/dist/{chunk-RIEJGKD3.js → chunk-ESJTIADP.js} +15 -6
  11. package/dist/{chunk-RIEJGKD3.js.map → chunk-ESJTIADP.js.map} +1 -1
  12. package/dist/{chunk-HFBOFZ3Z.js → chunk-GBGYYMC6.js} +317 -251
  13. package/dist/chunk-GBGYYMC6.js.map +1 -0
  14. package/dist/{chunk-SMJZMKYN.js → chunk-GEVIB2UB.js} +43 -10
  15. package/dist/chunk-GEVIB2UB.js.map +1 -0
  16. package/dist/{chunk-TDNI6ZWL.js → chunk-IJOZZOGT.js} +7 -7
  17. package/dist/chunk-IJOZZOGT.js.map +1 -0
  18. package/dist/{chunk-GZRXOUBE.js → chunk-M6DDYFUD.js} +2 -2
  19. package/dist/chunk-M6DDYFUD.js.map +1 -0
  20. package/dist/{chunk-B4GZ2BXO.js → chunk-NZGLXZGP.js} +3 -3
  21. package/dist/{chunk-NZ32EONV.js → chunk-QWNJCQXZ.js} +2 -2
  22. package/dist/{chunk-QPI2CCBA.js → chunk-VPUCTHTY.js} +149 -96
  23. package/dist/chunk-VPUCTHTY.js.map +1 -0
  24. package/dist/{chunk-FKFHZUGF.js → chunk-XN6GWKMV.js} +43 -56
  25. package/dist/chunk-XN6GWKMV.js.map +1 -0
  26. package/dist/{chunk-BHWIUEYH.js → chunk-ZBLK676C.js} +1 -61
  27. package/dist/chunk-ZBLK676C.js.map +1 -0
  28. package/dist/components.d.ts +1 -1
  29. package/dist/components.js +11 -11
  30. package/dist/{formatting-B1jSqgl-.d.ts → formatting-DFcCxUEk.d.ts} +1 -1
  31. package/dist/hooks.d.ts +1 -1
  32. package/dist/hooks.js +9 -8
  33. package/dist/hooks.js.map +1 -1
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.js +19 -17
  36. package/dist/index.js.map +1 -1
  37. package/dist/providers.d.ts +2 -2
  38. package/dist/providers.js +2 -3
  39. package/dist/rbac/index.js +7 -8
  40. package/dist/styles/index.d.ts +1 -1
  41. package/dist/styles/index.js +5 -3
  42. package/dist/theming/runtime.d.ts +73 -1
  43. package/dist/theming/runtime.js +5 -5
  44. package/dist/{usePublicRouteParams-BdF8bZgs.d.ts → usePublicRouteParams-Dyt1tzI9.d.ts} +60 -8
  45. package/dist/utils.d.ts +1 -1
  46. package/dist/utils.js +5 -5
  47. package/docs/api/classes/ColumnFactory.md +1 -1
  48. package/docs/api/classes/ErrorBoundary.md +1 -1
  49. package/docs/api/classes/InvalidScopeError.md +1 -1
  50. package/docs/api/classes/MissingUserContextError.md +1 -1
  51. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  52. package/docs/api/classes/PermissionDeniedError.md +1 -1
  53. package/docs/api/classes/PublicErrorBoundary.md +6 -6
  54. package/docs/api/classes/RBACAuditManager.md +1 -1
  55. package/docs/api/classes/RBACCache.md +1 -1
  56. package/docs/api/classes/RBACEngine.md +1 -1
  57. package/docs/api/classes/RBACError.md +1 -1
  58. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  59. package/docs/api/classes/SecureSupabaseClient.md +6 -6
  60. package/docs/api/classes/StorageUtils.md +1 -1
  61. package/docs/api/enums/FileCategory.md +1 -1
  62. package/docs/api/interfaces/AggregateConfig.md +1 -1
  63. package/docs/api/interfaces/ButtonProps.md +1 -1
  64. package/docs/api/interfaces/CardProps.md +1 -1
  65. package/docs/api/interfaces/ColorPalette.md +1 -1
  66. package/docs/api/interfaces/ColorShade.md +1 -1
  67. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  68. package/docs/api/interfaces/DataRecord.md +1 -1
  69. package/docs/api/interfaces/DataTableAction.md +1 -1
  70. package/docs/api/interfaces/DataTableColumn.md +1 -1
  71. package/docs/api/interfaces/DataTableProps.md +1 -1
  72. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  73. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  74. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  76. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  77. package/docs/api/interfaces/FileMetadata.md +1 -1
  78. package/docs/api/interfaces/FileReference.md +1 -1
  79. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  80. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  81. package/docs/api/interfaces/FileUploadProps.md +1 -1
  82. package/docs/api/interfaces/FooterProps.md +1 -1
  83. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  84. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  85. package/docs/api/interfaces/InputProps.md +1 -1
  86. package/docs/api/interfaces/LabelProps.md +1 -1
  87. package/docs/api/interfaces/LoginFormProps.md +1 -1
  88. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  89. package/docs/api/interfaces/NavigationContextType.md +1 -1
  90. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  91. package/docs/api/interfaces/NavigationItem.md +1 -1
  92. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  93. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  94. package/docs/api/interfaces/Organisation.md +1 -1
  95. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  96. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  97. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  98. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  99. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  100. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  101. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  102. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  103. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  104. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  105. package/docs/api/interfaces/PaletteData.md +1 -1
  106. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  107. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  108. package/docs/api/interfaces/PublicErrorBoundaryProps.md +7 -7
  109. package/docs/api/interfaces/PublicErrorBoundaryState.md +5 -5
  110. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +7 -7
  111. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  112. package/docs/api/interfaces/PublicPageHeaderProps.md +51 -12
  113. package/docs/api/interfaces/PublicPageLayoutProps.md +72 -12
  114. package/docs/api/interfaces/RBACConfig.md +1 -1
  115. package/docs/api/interfaces/RBACLogger.md +1 -1
  116. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  117. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  118. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  119. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  120. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  121. package/docs/api/interfaces/RouteConfig.md +1 -1
  122. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  123. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  124. package/docs/api/interfaces/StorageConfig.md +1 -1
  125. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  126. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  127. package/docs/api/interfaces/StorageListOptions.md +1 -1
  128. package/docs/api/interfaces/StorageListResult.md +1 -1
  129. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  130. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  131. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  132. package/docs/api/interfaces/StyleImport.md +1 -1
  133. package/docs/api/interfaces/SwitchProps.md +1 -1
  134. package/docs/api/interfaces/ToastActionElement.md +1 -1
  135. package/docs/api/interfaces/ToastProps.md +1 -1
  136. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  137. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  138. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  139. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  140. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  141. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  142. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  143. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  144. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  145. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  146. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  147. package/docs/api/interfaces/UserEventAccess.md +1 -1
  148. package/docs/api/interfaces/UserMenuProps.md +1 -1
  149. package/docs/api/interfaces/UserProfile.md +1 -1
  150. package/docs/api/modules.md +140 -30
  151. package/docs/best-practices/README.md +1 -1
  152. package/docs/implementation-guides/datatable-filtering.md +313 -0
  153. package/docs/implementation-guides/datatable-rbac-usage.md +317 -0
  154. package/docs/implementation-guides/hierarchical-datatable.md +850 -0
  155. package/docs/implementation-guides/large-datasets.md +281 -0
  156. package/docs/implementation-guides/performance.md +403 -0
  157. package/docs/implementation-guides/public-pages.md +4 -4
  158. package/docs/migration/quick-migration-guide.md +320 -0
  159. package/docs/rbac/quick-start.md +16 -16
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +1 -1
  162. package/docs/troubleshooting/debugging.md +1117 -0
  163. package/docs/troubleshooting/migration.md +918 -0
  164. package/examples/public-pages/CorrectPublicPageImplementation.tsx +30 -30
  165. package/examples/public-pages/PublicEventPage.tsx +41 -41
  166. package/examples/public-pages/PublicPageApp.tsx +33 -33
  167. package/examples/public-pages/PublicPageUsageExample.tsx +30 -30
  168. package/package.json +4 -4
  169. package/src/__tests__/hooks/usePermissions.test.ts +265 -0
  170. package/src/components/DataTable/DataTable.test.tsx +9 -38
  171. package/src/components/DataTable/DataTable.tsx +0 -7
  172. package/src/components/DataTable/components/DataTableCore.tsx +125 -144
  173. package/src/components/DataTable/components/DataTableModals.tsx +25 -22
  174. package/src/components/DataTable/components/DataTableToolbar.tsx +14 -1
  175. package/src/components/DataTable/components/EditableRow.tsx +118 -42
  176. package/src/components/DataTable/components/UnifiedTableBody.tsx +129 -76
  177. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +33 -14
  178. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +17 -5
  179. package/src/components/DataTable/utils/exportUtils.ts +3 -2
  180. package/src/components/Dialog/Dialog.tsx +1 -1
  181. package/src/components/Dialog/README.md +24 -24
  182. package/src/components/Dialog/examples/BasicHtmlTest.tsx +2 -2
  183. package/src/components/Dialog/examples/DebugHtmlExample.tsx +6 -6
  184. package/src/components/Dialog/examples/HtmlDialogExample.tsx +2 -2
  185. package/src/components/Dialog/examples/SimpleHtmlTest.tsx +3 -3
  186. package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +4 -4
  187. package/src/components/PaceAppLayout/PaceAppLayout.tsx +12 -1
  188. package/src/components/PublicLayout/EventLogo.tsx +175 -0
  189. package/src/components/PublicLayout/PublicErrorBoundary.tsx +22 -18
  190. package/src/components/PublicLayout/PublicLoadingSpinner.tsx +22 -14
  191. package/src/components/PublicLayout/PublicPageHeader.tsx +133 -40
  192. package/src/components/PublicLayout/PublicPageLayout.tsx +75 -72
  193. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +1 -1
  194. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +8 -8
  195. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +23 -16
  196. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +86 -14
  197. package/src/examples/CorrectPublicPageImplementation.tsx +30 -30
  198. package/src/examples/PublicEventPage.tsx +41 -41
  199. package/src/examples/PublicPageApp.tsx +33 -33
  200. package/src/examples/PublicPageUsageExample.tsx +30 -30
  201. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  202. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +10 -3
  203. package/src/hooks/index.ts +1 -1
  204. package/src/hooks/public/usePublicEventLogo.ts +285 -0
  205. package/src/hooks/public/usePublicRouteParams.ts +21 -4
  206. package/src/hooks/useEventTheme.test.ts +119 -43
  207. package/src/hooks/useEventTheme.ts +84 -55
  208. package/src/index.ts +3 -1
  209. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +630 -0
  210. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +667 -0
  211. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +647 -0
  212. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +496 -0
  213. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +496 -0
  214. package/src/rbac/secureClient.ts +4 -2
  215. package/src/services/EventService.ts +0 -66
  216. package/src/services/__tests__/EventService.eventColours.test.ts +44 -40
  217. package/src/styles/index.ts +1 -1
  218. package/src/theming/__tests__/parseEventColours.test.ts +209 -0
  219. package/src/theming/parseEventColours.ts +123 -0
  220. package/src/theming/runtime.ts +3 -0
  221. package/src/types/__tests__/file-reference.test.ts +447 -0
  222. package/src/utils/formatDate.test.ts +11 -11
  223. package/src/utils/formatting.ts +3 -2
  224. package/dist/chunk-BDZUMRBD.js 3.map +0 -1
  225. package/dist/chunk-BHWIUEYH.js.map +0 -1
  226. package/dist/chunk-CGURJ27Z.js.map +0 -1
  227. package/dist/chunk-FKFHZUGF.js.map +0 -1
  228. package/dist/chunk-GKHF54DI 2.js +0 -619
  229. package/dist/chunk-GKHF54DI.js 2.map +0 -1
  230. package/dist/chunk-GZRXOUBE.js.map +0 -1
  231. package/dist/chunk-HFBOFZ3Z.js.map +0 -1
  232. package/dist/chunk-NZ32EONV.js.map +0 -1
  233. package/dist/chunk-O3NWNXDY 2.js +0 -76
  234. package/dist/chunk-QPI2CCBA.js.map +0 -1
  235. package/dist/chunk-SMJZMKYN.js.map +0 -1
  236. package/dist/chunk-TDNI6ZWL.js 2.map +0 -1
  237. package/dist/chunk-TDNI6ZWL.js.map +0 -1
  238. package/dist/chunk-VKOCWWVY.js.map +0 -1
  239. package/dist/chunk-WP5I5GLN 2.js +0 -1564
  240. package/dist/index 3.js +0 -856
  241. package/dist/providers 3.js +0 -38
  242. package/dist/providers.js 3.map +0 -1
  243. package/dist/types 3.js +0 -128
  244. package/dist/types.js 3.map +0 -1
  245. package/dist/useInactivityTracker-MRUU55XI.js 3.map +0 -1
  246. package/dist/utils.js 3.map +0 -1
  247. package/dist/validation 3.js +0 -479
  248. package/src/styles/semantic.css +0 -24
  249. /package/dist/{DataTable-DGZDJUYM.js.map → DataTable-OKDYRW2S.js.map} +0 -0
  250. /package/dist/{UnifiedAuthProvider-UACKFATV.js.map → UnifiedAuthProvider-6C47WIML.js.map} +0 -0
  251. /package/dist/{chunk-D6BOFXYR.js.map → chunk-35ZDPMBM.js.map} +0 -0
  252. /package/dist/{chunk-CGURJ27Z.js 2.map → chunk-4MXVZVNS.js.map} +0 -0
  253. /package/dist/{chunk-ZYJ6O5CA.js.map → chunk-C43QIDN3.js.map} +0 -0
  254. /package/dist/{chunk-B4GZ2BXO.js.map → chunk-NZGLXZGP.js.map} +0 -0
  255. /package/dist/{chunk-NZ32EONV.js 2.map → chunk-QWNJCQXZ.js.map} +0 -0
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  import React from 'react';
40
+ import { cn } from '../../utils/cn';
40
41
 
41
42
  export interface PublicLoadingSpinnerProps {
42
43
  /** Loading message to display */
@@ -84,7 +85,7 @@ export function PublicLoadingSpinner({
84
85
  const displayMessage = customMessage || message;
85
86
 
86
87
  const content = (
87
- <div className={`flex flex-col items-center ${className}`}>
88
+ <div className={cn("flex flex-col items-center", className)}>
88
89
  {/* App Logo */}
89
90
  {showLogo && (
90
91
  <div className="mb-4">
@@ -99,7 +100,10 @@ export function PublicLoadingSpinner({
99
100
  {/* Spinner */}
100
101
  <div className="relative">
101
102
  <div
102
- className={`${sizeClass} border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin`}
103
+ className={cn(
104
+ sizeClass,
105
+ "border-2 border-sec-200 border-t-main-600 rounded-full animate-spin"
106
+ )}
103
107
  role="status"
104
108
  aria-label="Loading"
105
109
  />
@@ -108,7 +112,7 @@ export function PublicLoadingSpinner({
108
112
 
109
113
  {/* Loading Message */}
110
114
  {displayMessage && (
111
- <p className="mt-4 text-sm text-gray-600 text-center">
115
+ <p className="mt-4 text-sm text-sec-600 text-center">
112
116
  {displayMessage}
113
117
  </p>
114
118
  )}
@@ -117,7 +121,7 @@ export function PublicLoadingSpinner({
117
121
 
118
122
  if (centered) {
119
123
  return (
120
- <div className="min-h-screen bg-white flex items-center justify-center">
124
+ <div className="min-h-screen bg-background flex items-center justify-center">
121
125
  <div className="max-w-md mx-auto px-4">
122
126
  {content}
123
127
  </div>
@@ -140,7 +144,10 @@ export function PublicLoadingSpinnerFullPage({
140
144
  eventName?: string;
141
145
  }) {
142
146
  return (
143
- <div className={`min-h-screen bg-white flex items-center justify-center ${className}`}>
147
+ <div className={cn(
148
+ "min-h-screen bg-background flex items-center justify-center",
149
+ className
150
+ )}>
144
151
  <div className="max-w-md mx-auto text-center px-4">
145
152
  {/* App Logo */}
146
153
  <div className="mb-8">
@@ -153,7 +160,7 @@ export function PublicLoadingSpinnerFullPage({
153
160
 
154
161
  {/* Event Name */}
155
162
  {eventName && (
156
- <h1 className="text-2xl font-bold text-gray-900 mb-4">
163
+ <h1 className="text-2xl font-bold text-sec-900 mb-4">
157
164
  {eventName}
158
165
  </h1>
159
166
  )}
@@ -161,22 +168,22 @@ export function PublicLoadingSpinnerFullPage({
161
168
  {/* Spinner */}
162
169
  <div className="relative mb-6">
163
170
  <div
164
- className="h-12 w-12 border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin mx-auto"
171
+ className="h-12 w-12 border-4 border-sec-200 border-t-main-600 rounded-full animate-spin mx-auto"
165
172
  role="status"
166
173
  aria-label="Loading"
167
174
  />
168
175
  </div>
169
176
 
170
177
  {/* Loading Message */}
171
- <p className="text-lg text-gray-600">
178
+ <p className="text-lg text-sec-600">
172
179
  {message}
173
180
  </p>
174
181
 
175
182
  {/* Loading Dots Animation */}
176
183
  <div className="mt-4 flex justify-center space-x-1">
177
- <div className="h-2 w-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
178
- <div className="h-2 w-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
179
- <div className="h-2 w-2 bg-blue-600 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
184
+ <div className="h-2 w-2 bg-main-600 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
185
+ <div className="h-2 w-2 bg-main-600 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
186
+ <div className="h-2 w-2 bg-main-600 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
180
187
  </div>
181
188
  </div>
182
189
  </div>
@@ -194,13 +201,14 @@ export function PublicLoadingSkeleton({
194
201
  className?: string;
195
202
  }) {
196
203
  return (
197
- <div className={`animate-pulse ${className}`}>
204
+ <div className={cn("animate-pulse", className)}>
198
205
  {Array.from({ length: lines }).map((_, index) => (
199
206
  <div
200
207
  key={index}
201
- className={`h-4 bg-gray-200 rounded mb-2 ${
208
+ className={cn(
209
+ "h-4 bg-sec-200 rounded mb-2",
202
210
  index === lines - 1 ? 'w-3/4' : 'w-full'
203
- }`}
211
+ )}
204
212
  />
205
213
  ))}
206
214
  </div>
@@ -30,6 +30,27 @@
30
30
  * );
31
31
  * }
32
32
  * ```
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Using custom logo URL
37
+ * <PublicPageHeader
38
+ * event={event}
39
+ * logoUrl="/custom-logo.svg"
40
+ * logoAlt="My Custom Logo"
41
+ * logoHref="/"
42
+ * />
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * // Using custom logo component
48
+ * <PublicPageHeader
49
+ * event={event}
50
+ * customAppLogo={<CustomLogoComponent />}
51
+ * logoHref="/dashboard"
52
+ * />
53
+ * ```
33
54
  *
34
55
  * @accessibility
35
56
  * - WCAG 2.1 AA compliant
@@ -46,10 +67,12 @@
46
67
  */
47
68
 
48
69
  import React, { ReactNode } from 'react';
70
+ import { Link } from 'react-router-dom';
49
71
  import type { Event } from '../../types/unified';
50
72
  import { FileDisplay } from '../FileDisplay/FileDisplay';
51
73
  import { FileCategory } from '../../types/file-reference';
52
74
  import { useAppConfig } from '../../hooks/useAppConfig';
75
+ import { cn } from '../../utils/cn';
53
76
 
54
77
  export interface PublicPageHeaderProps {
55
78
  /** The event data for this public page */
@@ -68,10 +91,16 @@ export interface PublicPageHeaderProps {
68
91
  className?: string;
69
92
  /** Custom content to display in the header */
70
93
  children?: ReactNode;
71
- /** Custom app logo component */
94
+ /** Custom app logo component (overrides logoUrl and auto-generated path) */
72
95
  customAppLogo?: ReactNode;
73
96
  /** Custom event logo component */
74
97
  customEventLogo?: ReactNode;
98
+ /** URL to the app logo image (overrides auto-generated path, but customAppLogo takes precedence) */
99
+ logoUrl?: string;
100
+ /** Alt text for the app logo (defaults to appName from useAppConfig) */
101
+ logoAlt?: string;
102
+ /** URL to navigate to when app logo is clicked */
103
+ logoHref?: string;
75
104
  }
76
105
 
77
106
  /**
@@ -80,6 +109,15 @@ export interface PublicPageHeaderProps {
80
109
  * This component displays the app logo, event logo, and event information
81
110
  * in a clean, accessible layout suitable for public pages.
82
111
  *
112
+ * Logo handling follows a priority order:
113
+ * 1. customAppLogo prop (if provided) - highest priority
114
+ * 2. logoUrl prop (if provided) - direct URL override
115
+ * 3. Auto-generated path from appName via useAppConfig() - convention-based
116
+ * 4. Default SVG fallback - lowest priority
117
+ *
118
+ * The logo can be made clickable by providing the logoHref prop, which will
119
+ * wrap the logo in a Link component for navigation.
120
+ *
83
121
  * @param props - Header configuration and content
84
122
  * @returns React element with public page header
85
123
  */
@@ -93,32 +131,96 @@ export function PublicPageHeader({
93
131
  className = '',
94
132
  children,
95
133
  customAppLogo,
96
- customEventLogo
134
+ customEventLogo,
135
+ logoUrl,
136
+ logoAlt,
137
+ logoHref
97
138
  }: PublicPageHeaderProps) {
98
139
  const { appName } = useAppConfig();
99
- const headerClasses = `bg-white border-b border-gray-200 ${className}`.trim();
100
140
 
101
141
  return (
102
- <header className={headerClasses}>
103
- <div className="px-4 w-[min(var(--app-width),100%)] mx-auto">
142
+ <header className={cn(
143
+ " px-4 w-[min(var(--app-width),100%)] mx-auto bg-background border-b border-sec-200 grid grid-cols-[auto_1fr_auto] place-items-center gap-2",
144
+ className
145
+ )}>
146
+
104
147
  {/* Top row with logos */}
105
- <div className="flex items-center justify-between py-4">
148
+
106
149
  {/* App Logo */}
107
150
  {showAppLogo && (
108
- <div className="flex-shrink-0">
109
- {customAppLogo || (
110
- <img
111
- className="h-8 w-8 object-contain"
112
- src={`/${appName.toLowerCase()}_logo_square.svg`}
113
- alt={appName}
114
- />
151
+ <>
152
+ {customAppLogo ? (
153
+ logoHref ? (
154
+ <Link to={logoHref} className="cursor-pointer hover:opacity-80 transition-opacity">
155
+ {customAppLogo}
156
+ </Link>
157
+ ) : (
158
+ customAppLogo
159
+ )
160
+ ) : logoUrl ? (
161
+ logoHref ? (
162
+ <Link to={logoHref} className="cursor-pointer hover:opacity-80 transition-opacity">
163
+ <img
164
+ className="max-w-36 object-contain row-span-2"
165
+ src={logoUrl}
166
+ alt={logoAlt || appName}
167
+ />
168
+ </Link>
169
+ ) : (
170
+ <img
171
+ className="max-w-36 object-contain row-span-2"
172
+ src={logoUrl}
173
+ alt={logoAlt || appName}
174
+ />
175
+ )
176
+ ) : appName ? (
177
+ logoHref ? (
178
+ <Link to={logoHref} className="cursor-pointer hover:opacity-80 transition-opacity">
179
+ <img
180
+ className="max-w-36 object-contain row-span-2"
181
+ src={`/${appName.toLowerCase()}_logo_wide.svg`}
182
+ alt={logoAlt || appName}
183
+ />
184
+ </Link>
185
+ ) : (
186
+ <img
187
+ className="max-w-36 object-contain row-span-2"
188
+ src={`/${appName.toLowerCase()}_logo_wide.svg`}
189
+ alt={logoAlt || appName}
190
+ />
191
+ )
192
+ ) : (
193
+ logoHref ? (
194
+ <Link to={logoHref} className="cursor-pointer hover:opacity-80 transition-opacity">
195
+ <img
196
+ src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23000'/%3E%3Ctext x='16' y='20' text-anchor='middle' fill='white' font-family='Arial' font-size='14' font-weight='bold'%3EL%3C/text%3E%3C/svg%3E"
197
+ alt={logoAlt || 'Logo'}
198
+ className="max-w-36 object-contain row-span-2"
199
+ />
200
+ </Link>
201
+ ) : (
202
+ <img
203
+ src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23000'/%3E%3Ctext x='16' y='20' text-anchor='middle' fill='white' font-family='Arial' font-size='14' font-weight='bold'%3EL%3C/text%3E%3C/svg%3E"
204
+ alt={logoAlt || 'Logo'}
205
+ className="max-w-36 object-contain row-span-2"
206
+ />
207
+ )
115
208
  )}
116
- </div>
209
+ </>
117
210
  )}
118
211
 
212
+
213
+ {/* Event Information */}
214
+
215
+
216
+ {event && (
217
+ <>
218
+ <h1>
219
+ {event.event_name}
220
+ </h1>
119
221
  {/* Event Logo */}
120
222
  {showEventLogo && event && (
121
- <div className="flex-shrink-0">
223
+ <>
122
224
  {customEventLogo || (
123
225
  <FileDisplay
124
226
  table_name="event"
@@ -128,7 +230,7 @@ export function PublicPageHeader({
128
230
  displayOnly={true}
129
231
  showFallback={true}
130
232
  fallbackSize="md"
131
- className="[&_img]:h-12 [&_img]:w-12 [&_img]:object-contain [&>div]:h-12 [&>div]:w-12"
233
+ className="max-w-36 row-span-2"
132
234
  generateFallbackText={(fileName) => {
133
235
  if (!event.event_name) return 'EV';
134
236
  return event.event_name
@@ -139,50 +241,41 @@ export function PublicPageHeader({
139
241
  }}
140
242
  />
141
243
  )}
142
- </div>
244
+ </>
143
245
  )}
144
- </div>
145
-
146
- {/* Event Information */}
147
- <div className="pb-4">
148
- <div className="text-center">
149
- {event && (
150
- <>
151
- <h1 className="text-3xl font-bold text-gray-900 mb-2">
152
- {event.event_name}
153
- </h1>
154
-
155
246
  {event.event_venue && (
156
- <p className="text-md text-gray-500 mb-4">
247
+ <h4>
157
248
  {event.event_venue}
158
- </p>
249
+ </h4>
159
250
  )}
160
251
  </>
161
252
  )}
162
253
 
163
254
  {/* Page Title and Description */}
164
255
  {title && (
165
- <div className="mt-6">
166
- <h2 className="text-2xl font-semibold text-gray-800 mb-2">
256
+ <>
257
+ <h1>
167
258
  {title}
168
- </h2>
259
+ </h1>
169
260
  {description && (
170
- <p className="text-lg text-gray-600 max-w-3xl mx-auto">
261
+ <p className="text-lg text-sec-600 max-w-3xl mx-auto">
171
262
  {description}
172
263
  </p>
173
264
  )}
174
- </div>
265
+ </>
175
266
  )}
176
267
 
177
268
  {/* Custom Content */}
178
269
  {children && (
179
- <div className="mt-4">
270
+ <>
180
271
  {children}
181
- </div>
272
+ </>
182
273
  )}
183
- </div>
184
- </div>
185
- </div>
274
+
275
+
276
+
277
+
278
+
186
279
  </header>
187
280
  );
188
281
  }
@@ -10,6 +10,7 @@
10
10
  * Features:
11
11
  * - No authentication required
12
12
  * - Event-specific header and branding
13
+ * - Automatic event color theming (applies event_colours when available)
13
14
  * - Responsive design
14
15
  * - Print-friendly styling
15
16
  * - Error boundary integration
@@ -18,18 +19,22 @@
18
19
  *
19
20
  * @example
20
21
  * ```tsx
21
- * import { PublicPageLayout, PublicPageHeader } from '@jmruthers/pace-core';
22
+ * import { PublicPageLayout, usePublicRouteParams, usePublicEvent } from '@jmruthers/pace-core';
22
23
  *
23
24
  * function PublicEventPage() {
24
- * const { eventCode } = usePublicRouteParams();
25
+ * const { eventCode } = usePublicRouteParams({ fetchEventData: false });
26
+ * const { event, isLoading, error, refetch } = usePublicEvent(eventCode || '');
25
27
  *
28
+ * // PublicPageLayout handles all loading, error, and missing event states
26
29
  * return (
27
- * <PublicPageLayout eventCode={eventCode}>
28
- * <PublicPageHeader
29
- * event={event}
30
- * title="Event Details"
31
- * description="Public information about this event"
32
- * />
30
+ * <PublicPageLayout
31
+ * eventCode={eventCode || ''}
32
+ * event={event}
33
+ * isLoading={isLoading}
34
+ * error={error}
35
+ * refetch={refetch}
36
+ * >
37
+ * <h1>Event Details</h1>
33
38
  * <div className="content">
34
39
  * Your public page content
35
40
  * </div>
@@ -59,11 +64,13 @@
59
64
  * - Tailwind CSS - Styling
60
65
  */
61
66
 
62
- import React, { ReactNode, useMemo } from 'react';
67
+ import React, { ReactNode } from 'react';
63
68
  import { PublicPageHeader } from './PublicPageHeader';
64
69
  import { PublicPageFooter } from './PublicPageFooter';
65
70
  import { PublicErrorBoundary } from './PublicErrorBoundary';
66
71
  import { PublicLoadingSpinner } from './PublicLoadingSpinner';
72
+ import { Button } from '../Button';
73
+ import { useEventTheme } from '../../hooks/useEventTheme';
67
74
  import type { Event } from '../../types/unified';
68
75
 
69
76
  export interface PublicPageLayoutProps {
@@ -73,9 +80,15 @@ export interface PublicPageLayoutProps {
73
80
  children: ReactNode;
74
81
  /** Optional event data - if not provided, will be fetched by parent component */
75
82
  event?: Event | null;
83
+ /** Loading state - if true, shows loading spinner */
84
+ isLoading?: boolean;
85
+ /** Error state - if provided, shows error message */
86
+ error?: Error | null;
87
+ /** Function to retry loading event data */
88
+ refetch?: () => Promise<void> | void;
76
89
  /** Whether to show the footer (default: true) */
77
90
  showFooter?: boolean;
78
- /** Custom CSS classes for the layout */
91
+ /** @deprecated Custom CSS classes for the layout - no longer used as wrapper div was removed */
79
92
  className?: string;
80
93
  /** Custom error fallback component */
81
94
  errorFallback?: React.ComponentType<{ error: Error; retry: () => void }>;
@@ -87,6 +100,8 @@ export interface PublicPageLayoutProps {
87
100
  customFooter?: ReactNode;
88
101
  /** Whether to show event validation errors (default: true) */
89
102
  showValidationErrors?: boolean;
103
+ /** Custom loading message */
104
+ loadingMessage?: string;
90
105
  }
91
106
 
92
107
  /**
@@ -95,6 +110,9 @@ export interface PublicPageLayoutProps {
95
110
  * This component provides a consistent structure for public event pages
96
111
  * with event-specific branding, error handling, and loading states.
97
112
  *
113
+ * Automatically applies event colors from the event's event_colours field
114
+ * when an event is provided, ensuring consistent theming across public pages.
115
+ *
98
116
  * @param props - Layout configuration and content
99
117
  * @returns React element with complete public page layout
100
118
  */
@@ -102,96 +120,81 @@ export function PublicPageLayout({
102
120
  eventCode,
103
121
  children,
104
122
  event = null,
123
+ isLoading = false,
124
+ error = null,
125
+ refetch,
105
126
  showFooter = true,
106
127
  className = '',
107
128
  errorFallback: ErrorFallback,
108
129
  loadingFallback: LoadingFallback = PublicLoadingSpinner,
109
130
  customHeader,
110
131
  customFooter,
111
- showValidationErrors = true
132
+ showValidationErrors = true,
133
+ loadingMessage
112
134
  }: PublicPageLayoutProps) {
113
- // Don't automatically fetch event data - let the parent component handle this
114
- // This prevents authentication context conflicts
115
- const isLoading = false;
116
- const error = null;
117
- const refetch = async () => {};
135
+ // Apply event theme colors automatically when event is available
136
+ // This works in public page context without requiring EventProvider
137
+ useEventTheme(event);
118
138
 
119
- // Memoize the layout classes
120
- const layoutClasses = useMemo(() => {
121
- const baseClasses = 'min-h-screen bg-white flex flex-col';
122
- return `${baseClasses} ${className}`.trim();
123
- }, [className]);
139
+ // Default refetch function
140
+ const handleRefetch = refetch || (async () => {});
124
141
 
125
142
  // Handle loading state
126
143
  if (isLoading) {
127
- return (
128
- <div className={layoutClasses}>
129
- <div className="flex-1 flex items-center justify-center">
130
- <LoadingFallback />
131
- </div>
132
- </div>
133
- );
144
+ if (LoadingFallback === PublicLoadingSpinner) {
145
+ return (
146
+ <PublicLoadingSpinner
147
+ className="items-center justify-center"
148
+ message={loadingMessage}
149
+ />
150
+ );
151
+ }
152
+ return <LoadingFallback />;
134
153
  }
135
154
 
136
155
  // Handle error state
137
156
  if (error && showValidationErrors) {
157
+ if (ErrorFallback) {
158
+ return <ErrorFallback error={error} retry={handleRefetch} />;
159
+ }
138
160
  return (
139
- <div className={layoutClasses}>
140
- <div className="flex-1 flex items-center justify-center">
141
- {ErrorFallback ? (
142
- <ErrorFallback error={error} retry={refetch} />
143
- ) : (
144
- <div className="text-center p-8">
145
- <h1 className="text-2xl font-bold text-gray-900 mb-4">
146
- Event Not Found
147
- </h1>
148
- <p className="text-gray-600 mb-6">
149
- The event code "{eventCode}" is invalid or the event is not available for public viewing.
150
- </p>
151
- <button
152
- onClick={refetch}
153
- className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
154
- >
155
- Try Again
156
- </button>
157
- </div>
158
- )}
161
+ <main className="flex flex-col items-center justify-center px-4 w-[min(var(--app-width),100%)] mx-auto py-8">
162
+ <div className="text-center">
163
+ <h1>Event Not Found</h1>
164
+ <p>
165
+ The event code "{eventCode}" is invalid or the event is not available for public viewing.
166
+ </p>
167
+ <Button onClick={handleRefetch}>Try Again</Button>
159
168
  </div>
160
- </div>
169
+ </main>
161
170
  );
162
171
  }
163
172
 
164
173
  // Handle missing event
165
- if (!event) {
174
+ if (!event && showValidationErrors) {
166
175
  return (
167
- <div className={layoutClasses}>
168
- <div className="flex-1 flex items-center justify-center">
169
- <div className="text-center p-8">
170
- <h1 className="text-2xl font-bold text-gray-900 mb-4">
171
- Event Not Available
172
- </h1>
173
- <p className="text-gray-600 mb-6">
174
- This event is not available for public viewing.
175
- </p>
176
- <button
177
- onClick={refetch}
178
- className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
179
- >
180
- Try Again
181
- </button>
182
- </div>
176
+ <main className="flex flex-col items-center justify-center px-4 w-[min(var(--app-width),100%)] mx-auto py-8">
177
+ <div className="text-center">
178
+ <h1>Event Not Available</h1>
179
+ <p>
180
+ This event is not available for public viewing.
181
+ </p>
182
+ {handleRefetch && <Button onClick={handleRefetch}>Try Again</Button>}
183
183
  </div>
184
- </div>
184
+ </main>
185
185
  );
186
186
  }
187
187
 
188
+ // At this point, if showValidationErrors is true, event must exist
189
+ // If showValidationErrors is false, we allow rendering without event
190
+ // But footer requires event, so only show it if event exists
188
191
  return (
189
192
  <PublicErrorBoundary>
190
- <div className={layoutClasses}>
193
+ <>
191
194
  {/* Header */}
192
195
  {customHeader || (
193
196
  <PublicPageHeader
194
- event={event}
197
+ event={event || undefined}
195
198
  eventCode={eventCode}
196
199
  />
197
200
  )}
@@ -201,11 +204,11 @@ export function PublicPageLayout({
201
204
  {children}
202
205
  </main>
203
206
 
204
- {/* Footer */}
205
- {showFooter && (
207
+ {/* Footer - only show if event exists */}
208
+ {showFooter && event && (
206
209
  customFooter || <PublicPageFooter event={event} />
207
210
  )}
208
- </div>
211
+ </>
209
212
  </PublicErrorBoundary>
210
213
  );
211
214
  }
@@ -278,7 +278,7 @@ describe('[component] PublicErrorBoundary', () => {
278
278
  );
279
279
 
280
280
  const tryAgainButton = screen.getByRole('button', { name: 'Try Again' });
281
- expect(tryAgainButton).toHaveClass('focus:outline-none', 'focus:ring-2', 'focus:ring-blue-500', 'focus:ring-offset-2');
281
+ expect(tryAgainButton).toHaveClass('focus:outline-none', 'focus:ring-2', 'focus:ring-main-500', 'focus:ring-offset-2');
282
282
  });
283
283
  });
284
284
  });