@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.
- package/CHANGELOG.md +75 -177
- package/dist/{AuthService-1D2ifNfa.d.ts → AuthService-DrHrvXNZ.d.ts} +8 -1
- package/dist/{DataTable-WFCHVWTY.js → DataTable-5HITILXS.js} +7 -7
- package/dist/{UnifiedAuthProvider-XU4BHFXZ.js → UnifiedAuthProvider-A7I23UCN.js} +3 -3
- package/dist/{api-KG4A2X7P.js → api-5I3E47G2.js} +2 -2
- package/dist/{chunk-DMNMZKWS.js → chunk-2W4WKJVF.js} +4 -4
- package/dist/{chunk-MOMYOQMC.js → chunk-3TKTL5AZ.js} +13 -13
- package/dist/{chunk-X4FRXJV6.js → chunk-AUXS7XSO.js} +57 -6
- package/dist/{chunk-X4FRXJV6.js.map → chunk-AUXS7XSO.js.map} +1 -1
- package/dist/{chunk-LT6RKRA7.js → chunk-D6MEKC27.js} +2 -2
- package/dist/{chunk-KBG34SVL.js → chunk-EYSXQ756.js} +2 -2
- package/dist/{chunk-ZXY5NTJB.js → chunk-EZ64QG2I.js} +2 -2
- package/dist/{chunk-S63MFSY6.js → chunk-F6TSYCKP.js} +4 -2
- package/dist/{chunk-S63MFSY6.js.map → chunk-F6TSYCKP.js.map} +1 -1
- package/dist/chunk-GZRXOUBE.js +176 -0
- package/dist/chunk-GZRXOUBE.js.map +1 -0
- package/dist/{chunk-B3QX32P5.js → chunk-P72NKAT5.js} +41 -24
- package/dist/chunk-P72NKAT5.js.map +1 -0
- package/dist/{chunk-VJ7MPS2K.js → chunk-S4D3Z723.js} +6 -6
- package/dist/{chunk-IMZGJ2X7.js → chunk-UW2DE6JX.js} +4 -4
- package/dist/{chunk-QDDUU625.js → chunk-WWNOVFDC.js} +4 -4
- package/dist/{chunk-GVRSXXAA.js → chunk-YFMENCR4.js} +3 -3
- package/dist/components.js +9 -9
- package/dist/{database-BXAfr2Y_.d.ts → database-C6jy7EOu.d.ts} +21 -9
- package/dist/{formatting-BiEv5oEk.d.ts → formatting-B1jSqgl-.d.ts} +16 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +6 -6
- package/dist/index.js +16 -14
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +4 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +8 -8
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-CnM-IK2I.d.ts → usePublicRouteParams-BdF8bZgs.d.ts} +1 -1
- package/dist/utils.d.ts +2 -15
- package/dist/utils.js +4 -145
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +3 -3
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +37 -3
- package/docs/api-reference/hooks.md +53 -0
- package/docs/api-reference/providers.md +60 -0
- package/docs/core-concepts/authentication.md +2 -0
- package/docs/implementation-guides/authentication.md +1 -0
- package/docs/security/README.md +59 -0
- package/package.json +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +48 -16
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +2 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +9 -9
- package/src/index.ts +3 -0
- package/src/providers/services/AuthServiceProvider.tsx +4 -3
- package/src/providers/services/UnifiedAuthProvider.tsx +1 -1
- package/src/rbac/engine.ts +2 -0
- package/src/services/AuthService.ts +79 -1
- package/src/services/__tests__/AuthService.test.ts +184 -0
- package/src/types/database.ts +21 -9
- package/src/types/rbac-functions.ts +2 -1
- package/src/utils/__tests__/sessionTracking.unit.test.ts +6 -171
- package/src/utils/sessionTracking.ts +7 -81
- package/dist/chunk-B3QX32P5.js.map +0 -1
- package/dist/chunk-NFPV7MRN.js +0 -94
- package/dist/chunk-NFPV7MRN.js.map +0 -1
- package/src/providers/AuthProvider.simplified.tsx +0 -974
- package/dist/{DataTable-WFCHVWTY.js.map → DataTable-5HITILXS.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-XU4BHFXZ.js.map → UnifiedAuthProvider-A7I23UCN.js.map} +0 -0
- package/dist/{api-KG4A2X7P.js.map → api-5I3E47G2.js.map} +0 -0
- package/dist/{chunk-DMNMZKWS.js.map → chunk-2W4WKJVF.js.map} +0 -0
- package/dist/{chunk-MOMYOQMC.js.map → chunk-3TKTL5AZ.js.map} +0 -0
- package/dist/{chunk-LT6RKRA7.js.map → chunk-D6MEKC27.js.map} +0 -0
- package/dist/{chunk-KBG34SVL.js.map → chunk-EYSXQ756.js.map} +0 -0
- package/dist/{chunk-ZXY5NTJB.js.map → chunk-EZ64QG2I.js.map} +0 -0
- package/dist/{chunk-VJ7MPS2K.js.map → chunk-S4D3Z723.js.map} +0 -0
- package/dist/{chunk-IMZGJ2X7.js.map → chunk-UW2DE6JX.js.map} +0 -0
- package/dist/{chunk-QDDUU625.js.map → chunk-WWNOVFDC.js.map} +0 -0
- package/dist/{chunk-GVRSXXAA.js.map → chunk-YFMENCR4.js.map} +0 -0
- 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-
|
|
28
|
+
} from "./chunk-UW2DE6JX.js";
|
|
29
29
|
import {
|
|
30
30
|
usePermissions,
|
|
31
31
|
useRBAC,
|
|
32
32
|
useResolvedScope
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-WWNOVFDC.js";
|
|
34
34
|
import {
|
|
35
35
|
isPermitted,
|
|
36
36
|
isSuperAdmin
|
|
37
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
59
|
+
} from "./chunk-EZ64QG2I.js";
|
|
60
60
|
import {
|
|
61
61
|
UnifiedAuthProvider_exports,
|
|
62
62
|
init_UnifiedAuthProvider as init_UnifiedAuthProvider2
|
|
63
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
1522
|
-
|
|
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
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
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-
|
|
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-
|
|
4416
|
+
//# sourceMappingURL=chunk-P72NKAT5.js.map
|