@jmruthers/pace-core 0.5.108 → 0.5.109

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 (177) hide show
  1. package/CHANGELOG.md +75 -177
  2. package/dist/{AuthService-1D2ifNfa.d.ts → AuthService-DrHrvXNZ.d.ts} +8 -1
  3. package/dist/{DataTable-WFCHVWTY.js → DataTable-5HITILXS.js} +7 -7
  4. package/dist/{UnifiedAuthProvider-XU4BHFXZ.js → UnifiedAuthProvider-A7I23UCN.js} +3 -3
  5. package/dist/{api-KG4A2X7P.js → api-5I3E47G2.js} +2 -2
  6. package/dist/{chunk-DMNMZKWS.js → chunk-2W4WKJVF.js} +4 -4
  7. package/dist/{chunk-MOMYOQMC.js → chunk-3TKTL5AZ.js} +13 -13
  8. package/dist/{chunk-X4FRXJV6.js → chunk-AUXS7XSO.js} +57 -6
  9. package/dist/{chunk-X4FRXJV6.js.map → chunk-AUXS7XSO.js.map} +1 -1
  10. package/dist/{chunk-LT6RKRA7.js → chunk-D6MEKC27.js} +2 -2
  11. package/dist/{chunk-KBG34SVL.js → chunk-EYSXQ756.js} +2 -2
  12. package/dist/{chunk-ZXY5NTJB.js → chunk-EZ64QG2I.js} +2 -2
  13. package/dist/{chunk-S63MFSY6.js → chunk-F6TSYCKP.js} +4 -2
  14. package/dist/{chunk-S63MFSY6.js.map → chunk-F6TSYCKP.js.map} +1 -1
  15. package/dist/chunk-GZRXOUBE.js +176 -0
  16. package/dist/chunk-GZRXOUBE.js.map +1 -0
  17. package/dist/{chunk-B3QX32P5.js → chunk-P72NKAT5.js} +41 -24
  18. package/dist/chunk-P72NKAT5.js.map +1 -0
  19. package/dist/{chunk-VJ7MPS2K.js → chunk-S4D3Z723.js} +6 -6
  20. package/dist/{chunk-IMZGJ2X7.js → chunk-UW2DE6JX.js} +4 -4
  21. package/dist/{chunk-QDDUU625.js → chunk-WWNOVFDC.js} +4 -4
  22. package/dist/{chunk-GVRSXXAA.js → chunk-YFMENCR4.js} +3 -3
  23. package/dist/components.js +9 -9
  24. package/dist/{database-BXAfr2Y_.d.ts → database-C6jy7EOu.d.ts} +21 -9
  25. package/dist/{formatting-BiEv5oEk.d.ts → formatting-B1jSqgl-.d.ts} +16 -1
  26. package/dist/hooks.d.ts +2 -2
  27. package/dist/hooks.js +7 -7
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +16 -14
  30. package/dist/index.js.map +1 -1
  31. package/dist/providers.d.ts +4 -3
  32. package/dist/providers.js +2 -2
  33. package/dist/rbac/index.d.ts +1 -1
  34. package/dist/rbac/index.js +8 -8
  35. package/dist/types.d.ts +2 -2
  36. package/dist/{usePublicRouteParams-CnM-IK2I.d.ts → usePublicRouteParams-BdF8bZgs.d.ts} +1 -1
  37. package/dist/utils.d.ts +2 -15
  38. package/dist/utils.js +4 -145
  39. package/dist/utils.js.map +1 -1
  40. package/dist/validation.d.ts +1 -1
  41. package/docs/api/classes/ColumnFactory.md +1 -1
  42. package/docs/api/classes/ErrorBoundary.md +1 -1
  43. package/docs/api/classes/InvalidScopeError.md +1 -1
  44. package/docs/api/classes/MissingUserContextError.md +1 -1
  45. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  46. package/docs/api/classes/PermissionDeniedError.md +1 -1
  47. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  48. package/docs/api/classes/RBACAuditManager.md +1 -1
  49. package/docs/api/classes/RBACCache.md +1 -1
  50. package/docs/api/classes/RBACEngine.md +1 -1
  51. package/docs/api/classes/RBACError.md +1 -1
  52. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  53. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  54. package/docs/api/classes/StorageUtils.md +1 -1
  55. package/docs/api/enums/FileCategory.md +1 -1
  56. package/docs/api/interfaces/AggregateConfig.md +1 -1
  57. package/docs/api/interfaces/ButtonProps.md +1 -1
  58. package/docs/api/interfaces/CardProps.md +1 -1
  59. package/docs/api/interfaces/ColorPalette.md +1 -1
  60. package/docs/api/interfaces/ColorShade.md +1 -1
  61. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  62. package/docs/api/interfaces/DataRecord.md +1 -1
  63. package/docs/api/interfaces/DataTableAction.md +1 -1
  64. package/docs/api/interfaces/DataTableColumn.md +3 -3
  65. package/docs/api/interfaces/DataTableProps.md +1 -1
  66. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  67. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  68. package/docs/api/interfaces/EnhancedNavigationMenuProps.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/InactivityWarningModalProps.md +1 -1
  77. package/docs/api/interfaces/InputProps.md +1 -1
  78. package/docs/api/interfaces/LabelProps.md +1 -1
  79. package/docs/api/interfaces/LoginFormProps.md +1 -1
  80. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  81. package/docs/api/interfaces/NavigationContextType.md +1 -1
  82. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  83. package/docs/api/interfaces/NavigationItem.md +1 -1
  84. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  85. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  86. package/docs/api/interfaces/Organisation.md +1 -1
  87. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  88. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  89. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  90. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  91. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  92. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  93. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  94. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  95. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  96. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  97. package/docs/api/interfaces/PaletteData.md +1 -1
  98. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  99. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  100. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  101. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  102. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  103. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  104. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  105. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  106. package/docs/api/interfaces/RBACConfig.md +1 -1
  107. package/docs/api/interfaces/RBACLogger.md +1 -1
  108. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  109. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  110. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  111. package/docs/api/interfaces/RouteConfig.md +1 -1
  112. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  113. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  114. package/docs/api/interfaces/StorageConfig.md +1 -1
  115. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  116. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  117. package/docs/api/interfaces/StorageListOptions.md +1 -1
  118. package/docs/api/interfaces/StorageListResult.md +1 -1
  119. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  120. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  121. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  122. package/docs/api/interfaces/StyleImport.md +1 -1
  123. package/docs/api/interfaces/SwitchProps.md +1 -1
  124. package/docs/api/interfaces/ToastActionElement.md +1 -1
  125. package/docs/api/interfaces/ToastProps.md +1 -1
  126. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  127. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  128. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  129. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  130. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  131. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  132. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  133. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  134. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  135. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  136. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  137. package/docs/api/interfaces/UserEventAccess.md +1 -1
  138. package/docs/api/interfaces/UserMenuProps.md +1 -1
  139. package/docs/api/interfaces/UserProfile.md +1 -1
  140. package/docs/api/modules.md +37 -3
  141. package/docs/api-reference/hooks.md +53 -0
  142. package/docs/api-reference/providers.md +60 -0
  143. package/docs/core-concepts/authentication.md +2 -0
  144. package/docs/implementation-guides/authentication.md +1 -0
  145. package/docs/security/README.md +59 -0
  146. package/package.json +1 -1
  147. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +2 -2
  148. package/src/components/PaceAppLayout/PaceAppLayout.tsx +48 -16
  149. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +2 -1
  150. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +9 -9
  151. package/src/index.ts +3 -0
  152. package/src/providers/services/AuthServiceProvider.tsx +4 -3
  153. package/src/providers/services/UnifiedAuthProvider.tsx +1 -1
  154. package/src/rbac/engine.ts +2 -0
  155. package/src/services/AuthService.ts +79 -1
  156. package/src/services/__tests__/AuthService.test.ts +184 -0
  157. package/src/types/database.ts +21 -9
  158. package/src/types/rbac-functions.ts +2 -1
  159. package/src/utils/__tests__/sessionTracking.unit.test.ts +6 -171
  160. package/src/utils/sessionTracking.ts +7 -81
  161. package/dist/chunk-B3QX32P5.js.map +0 -1
  162. package/dist/chunk-NFPV7MRN.js +0 -94
  163. package/dist/chunk-NFPV7MRN.js.map +0 -1
  164. package/src/providers/AuthProvider.simplified.tsx +0 -974
  165. package/dist/{DataTable-WFCHVWTY.js.map → DataTable-5HITILXS.js.map} +0 -0
  166. package/dist/{UnifiedAuthProvider-XU4BHFXZ.js.map → UnifiedAuthProvider-A7I23UCN.js.map} +0 -0
  167. package/dist/{api-KG4A2X7P.js.map → api-5I3E47G2.js.map} +0 -0
  168. package/dist/{chunk-DMNMZKWS.js.map → chunk-2W4WKJVF.js.map} +0 -0
  169. package/dist/{chunk-MOMYOQMC.js.map → chunk-3TKTL5AZ.js.map} +0 -0
  170. package/dist/{chunk-LT6RKRA7.js.map → chunk-D6MEKC27.js.map} +0 -0
  171. package/dist/{chunk-KBG34SVL.js.map → chunk-EYSXQ756.js.map} +0 -0
  172. package/dist/{chunk-ZXY5NTJB.js.map → chunk-EZ64QG2I.js.map} +0 -0
  173. package/dist/{chunk-VJ7MPS2K.js.map → chunk-S4D3Z723.js.map} +0 -0
  174. package/dist/{chunk-IMZGJ2X7.js.map → chunk-UW2DE6JX.js.map} +0 -0
  175. package/dist/{chunk-QDDUU625.js.map → chunk-WWNOVFDC.js.map} +0 -0
  176. package/dist/{chunk-GVRSXXAA.js.map → chunk-YFMENCR4.js.map} +0 -0
  177. package/dist/{validation-D8VcbTzC.d.ts → validation-DnhrNMju.d.ts} +2 -2
@@ -0,0 +1,176 @@
1
+ // src/utils/sessionTracking.ts
2
+ function useSessionTracking(supabaseClient, appName) {
3
+ const resolveAppId = async () => {
4
+ if (!appName) return void 0;
5
+ try {
6
+ const { data, error } = await supabaseClient.from("rbac_apps").select("id").eq("name", appName).eq("is_active", true).single();
7
+ if (error || !data) {
8
+ console.warn("App not found or inactive:", appName);
9
+ return void 0;
10
+ }
11
+ return data.id;
12
+ } catch (error) {
13
+ console.error("Failed to resolve app ID:", error);
14
+ return void 0;
15
+ }
16
+ };
17
+ const trackEventSwitch = async (eventId) => {
18
+ try {
19
+ const { data: { user } } = await supabaseClient.auth.getUser();
20
+ if (!user) {
21
+ console.warn("No authenticated user found for session tracking");
22
+ return;
23
+ }
24
+ const appId = await resolveAppId();
25
+ const params = {
26
+ p_session_type: "event_switch",
27
+ p_event_id: eventId,
28
+ p_app_id: appId
29
+ };
30
+ const { error } = await supabaseClient.rpc("rbac_session_track", {
31
+ p_user_id: user?.id,
32
+ p_session_type: params.p_session_type,
33
+ p_event_id: params.p_event_id,
34
+ p_app_id: params.p_app_id,
35
+ p_ip_address: params.ip_address,
36
+ p_user_agent: params.user_agent
37
+ });
38
+ if (error) {
39
+ console.error("Failed to track event switch session:", error);
40
+ } else {
41
+ console.log("Event switch session tracked successfully");
42
+ }
43
+ } catch (error) {
44
+ console.error("Failed to track event switch:", error);
45
+ }
46
+ };
47
+ const trackSessionExpired = async () => {
48
+ try {
49
+ const { data: { user } } = await supabaseClient.auth.getUser();
50
+ if (!user) {
51
+ console.warn("No authenticated user found for session tracking");
52
+ return;
53
+ }
54
+ const appId = await resolveAppId();
55
+ const params = {
56
+ p_session_type: "session_expired",
57
+ p_app_id: appId
58
+ };
59
+ const { error } = await supabaseClient.rpc("rbac_session_track", {
60
+ p_user_id: user?.id,
61
+ p_session_type: params.p_session_type,
62
+ p_event_id: params.p_event_id,
63
+ p_app_id: params.p_app_id,
64
+ p_ip_address: params.ip_address,
65
+ p_user_agent: params.user_agent
66
+ });
67
+ if (error) {
68
+ console.error("Failed to track session expiration:", error);
69
+ } else {
70
+ console.log("Session expiration tracked successfully");
71
+ }
72
+ } catch (error) {
73
+ console.error("Failed to track session expiration:", error);
74
+ }
75
+ };
76
+ return {
77
+ trackEventSwitch,
78
+ trackSessionExpired
79
+ };
80
+ }
81
+
82
+ // src/utils/appConfig.ts
83
+ var currentAppConfig = null;
84
+ function setAppConfig(config) {
85
+ currentAppConfig = config;
86
+ }
87
+ function getAppConfig() {
88
+ if (!currentAppConfig) {
89
+ const appName = import.meta.env.REACT_APP_NAME || "PACE";
90
+ return {
91
+ appName,
92
+ appId: appName
93
+ };
94
+ }
95
+ return currentAppConfig;
96
+ }
97
+ function getCurrentAppName() {
98
+ return getAppConfig().appName;
99
+ }
100
+ function getCurrentAppId() {
101
+ return getAppConfig().appId;
102
+ }
103
+
104
+ // src/utils/formatting.ts
105
+ function formatDate(date) {
106
+ const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
107
+ return dateObj.toLocaleDateString(void 0, {
108
+ year: "numeric",
109
+ month: "short",
110
+ day: "numeric"
111
+ });
112
+ }
113
+ function formatCurrency(value, currencyCode = "USD", locale = "en-US") {
114
+ return new Intl.NumberFormat(locale, {
115
+ style: "currency",
116
+ currency: currencyCode
117
+ }).format(value);
118
+ }
119
+ function formatNumber(value, options = {}, locale = "en-US") {
120
+ return new Intl.NumberFormat(locale, options).format(value);
121
+ }
122
+ function formatPercent(value, locale = "en-US", decimalsOrOptions) {
123
+ let decimals;
124
+ if (typeof decimalsOrOptions === "number") {
125
+ decimals = decimalsOrOptions;
126
+ } else if (decimalsOrOptions && typeof decimalsOrOptions === "object") {
127
+ if (decimalsOrOptions.preserveDecimals) {
128
+ const valueStr = value.toString();
129
+ const decimalIndex = valueStr.indexOf(".");
130
+ if (decimalIndex !== -1) {
131
+ const detectedDecimals = valueStr.length - decimalIndex - 1;
132
+ const maxDecimals = decimalsOrOptions.maxDecimals ?? 10;
133
+ decimals = Math.min(detectedDecimals, maxDecimals);
134
+ } else {
135
+ decimals = 0;
136
+ }
137
+ } else {
138
+ decimals = decimalsOrOptions.decimals ?? 1;
139
+ }
140
+ } else {
141
+ decimals = 1;
142
+ }
143
+ return new Intl.NumberFormat(locale, {
144
+ style: "percent",
145
+ minimumFractionDigits: decimals,
146
+ maximumFractionDigits: decimals
147
+ }).format(value / 100);
148
+ }
149
+ function formatCompactNumber(value, locale = "en-US") {
150
+ return new Intl.NumberFormat(locale, {
151
+ notation: "compact",
152
+ compactDisplay: "short"
153
+ }).format(value);
154
+ }
155
+ function formatFileSize(bytes) {
156
+ if (bytes === 0) return "0 Bytes";
157
+ const k = 1024;
158
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
159
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
160
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
161
+ }
162
+
163
+ export {
164
+ useSessionTracking,
165
+ setAppConfig,
166
+ getAppConfig,
167
+ getCurrentAppName,
168
+ getCurrentAppId,
169
+ formatDate,
170
+ formatCurrency,
171
+ formatNumber,
172
+ formatPercent,
173
+ formatCompactNumber,
174
+ formatFileSize
175
+ };
176
+ //# sourceMappingURL=chunk-GZRXOUBE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/sessionTracking.ts","../src/utils/appConfig.ts","../src/utils/formatting.ts"],"sourcesContent":["import type { SupabaseClient } from '@supabase/supabase-js';\n\n// Define the tracking parameters locally since old RBAC types are removed\ninterface TrackUserSessionParams {\n p_session_type: 'event_switch' | 'session_expired';\n p_event_id?: string;\n p_app_id?: string;\n ip_address?: string;\n user_agent?: string;\n}\n\n/**\n * Hook for manual session tracking (event switches and session expiration).\n * \n * Note: Login and logout tracking is automatically handled by UnifiedAuthProvider.\n * You should only use this hook for tracking event switches or session expirations.\n * \n * @param supabaseClient - Supabase client instance\n * @param appName - Optional application name for tracking\n * @returns Object containing tracking functions for event switches and session expiration\n */\nexport function useSessionTracking(supabaseClient: SupabaseClient, appName?: string) {\n // Resolve app name to app_id\n const resolveAppId = async (): Promise<string | undefined> => {\n if (!appName) return undefined;\n \n try {\n const { data, error } = await supabaseClient\n .from('rbac_apps')\n .select('id')\n .eq('name', appName)\n .eq('is_active', true)\n .single();\n \n if (error || !data) {\n console.warn('App not found or inactive:', appName);\n return undefined;\n }\n \n return data.id;\n } catch (error) {\n console.error('Failed to resolve app ID:', error);\n return undefined;\n }\n };\n /**\n * Track an event switch\n * @param eventId - ID of the event being switched to\n */\n const trackEventSwitch = async (eventId: string) => {\n try {\n const { data: { user } } = await supabaseClient.auth.getUser();\n if (!user) {\n console.warn('No authenticated user found for session tracking');\n return;\n }\n\n const appId = await resolveAppId();\n\n const params: TrackUserSessionParams = {\n p_session_type: 'event_switch',\n p_event_id: eventId,\n p_app_id: appId\n };\n\n const { error } = await supabaseClient.rpc('rbac_session_track', {\n p_user_id: user?.id,\n p_session_type: params.p_session_type,\n p_event_id: params.p_event_id,\n p_app_id: params.p_app_id,\n p_ip_address: params.ip_address,\n p_user_agent: params.user_agent\n });\n \n if (error) {\n console.error('Failed to track event switch session:', error);\n } else {\n console.log('Event switch session tracked successfully');\n }\n } catch (error) {\n console.error('Failed to track event switch:', error);\n }\n };\n\n /**\n * Track a session expiration\n */\n const trackSessionExpired = async () => {\n try {\n const { data: { user } } = await supabaseClient.auth.getUser();\n if (!user) {\n console.warn('No authenticated user found for session tracking');\n return;\n }\n\n const appId = await resolveAppId();\n\n const params: TrackUserSessionParams = {\n p_session_type: 'session_expired',\n p_app_id: appId\n };\n\n const { error } = await supabaseClient.rpc('rbac_session_track', {\n p_user_id: user?.id,\n p_session_type: params.p_session_type,\n p_event_id: params.p_event_id,\n p_app_id: params.p_app_id,\n p_ip_address: params.ip_address,\n p_user_agent: params.user_agent\n });\n \n if (error) {\n console.error('Failed to track session expiration:', error);\n } else {\n console.log('Session expiration tracked successfully');\n }\n } catch (error) {\n console.error('Failed to track session expiration:', error);\n }\n };\n\n return {\n trackEventSwitch,\n trackSessionExpired\n };\n} ","\n/**\n * Application configuration utilities\n */\n\nexport interface AppConfig {\n appName: string;\n appId: string;\n}\n\nlet currentAppConfig: AppConfig | null = null;\n\n/**\n * Set the current application configuration\n */\nexport function setAppConfig(config: AppConfig) {\n currentAppConfig = config;\n}\n\n/**\n * Get the current application configuration\n */\nexport function getAppConfig(): AppConfig {\n if (!currentAppConfig) {\n // Fallback to environment or default\n const appName = import.meta.env.REACT_APP_NAME || 'PACE';\n return {\n appName,\n appId: appName\n };\n }\n return currentAppConfig;\n}\n\n/**\n * Get the current app name\n */\nexport function getCurrentAppName(): string {\n return getAppConfig().appName;\n}\n\n/**\n * Get the current app ID\n */\nexport function getCurrentAppId(): string {\n return getAppConfig().appId;\n}\n","/**\n * Utility functions for formatting data in the application\n */\n\n/**\n * Format a date as a readable string\n */\nexport function formatDate(date: Date | string | number): string {\n const dateObj = typeof date === 'string' || typeof date === 'number' \n ? new Date(date) \n : date;\n \n return dateObj.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n}\n\n/**\n * Format a number as a currency\n */\nexport function formatCurrency(value: number, currencyCode = 'USD', locale = 'en-US'): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: currencyCode,\n }).format(value);\n}\n\n/**\n * Format a number with custom options\n */\nexport function formatNumber(\n value: number,\n options: Intl.NumberFormatOptions = {},\n locale = 'en-US'\n): string {\n return new Intl.NumberFormat(locale, options).format(value);\n}\n\n/**\n * Format a number as a percentage.\n * \n * The third parameter can be either:\n * - A number for fixed decimal places (backward compatible): `formatPercent(0.81, 'en-US', 2)`\n * - An options object with:\n * - `decimals`: Fixed number of decimal places (default: 1)\n * - `preserveDecimals`: Auto-detect and preserve decimal places from the input value\n * - `maxDecimals`: Maximum decimal places when preserving (default: 10)\n * \n * @param value - The percentage value as a decimal (e.g., 0.81 for 0.81%)\n * @param locale - The locale string (default: 'en-US')\n * @param decimalsOrOptions - Either a number for fixed decimals, or an options object with:\n * - `decimals` - Fixed number of decimal places (default: 1)\n * - `preserveDecimals` - Auto-detect and preserve decimal places from the input value\n * - `maxDecimals` - Maximum decimal places when preserving (default: 10)\n * @returns Formatted percentage string (e.g., \"0.81%\", \"81%\")\n * \n * @example\n * ```ts\n * // Fixed decimals (default behavior)\n * formatPercent(0.5) // '0.5%'\n * formatPercent(0.81, 'en-US', 1) // '0.8%' (loses precision)\n * \n * // Preserve decimal places dynamically\n * formatPercent(0.81, 'en-US', { preserveDecimals: true }) // '0.81%'\n * formatPercent(0.8123, 'en-US', { preserveDecimals: true, maxDecimals: 2 }) // '0.81%'\n * ```\n */\nexport function formatPercent(\n value: number,\n locale: string = 'en-US',\n decimalsOrOptions?: number | {\n decimals?: number;\n preserveDecimals?: boolean;\n maxDecimals?: number;\n }\n): string {\n let decimals: number;\n\n // Backward compatibility: if decimalsOrOptions is a number, use it directly\n if (typeof decimalsOrOptions === 'number') {\n decimals = decimalsOrOptions;\n } else if (decimalsOrOptions && typeof decimalsOrOptions === 'object') {\n // New options object: check if we should preserve decimals\n if (decimalsOrOptions.preserveDecimals) {\n const valueStr = value.toString();\n const decimalIndex = valueStr.indexOf('.');\n \n if (decimalIndex !== -1) {\n const detectedDecimals = valueStr.length - decimalIndex - 1;\n const maxDecimals = decimalsOrOptions.maxDecimals ?? 10;\n decimals = Math.min(detectedDecimals, maxDecimals);\n } else {\n decimals = 0;\n }\n } else {\n decimals = decimalsOrOptions.decimals ?? 1;\n }\n } else {\n decimals = 1;\n }\n\n return new Intl.NumberFormat(locale, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(value / 100);\n}\n\n/**\n * Format a large number with abbreviations (K, M, B)\n */\nexport function formatCompactNumber(value: number, locale = 'en-US'): string {\n return new Intl.NumberFormat(locale, {\n notation: 'compact',\n compactDisplay: 'short'\n }).format(value);\n}\n\n/**\n * Format a file size in bytes to a human-readable string\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n}\n"],"mappings":"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","names":[]}
@@ -25,16 +25,16 @@ import {
25
25
  SelectSeparator,
26
26
  SelectTrigger,
27
27
  SelectValue
28
- } from "./chunk-IMZGJ2X7.js";
28
+ } from "./chunk-UW2DE6JX.js";
29
29
  import {
30
30
  usePermissions,
31
31
  useRBAC,
32
32
  useResolvedScope
33
- } from "./chunk-QDDUU625.js";
33
+ } from "./chunk-WWNOVFDC.js";
34
34
  import {
35
35
  isPermitted,
36
36
  isSuperAdmin
37
- } from "./chunk-S63MFSY6.js";
37
+ } from "./chunk-F6TSYCKP.js";
38
38
  import {
39
39
  OrganisationProvider_exports,
40
40
  PublicErrorBoundary,
@@ -49,18 +49,18 @@ import {
49
49
  useIsPublicPage,
50
50
  usePublicFileDisplay,
51
51
  usePublicPageContext
52
- } from "./chunk-DMNMZKWS.js";
52
+ } from "./chunk-2W4WKJVF.js";
53
53
  import {
54
54
  useToast
55
55
  } from "./chunk-4OX5PXHX.js";
56
56
  import {
57
57
  useEvents,
58
58
  useOrganisations
59
- } from "./chunk-ZXY5NTJB.js";
59
+ } from "./chunk-EZ64QG2I.js";
60
60
  import {
61
61
  UnifiedAuthProvider_exports,
62
62
  init_UnifiedAuthProvider as init_UnifiedAuthProvider2
63
- } from "./chunk-KBG34SVL.js";
63
+ } from "./chunk-EYSXQ756.js";
64
64
  import {
65
65
  EventServiceContext,
66
66
  EventServiceProvider,
@@ -70,7 +70,7 @@ import {
70
70
  useEventService,
71
71
  useSessionRestoration,
72
72
  useUnifiedAuth
73
- } from "./chunk-X4FRXJV6.js";
73
+ } from "./chunk-AUXS7XSO.js";
74
74
  import {
75
75
  LoadingSpinner
76
76
  } from "./chunk-CDQ3PX7L.js";
@@ -1379,7 +1379,7 @@ function PaceAppLayout({
1379
1379
  appId: user.user_metadata?.appId || user.app_metadata?.appId
1380
1380
  };
1381
1381
  try {
1382
- const { isSuperAdmin: isSuperAdmin2 } = await import("./api-KG4A2X7P.js");
1382
+ const { isSuperAdmin: isSuperAdmin2 } = await import("./api-5I3E47G2.js");
1383
1383
  const isSuper = await isSuperAdmin2(user.id);
1384
1384
  if (isSuper) {
1385
1385
  return true;
@@ -1394,10 +1394,11 @@ function PaceAppLayout({
1394
1394
  console.warn("No organisation context available for permission check, denying access");
1395
1395
  return false;
1396
1396
  }
1397
+ const fullPermission = permission.includes(":") ? permission : pageId ? `${permission}:page.${pageId}` : permission;
1397
1398
  return await isPermitted({
1398
1399
  userId: user.id,
1399
1400
  scope,
1400
- permission,
1401
+ permission: fullPermission,
1401
1402
  pageId
1402
1403
  });
1403
1404
  } catch (error) {
@@ -1498,7 +1499,7 @@ function PaceAppLayout({
1498
1499
  appId: user.user_metadata?.appId || user.app_metadata?.appId
1499
1500
  };
1500
1501
  try {
1501
- const { isSuperAdmin: isSuperAdmin2 } = await import("./api-KG4A2X7P.js");
1502
+ const { isSuperAdmin: isSuperAdmin2 } = await import("./api-5I3E47G2.js");
1502
1503
  const isSuper = await isSuperAdmin2(user.id);
1503
1504
  if (isSuper) {
1504
1505
  if (isMounted) {
@@ -1518,22 +1519,38 @@ function PaceAppLayout({
1518
1519
  }
1519
1520
  return;
1520
1521
  }
1521
- const filtered = await Promise.all(
1522
- baseMenuItems.map(async (item) => {
1522
+ try {
1523
+ const { getPermissionMap } = await import("./api-5I3E47G2.js");
1524
+ const permissionMap = await getPermissionMap({
1525
+ userId: user.id,
1526
+ scope
1527
+ });
1528
+ const filtered = baseMenuItems.map((item) => {
1523
1529
  if (!item.href) return { item, hasAccess: true };
1524
1530
  const pageId = pageIdMapping[item.href] || item.href.slice(1) || "home";
1525
1531
  const permission = routePermissions[item.href] || defaultPermission;
1526
- try {
1527
- const hasAccess = await checkPermission(permission, pageId);
1528
- return { item, hasAccess };
1529
- } catch {
1530
- return { item, hasAccess: false };
1532
+ const fullPermission = permission.includes(":") ? permission : pageId ? `${permission}:page.${pageId}` : permission;
1533
+ const hasAccess = permissionMap["*"] === true || permissionMap[fullPermission] === true;
1534
+ if (auditLog) {
1535
+ console.log(`[PaceAppLayout] Navigation filtering:`, {
1536
+ item: item.label,
1537
+ href: item.href,
1538
+ pageId,
1539
+ permission: fullPermission,
1540
+ hasAccess
1541
+ });
1531
1542
  }
1532
- })
1533
- );
1534
- if (!isMounted) return;
1535
- const accessibleItems = filtered.filter(({ hasAccess }) => hasAccess).map(({ item }) => item);
1536
- setFilteredMenuItems(accessibleItems);
1543
+ return { item, hasAccess };
1544
+ });
1545
+ if (!isMounted) return;
1546
+ const accessibleItems = filtered.filter(({ hasAccess }) => hasAccess).map(({ item }) => item);
1547
+ setFilteredMenuItems(accessibleItems);
1548
+ } catch (error) {
1549
+ console.error("[PaceAppLayout] Failed to load permission map for navigation filtering:", error);
1550
+ if (isMounted) {
1551
+ setFilteredMenuItems(baseMenuItems);
1552
+ }
1553
+ }
1537
1554
  };
1538
1555
  filterItems();
1539
1556
  return () => {
@@ -1572,7 +1589,7 @@ function PaceAppLayout({
1572
1589
  }
1573
1590
  }
1574
1591
  if (hasAccess && currentRoute.roles && currentRoute.roles.length > 0 && user?.id) {
1575
- const { useUnifiedAuth: useUnifiedAuth2 } = await import("./UnifiedAuthProvider-XU4BHFXZ.js");
1592
+ const { useUnifiedAuth: useUnifiedAuth2 } = await import("./UnifiedAuthProvider-A7I23UCN.js");
1576
1593
  hasAccess = true;
1577
1594
  }
1578
1595
  if (!isMounted) return;
@@ -4396,4 +4413,4 @@ export {
4396
4413
  PublicPageDiagnostic,
4397
4414
  PublicPageContextChecker
4398
4415
  };
4399
- //# sourceMappingURL=chunk-B3QX32P5.js.map
4416
+ //# sourceMappingURL=chunk-P72NKAT5.js.map