varminer-app-header 2.6.2 → 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.
- package/dist/AppHeader.js +506 -0
- package/dist/DrawerContext.js +22 -0
- package/dist/LanguageSelector.js +82 -0
- package/dist/autoInjectTenantFallbackCss.d.ts +2 -0
- package/dist/autoInjectTenantFallbackCss.d.ts.map +1 -0
- package/dist/generatedTenantFallbackCss.d.ts +3 -0
- package/dist/generatedTenantFallbackCss.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +34 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/injectTenantFallbackCss.d.ts +2 -0
- package/dist/injectTenantFallbackCss.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/localStorage.js +571 -0
- package/dist/utils/translations.js +25 -0
- package/package.json +4 -3
|
@@ -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 @@
|
|
|
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
|