varminer-app-header 2.6.1 → 2.6.3

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.
@@ -0,0 +1,506 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import AccountCircle from "@mui/icons-material/AccountCircle";
3
+ import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
4
+ import MailIcon from "@mui/icons-material/Mail";
5
+ import MenuIcon from "@mui/icons-material/Menu";
6
+ import MenuOpenIcon from "@mui/icons-material/MenuOpen";
7
+ import MoreIcon from "@mui/icons-material/MoreVert";
8
+ import NotificationsActiveOutlined from "@mui/icons-material/NotificationsActiveOutlined";
9
+ import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined";
10
+ import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
11
+ import { AppBar, Avatar, Badge, Box, Divider, IconButton, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, Menu, MenuItem, styled, Toolbar, Typography, } from "@mui/material";
12
+ import { deepOrange } from "@mui/material/colors";
13
+ import React from "react";
14
+ import { useDrawer } from "./DrawerContext";
15
+ import { getUserDataFromStorage, getNotificationCountFromStorage, getMessageCountFromStorage, getI18nLocaleFromStorage, setI18nLocaleToStorage, getAllDataFromStorage, fetchProfilePictureAsBlobUrl, getAccessTokenForRequest } from "./utils/localStorage";
16
+ import { getTranslations } from "./utils/translations";
17
+ import LanguageSelector from "./LanguageSelector";
18
+ import "./styles/Header.scss";
19
+ const DEFAULT_ROUTES = {
20
+ settings: "/account/overview",
21
+ profile: "/user/profile",
22
+ logout: "/user/login",
23
+ };
24
+ const AppHeader = ({ language: languageProp, accessToken: accessTokenProp, objectStoreUrl }) => {
25
+ // Get initial language from props, URL, localStorage, or default to 'en'
26
+ const getInitialLanguage = () => {
27
+ // Priority 1: Props
28
+ if (languageProp) {
29
+ setI18nLocaleToStorage(languageProp);
30
+ return languageProp;
31
+ }
32
+ // Priority 2: URL path - pattern is /:locale/base-name/...
33
+ const currentPath = window.location.pathname;
34
+ const localePattern = /^\/(en|es)(\/|$)/;
35
+ const localeMatch = currentPath.match(localePattern);
36
+ if (localeMatch && localeMatch[1]) {
37
+ const urlLocale = localeMatch[1];
38
+ setI18nLocaleToStorage(urlLocale);
39
+ return urlLocale;
40
+ }
41
+ // Priority 3: localStorage
42
+ const storedLocale = getI18nLocaleFromStorage();
43
+ if (storedLocale && (storedLocale === 'en' || storedLocale === 'es')) {
44
+ return storedLocale;
45
+ }
46
+ // Default
47
+ return 'en';
48
+ };
49
+ const [currentLanguage, setCurrentLanguage] = React.useState(getInitialLanguage);
50
+ const t = getTranslations(currentLanguage);
51
+ const { isDrawerOpen, toggleDrawer } = useDrawer();
52
+ const [anchorEl, setAnchorEl] = React.useState(null);
53
+ const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);
54
+ const [user, setUser] = React.useState(() => {
55
+ const allData = getAllDataFromStorage();
56
+ let userName = "";
57
+ let userEmail = "";
58
+ let userRole = "";
59
+ const buildNameFromFirstLast = (obj) => {
60
+ if (!obj || typeof obj !== "object")
61
+ return "";
62
+ const first = (obj.firstName ?? obj.given_name ?? obj.first_name);
63
+ const last = (obj.lastName ?? obj.family_name ?? obj.last_name);
64
+ return [first, last].filter(Boolean).join(" ").trim();
65
+ };
66
+ // Prefer getUserDataFromStorage first (IAM firstName+lastName or persist:userdb name)
67
+ const storedUser = getUserDataFromStorage();
68
+ if (storedUser) {
69
+ userName = storedUser.name || "";
70
+ userEmail = userEmail || storedUser.email || "";
71
+ userRole = userRole || storedUser.role || "";
72
+ }
73
+ // Then enrich from decoded token (email from sub; name only if token has name/given_name/family_name, never use sub as name)
74
+ if (allData.decodedToken) {
75
+ const token = allData.decodedToken;
76
+ userEmail = userEmail || token.sub || token.email || "";
77
+ const tokenName = token.name || buildNameFromFirstLast(token);
78
+ userName = userName || (tokenName || "");
79
+ userRole = userRole || token.role || "";
80
+ }
81
+ if (allData.auth) {
82
+ const auth = allData.auth;
83
+ userEmail = userEmail || auth.email || auth.sub || "";
84
+ userName = userName || auth.name || buildNameFromFirstLast(auth) || "";
85
+ userRole = userRole || auth.role || auth.activeRole || "";
86
+ }
87
+ if (allData.userDetails) {
88
+ const userDetails = allData.userDetails.user
89
+ || allData.userDetails.data
90
+ || allData.userDetails;
91
+ const details = userDetails;
92
+ if (details) {
93
+ userEmail = userEmail || details.email || details.emailAddress || "";
94
+ userName = userName || details.name || details.fullName || buildNameFromFirstLast(details) || "";
95
+ userRole = userRole || details.role || details.userRole || "";
96
+ }
97
+ }
98
+ if (allData.user) {
99
+ const u = allData.user.user
100
+ || allData.user.data
101
+ || allData.user.currentUser
102
+ || allData.user;
103
+ const userData = u;
104
+ if (userData) {
105
+ userEmail = userEmail || userData.email || userData.emailAddress || "";
106
+ userName = userName || userData.name || userData.fullName || buildNameFromFirstLast(userData) || "";
107
+ userRole = userRole || userData.role || userData.userRole || "";
108
+ }
109
+ }
110
+ if (allData.profile) {
111
+ const p = allData.profile.user
112
+ || allData.profile.data
113
+ || allData.profile;
114
+ const profileData = p;
115
+ if (profileData) {
116
+ userEmail = userEmail || profileData.email || profileData.emailAddress || "";
117
+ userName = userName || profileData.name || profileData.fullName || buildNameFromFirstLast(profileData) || "";
118
+ userRole = userRole || profileData.role || profileData.userRole || "";
119
+ }
120
+ }
121
+ const avatarSrc = storedUser?.avatar ??
122
+ allData.auth?.avatar ??
123
+ allData.profile?.avatar;
124
+ const initialsVal = storedUser?.initials ??
125
+ allData.auth?.initials ??
126
+ allData.profile?.initials;
127
+ return {
128
+ name: userName || "",
129
+ email: userEmail || "",
130
+ role: userRole || "",
131
+ avatar: typeof avatarSrc === "string" ? avatarSrc : undefined,
132
+ initials: typeof initialsVal === "string" ? initialsVal : undefined,
133
+ };
134
+ });
135
+ const [notificationCount, setNotificationCount] = React.useState(() => {
136
+ const count = getNotificationCountFromStorage();
137
+ return count !== null && count !== undefined ? count : 0;
138
+ });
139
+ const [messageCount, setMessageCount] = React.useState(() => {
140
+ return getMessageCountFromStorage() ?? undefined;
141
+ });
142
+ // State for profile picture blob URL
143
+ const [profilePictureBlobUrl, setProfilePictureBlobUrl] = React.useState(null);
144
+ // Fetch profile picture from API when component mounts or user data changes
145
+ React.useEffect(() => {
146
+ const fetchProfilePicture = async () => {
147
+ const token = accessTokenProp ?? getAccessTokenForRequest();
148
+ const blobUrl = await fetchProfilePictureAsBlobUrl(objectStoreUrl ?? undefined, token ?? undefined);
149
+ if (blobUrl) {
150
+ // Clean up previous blob URL if it exists
151
+ setProfilePictureBlobUrl((prevUrl) => {
152
+ if (prevUrl) {
153
+ URL.revokeObjectURL(prevUrl);
154
+ }
155
+ return blobUrl;
156
+ });
157
+ }
158
+ };
159
+ fetchProfilePicture();
160
+ // Cleanup function to revoke blob URL when component unmounts or effect re-runs
161
+ return () => {
162
+ setProfilePictureBlobUrl((prevUrl) => {
163
+ if (prevUrl) {
164
+ URL.revokeObjectURL(prevUrl);
165
+ }
166
+ return null;
167
+ });
168
+ };
169
+ }, [accessTokenProp, objectStoreUrl]); // Refetch when accessToken or objectStoreUrl changes (e.g. after login or env switch)
170
+ React.useEffect(() => {
171
+ const allData = getAllDataFromStorage();
172
+ let userName = "";
173
+ let userEmail = "";
174
+ let userRole = "";
175
+ let userAvatar;
176
+ let userInitials;
177
+ const buildNameFromFirstLast = (obj) => {
178
+ if (!obj || typeof obj !== "object")
179
+ return "";
180
+ const first = (obj.firstName ?? obj.given_name ?? obj.first_name);
181
+ const last = (obj.lastName ?? obj.family_name ?? obj.last_name);
182
+ return [first, last].filter(Boolean).join(" ").trim();
183
+ };
184
+ const storedUser = getUserDataFromStorage();
185
+ if (storedUser) {
186
+ userName = storedUser.name || "";
187
+ userEmail = userEmail || storedUser.email || "";
188
+ userRole = userRole || storedUser.role || "";
189
+ userAvatar = storedUser.avatar;
190
+ userInitials = storedUser.initials;
191
+ }
192
+ if (allData.decodedToken) {
193
+ const token = allData.decodedToken;
194
+ userEmail = userEmail || token.sub || token.email || "";
195
+ const tokenName = token.name || buildNameFromFirstLast(token);
196
+ userName = userName || (tokenName || "");
197
+ userRole = userRole || token.role || "";
198
+ }
199
+ if (allData.auth) {
200
+ const auth = allData.auth;
201
+ userEmail = userEmail || auth.email || auth.sub || "";
202
+ userName = userName || auth.name || buildNameFromFirstLast(auth) || "";
203
+ userRole = userRole || auth.role || auth.activeRole || "";
204
+ userAvatar = userAvatar || auth.avatar || undefined;
205
+ userInitials = userInitials || auth.initials || undefined;
206
+ }
207
+ if (allData.userDetails) {
208
+ const userDetails = allData.userDetails.user
209
+ || allData.userDetails.data
210
+ || allData.userDetails;
211
+ const details = userDetails;
212
+ if (details) {
213
+ userEmail = userEmail || details.email || details.emailAddress || "";
214
+ userName = userName || details.name || details.fullName || buildNameFromFirstLast(details) || "";
215
+ userRole = userRole || details.role || details.userRole || "";
216
+ userAvatar = userAvatar || details.avatar || details.profilePicture || undefined;
217
+ userInitials = userInitials || details.initials || undefined;
218
+ }
219
+ }
220
+ if (allData.user) {
221
+ const u = allData.user.user
222
+ || allData.user.data
223
+ || allData.user.currentUser
224
+ || allData.user;
225
+ const userData = u;
226
+ if (userData) {
227
+ userEmail = userEmail || userData.email || userData.emailAddress || "";
228
+ userName = userName || userData.name || userData.fullName || buildNameFromFirstLast(userData) || "";
229
+ userRole = userRole || userData.role || userData.userRole || "";
230
+ userAvatar = userAvatar || userData.avatar || userData.profilePicture || undefined;
231
+ userInitials = userInitials || userData.initials || undefined;
232
+ }
233
+ }
234
+ if (allData.profile) {
235
+ const p = allData.profile.user
236
+ || allData.profile.data
237
+ || allData.profile;
238
+ const profileData = p;
239
+ if (profileData) {
240
+ userEmail = userEmail || profileData.email || profileData.emailAddress || "";
241
+ userName = userName || profileData.name || profileData.fullName || buildNameFromFirstLast(profileData) || "";
242
+ userRole = userRole || profileData.role || profileData.userRole || "";
243
+ userAvatar = userAvatar || profileData.avatar || profileData.profilePicture || undefined;
244
+ userInitials = userInitials || profileData.initials || undefined;
245
+ }
246
+ }
247
+ setUser({
248
+ name: userName || "",
249
+ email: userEmail || "",
250
+ role: userRole || "",
251
+ avatar: profilePictureBlobUrl || userAvatar,
252
+ initials: userInitials,
253
+ });
254
+ // Update online status
255
+ if (allData.auth?.isOnline !== undefined) {
256
+ setIsOnlineStatus(allData.auth.isOnline);
257
+ }
258
+ else if (allData.userDetails?.isOnline !== undefined) {
259
+ setIsOnlineStatus(allData.userDetails.isOnline);
260
+ }
261
+ else if (allData.user?.isOnline !== undefined) {
262
+ setIsOnlineStatus(allData.user.isOnline);
263
+ }
264
+ else if (allData.profile?.isOnline !== undefined) {
265
+ setIsOnlineStatus(allData.profile.isOnline);
266
+ }
267
+ const notifCount = getNotificationCountFromStorage();
268
+ setNotificationCount(notifCount !== null && notifCount !== undefined ? notifCount : 0);
269
+ const msgCount = getMessageCountFromStorage();
270
+ setMessageCount(msgCount ?? undefined);
271
+ // Initialize language from props if provided
272
+ if (languageProp && languageProp !== currentLanguage) {
273
+ setCurrentLanguage(languageProp);
274
+ setI18nLocaleToStorage(languageProp);
275
+ }
276
+ }, [languageProp, profilePictureBlobUrl]); // Also update when profile picture is fetched
277
+ const finalRoutes = DEFAULT_ROUTES;
278
+ // Get online status from localStorage dynamically
279
+ const [isOnlineStatus, setIsOnlineStatus] = React.useState(() => {
280
+ const allData = getAllDataFromStorage();
281
+ if (allData.auth?.isOnline !== undefined) {
282
+ return allData.auth.isOnline;
283
+ }
284
+ if (allData.userDetails?.isOnline !== undefined) {
285
+ return allData.userDetails.isOnline;
286
+ }
287
+ if (allData.user?.isOnline !== undefined) {
288
+ return allData.user.isOnline;
289
+ }
290
+ if (allData.profile?.isOnline !== undefined) {
291
+ return allData.profile.isOnline;
292
+ }
293
+ // Default to true if not specified (for backward compatibility)
294
+ return true;
295
+ });
296
+ const notificationCountValue = notificationCount;
297
+ const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
298
+ const OnlineBadge = styled(Badge)(({ theme }) => ({
299
+ "& .MuiBadge-badge": {
300
+ backgroundColor: "var(--color-status-online, #44b700)",
301
+ color: "var(--color-status-online, #44b700)",
302
+ boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
303
+ width: 12,
304
+ height: 12,
305
+ borderRadius: "50%",
306
+ },
307
+ }));
308
+ const OfflineBadge = styled(Badge)(({ theme }) => ({
309
+ "& .MuiBadge-badge": {
310
+ backgroundColor: "var(--color-status-offline, #808080)",
311
+ color: "var(--color-status-offline, #808080)",
312
+ boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
313
+ width: 12,
314
+ height: 12,
315
+ borderRadius: "50%",
316
+ },
317
+ }));
318
+ const handleProfileMenuOpen = (event) => {
319
+ setAnchorEl(event.currentTarget);
320
+ };
321
+ const handleMobileMenuClose = () => {
322
+ setMobileMoreAnchorEl(null);
323
+ };
324
+ const handleMobileMenuOpen = (event) => {
325
+ setMobileMoreAnchorEl(event.currentTarget);
326
+ };
327
+ const open = Boolean(anchorEl);
328
+ const handleClose = () => {
329
+ setAnchorEl(null);
330
+ };
331
+ const handleSettingsClick = () => {
332
+ handleClose();
333
+ const path = finalRoutes.settings.startsWith('/')
334
+ ? finalRoutes.settings
335
+ : `/${finalRoutes.settings}`;
336
+ window.location.href = path;
337
+ };
338
+ const handleProfileClick = () => {
339
+ handleClose();
340
+ const path = finalRoutes.profile.startsWith('/')
341
+ ? finalRoutes.profile
342
+ : `/${finalRoutes.profile}`;
343
+ window.location.href = path;
344
+ };
345
+ const handleSignOutClick = () => {
346
+ handleClose();
347
+ try {
348
+ localStorage.clear();
349
+ sessionStorage.clear();
350
+ }
351
+ catch (e) {
352
+ console.warn("Clear storage on logout:", e);
353
+ }
354
+ const path = finalRoutes.logout.startsWith('/')
355
+ ? finalRoutes.logout
356
+ : `/${finalRoutes.logout}`;
357
+ window.location.href = path;
358
+ };
359
+ const menuId = "primary-account-menu";
360
+ const getInitials = () => {
361
+ if (user.initials)
362
+ return user.initials;
363
+ return user.name
364
+ .split(" ")
365
+ .map((n) => n[0])
366
+ .join("")
367
+ .toUpperCase()
368
+ .slice(0, 2);
369
+ };
370
+ // Check if user has permission to see settings (Account Owner, Account Admin, or Global Admin)
371
+ const canViewSettings = React.useMemo(() => {
372
+ const allData = getAllDataFromStorage();
373
+ let userRole = "";
374
+ // Get role from decoded token
375
+ if (allData.decodedToken?.role) {
376
+ userRole = allData.decodedToken.role;
377
+ }
378
+ // Get role from auth data
379
+ else if (allData.auth?.role) {
380
+ userRole = allData.auth.role;
381
+ }
382
+ // Get role from user data
383
+ else if (user.role) {
384
+ userRole = user.role;
385
+ }
386
+ // Get role from userDetails
387
+ else if (allData.userDetails) {
388
+ const userDetails = allData.userDetails.user || allData.userDetails.data || allData.userDetails;
389
+ userRole = userDetails?.role || userDetails?.userRole || "";
390
+ }
391
+ // Get role from user
392
+ else if (allData.user) {
393
+ const userData = allData.user.user || allData.user.data || allData.user.currentUser || allData.user;
394
+ userRole = userData?.role || userData?.userRole || "";
395
+ }
396
+ // Normalize role for comparison (case-insensitive, handle spaces/underscores/hyphens)
397
+ const normalizedRole = userRole.toLowerCase().trim().replace(/[_\s-]+/g, " ");
398
+ const allowedRoles = [
399
+ "account owner",
400
+ "account admin",
401
+ "global admin"
402
+ ];
403
+ // Check for exact match or if role starts with allowed role
404
+ // This handles variations like "Account Owner", "ACCOUNT_OWNER", "Account-Owner", "Account Owner - Manager"
405
+ return allowedRoles.some(allowedRole => {
406
+ return normalizedRole === allowedRole || normalizedRole.startsWith(allowedRole + " ");
407
+ });
408
+ }, [user.role]);
409
+ const profileList = (_jsx(List, { className: "profile-menu-section", sx: {
410
+ width: "100%",
411
+ maxWidth: 360,
412
+ pointerEvents: "none",
413
+ }, component: "div", "aria-hidden": "true", tabIndex: -1, children: _jsxs(ListItem, { alignItems: "flex-start", className: "profile-menu-item", children: [_jsx(ListItemAvatar, { children: isOnlineStatus ? (_jsx(OnlineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.online, "aria-label": `${t.online} status badge`, "data-testid": "online-badge", children: _jsx(Avatar, { sx: { bgcolor: deepOrange[500] }, alt: user.name, title: user.name, src: user.avatar, children: getInitials() }) })) : (_jsx(OfflineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.offline, "aria-label": `${t.offline} status badge`, "data-testid": "offline-badge", children: _jsx(Avatar, { sx: { bgcolor: deepOrange[500] }, alt: user.name, title: user.name, src: user.avatar, children: getInitials() }) })) }), _jsx(ListItemText, { primary: _jsx(Typography, { className: "profile-name", component: "span", children: user.name || user.email }), secondary: _jsxs(React.Fragment, { children: [user.name && user.email ? (_jsx(Typography, { className: "profile-email", component: "p", children: user.email })) : null, _jsxs(Typography, { className: "profile-role", component: "p", children: [t.role, ": ", user.role] })] }) })] }) }));
414
+ const renderMenu = (_jsxs(Menu, { anchorEl: anchorEl, id: "account-menu", open: open, onClose: handleClose, onClick: handleClose, slotProps: {
415
+ paper: {
416
+ elevation: 0,
417
+ sx: {
418
+ overflow: "visible",
419
+ filter: "drop-shadow(0px 2px 8px var(--color-menu-shadow, rgba(0,0,0,0.32)))",
420
+ mt: 1.5,
421
+ "& .MuiAvatar-root": {
422
+ width: 32,
423
+ height: 32,
424
+ ml: -0.5,
425
+ mr: 1,
426
+ },
427
+ "&::before": {
428
+ content: '""',
429
+ display: "block",
430
+ position: "absolute",
431
+ top: 0,
432
+ right: 14,
433
+ width: 10,
434
+ height: 10,
435
+ bgcolor: "background.paper",
436
+ transform: "translateY(-50%) rotate(45deg)",
437
+ zIndex: 0,
438
+ },
439
+ },
440
+ },
441
+ }, transformOrigin: { horizontal: "right", vertical: "top" }, anchorOrigin: { horizontal: "right", vertical: "bottom" }, children: [profileList, _jsx(Divider, {}), _jsxs(MenuItem, { onClick: handleProfileClick, children: [_jsx(ListItemIcon, { children: _jsx(PersonOutlineOutlinedIcon, { fontSize: "small" }) }), t.profile] }), canViewSettings && (_jsxs(MenuItem, { onClick: handleSettingsClick, children: [_jsx(ListItemIcon, { children: _jsx(SettingsOutlinedIcon, { fontSize: "small" }) }), t.accountSettings] })), _jsxs(MenuItem, { onClick: handleSignOutClick, children: [_jsx(ListItemIcon, { children: _jsx(LogoutOutlinedIcon, { fontSize: "small" }) }), t.signOut] })] }));
442
+ const mobileMenuId = "primary-account-menu-mobile";
443
+ const renderMobileMenu = (_jsxs(Menu, { anchorEl: mobileMoreAnchorEl, anchorOrigin: {
444
+ vertical: "top",
445
+ horizontal: "right",
446
+ }, id: mobileMenuId, keepMounted: true, transformOrigin: {
447
+ vertical: "top",
448
+ horizontal: "right",
449
+ }, open: isMobileMenuOpen, onClose: handleMobileMenuClose, children: [messageCount !== undefined && (_jsxs(MenuItem, { children: [_jsx(IconButton, { size: "large", "aria-label": `show ${messageCount} new mails`, sx: { color: 'var(--color-brand-primary, #1e2f97)' }, children: _jsx(Badge, { badgeContent: messageCount, color: "error", children: _jsx(MailIcon, {}) }) }), _jsx("p", { children: t.messages })] })), notificationCountValue > 0 && (_jsxs(MenuItem, { children: [_jsx(IconButton, { size: "large", "aria-label": `show ${notificationCountValue} new notifications`, sx: { color: 'var(--color-brand-primary, #1e2f97)' }, children: _jsx(Badge, { badgeContent: notificationCount, color: "error", children: _jsx(NotificationsActiveOutlined, {}) }) }), _jsx("p", { children: t.notifications })] })), _jsxs(MenuItem, { onClick: handleProfileMenuOpen, children: [_jsx(IconButton, { size: "large", "aria-label": "account of current user", "aria-controls": "primary-account-menu", "aria-haspopup": "true", sx: { color: 'var(--color-brand-primary, #1e2f97)' }, children: isOnlineStatus ? (_jsx(OnlineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.online, "aria-label": `${t.online} status badge`, "data-testid": "online-badge", children: _jsx(AccountCircle, { titleAccess: user.name, "aria-label": "User avatar" }) })) : (_jsx(OfflineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.offline, "aria-label": `${t.offline} status badge`, "data-testid": "offline-badge", children: _jsx(AccountCircle, { titleAccess: user.name, "aria-label": "User avatar" }) })) }), _jsx("p", { children: t.profile })] })] }));
450
+ return (_jsxs(Box, { sx: { height: 'var(--size-header-height, 65px)', flexShrink: 0 }, className: "app-header", children: [_jsx(AppBar, { elevation: 0, sx: {
451
+ backgroundColor: "var(--color-header-bg, #ffffff)",
452
+ boxShadow: 'none !important',
453
+ position: 'fixed',
454
+ zIndex: 'var(--size-header-zindex, 200)',
455
+ height: 'var(--size-header-height, 65px)',
456
+ }, children: _jsxs(Toolbar, { sx: { minHeight: 'var(--size-header-height, 65px) !important', height: 'var(--size-header-height, 65px)' }, children: [_jsx(IconButton, { size: "large", "aria-label": "open drawer", sx: {
457
+ mr: '10px !important',
458
+ color: 'var(--color-brand-primary, #1e2f97)',
459
+ padding: 0,
460
+ '&:hover': {
461
+ backgroundColor: 'var(--color-brand-primary-hover-bg, rgba(30, 47, 151, 0.04))',
462
+ }
463
+ }, onClick: () => {
464
+ toggleDrawer();
465
+ }, children: isDrawerOpen ? (_jsx(MenuOpenIcon, { fontSize: "large" })) : (_jsx(MenuIcon, { fontSize: "large" })) }), _jsx(Box, { component: "div", sx: {
466
+ display: { xs: "none", sm: "flex" },
467
+ alignItems: 'center',
468
+ height: 'var(--size-header-height, 65px)',
469
+ }, "data-testid": "impactomics-logo-wrapper", children: _jsx(Box, { sx: {
470
+ width: '134px',
471
+ height: '23px',
472
+ backgroundImage: 'var(--image-logo-svg)',
473
+ backgroundSize: 'contain',
474
+ backgroundRepeat: 'no-repeat',
475
+ backgroundPosition: 'left center',
476
+ }, role: "img", "aria-label": "Impactomics" }) }), _jsx(Box, { sx: { flexGrow: 1 } }), _jsxs(Box, { sx: { display: { xs: "none", md: "flex" }, alignItems: "center", gap: "4px" }, children: [_jsx(LanguageSelector, { currentLanguage: currentLanguage, onLanguageChange: (lang) => {
477
+ setCurrentLanguage(lang);
478
+ // The LanguageSelector component handles URL and localStorage updates
479
+ } }), _jsx(IconButton, { size: "large", "aria-label": notificationCountValue > 0 ? `show ${notificationCountValue} new notifications` : "show notifications", sx: {
480
+ color: 'var(--color-brand-primary, #1e2f97)',
481
+ '&:hover': {
482
+ backgroundColor: 'var(--color-brand-primary-hover-bg, rgba(30, 47, 151, 0.04))',
483
+ }
484
+ }, children: _jsx(Badge, { badgeContent: notificationCountValue > 0 ? notificationCountValue : 0, color: "error", children: _jsx(NotificationsActiveOutlined, {}) }) }), _jsx(IconButton, { size: "large", edge: "end", "aria-label": "account of current user", "aria-controls": menuId, "aria-haspopup": "true", onClick: handleProfileMenuOpen, sx: {
485
+ color: 'var(--color-brand-primary, #1e2f97)',
486
+ '&:hover': {
487
+ backgroundColor: 'var(--color-brand-primary-hover-bg, rgba(30, 47, 151, 0.04))',
488
+ }
489
+ }, children: isOnlineStatus ? (_jsx(OnlineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.online, "aria-label": `${t.online} status badge`, "data-testid": "online-badge", children: _jsx(Avatar, { sx: {
490
+ bgcolor: deepOrange[500],
491
+ width: 20,
492
+ height: 20,
493
+ p: 1,
494
+ }, alt: user.name, title: user.name, src: user.avatar, sizes: "large", children: getInitials() }) })) : (_jsx(OfflineBadge, { overlap: "circular", anchorOrigin: { vertical: "bottom", horizontal: "right" }, variant: "dot", title: t.offline, "aria-label": `${t.offline} status badge`, "data-testid": "offline-badge", children: _jsx(Avatar, { sx: {
495
+ bgcolor: deepOrange[500],
496
+ width: 20,
497
+ height: 20,
498
+ p: 1,
499
+ }, alt: user.name, title: user.name, src: user.avatar, children: getInitials() }) })) })] }), _jsx(Box, { sx: { display: { xs: "flex", md: "none" } }, children: _jsx(IconButton, { size: "large", "aria-label": "show more", "aria-controls": mobileMenuId, "aria-haspopup": "true", onClick: handleMobileMenuOpen, sx: {
500
+ color: 'var(--color-brand-primary, #1e2f97)',
501
+ '&:hover': {
502
+ backgroundColor: 'var(--color-brand-primary-hover-bg, rgba(30, 47, 151, 0.04))',
503
+ }
504
+ }, children: _jsx(MoreIcon, {}) }) })] }) }), renderMobileMenu, renderMenu] }));
505
+ };
506
+ export default AppHeader;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ const DrawerContext = React.createContext({
4
+ isDrawerOpen: false,
5
+ openDrawer: () => { },
6
+ closeDrawer: () => { },
7
+ toggleDrawer: () => { },
8
+ });
9
+ export const DrawerProvider = ({ children }) => {
10
+ const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
11
+ const openDrawer = () => {
12
+ setIsDrawerOpen(true);
13
+ };
14
+ const closeDrawer = () => {
15
+ setIsDrawerOpen(false);
16
+ };
17
+ const toggleDrawer = () => {
18
+ setIsDrawerOpen((prev) => !prev);
19
+ };
20
+ return (_jsx(DrawerContext.Provider, { value: { isDrawerOpen, openDrawer, closeDrawer, toggleDrawer }, children: children }));
21
+ };
22
+ export const useDrawer = () => React.useContext(DrawerContext);
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Button, Menu, MenuItem, ListItemIcon, ListItemText, Box, } from "@mui/material";
4
+ import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
5
+ import { setI18nLocaleToStorage } from "./utils/localStorage";
6
+ import GBFlag from "./asset/flags/gb.png";
7
+ import ESFlag from "./asset/flags/es.png";
8
+ const languages = [
9
+ { code: "en", name: "English", flag: GBFlag },
10
+ { code: "es", name: "Spanish", flag: ESFlag },
11
+ ];
12
+ const LanguageSelector = ({ currentLanguage, onLanguageChange: _onLanguageChange, // reserved for parent callback; we reload the page on select
13
+ }) => {
14
+ const [anchorEl, setAnchorEl] = React.useState(null);
15
+ const open = Boolean(anchorEl);
16
+ const currentLang = languages.find((lang) => lang.code === currentLanguage) || languages[0];
17
+ const handleClick = (event) => {
18
+ setAnchorEl(event.currentTarget);
19
+ };
20
+ const handleClose = () => {
21
+ setAnchorEl(null);
22
+ };
23
+ const handleLanguageSelect = (languageCode) => {
24
+ // Update localStorage
25
+ setI18nLocaleToStorage(languageCode);
26
+ // Get current path
27
+ const currentPath = window.location.pathname;
28
+ const search = window.location.search;
29
+ const hash = window.location.hash;
30
+ // Find and replace existing locale /:locale/ with new locale
31
+ const newPath = currentPath.replace(/\/(en|es)\//, `/${languageCode}/`);
32
+ // Construct full URL and reload the page to apply translations
33
+ const fullUrl = newPath + search + hash;
34
+ window.location.href = fullUrl;
35
+ handleClose();
36
+ };
37
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { onClick: handleClick, sx: {
38
+ color: "var(--color-brand-primary, #1e2f97)",
39
+ textTransform: "none",
40
+ padding: "4px 8px",
41
+ minWidth: "auto",
42
+ display: "flex",
43
+ alignItems: "center",
44
+ gap: "4px",
45
+ "&:hover": {
46
+ backgroundColor: "var(--color-brand-primary-hover-bg, rgba(30, 47, 151, 0.04))",
47
+ },
48
+ }, "aria-label": "Select language", "aria-controls": open ? "language-menu" : undefined, "aria-haspopup": "true", "aria-expanded": open ? "true" : undefined, children: [_jsx(Box, { component: "img", src: currentLang.flag, alt: currentLang.name, sx: {
49
+ width: "20px",
50
+ height: "15px",
51
+ objectFit: "cover",
52
+ borderRadius: "2px",
53
+ } }), _jsx(Box, { component: "span", sx: {
54
+ fontSize: "14px",
55
+ fontWeight: 500,
56
+ marginLeft: "4px",
57
+ }, children: currentLang.code.toUpperCase() }), _jsx(ArrowDropDownIcon, { sx: {
58
+ fontSize: "20px",
59
+ marginLeft: "2px",
60
+ display: "flex",
61
+ alignSelf: "center",
62
+ } })] }), _jsx(Menu, { id: "language-menu", anchorEl: anchorEl, open: open, onClose: handleClose, anchorOrigin: {
63
+ vertical: "bottom",
64
+ horizontal: "right",
65
+ }, transformOrigin: {
66
+ vertical: "top",
67
+ horizontal: "right",
68
+ }, slotProps: {
69
+ paper: {
70
+ sx: {
71
+ mt: 1,
72
+ minWidth: 150,
73
+ },
74
+ },
75
+ }, children: languages.map((language) => (_jsxs(MenuItem, { onClick: () => handleLanguageSelect(language.code), selected: language.code === currentLanguage, children: [_jsx(ListItemIcon, { children: _jsx(Box, { component: "img", src: language.flag, alt: language.name, sx: {
76
+ width: "24px",
77
+ height: "18px",
78
+ objectFit: "cover",
79
+ borderRadius: "2px",
80
+ } }) }), _jsx(ListItemText, { primary: language.name })] }, language.code))) })] }));
81
+ };
82
+ export default LanguageSelector;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=autoInjectTenantFallbackCss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoInjectTenantFallbackCss.d.ts","sourceRoot":"","sources":["../src/autoInjectTenantFallbackCss.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ declare const tenantFallbackCss = "/* ================================================================\r\n TENANT MODULE CSS \u2014 varminer-app-header-npm-fe\r\n Auto-generated by generate-tenant-css.js\r\n Branch scanned: Development\r\n Covers: CSS var() references, SCSS $variables, and hardcoded\r\n brand values detected in component files.\r\n ================================================================ */\r\n\r\n\r\n\n/* \u00E2\u201D\u20AC\u00E2\u201D\u20AC FONT FACES: local dev fallback (generate-tenant-css) \u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC\u00E2\u201D\u20AC */\n/* @font-face rules for local dev fallback. In production the CDN\n tenant.css @font-face declarations (built by generate-tenant-all.js)\n load via injectTenantCss() and override these automatically.\n To change the local font path, update LOCAL_FONT_BASE_URL above. */\n@font-face {\n font-family: \"OpenSans-Light\";\n src: url(\"/asset/font/OpenSans-Light.ttf\") format(\"truetype\");\n font-weight: 300;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-LightItalic\";\n src: url(\"/asset/font/OpenSans-LightItalic.ttf\") format(\"truetype\");\n font-weight: 300;\n font-style: italic;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-Regular\";\n src: url(\"/asset/font/OpenSans-Regular.ttf\") format(\"truetype\");\n font-weight: 400;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-Italic\";\n src: url(\"/asset/font/OpenSans-Italic.ttf\") format(\"truetype\");\n font-weight: 400;\n font-style: italic;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-Medium\";\n src: url(\"/asset/font/OpenSans-Medium.ttf\") format(\"truetype\");\n font-weight: 500;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-MediumItalic\";\n src: url(\"/asset/font/OpenSans-MediumItalic.ttf\") format(\"truetype\");\n font-weight: 500;\n font-style: italic;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-SemiBold\";\n src: url(\"/asset/font/OpenSans-SemiBold.ttf\") format(\"truetype\");\n font-weight: 600;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-SemiBoldItalic\";\n src: url(\"/asset/font/OpenSans-SemiBoldItalic.ttf\") format(\"truetype\");\n font-weight: 600;\n font-style: italic;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-Bold\";\n src: url(\"/asset/font/OpenSans-Bold.ttf\") format(\"truetype\");\n font-weight: 700;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-BoldItalic\";\n src: url(\"/asset/font/OpenSans-BoldItalic.ttf\") format(\"truetype\");\n font-weight: 700;\n font-style: italic;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-ExtraBold\";\n src: url(\"/asset/font/OpenSans-ExtraBold.ttf\") format(\"truetype\");\n font-weight: 800;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: \"OpenSans-ExtraBoldItalic\";\n src: url(\"/asset/font/OpenSans-ExtraBoldItalic.ttf\") format(\"truetype\");\n font-weight: 800;\n font-style: italic;\n font-display: swap;\n}\n/* \u00E2\u201D\u20AC\u00E2\u201D\u20AC END FONT FACES \u00E2\u201D\u20AC\u00E2\u201D\u20AC */\n\n/* \u2500\u2500 FONT FACES: local dev fallback (generate-tenant-css) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\r\n/* @font-face rules for local dev fallback. In production the CDN\r\n tenant.css @font-face declarations (built by generate-tenant-all.js)\r\n load via injectTenantCss() and override these automatically.\r\n To change the local font path, update LOCAL_FONT_BASE_URL above. */\r\n@font-face {\r\n font-family: \"OpenSans-Light\";\r\n src: url(\"/asset/font/OpenSans-Light.ttf\") format(\"truetype\");\r\n font-weight: 300;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-LightItalic\";\r\n src: url(\"/asset/font/OpenSans-LightItalic.ttf\") format(\"truetype\");\r\n font-weight: 300;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-Regular\";\r\n src: url(\"/asset/font/OpenSans-Regular.ttf\") format(\"truetype\");\r\n font-weight: 400;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-Italic\";\r\n src: url(\"/asset/font/OpenSans-Italic.ttf\") format(\"truetype\");\r\n font-weight: 400;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-Medium\";\r\n src: url(\"/asset/font/OpenSans-Medium.ttf\") format(\"truetype\");\r\n font-weight: 500;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-MediumItalic\";\r\n src: url(\"/asset/font/OpenSans-MediumItalic.ttf\") format(\"truetype\");\r\n font-weight: 500;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-SemiBold\";\r\n src: url(\"/asset/font/OpenSans-SemiBold.ttf\") format(\"truetype\");\r\n font-weight: 600;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-SemiBoldItalic\";\r\n src: url(\"/asset/font/OpenSans-SemiBoldItalic.ttf\") format(\"truetype\");\r\n font-weight: 600;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-Bold\";\r\n src: url(\"/asset/font/OpenSans-Bold.ttf\") format(\"truetype\");\r\n font-weight: 700;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-BoldItalic\";\r\n src: url(\"/asset/font/OpenSans-BoldItalic.ttf\") format(\"truetype\");\r\n font-weight: 700;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-ExtraBold\";\r\n src: url(\"/asset/font/OpenSans-ExtraBold.ttf\") format(\"truetype\");\r\n font-weight: 800;\r\n font-style: normal;\r\n font-display: swap;\r\n}\r\n@font-face {\r\n font-family: \"OpenSans-ExtraBoldItalic\";\r\n src: url(\"/asset/font/OpenSans-ExtraBoldItalic.ttf\") format(\"truetype\");\r\n font-weight: 800;\r\n font-style: italic;\r\n font-display: swap;\r\n}\r\n/* \u2500\u2500 END FONT FACES \u2500\u2500 */\r\n\r\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n MODULE: app-header (varminer-app-header-npm-fe)\r\n CSS custom-properties consumed by this micro-frontend.\r\n These are default / fallback values overridden by tenant.css.\r\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\r\n:root {\r\n --color-border-header: #e5e9eb;\r\n --color-brand-accent: #1aa7ee;\r\n --color-brand-deep-navy: #0f1e75;\r\n --color-brand-primary: #1e2f97;\r\n --color-brand-primary-hover-bg: rgba(30, 47, 151, 0.04);\r\n --color-header-bg: #ffffff;\r\n --color-menu-bg-gradient: linear-gradient(180deg, #fff, #ddf7ff, #ddf7ff);\r\n --color-menu-bg-gradient-end: #ddf7ff;\r\n --color-menu-shadow: rgba(0,0,0,0.32);\r\n --color-menu-text: #000;\r\n --color-profile-email: rgba(0, 0, 0, 0.6);\r\n --color-profile-name: #1e2f97;\r\n --color-profile-role: rgba(0, 0, 0, 0.38);\r\n --color-status-offline: #808080;\r\n --color-status-online: #44b700;\r\n --font-brand-regular: OpenSans-Regular, Roboto, sans-serif;\r\n --font-brand-semibold: OpenSans-SemiBold, Roboto, sans-serif;\r\n --image-logo-svg: url(\"asset/logo.svg\");\r\n --size-header-height: 65px;\r\n --size-header-zindex: 200;\r\n --size-menu-width: 280px;\r\n\n /* \u00E2\u201D\u20AC\u00E2\u201D\u20AC Auto-discovered on 2026-03-17 \u00E2\u201D\u20AC\u00E2\u201D\u20AC */\n --color-white: #fff;\n --color-hex-aacccc: #acc;\n --color-selected-row: #ddf7ff;\n --color-black: #000;\n --color-text-secondary-alpha: rgba(0, 0, 0, 0.6);\n --color-text-muted-alpha: rgba(0, 0, 0, 0.38);\n --color-brand-primary-dark: #0b1659;\n --color-primary-dark: #111c56;\n --color-brand-navy-header: #13257b;\n --color-brand-accent-hover: #0a8bcc;\n --color-brand-switch: #657bfd;\n --color-brand-secondary: #3f8cff;\n --color-igv-gradient-start: #1e3c72;\n --color-igv-gradient-end: #2a5298;\n --color-bg-page: #f5f9ff;\n --color-bg-title-section: #e6f2ff;\n --color-bg-dialog: #f7fbff;\n --color-bg-panel: #e3f4fd;\n --color-bg-side-menu-hover: #e3f2fd;\n --color-bg-subtle: #f5f5f5;\n --color-bg-scrollbar-track: #f1f1f1;\n --Grey-50: #ebeff5;\n --color-border-blue: #b5e6ff;\n --color-border-default: #dadada;\n --color-border-sky-blue-300: #4fc4ff;\n --color-border-light: #e4e4e4;\n --color-border-chip: #bdbdbd;\n --color-text-heading: #27323d;\n --color-text-dark: #0f172a;\n --color-text-input: #0a0a0a;\n --color-text-muted-slate: #64748b;\n --Grey-600: #586d81;\n --Grey-400: #7c8fa3;\n --color-status-success: #4caf50;\n --color-status-success-dark: #2e7d32;\n --color-status-warning: #ef6c00;\n --color-status-error: #d32f2f;\n --color-scrollbar-thumb: #1976d2;\n --color-scrollbar-track: #e3edf7;\n --color-side-menu-active-bg: #1565c0;\n --color-column-rows-even: #edfbff;\n --color-bg-error-row: #fcefef;\n --color-bg-error-row-hover: #f9e0e0;\n --color-border-error-row: #e78e8e;\n --color-bg-warning-row: #fff5ed;\n --color-bg-warning-row-hover: #ffe8d6;\n --color-border-warning-row: #e8a87c;\n --color-bg-success-row: #eff5ef;\n --color-bg-success-row-hover: #e5ede5;\n --color-shadow-dark: #b3b3b3;\n --color-bg-success-toast: #edf7ed;\n --color-bg-error-toast: #ffebee;\n --color-text-success-toast: #1e4620;\n --color-text-error-toast: #c62828;\n --color-gene-type-uncertain: #5034ee;\n --color-gene-type-candidate: #ff9800;\n --color-gene-type-known: #2196f3;\n --color-border-disabled: #ccc;\n --color-scrollbar-handle: #888;\n --color-text-placeholder: #666;\n --color-scrollbar-handle-dark: #555;\n --color-text-dark-secondary: #333;\n --color-text-near-black: #222;\n --color-hex-e0e0e0: #e0e0e0;\n --color-hex-c0c0c0: #c0c0c0;\n --color-hex-f8f9fa: #f8f9fa;\n --color-brand-primary-active-bg: rgba(30, 47, 151, 0.08);\n --color-chip-background: rgba(30, 47, 151, 0.12);\n --color-border-brand-muted: rgba(30, 47, 151, 0.23);\n --color-accent-hover-subtle: rgba(26, 167, 238, 0.04);\n --color-accent-hover-bg: rgba(26, 167, 238, 0.08);\n --color-accent-selected: rgba(26, 167, 238, 0.12);\n --color-accent-selected-dark: rgba(26, 167, 238, 0.16);\n --color-accent-light: rgba(26, 167, 238, 0.34);\n --color-accent-border: rgba(26, 167, 238, 0.54);\n --color-shadow-dialog: rgba(8, 37, 68, 0.18);\n --color-shadow-card: rgba(10, 33, 62, 0.08);\n --color-shadow-card-hover: rgba(10, 33, 62, 0.16);\n --color-deep-navy-hover-bg: rgba(15, 30, 117, 0.08);\n --color-shadow-body: rgba(15, 23, 42, 0.08);\n --color-white-overlay: rgba(255, 255, 255, 0.1);\n --color-action-hover-bg: rgba(0, 0, 0, 0.04);\n --color-overlay-subtle: rgba(0, 0, 0, 0.10);\n --color-divider: rgba(0, 0, 0, 0.12);\n --color-overlay-medium: rgba(0, 0, 0, 0.14);\n --color-shadow-light: rgba(0, 0, 0, 0.15);\n --color-shadow-medium: rgba(0, 0, 0, 0.20);\n --color-border-outline: rgba(0, 0, 0, 0.26);\n --color-border-medium: rgba(0, 0, 0, 0.5);\n --color-icon-default: rgba(0, 0, 0, 0.54);\n --color-action-active: rgba(0, 0, 0, 0.56);\n --color-border-dark: rgba(0, 0, 0, 0.7);\n --color-text-primary-alpha: rgba(0, 0, 0, 0.87);\n\n /* \u00E2\u201D\u20AC\u00E2\u201D\u20AC Auto-discovered on 2026-03-17 \u00E2\u201D\u20AC\u00E2\u201D\u20AC */\n --color-header-accent: #aacccc;\n --color-error-pink: #dc004e;\n --color-text-disabled: #999999;\n --color-overlay-40: rgba(0, 0, 0, 0.4);\n --color-white-70: rgba(255, 255, 255, 0.7);\n --color-white-90: rgba(255, 255, 255, 0.9);\n --color-white-95: rgba(255, 255, 255, 0.95);\n --color-success-90: rgba(76, 175, 80, 0.9);\n --color-error-solid: rgba(211, 47, 47, 1);\n --color-error-50: rgba(211, 47, 47, 0.5);\n --color-rgba-000000: rgb(0,0,0);\n}\n";
2
+ export default tenantFallbackCss;
3
+ //# sourceMappingURL=generatedTenantFallbackCss.d.ts.map