@the-portland-company/shell 0.32.2 → 0.34.0

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/index.cjs CHANGED
@@ -5,8 +5,6 @@ var jsxRuntime = require('react/jsx-runtime');
5
5
  var clsx2 = require('clsx');
6
6
  var reactRouterDom = require('react-router-dom');
7
7
  var react$1 = require('react');
8
- var fi = require('react-icons/fi');
9
- var react$2 = require('@emotion/react');
10
8
 
11
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
10
 
@@ -1265,432 +1263,6 @@ function BrandLogo({ src, alt = "Brand", className, style }) {
1265
1263
  }
1266
1264
  );
1267
1265
  }
1268
- var BrandLogo_default = BrandLogo;
1269
-
1270
- // src/lib/userDisplay.ts
1271
- function getUserDisplayName(user) {
1272
- if (!user) return "User";
1273
- return user.profile?.full_name || user.profile?.name || user.user_metadata?.full_name || user.user_metadata?.name || (typeof user.email === "string" ? user.email.split("@")[0] : "") || user.email || "User";
1274
- }
1275
-
1276
- // src/lib/resolveModeConfig.ts
1277
- function resolveModeConfig(mode) {
1278
- if (!mode) return null;
1279
- const modes = mode.modes ?? [];
1280
- if (typeof mode.getModeConfig === "function") {
1281
- try {
1282
- const result = mode.getModeConfig();
1283
- if (result) return result;
1284
- } catch {
1285
- }
1286
- }
1287
- return modes.find((m) => m.id === mode.currentMode) ?? modes[0] ?? null;
1288
- }
1289
-
1290
- // src/theme-preference.ts
1291
- var THEME_STORAGE_KEY = "politogy_theme";
1292
- var THEME_COOKIE = "politogy_theme";
1293
- var VALID = ["light", "dark", "system"];
1294
- function isThemePreference(v) {
1295
- return typeof v === "string" && VALID.includes(v);
1296
- }
1297
- function readThemeCookie() {
1298
- if (typeof document === "undefined") return null;
1299
- try {
1300
- const m = document.cookie.match(
1301
- new RegExp(`(?:^|;\\s*)${THEME_COOKIE}=([^;]+)`)
1302
- );
1303
- const v = m ? decodeURIComponent(m[1]) : null;
1304
- return isThemePreference(v) ? v : null;
1305
- } catch {
1306
- return null;
1307
- }
1308
- }
1309
- function writeThemeCookie(pref) {
1310
- if (typeof document === "undefined" || typeof window === "undefined") return;
1311
- const host = window.location.hostname;
1312
- const onParentDomain = /(^|\.)politogyvrm\.com$/i.test(host);
1313
- const domainAttr = onParentDomain ? "; Domain=.politogyvrm.com" : "";
1314
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
1315
- document.cookie = `${THEME_COOKIE}=${encodeURIComponent(
1316
- pref
1317
- )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
1318
- }
1319
- function resolveTheme(pref) {
1320
- if (pref === "system") {
1321
- try {
1322
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1323
- } catch {
1324
- return "light";
1325
- }
1326
- }
1327
- return pref;
1328
- }
1329
- function applyThemePreference(pref) {
1330
- if (typeof document === "undefined") return;
1331
- const resolved = resolveTheme(pref);
1332
- const d = document.documentElement;
1333
- d.classList.toggle("dark", resolved === "dark");
1334
- try {
1335
- d.style.colorScheme = resolved;
1336
- } catch {
1337
- }
1338
- }
1339
- `(function(){try{
1340
- var KEY=${JSON.stringify(THEME_STORAGE_KEY)};
1341
- var VALID=["light","dark","system"];
1342
- function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${THEME_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
1343
- var cookie=fromCookie();
1344
- var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
1345
- var pref=cookie||ls||"system";
1346
- if(VALID.indexOf(pref)===-1)pref="system";
1347
- if(cookie&&cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}
1348
- var resolved=pref;
1349
- if(pref==="system"){try{resolved=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";}catch(e){resolved="light";}}
1350
- var d=document.documentElement;
1351
- if(resolved==="dark"){d.classList.add("dark");}else{d.classList.remove("dark");}
1352
- try{d.style.colorScheme=resolved;}catch(e){}
1353
- }catch(e){}})();`;
1354
- function formatFirstLast(name) {
1355
- const parts = name.split(/\s+/).filter(Boolean);
1356
- if (parts.length <= 1) return name;
1357
- return `${parts[0]} ${parts[parts.length - 1][0].toUpperCase()}.`;
1358
- }
1359
- var THEME_OPTIONS = [
1360
- { value: "light", label: "Light" },
1361
- { value: "system", label: "System" },
1362
- { value: "dark", label: "Dark" }
1363
- ];
1364
- function AppearanceControl() {
1365
- const [value, setValue] = react$1.useState("system");
1366
- react$1.useEffect(() => {
1367
- const cookie = readThemeCookie();
1368
- let ls = null;
1369
- try {
1370
- ls = localStorage.getItem(THEME_STORAGE_KEY);
1371
- } catch {
1372
- }
1373
- setValue(cookie ?? (isThemePreference(ls) ? ls : null) ?? "system");
1374
- }, []);
1375
- const pick = (next) => {
1376
- setValue(next);
1377
- writeThemeCookie(next);
1378
- try {
1379
- localStorage.setItem(THEME_STORAGE_KEY, next);
1380
- } catch {
1381
- }
1382
- applyThemePreference(next);
1383
- try {
1384
- window.dispatchEvent(
1385
- new CustomEvent("politogy:theme-preference", { detail: next })
1386
- );
1387
- } catch {
1388
- }
1389
- };
1390
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: 3, py: 2, children: [
1391
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", fontWeight: "semibold", mb: 2, children: "APPEARANCE" }),
1392
- /* @__PURE__ */ jsxRuntime.jsx(
1393
- react.HStack,
1394
- {
1395
- spacing: 0,
1396
- borderWidth: "1px",
1397
- borderColor: "var(--line, #E2E8F0)",
1398
- borderRadius: "full",
1399
- overflow: "hidden",
1400
- children: THEME_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
1401
- react.Box,
1402
- {
1403
- as: "button",
1404
- type: "button",
1405
- role: "radio",
1406
- "aria-checked": value === opt.value,
1407
- onClick: () => pick(opt.value),
1408
- flex: 1,
1409
- px: 3,
1410
- py: 1.5,
1411
- fontSize: "xs",
1412
- fontWeight: "semibold",
1413
- bg: value === opt.value ? "var(--color-race-red, #E63946)" : "transparent",
1414
- color: value === opt.value ? "white" : "gray.500",
1415
- _hover: value === opt.value ? {} : { bg: "var(--panel, #F7FAFC)" },
1416
- transition: "background 0.15s",
1417
- children: opt.label
1418
- },
1419
- opt.value
1420
- ))
1421
- }
1422
- )
1423
- ] });
1424
- }
1425
- function avatarUrlFromUser(user) {
1426
- if (!user) return void 0;
1427
- const meta = user.raw.user_metadata;
1428
- return meta?.avatar_url ?? void 0;
1429
- }
1430
- function UserMenu() {
1431
- const { user, status, signOut } = useShellAuth();
1432
- const {
1433
- myProfileHref,
1434
- organizationSettingsHref,
1435
- showContinueOnboarding,
1436
- onContinueOnboarding,
1437
- userAvatarUrl
1438
- } = useShellChrome();
1439
- const linked = useShellLinkedAccounts();
1440
- const mode = useShellMode();
1441
- const org = useShellOrganization();
1442
- const role = useShellRole();
1443
- const navigate = reactRouterDom.useNavigate();
1444
- const menuListBg = "var(--topbar-bg, #FFFFFF)";
1445
- const menuListBorderColor = react.useColorModeValue("gray.200", "gray.700");
1446
- if (status !== "authenticated" || !user) return null;
1447
- const rawDisplayName = getUserDisplayName(user.raw);
1448
- const hasRealName = !!rawDisplayName && !rawDisplayName.includes("@");
1449
- const displayName = hasRealName ? formatFirstLast(rawDisplayName) : user.email ?? rawDisplayName;
1450
- const avatarUrl = userAvatarUrl ?? avatarUrlFromUser(user);
1451
- const modeConfig = resolveModeConfig(mode);
1452
- const currentOrg = org?.currentOrg ?? null;
1453
- const linkedAccounts = linked?.linkedAccounts ?? [];
1454
- const activeRoleConfig = role?.activeRoleConfig ?? null;
1455
- return /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { children: [
1456
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuButton, { py: 2, transition: "all 0.3s", _focus: { boxShadow: "none" }, children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { children: [
1457
- /* @__PURE__ */ jsxRuntime.jsx(
1458
- react.Avatar,
1459
- {
1460
- size: "sm",
1461
- src: avatarUrl,
1462
- name: displayName,
1463
- bg: modeConfig?.primaryColor || "brand.500"
1464
- }
1465
- ),
1466
- /* @__PURE__ */ jsxRuntime.jsxs(
1467
- react.VStack,
1468
- {
1469
- display: { base: "none", md: "flex" },
1470
- alignItems: "flex-start",
1471
- spacing: "1px",
1472
- ml: "2",
1473
- children: [
1474
- /* @__PURE__ */ jsxRuntime.jsx(react.HStack, { spacing: 2, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", children: displayName }) }),
1475
- hasRealName ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: user.email }) : null
1476
- ]
1477
- }
1478
- ),
1479
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: { base: "none", md: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}) })
1480
- ] }) }),
1481
- /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.MenuList, { bg: menuListBg, borderColor: menuListBorderColor, zIndex: 1500, children: [
1482
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiUser, {}), onClick: () => navigate(myProfileHref), children: "My Profile" }),
1483
- showContinueOnboarding && /* @__PURE__ */ jsxRuntime.jsx(
1484
- react.MenuItem,
1485
- {
1486
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiRotateCcw, {}),
1487
- onClick: () => {
1488
- if (onContinueOnboarding) onContinueOnboarding();
1489
- else window.dispatchEvent(new CustomEvent("app:continue-onboarding"));
1490
- },
1491
- children: "Continue Onboarding"
1492
- }
1493
- ),
1494
- organizationSettingsHref && /* @__PURE__ */ jsxRuntime.jsx(
1495
- react.MenuItem,
1496
- {
1497
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiBriefcase, {}),
1498
- onClick: () => navigate(organizationSettingsHref),
1499
- children: "Organization Settings"
1500
- }
1501
- ),
1502
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1503
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", px: 3, py: 1, fontWeight: "semibold", children: "ROLES & ORGANIZATIONS" }),
1504
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: 3, py: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 1, children: [
1505
- currentOrg && currentOrg.role && /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { children: [
1506
- /* @__PURE__ */ jsxRuntime.jsx(
1507
- react.Badge,
1508
- {
1509
- size: "sm",
1510
- colorScheme: currentOrg.role === "owner" ? "purple" : currentOrg.role === "admin" ? "blue" : "gray",
1511
- children: currentOrg.role.charAt(0).toUpperCase() + currentOrg.role.slice(1)
1512
- }
1513
- ),
1514
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "gray.500", children: [
1515
- "in ",
1516
- currentOrg.name
1517
- ] })
1518
- ] }),
1519
- activeRoleConfig && !currentOrg?.role && /* @__PURE__ */ jsxRuntime.jsx(react.Badge, { colorScheme: activeRoleConfig.color, size: "sm", children: activeRoleConfig.name })
1520
- ] }) }),
1521
- linked && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1522
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1523
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", px: 3, py: 1, fontWeight: "semibold", children: "ACCOUNTS" }),
1524
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: 3, py: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, children: [
1525
- /* @__PURE__ */ jsxRuntime.jsx(react.Avatar, { size: "xs", src: avatarUrl, name: displayName }),
1526
- /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "start", spacing: 0, flex: 1, children: [
1527
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", color: "brand.600", children: displayName }),
1528
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.400", children: "Active" })
1529
- ] })
1530
- ] }) }),
1531
- linkedAccounts.map((account) => /* @__PURE__ */ jsxRuntime.jsx(
1532
- react.MenuItem,
1533
- {
1534
- onClick: () => {
1535
- void linked.switchToAccount(account.id);
1536
- },
1537
- _hover: { bg: "gray.50" },
1538
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, flex: 1, justify: "space-between", children: [
1539
- /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, children: [
1540
- /* @__PURE__ */ jsxRuntime.jsx(
1541
- react.Avatar,
1542
- {
1543
- size: "xs",
1544
- src: account.avatarUrl ?? void 0,
1545
- name: account.displayName ?? account.email
1546
- }
1547
- ),
1548
- /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "start", spacing: 0, children: [
1549
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", noOfLines: 1, children: account.displayName ?? account.email.split("@")[0] }),
1550
- /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: account.email, placement: "top", hasArrow: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1551
- react.Text,
1552
- {
1553
- fontSize: "xs",
1554
- color: "gray.500",
1555
- maxW: "120px",
1556
- noOfLines: 1,
1557
- textOverflow: "ellipsis",
1558
- children: account.email
1559
- }
1560
- ) })
1561
- ] })
1562
- ] }),
1563
- /* @__PURE__ */ jsxRuntime.jsx(
1564
- react.IconButton,
1565
- {
1566
- as: "span",
1567
- role: "button",
1568
- tabIndex: 0,
1569
- "aria-label": "Remove account",
1570
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiTrash2, {}),
1571
- size: "xs",
1572
- variant: "ghost",
1573
- colorScheme: "red",
1574
- onClick: (e) => {
1575
- e.stopPropagation();
1576
- linked.onRemoveAccount(account.id);
1577
- }
1578
- }
1579
- )
1580
- ] })
1581
- },
1582
- account.id
1583
- )),
1584
- /* @__PURE__ */ jsxRuntime.jsx(
1585
- react.MenuItem,
1586
- {
1587
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiUserPlus, {}),
1588
- onClick: linked.onAddAccount,
1589
- color: "brand.500",
1590
- children: "Add Account"
1591
- }
1592
- )
1593
- ] }),
1594
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1595
- /* @__PURE__ */ jsxRuntime.jsx(AppearanceControl, {}),
1596
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1597
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiLogOut, {}), onClick: () => void signOut(), children: "Sign out" })
1598
- ] }) })
1599
- ] }) });
1600
- }
1601
- function Header({ onOpen, id }) {
1602
- const {
1603
- pageTitle,
1604
- breadcrumbs,
1605
- headerActions,
1606
- headerIconsSlot,
1607
- brandLogoSrc
1608
- } = useShellChrome();
1609
- const mobileNavBg = "var(--sidebar-bg, #FFFFFF)";
1610
- const mobileNavBorderColor = "var(--sidebar-border, #E2E8F0)";
1611
- return /* @__PURE__ */ jsxRuntime.jsx(
1612
- react.Flex,
1613
- {
1614
- id: id || "mobile-nav-container",
1615
- ml: { base: 0, md: 60 },
1616
- px: { base: 4, md: 4 },
1617
- height: "20",
1618
- alignItems: "center",
1619
- bg: mobileNavBg,
1620
- borderBottomWidth: "1px",
1621
- borderBottomColor: mobileNavBorderColor,
1622
- justifyContent: { base: "space-between", md: "space-between" },
1623
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { w: "full", mx: "auto", align: "center", justify: "space-between", children: [
1624
- /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 4, minW: "0", children: [
1625
- onOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1626
- react.IconButton,
1627
- {
1628
- display: { base: "flex", md: "none" },
1629
- onClick: onOpen,
1630
- variant: "outline",
1631
- "aria-label": "open menu",
1632
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiMenu, {})
1633
- }
1634
- ) : null,
1635
- brandLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: { base: "flex", md: "none" }, h: "32px", children: /* @__PURE__ */ jsxRuntime.jsx(BrandLogo_default, { src: brandLogoSrc, className: "h-full w-auto object-contain" }) }) : null,
1636
- /* @__PURE__ */ jsxRuntime.jsxs(
1637
- react.VStack,
1638
- {
1639
- align: "flex-start",
1640
- spacing: 0,
1641
- minW: "0",
1642
- display: { base: "none", md: "flex" },
1643
- children: [
1644
- pageTitle ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "lg", fontWeight: "bold", noOfLines: 1, children: pageTitle }) : null,
1645
- breadcrumbs && breadcrumbs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(react.HStack, { spacing: 2, color: "gray.500", fontSize: "sm", mt: "1px", minW: "0", children: breadcrumbs.map((b, idx) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", minW: "0", children: [
1646
- idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { color: "gray.400", mr: 2, children: " > " }) : null,
1647
- b.href ? /* @__PURE__ */ jsxRuntime.jsx(
1648
- react.Link,
1649
- {
1650
- as: reactRouterDom.NavLink,
1651
- to: b.href,
1652
- _hover: { textDecoration: "underline" },
1653
- noOfLines: 1,
1654
- children: b.label
1655
- }
1656
- ) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { noOfLines: 1, children: b.label })
1657
- ] }, `${b.label}-${idx}`)) }) : null
1658
- ]
1659
- }
1660
- )
1661
- ] }),
1662
- /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 0, align: "center", children: [
1663
- /* @__PURE__ */ jsxRuntime.jsxs(
1664
- react.HStack,
1665
- {
1666
- spacing: "var(--topbar-icon-gap, 8px)",
1667
- mr: "15px",
1668
- color: "var(--topbar-icon-color, #4A5568)",
1669
- sx: {
1670
- '& button:not([role="menuitem"]):not([data-menu-item])': {
1671
- width: "var(--topbar-icon-button-size, 32px)",
1672
- height: "var(--topbar-icon-button-size, 32px)",
1673
- minWidth: "var(--topbar-icon-button-size, 32px)",
1674
- padding: 0
1675
- },
1676
- '& button:not([role="menuitem"]):not([data-menu-item]) svg, & button:not([role="menuitem"]):not([data-menu-item]) img': {
1677
- width: "var(--topbar-icon-size, 20px)",
1678
- height: "var(--topbar-icon-size, 20px)",
1679
- strokeWidth: "var(--topbar-icon-stroke-width, 2)"
1680
- }
1681
- },
1682
- children: [
1683
- headerActions,
1684
- headerIconsSlot
1685
- ]
1686
- }
1687
- ),
1688
- /* @__PURE__ */ jsxRuntime.jsx(UserMenu, {})
1689
- ] })
1690
- ] })
1691
- }
1692
- );
1693
- }
1694
1266
  function BrandIcon({
1695
1267
  name,
1696
1268
  size,
@@ -1715,317 +1287,6 @@ function BrandIcon({
1715
1287
  }
1716
1288
  );
1717
1289
  }
1718
- var BrandIcon_default = BrandIcon;
1719
- var safeHref = (h) => h && /^(https?:|mailto:|tel:|\/|#|\.)/i.test(h.trim()) ? h : "#";
1720
- function NavItem({
1721
- icon,
1722
- brandIcon,
1723
- children,
1724
- href,
1725
- action,
1726
- subItems,
1727
- badge,
1728
- subLabel,
1729
- isSubItem = false,
1730
- isExpanded,
1731
- onToggle,
1732
- menuKey,
1733
- isDisabled: navIsDisabled,
1734
- disabled,
1735
- ...rest
1736
- }) {
1737
- const location = reactRouterDom.useLocation();
1738
- const navigate = reactRouterDom.useNavigate();
1739
- const mode = useShellMode();
1740
- const modeConfig = resolveModeConfig(mode);
1741
- const navPref = useShellNavigationPreference();
1742
- const showSubMenu = navPref?.showSubMenu ?? true;
1743
- const isDisabled = Boolean(navIsDisabled || disabled);
1744
- const effectiveHref = href || (Array.isArray(subItems) && subItems.length > 0 ? subItems[0].href : void 0);
1745
- const effectivePathname = effectiveHref?.split("?")[0];
1746
- const currentFullPath = location.pathname + location.search;
1747
- const currentSearchParams = react$1.useMemo(
1748
- () => new URLSearchParams(location.search),
1749
- [location.search]
1750
- );
1751
- const matchesSubItem = (itemHref) => {
1752
- if (!itemHref) return false;
1753
- if (itemHref === currentFullPath) return true;
1754
- const itemPath = itemHref.split("?")[0];
1755
- const itemSearch = itemHref.includes("?") ? itemHref.split("?")[1] : "";
1756
- if (itemPath === location.pathname && !itemSearch) {
1757
- return true;
1758
- }
1759
- if (location.pathname.startsWith(`${itemPath}/`)) {
1760
- const itemSegments = itemPath.split("/").filter(Boolean);
1761
- const locationSegments = location.pathname.split("/").filter(Boolean);
1762
- const itemLastSegment = itemSegments[itemSegments.length - 1];
1763
- const locationLastSegment = locationSegments[locationSegments.length - 1];
1764
- if (itemLastSegment && itemLastSegment === locationLastSegment) {
1765
- return true;
1766
- }
1767
- }
1768
- if (itemPath === location.pathname && itemSearch) {
1769
- const itemSearchParams = new URLSearchParams(itemSearch);
1770
- const itemTab = itemSearchParams.get("tab") || itemSearchParams.get("view");
1771
- if (!itemTab) return true;
1772
- return itemTab === currentSearchParams.get("tab");
1773
- }
1774
- if (itemPath && !itemHref.includes("?") && itemPath !== effectivePathname) {
1775
- const segment = itemPath.split("/").pop();
1776
- if (segment && location.pathname.endsWith("/" + segment) && location.pathname.startsWith(itemPath.split("/").slice(0, -1).join("/"))) {
1777
- return true;
1778
- }
1779
- }
1780
- return false;
1781
- };
1782
- const isActive = isSubItem ? matchesSubItem(effectiveHref || "") : (() => {
1783
- if (!effectivePathname) return false;
1784
- if (location.pathname === effectivePathname) return true;
1785
- if (location.pathname.startsWith(`${effectivePathname}/`)) return true;
1786
- const dashboardOverviewMatch = effectivePathname.match(
1787
- /^\/[^/]+\/[^/]*\/dashboard\/overview$/
1788
- );
1789
- if (dashboardOverviewMatch) {
1790
- const dashboardRoot = effectivePathname.replace(/\/overview$/, "");
1791
- return location.pathname === dashboardRoot || location.pathname.startsWith(`${dashboardRoot}/`) || location.pathname.startsWith(`${dashboardRoot}&`);
1792
- }
1793
- return effectivePathname !== `/${location.pathname.split("/")[1]}/dashboard` && location.pathname.startsWith(effectivePathname + "/");
1794
- })();
1795
- const hasSubItems = showSubMenu && Array.isArray(subItems) && subItems.length > 0;
1796
- const hasActiveChild = Array.isArray(subItems) && subItems.some((item) => matchesSubItem(item.href || ""));
1797
- const isParentActive = isActive || hasActiveChild;
1798
- const isSubMenuOpen = isExpanded !== void 0 ? isExpanded : hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1799
- const submenuBg = "var(--sidebar-submenu-bg, #F1F5F9)";
1800
- const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? submenuBg : "transparent";
1801
- const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "var(--sidebar-text, #1A202C)" : "var(--sidebar-text-muted, #4A5568)";
1802
- const disabledRowBg = isSubItem ? submenuBg : "transparent";
1803
- const disabledRowTextColor = "gray.400";
1804
- const renderIcon = (activeForHover) => {
1805
- if (brandIcon) {
1806
- return /* @__PURE__ */ jsxRuntime.jsx(
1807
- react.Box,
1808
- {
1809
- mr: "4",
1810
- display: "inline-flex",
1811
- alignItems: "center",
1812
- filter: isParentActive || isActive ? "brightness(0) invert(1)" : void 0,
1813
- _groupHover: isDisabled ? void 0 : {
1814
- filter: activeForHover ? "brightness(0) invert(1)" : void 0
1815
- },
1816
- children: /* @__PURE__ */ jsxRuntime.jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1817
- }
1818
- );
1819
- }
1820
- if (icon) {
1821
- return /* @__PURE__ */ jsxRuntime.jsx(
1822
- react.Box,
1823
- {
1824
- mr: "4",
1825
- fontSize: "16",
1826
- _groupHover: {
1827
- color: isDisabled ? disabledRowTextColor : activeForHover ? "white" : "var(--sidebar-hover-text, #FFFFFF)"
1828
- },
1829
- as: icon
1830
- }
1831
- );
1832
- }
1833
- return null;
1834
- };
1835
- if (hasSubItems) {
1836
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
1837
- effectiveHref ? /* @__PURE__ */ jsxRuntime.jsxs(
1838
- react.Flex,
1839
- {
1840
- align: "center",
1841
- px: "4",
1842
- py: "2.5",
1843
- mx: "4",
1844
- w: "auto",
1845
- borderTopRadius: "lg",
1846
- borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1847
- role: "group",
1848
- cursor: isDisabled ? "not-allowed" : "pointer",
1849
- opacity: 1,
1850
- pointerEvents: isDisabled ? "none" : "auto",
1851
- "data-nav-item-active": String(isParentActive),
1852
- bg: isDisabled ? disabledRowBg : expandedRowBg,
1853
- color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1854
- _hover: {
1855
- bg: isDisabled ? disabledRowBg : isParentActive ? modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #E2E8F0)",
1856
- color: isDisabled ? disabledRowTextColor : isParentActive ? "white" : "var(--sidebar-hover-text, #FFFFFF)"
1857
- },
1858
- "aria-disabled": isDisabled ? "true" : "false",
1859
- onClick: (e) => {
1860
- if (isDisabled) return;
1861
- e.preventDefault();
1862
- e.stopPropagation();
1863
- onToggle?.(menuKey || null);
1864
- },
1865
- ...rest,
1866
- children: [
1867
- renderIcon(isParentActive),
1868
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1", children }),
1869
- /* @__PURE__ */ jsxRuntime.jsx(
1870
- react.Box,
1871
- {
1872
- as: fi.FiChevronDown,
1873
- transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1874
- transition: "transform 0.2s"
1875
- }
1876
- )
1877
- ]
1878
- }
1879
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
1880
- react.Flex,
1881
- {
1882
- align: "center",
1883
- px: "4",
1884
- py: "2.5",
1885
- mx: "4",
1886
- w: "auto",
1887
- borderTopRadius: "lg",
1888
- borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1889
- role: "group",
1890
- cursor: isDisabled ? "not-allowed" : "pointer",
1891
- opacity: 1,
1892
- pointerEvents: isDisabled ? "none" : "auto",
1893
- "data-nav-item-active": String(isParentActive),
1894
- bg: isDisabled ? disabledRowBg : expandedRowBg,
1895
- color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1896
- _hover: {
1897
- bg: isDisabled ? disabledRowBg : hasActiveChild ? modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #E2E8F0)",
1898
- color: isDisabled ? disabledRowTextColor : hasActiveChild ? "white" : "var(--sidebar-hover-text, #FFFFFF)"
1899
- },
1900
- "aria-disabled": isDisabled ? "true" : "false",
1901
- onClick: (e) => {
1902
- if (isDisabled) return;
1903
- e.stopPropagation();
1904
- onToggle?.(menuKey || null);
1905
- },
1906
- ...rest,
1907
- children: [
1908
- renderIcon(hasActiveChild),
1909
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1", children }),
1910
- /* @__PURE__ */ jsxRuntime.jsx(
1911
- react.Box,
1912
- {
1913
- as: fi.FiChevronDown,
1914
- transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1915
- transition: "transform 0.2s"
1916
- }
1917
- )
1918
- ]
1919
- }
1920
- ),
1921
- /* @__PURE__ */ jsxRuntime.jsx(react.Collapse, { in: isSubMenuOpen, animateOpacity: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1922
- react.Box,
1923
- {
1924
- px: "0",
1925
- mx: "4",
1926
- w: "auto",
1927
- mb: "2",
1928
- py: "1",
1929
- borderTopRadius: 0,
1930
- borderBottomRadius: "md",
1931
- bg: submenuBg,
1932
- children: subItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1933
- NavItem,
1934
- {
1935
- icon: item.icon,
1936
- brandIcon: item.brandIcon,
1937
- href: item.href,
1938
- isSubItem: true,
1939
- isDisabled: item.disabled,
1940
- subLabel: item.subLabel,
1941
- subItems: item.subItems,
1942
- children: item.name
1943
- },
1944
- item.name
1945
- ))
1946
- }
1947
- ) })
1948
- ] });
1949
- }
1950
- const handleClick = (e) => {
1951
- if (isDisabled) {
1952
- e.preventDefault();
1953
- return;
1954
- }
1955
- if (action) {
1956
- e.preventDefault();
1957
- action();
1958
- return;
1959
- }
1960
- if (effectiveHref) {
1961
- e.preventDefault();
1962
- navigate(effectiveHref);
1963
- }
1964
- };
1965
- const rowActive = isSubItem ? isActive : isParentActive;
1966
- return /* @__PURE__ */ jsxRuntime.jsx(
1967
- react.Box,
1968
- {
1969
- as: "a",
1970
- href: safeHref(effectiveHref),
1971
- onClick: handleClick,
1972
- style: { textDecoration: "none" },
1973
- children: /* @__PURE__ */ jsxRuntime.jsxs(
1974
- react.Flex,
1975
- {
1976
- align: "center",
1977
- px: "4",
1978
- py: "2.5",
1979
- mx: isSubItem ? "0" : "4",
1980
- w: isSubItem ? "full" : "auto",
1981
- borderRadius: isSubItem ? "0" : "lg",
1982
- role: "group",
1983
- cursor: isDisabled ? "not-allowed" : "pointer",
1984
- opacity: 1,
1985
- pointerEvents: isDisabled ? "none" : "auto",
1986
- "data-nav-item-active": String(rowActive),
1987
- bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "var(--sidebar-hover-bg, #EDF2F7)" : modeConfig?.primaryColor || "brand.500" : "transparent",
1988
- color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1989
- _hover: {
1990
- bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "var(--sidebar-hover-bg, #E2E8F0)" : modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #EDF2F7)",
1991
- color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "var(--sidebar-hover-text, #FFFFFF)"
1992
- },
1993
- "aria-disabled": isDisabled ? "true" : "false",
1994
- onClick: handleClick,
1995
- ...rest,
1996
- children: [
1997
- brandIcon ? /* @__PURE__ */ jsxRuntime.jsx(
1998
- react.Box,
1999
- {
2000
- mr: "4",
2001
- display: "inline-flex",
2002
- alignItems: "center",
2003
- filter: rowActive && !isSubItem ? "brightness(0) invert(1)" : void 0,
2004
- children: /* @__PURE__ */ jsxRuntime.jsx(BrandIcon_default, { name: brandIcon, size: 18 })
2005
- }
2006
- ) : icon ? /* @__PURE__ */ jsxRuntime.jsx(
2007
- react.Box,
2008
- {
2009
- mr: "4",
2010
- fontSize: "16",
2011
- _groupHover: {
2012
- color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "var(--sidebar-hover-text, #FFFFFF)"
2013
- },
2014
- as: icon
2015
- }
2016
- ) : null,
2017
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { flex: "1", children: [
2018
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { children }),
2019
- subLabel ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { mt: 0.5, fontSize: "10px", lineHeight: "1.3", color: "gray.400", children: subLabel }) : null,
2020
- badge && /* @__PURE__ */ jsxRuntime.jsx(react.Badge, { colorScheme: "red", size: "xs", mt: 1, fontSize: "xs", display: "block", children: badge })
2021
- ] })
2022
- ]
2023
- }
2024
- )
2025
- }
2026
- );
2027
- }
2028
- var NavItem_default = NavItem;
2029
1290
 
2030
1291
  // src/lib/orgColors.ts
2031
1292
  var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
@@ -2073,1181 +1334,11 @@ function getOrganizationBadgeLetters(name) {
2073
1334
  const compact = parts[0] || normalizedName.replace(/\s+/g, "");
2074
1335
  return compact.slice(0, 2).toUpperCase() || "OR";
2075
1336
  }
2076
- function OrgBadge({ name, logoUrl, brandColor, size }) {
2077
- const normalizedColor = normalizeHexColor(brandColor);
2078
- const badgeColor = normalizedColor || "#5a5a5a";
2079
- const badgeLetters = getOrganizationBadgeLetters(name);
2080
- return /* @__PURE__ */ jsxRuntime.jsx(
2081
- react.Box,
2082
- {
2083
- boxSize: size,
2084
- borderRadius: "full",
2085
- overflow: "hidden",
2086
- border: "1px solid white",
2087
- flexShrink: 0,
2088
- bg: logoUrl ? "whiteAlpha.300" : badgeColor,
2089
- color: "white",
2090
- display: "inline-flex",
2091
- alignItems: "center",
2092
- justifyContent: "center",
2093
- fontSize: "xs",
2094
- fontWeight: "bold",
2095
- letterSpacing: "0.08em",
2096
- textTransform: "uppercase",
2097
- children: logoUrl ? /* @__PURE__ */ jsxRuntime.jsx(react.Image, { src: logoUrl, alt: `${name ?? "Organization"} logo`, boxSize: "full", objectFit: "cover" }) : badgeLetters
2098
- }
2099
- );
2100
- }
2101
- function OrganizationIdCopy({ orgId }) {
2102
- const { hasCopied, onCopy } = react.useClipboard(orgId);
2103
- const copiedAnimation = react$2.keyframes`
2104
- 0% { opacity: 0; transform: translateY(6px); }
2105
- 20% { opacity: 1; transform: translateY(0); }
2106
- 100% { opacity: 0; transform: translateY(-8px); }
2107
- `;
2108
- return /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: orgId, placement: "top", hasArrow: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
2109
- react.Flex,
2110
- {
2111
- align: "center",
2112
- cursor: "pointer",
2113
- onClick: onCopy,
2114
- _hover: { bg: "gray.100" },
2115
- borderRadius: "md",
2116
- px: 2,
2117
- py: 1,
2118
- position: "relative",
2119
- transition: "all 0.2s",
2120
- children: [
2121
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: hasCopied ? "green.500" : "gray.600", fontWeight: "normal", children: [
2122
- "...",
2123
- orgId.slice(-8)
2124
- ] }),
2125
- hasCopied && /* @__PURE__ */ jsxRuntime.jsx(
2126
- react.Box,
2127
- {
2128
- position: "absolute",
2129
- bottom: "100%",
2130
- mb: "6px",
2131
- px: "2",
2132
- py: "1",
2133
- fontSize: "xs",
2134
- bg: "gray.700",
2135
- color: "white",
2136
- borderRadius: "md",
2137
- animation: `${copiedAnimation} 0.9s ease-out`,
2138
- pointerEvents: "none",
2139
- children: "Copied!"
2140
- }
2141
- )
2142
- ]
2143
- }
2144
- ) });
2145
- }
2146
- function OrgPill() {
2147
- const org = useShellOrganization();
2148
- const { user } = useShellAuth();
2149
- const currentOrg = org?.currentOrg ?? null;
2150
- const organizations = org?.organizations ?? [];
2151
- const switchOrganization = org?.switchOrganization;
2152
- const loadingOrgs = org?.loadingOrgs ?? false;
2153
- const unselectedOrganizations = react$1.useMemo(
2154
- () => organizations.filter((o) => o.id !== currentOrg?.id),
2155
- [organizations, currentOrg?.id]
2156
- );
2157
- const selectedOrgBrandColor = react$1.useMemo(
2158
- () => currentOrg ? getOrganizationBrandColor(currentOrg) : null,
2159
- [currentOrg]
2160
- );
2161
- const selectedOrgColor = selectedOrgBrandColor || "var(--mode-primary-color, var(--color-race-red-500, #E63946))";
2162
- const selectedOrgMenuBg = selectedOrgBrandColor && lightenHexColor(selectedOrgBrandColor, 0.12) || "#4a4a4a";
2163
- if (!org) return null;
2164
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { mx: "4", mb: "3", children: [
2165
- /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { justify: "space-between", align: "center", mb: "2", children: [
2166
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", fontWeight: "semibold", children: "ORGANIZATION" }),
2167
- currentOrg?.id && currentOrg.id !== user?.id && /* @__PURE__ */ jsxRuntime.jsx(OrganizationIdCopy, { orgId: currentOrg.id })
2168
- ] }),
2169
- loadingOrgs && !currentOrg ? /* @__PURE__ */ jsxRuntime.jsx(react.Skeleton, { height: "32px", width: "full", borderRadius: "md" }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { gutter: 0, children: [
2170
- /* @__PURE__ */ jsxRuntime.jsx(
2171
- react.MenuButton,
2172
- {
2173
- as: react.Button,
2174
- rightIcon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}),
2175
- size: "sm",
2176
- width: "full",
2177
- bg: currentOrg?.id ? selectedOrgColor : "gray.100",
2178
- borderColor: currentOrg?.id ? selectedOrgColor : "gray.300",
2179
- color: currentOrg?.id ? "white" : "gray.700",
2180
- _hover: {
2181
- bg: currentOrg?.id ? selectedOrgColor : "gray.200",
2182
- color: currentOrg?.id ? "white" : "gray.900"
2183
- },
2184
- _active: {
2185
- bg: currentOrg?.id ? selectedOrgColor : "gray.200",
2186
- color: currentOrg?.id ? "white" : "gray.900"
2187
- },
2188
- _expanded: {
2189
- bg: currentOrg?.id ? selectedOrgColor : "gray.200",
2190
- color: currentOrg?.id ? "white" : "gray.900"
2191
- },
2192
- textAlign: "left",
2193
- justifyContent: "flex-start",
2194
- isDisabled: loadingOrgs && organizations.length === 0,
2195
- sx: currentOrg?.id ? {
2196
- color: "white",
2197
- ".chakra-button__icon": { color: "white" }
2198
- } : void 0,
2199
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: "2.5", minW: 0, align: "center", children: [
2200
- /* @__PURE__ */ jsxRuntime.jsx(
2201
- OrgBadge,
2202
- {
2203
- name: currentOrg?.name,
2204
- logoUrl: currentOrg?.logoUrl,
2205
- brandColor: selectedOrgBrandColor,
2206
- size: "20px"
2207
- }
2208
- ),
2209
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: currentOrg?.name || "No organizations" })
2210
- ] })
2211
- }
2212
- ),
2213
- /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2214
- react.MenuList,
2215
- {
2216
- bg: currentOrg?.id ? selectedOrgMenuBg : "gray.100",
2217
- borderColor: currentOrg?.id ? selectedOrgColor : "gray.300",
2218
- color: currentOrg?.id ? "white" : "gray.800",
2219
- mt: "1",
2220
- minW: "13rem",
2221
- maxH: "60vh",
2222
- overflowY: "auto",
2223
- zIndex: "modal",
2224
- boxShadow: "2xl",
2225
- children: [
2226
- unselectedOrganizations.map((o) => {
2227
- const orgBrandColor = getOrganizationBrandColor(o);
2228
- return /* @__PURE__ */ jsxRuntime.jsx(
2229
- react.MenuItem,
2230
- {
2231
- onClick: () => switchOrganization?.(o.id),
2232
- bg: "transparent",
2233
- color: currentOrg?.id ? "white" : "inherit",
2234
- _hover: {
2235
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.2) || "#555555" : "gray.100",
2236
- color: currentOrg?.id ? "white" : "gray.900"
2237
- },
2238
- _focus: {
2239
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.2) || "#555555" : "gray.100",
2240
- color: currentOrg?.id ? "white" : "gray.900"
2241
- },
2242
- _active: {
2243
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.24) || "#5d5d5d" : "gray.200",
2244
- color: currentOrg?.id ? "white" : "gray.900"
2245
- },
2246
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", width: "100%", overflow: "hidden", children: [
2247
- /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { minW: 0, flex: "1", spacing: "2.5", children: [
2248
- /* @__PURE__ */ jsxRuntime.jsx(
2249
- OrgBadge,
2250
- {
2251
- name: o.name,
2252
- logoUrl: o.logoUrl,
2253
- brandColor: orgBrandColor,
2254
- size: "24px"
2255
- }
2256
- ),
2257
- /* @__PURE__ */ jsxRuntime.jsx(
2258
- react.Text,
2259
- {
2260
- overflow: "hidden",
2261
- textOverflow: "ellipsis",
2262
- whiteSpace: "nowrap",
2263
- flex: "1",
2264
- children: o.name
2265
- }
2266
- )
2267
- ] }),
2268
- /* @__PURE__ */ jsxRuntime.jsx(
2269
- react.Text,
2270
- {
2271
- fontSize: "xs",
2272
- color: currentOrg?.id ? "whiteAlpha.700" : "gray.500",
2273
- ml: 2,
2274
- flexShrink: 0,
2275
- children: o.id.slice(-8)
2276
- }
2277
- )
2278
- ] })
2279
- },
2280
- o.id
2281
- );
2282
- }),
2283
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, { my: 0, borderColor: currentOrg?.id ? "whiteAlpha.300" : void 0 }),
2284
- /* @__PURE__ */ jsxRuntime.jsx(
2285
- react.MenuItem,
2286
- {
2287
- onClick: () => window.dispatchEvent(new CustomEvent("open-create-org")),
2288
- fontWeight: "bold",
2289
- bg: "transparent",
2290
- color: currentOrg?.id ? "white" : "inherit",
2291
- my: 0,
2292
- _hover: {
2293
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.2) || "#555555" : "gray.100",
2294
- color: currentOrg?.id ? "white" : "gray.900"
2295
- },
2296
- _focus: {
2297
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.2) || "#555555" : "gray.100",
2298
- color: currentOrg?.id ? "white" : "gray.900"
2299
- },
2300
- _active: {
2301
- bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#E63946", 0.24) || "#5d5d5d" : "gray.200",
2302
- color: currentOrg?.id ? "white" : "gray.900"
2303
- },
2304
- fontSize: "sm",
2305
- children: "+ New Organization"
2306
- }
2307
- )
2308
- ]
2309
- }
2310
- ) })
2311
- ] })
2312
- ] });
2313
- }
2314
- var OrgPill_default = OrgPill;
2315
- function ModePill() {
2316
- const mode = useShellMode();
2317
- if (!mode) return null;
2318
- const { currentMode, modes, switchMode } = mode;
2319
- const modeConfig = resolveModeConfig(mode);
2320
- if (!modeConfig) return null;
2321
- const hasMultipleModes = modes.length > 1;
2322
- const isDisabled = !hasMultipleModes;
2323
- return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mx: "4", mb: "4", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { gutter: 0, children: [
2324
- /* @__PURE__ */ jsxRuntime.jsx(
2325
- react.MenuButton,
2326
- {
2327
- as: react.Button,
2328
- leftIcon: modeConfig.icon,
2329
- rightIcon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}),
2330
- size: "sm",
2331
- width: "full",
2332
- bg: modeConfig.primaryColor,
2333
- borderColor: modeConfig.primaryColor,
2334
- color: modeConfig.white,
2335
- _disabled: {
2336
- bg: modeConfig.primaryColor,
2337
- borderColor: modeConfig.primaryColor,
2338
- color: modeConfig.white,
2339
- cursor: "not-allowed",
2340
- opacity: 1
2341
- },
2342
- isDisabled,
2343
- _hover: {
2344
- bg: modeConfig.primaryColor,
2345
- color: modeConfig.white
2346
- },
2347
- _active: {
2348
- bg: modeConfig.primaryColor,
2349
- color: modeConfig.white
2350
- },
2351
- children: modeConfig.name
2352
- }
2353
- ),
2354
- hasMultipleModes && /* @__PURE__ */ jsxRuntime.jsx(react.MenuList, { children: modes.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
2355
- react.MenuItem,
2356
- {
2357
- onClick: () => switchMode(m.id),
2358
- bg: "transparent",
2359
- color: currentMode === m.id ? m.primaryColor : "inherit",
2360
- _hover: { bg: m.bgColor, color: m.primaryColor },
2361
- icon: m.icon,
2362
- children: m.name
2363
- },
2364
- m.id
2365
- )) })
2366
- ] }) });
2367
- }
2368
- var ModePill_default = ModePill;
2369
- function ActivityIcon() {
2370
- return /* @__PURE__ */ jsxRuntime.jsx(
2371
- "svg",
2372
- {
2373
- width: "12",
2374
- height: "12",
2375
- viewBox: "0 0 24 24",
2376
- fill: "none",
2377
- stroke: "currentColor",
2378
- strokeWidth: "2",
2379
- strokeLinecap: "round",
2380
- strokeLinejoin: "round",
2381
- "aria-hidden": "true",
2382
- children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" })
2383
- }
2384
- );
2385
- }
2386
- function defaultEnvChip(label) {
2387
- if (!label) return null;
2388
- const lower = label.toLowerCase();
2389
- if (lower.includes("prod")) {
2390
- return { color: "#ef4444", bg: "#fee2e2", border: "#fecaca", name: label };
2391
- }
2392
- if (lower.includes("stag")) {
2393
- return { color: "#f97316", bg: "#ffedd5", border: "#fed7aa", name: label };
2394
- }
2395
- if (lower.includes("local") || lower.includes("dev")) {
2396
- return { color: "#22c55e", bg: "#dcfce7", border: "#bbf7d0", name: label };
2397
- }
2398
- return { color: "#6b7280", bg: "#f3f4f6", border: "#e5e7eb", name: label };
2399
- }
2400
- var PLACEHOLDER_SHA_VALUES = /* @__PURE__ */ new Set(["unknown", "n/a", "null", "undefined", ""]);
2401
- function isMeaningfulCommit(value) {
2402
- if (typeof value !== "string") return false;
2403
- const trimmed = value.trim();
2404
- if (!trimmed) return false;
2405
- return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2406
- }
2407
- function isMeaningfulAuthor(value) {
2408
- if (typeof value !== "string") return false;
2409
- const trimmed = value.trim();
2410
- if (!trimmed) return false;
2411
- return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2412
- }
2413
- function getFirstName(value) {
2414
- if (!value) return "";
2415
- const stripped = value.replace(/<[^>]+>/g, "").trim();
2416
- if (!stripped) return "";
2417
- const [firstToken] = stripped.split(/\s+/);
2418
- return firstToken || stripped;
2419
- }
2420
- function CommitCopyButton({
2421
- envNode,
2422
- environmentLabel,
2423
- commit,
2424
- version = "",
2425
- commitDate = "",
2426
- commitAuthor = "",
2427
- commitLog = "",
2428
- onDiagnosticsClick
2429
- }) {
2430
- const safeCommit = isMeaningfulCommit(commit) ? commit.trim() : "";
2431
- const { hasCopied, onCopy } = react.useClipboard(safeCommit);
2432
- const shortCommit = safeCommit ? safeCommit.slice(-4) : "";
2433
- const copiedAnimation = react$2.keyframes`
2434
- 0% { opacity: 0; transform: translateY(6px); }
2435
- 20% { opacity: 1; transform: translateY(0); }
2436
- 100% { opacity: 0; transform: translateY(-8px); }
2437
- `;
2438
- const formattedDate = react$1.useMemo(() => {
2439
- if (!commitDate) return "";
2440
- const parsed = new Date(commitDate);
2441
- if (Number.isNaN(parsed.getTime())) return commitDate;
2442
- return parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2443
- }, [commitDate]);
2444
- const metaLine = react$1.useMemo(() => {
2445
- const firstName = isMeaningfulAuthor(commitAuthor) ? getFirstName(commitAuthor) : "";
2446
- return [formattedDate, firstName].filter(Boolean).join(" | ");
2447
- }, [formattedDate, commitAuthor]);
2448
- const normalizedCommitLog = react$1.useMemo(() => {
2449
- if (commitLog?.trim()) {
2450
- return commitLog.replace(/\r\n/g, "\n").replace(/\\n/g, "\n");
2451
- }
2452
- return "";
2453
- }, [commitLog]);
2454
- const tooltipContent = normalizedCommitLog.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2455
- react.Box,
2456
- {
2457
- maxW: "600px",
2458
- width: "min(600px, 90vw)",
2459
- maxH: "80vh",
2460
- whiteSpace: "pre-wrap",
2461
- overflowY: "auto",
2462
- overflowX: "auto",
2463
- fontSize: "xs",
2464
- fontFamily: "mono",
2465
- wordBreak: "break-word",
2466
- children: normalizedCommitLog
2467
- }
2468
- ) : void 0;
2469
- const envChip = react$1.useMemo(() => {
2470
- if (envNode) return envNode;
2471
- const chip = defaultEnvChip(environmentLabel);
2472
- if (!chip) return null;
2473
- return /* @__PURE__ */ jsxRuntime.jsx(
2474
- react.Box,
2475
- {
2476
- as: "span",
2477
- display: "inline-flex",
2478
- alignItems: "center",
2479
- px: "1.5",
2480
- py: "0.5",
2481
- borderWidth: "1px",
2482
- borderColor: chip.border,
2483
- bg: chip.bg,
2484
- color: chip.color,
2485
- borderRadius: "md",
2486
- fontSize: "2xs",
2487
- fontWeight: "600",
2488
- lineHeight: "1",
2489
- textTransform: "capitalize",
2490
- "aria-label": `Environment: ${chip.name}`,
2491
- children: chip.name
2492
- }
2493
- );
2494
- }, [envNode, environmentLabel]);
2495
- const showEnv = Boolean(envChip);
2496
- return /* @__PURE__ */ jsxRuntime.jsxs(
2497
- react.VStack,
2498
- {
2499
- justify: "center",
2500
- align: "center",
2501
- borderRadius: "md",
2502
- py: 0.5,
2503
- px: 1,
2504
- position: "relative",
2505
- spacing: 0,
2506
- textAlign: "center",
2507
- w: "full",
2508
- children: [
2509
- /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { justify: "center", align: "center", gap: 2, whiteSpace: "nowrap", children: [
2510
- showEnv ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2511
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(
2512
- react.Tooltip,
2513
- {
2514
- label: tooltipContent,
2515
- isDisabled: !tooltipContent,
2516
- hasArrow: true,
2517
- placement: "top-start",
2518
- openDelay: 150,
2519
- closeOnClick: false,
2520
- shouldWrapChildren: true,
2521
- modifiers: [
2522
- { name: "preventOverflow", options: { padding: 16 } },
2523
- { name: "offset", options: { offset: [0, 12] } }
2524
- ],
2525
- children: /* @__PURE__ */ jsxRuntime.jsx(
2526
- react.Button,
2527
- {
2528
- variant: "unstyled",
2529
- minW: "auto",
2530
- h: "auto",
2531
- p: 0,
2532
- lineHeight: "1",
2533
- color: "inherit",
2534
- onClick: onCopy,
2535
- "aria-label": "Copy commit ID",
2536
- isDisabled: !safeCommit,
2537
- children: envChip
2538
- }
2539
- )
2540
- }
2541
- ) }),
2542
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "var(--sidebar-text-muted, #CBD5E0)", flexShrink: 0, children: "|" })
2543
- ] }) : null,
2544
- shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: hasCopied ? "green.500" : "var(--sidebar-text-muted, #718096)", flexShrink: 0, children: shortCommit }) : null,
2545
- version ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2546
- shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "var(--sidebar-text-muted, #CBD5E0)", flexShrink: 0, children: "|" }) : null,
2547
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "2xs", color: "var(--sidebar-text-muted, #718096)", flexShrink: 0, children: [
2548
- "v",
2549
- version
2550
- ] })
2551
- ] }) : null,
2552
- onDiagnosticsClick ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2553
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "var(--sidebar-text-muted, #CBD5E0)", flexShrink: 0, children: "|" }),
2554
- /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: "Diagnostics", hasArrow: true, placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(
2555
- react.Button,
2556
- {
2557
- variant: "unstyled",
2558
- minW: "auto",
2559
- h: "auto",
2560
- p: 0,
2561
- lineHeight: "1",
2562
- color: "gray.400",
2563
- _hover: { color: "gray.600" },
2564
- onClick: (e) => {
2565
- e.stopPropagation();
2566
- onDiagnosticsClick();
2567
- },
2568
- "aria-label": "Diagnostics",
2569
- children: /* @__PURE__ */ jsxRuntime.jsx(react.Icon, { as: ActivityIcon, boxSize: 3 })
2570
- }
2571
- ) })
2572
- ] }) : null
2573
- ] }),
2574
- metaLine ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "var(--sidebar-text-muted, #718096)", textAlign: "center", whiteSpace: "nowrap", children: metaLine }) : null,
2575
- hasCopied && /* @__PURE__ */ jsxRuntime.jsx(
2576
- react.Box,
2577
- {
2578
- position: "absolute",
2579
- bottom: "100%",
2580
- mb: "6px",
2581
- px: "2",
2582
- py: "1",
2583
- fontSize: "xs",
2584
- bg: "gray.700",
2585
- color: "white",
2586
- borderRadius: "md",
2587
- animation: `${copiedAnimation} 0.9s ease-out`,
2588
- pointerEvents: "none",
2589
- children: "Copied"
2590
- }
2591
- )
2592
- ]
2593
- }
2594
- );
2595
- }
2596
- function Sidebar({ onClose, id }) {
2597
- const { brandLogoSrc } = useShellChrome();
2598
- const navItems = useShellNavItems()?.items ?? [];
2599
- const footer = useShellFooter();
2600
- const org = useShellOrganization();
2601
- const currentOrg = org?.currentOrg ?? null;
2602
- const location = reactRouterDom.useLocation();
2603
- const sidebarBg = "var(--sidebar-bg, #FFFFFF)";
2604
- const sidebarBorderColor = "var(--sidebar-border, #E2E8F0)";
2605
- const [expandedMenu, setExpandedMenu] = react$1.useState(null);
2606
- react$1.useEffect(() => {
2607
- const activeMenu = navItems.find(
2608
- (item) => item.children && item.children.some((child) => {
2609
- if (!child.href) return false;
2610
- const childPath = child.href.split("?")[0];
2611
- if (!childPath) return false;
2612
- return location.pathname === childPath || location.pathname.startsWith(`${childPath}/`);
2613
- })
2614
- )?.name || null;
2615
- setExpandedMenu(activeMenu);
2616
- }, [location.pathname, navItems]);
2617
- const handleMenuToggle = (menuKey) => {
2618
- if (!menuKey) {
2619
- setExpandedMenu(null);
2620
- return;
2621
- }
2622
- setExpandedMenu((prev) => prev === menuKey ? null : menuKey);
2623
- };
2624
- return /* @__PURE__ */ jsxRuntime.jsxs(
2625
- react.Flex,
2626
- {
2627
- id: id || "sidebar-container",
2628
- transition: "3s ease",
2629
- bg: sidebarBg,
2630
- borderRight: "1px",
2631
- borderRightColor: sidebarBorderColor,
2632
- w: { base: "full", md: "var(--sidebar-width, 240px)" },
2633
- pos: "fixed",
2634
- h: "100vh",
2635
- direction: "column",
2636
- overflow: "hidden",
2637
- children: [
2638
- /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { alignItems: "center", mx: "8", mt: "3", mb: "2", justifyContent: "space-between", flexShrink: 0, children: [
2639
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { h: "40px", children: brandLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx(BrandLogo_default, { src: brandLogoSrc, className: "h-full w-auto object-contain" }) : null }),
2640
- onClose ? /* @__PURE__ */ jsxRuntime.jsx(
2641
- react.IconButton,
2642
- {
2643
- display: { base: "flex", md: "none" },
2644
- onClick: onClose,
2645
- variant: "outline",
2646
- "aria-label": "close menu",
2647
- icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiX, {})
2648
- }
2649
- ) : null
2650
- ] }),
2651
- /* @__PURE__ */ jsxRuntime.jsxs(
2652
- react.Box,
2653
- {
2654
- flex: "1",
2655
- overflowY: "auto",
2656
- css: {
2657
- "&::-webkit-scrollbar": { width: "8px" },
2658
- "&::-webkit-scrollbar-track": { background: "transparent" },
2659
- "&::-webkit-scrollbar-thumb": { background: "#CBD5E0", borderRadius: "4px" },
2660
- "&::-webkit-scrollbar-thumb:hover": { background: "#A0AEC0" }
2661
- },
2662
- children: [
2663
- currentOrg?.logoUrl && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mx: "4", mb: "3", children: /* @__PURE__ */ jsxRuntime.jsx(
2664
- react.Image,
2665
- {
2666
- src: currentOrg.logoUrl,
2667
- alt: `${currentOrg.name} logo`,
2668
- maxH: "70px",
2669
- w: "full",
2670
- objectFit: "contain",
2671
- borderRadius: "md"
2672
- }
2673
- ) }),
2674
- /* @__PURE__ */ jsxRuntime.jsx(OrgPill_default, {}),
2675
- /* @__PURE__ */ jsxRuntime.jsx(ModePill_default, {}),
2676
- navItems.map((link) => /* @__PURE__ */ jsxRuntime.jsx(
2677
- NavItem_default,
2678
- {
2679
- icon: link.icon,
2680
- brandIcon: link.brandIcon,
2681
- href: link.href,
2682
- subItems: link.children,
2683
- badge: link.badge,
2684
- subLabel: link.subLabel,
2685
- action: link.action,
2686
- menuKey: String(link.name),
2687
- isExpanded: expandedMenu === link.name,
2688
- onToggle: handleMenuToggle,
2689
- isDisabled: Boolean(link.disabled),
2690
- children: link.name
2691
- },
2692
- link.name
2693
- ))
2694
- ]
2695
- }
2696
- ),
2697
- footer && /* @__PURE__ */ jsxRuntime.jsx(
2698
- react.Box,
2699
- {
2700
- px: "1",
2701
- py: "1",
2702
- borderTop: "1px",
2703
- borderColor: "var(--sidebar-border, #E2E8F0)",
2704
- flexShrink: 0,
2705
- bg: sidebarBg,
2706
- zIndex: 1,
2707
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 2, px: "3", py: "2", textAlign: "center", children: [
2708
- /* @__PURE__ */ jsxRuntime.jsx(react.HStack, { spacing: 1, fontSize: "xs", color: "gray.600", wrap: "wrap", justify: "center", w: "full", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", children: [
2709
- idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { color: "var(--sidebar-text-muted, #929599)", mx: 1, children: "|" }) : null,
2710
- l.external ? /* @__PURE__ */ jsxRuntime.jsx(
2711
- react.Link,
2712
- {
2713
- href: l.href,
2714
- isExternal: true,
2715
- color: "var(--sidebar-text-muted, #4A5568)",
2716
- _hover: {
2717
- color: "var(--sidebar-hover-text, #FFFFFF)",
2718
- textDecoration: "underline"
2719
- },
2720
- children: l.label
2721
- }
2722
- ) : /* @__PURE__ */ jsxRuntime.jsx(
2723
- react.Link,
2724
- {
2725
- as: reactRouterDom.NavLink,
2726
- to: l.href,
2727
- color: "var(--sidebar-text-muted, #4A5568)",
2728
- _hover: {
2729
- color: "var(--sidebar-hover-text, #FFFFFF)",
2730
- textDecoration: "underline"
2731
- },
2732
- children: l.label
2733
- }
2734
- )
2735
- ] }, `${l.label}-${idx}`)) }),
2736
- (footer.version || footer.commit || footer.commitFull || footer.environmentLabel || footer.envNode) && /* @__PURE__ */ jsxRuntime.jsx(
2737
- CommitCopyButton,
2738
- {
2739
- envNode: footer.envNode,
2740
- environmentLabel: footer.environmentLabel,
2741
- commit: footer.commitFull ?? footer.commit,
2742
- version: footer.version,
2743
- commitDate: footer.commitDate,
2744
- commitAuthor: footer.commitAuthor,
2745
- commitLog: footer.commitLog,
2746
- onDiagnosticsClick: footer.onDiagnosticsClick
2747
- }
2748
- )
2749
- ] })
2750
- }
2751
- )
2752
- ]
2753
- }
2754
- );
2755
- }
2756
- function Footer({ buildInfo }) {
2757
- return /* @__PURE__ */ jsxRuntime.jsx(
2758
- react.Box,
2759
- {
2760
- as: "footer",
2761
- px: 4,
2762
- py: 2,
2763
- borderTop: "1px solid",
2764
- borderColor: "gray.200",
2765
- fontSize: "xs",
2766
- color: "gray.500",
2767
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { children: [
2768
- "politogy ",
2769
- buildInfo ? `\xB7 ${buildInfo}` : ""
2770
- ] })
2771
- }
2772
- );
2773
- }
2774
- function AppLayout({
2775
- children,
2776
- buildInfo,
2777
- modalsSlot,
2778
- notificationsSlot,
2779
- overlaySlot
2780
- }) {
2781
- const [mobileOpen, setMobileOpen] = react$1.useState(false);
2782
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", minH: "100vh", children: [
2783
- /* @__PURE__ */ jsxRuntime.jsx(
2784
- Sidebar,
2785
- {
2786
- onClose: mobileOpen ? () => setMobileOpen(false) : void 0
2787
- }
2788
- ),
2789
- /* @__PURE__ */ jsxRuntime.jsx(Header, { onOpen: () => setMobileOpen(true) }),
2790
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "main", flex: "1", ml: { base: 0, md: 60 }, position: "relative", overflow: "auto", children: [
2791
- children,
2792
- notificationsSlot
2793
- ] }),
2794
- buildInfo ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { buildInfo }) : null,
2795
- modalsSlot,
2796
- overlaySlot
2797
- ] });
2798
- }
2799
- var GLYPHS = {
2800
- // Communications Fuel reads as a LEVEL, so its glyph is a battery. `bolt`/
2801
- // `zap` stay mapped for older Brand values.
2802
- "battery-charging": fi.FiBatteryCharging,
2803
- battery: fi.FiBattery,
2804
- fuel: fi.FiBatteryCharging,
2805
- bolt: fi.FiZap,
2806
- zap: fi.FiZap,
2807
- grid: fi.FiGrid,
2808
- apps: fi.FiGrid,
2809
- bell: fi.FiBell,
2810
- notification: fi.FiBell,
2811
- "alert-triangle": fi.FiAlertTriangle,
2812
- alert: fi.FiAlertTriangle,
2813
- "alert-circle": fi.FiAlertCircle,
2814
- "life-buoy": fi.FiLifeBuoy,
2815
- "life-ring": fi.FiLifeBuoy,
2816
- support: fi.FiLifeBuoy,
2817
- "help-circle": fi.FiHelpCircle,
2818
- moon: fi.FiMoon,
2819
- sun: fi.FiSun,
2820
- info: fi.FiInfo,
2821
- mail: fi.FiMail,
2822
- "message-square": fi.FiMessageSquare,
2823
- settings: fi.FiSettings,
2824
- user: fi.FiUser
2825
- };
2826
- function resolveBrandGlyph(name) {
2827
- if (!name) return null;
2828
- return GLYPHS[name.trim().toLowerCase()] ?? null;
2829
- }
2830
- function useTopBarIconSlotGlyph(slot) {
2831
- const [name, setName] = react$1.useState(null);
2832
- react$1.useEffect(() => {
2833
- if (typeof window === "undefined") return;
2834
- const read = () => {
2835
- const raw = window.getComputedStyle(document.documentElement).getPropertyValue(`--topbar-icon-${slot}`).trim().replace(/^['"]|['"]$/g, "");
2836
- setName(raw.length > 0 ? raw : null);
2837
- };
2838
- read();
2839
- const observer = new MutationObserver(read);
2840
- observer.observe(document.head, { childList: true, subtree: true });
2841
- return () => observer.disconnect();
2842
- }, [slot]);
2843
- return name;
2844
- }
2845
- function TopBarBadge({ count }) {
2846
- if (!count || count <= 0) return null;
2847
- return /* @__PURE__ */ jsxRuntime.jsx(
2848
- react.Badge,
2849
- {
2850
- position: "absolute",
2851
- top: "-2px",
2852
- right: "-2px",
2853
- bg: "var(--topbar-badge-bg, #E63946)",
2854
- color: "var(--topbar-badge-fg, #FFFFFF)",
2855
- rounded: "full",
2856
- fontSize: "0.625rem",
2857
- fontWeight: "bold",
2858
- lineHeight: "1",
2859
- minW: "1rem",
2860
- h: "1rem",
2861
- px: "1",
2862
- display: "flex",
2863
- alignItems: "center",
2864
- justifyContent: "center",
2865
- pointerEvents: "none",
2866
- "aria-hidden": true,
2867
- children: count > 9 ? "9+" : count
2868
- }
2869
- );
2870
- }
2871
- var TopBarIcon = react$1.forwardRef(
2872
- function TopBarIcon2({ slot, fallbackIcon, isSelected, badgeCount, children, ...rest }, ref) {
2873
- const glyphName = useTopBarIconSlotGlyph(slot);
2874
- const Glyph = resolveBrandGlyph(glyphName);
2875
- const icon = Glyph ? /* @__PURE__ */ jsxRuntime.jsx(Glyph, {}) : fallbackIcon;
2876
- const button = /* @__PURE__ */ jsxRuntime.jsx(
2877
- react.IconButton,
2878
- {
2879
- ref,
2880
- icon,
2881
- variant: "ghost",
2882
- minW: "var(--topbar-icon-button-size, 32px)",
2883
- w: "var(--topbar-icon-button-size, 32px)",
2884
- h: "var(--topbar-icon-button-size, 32px)",
2885
- p: "0",
2886
- bg: isSelected ? "var(--topbar-icon-selected-bg, transparent)" : "transparent",
2887
- color: isSelected ? "var(--topbar-icon-selected-color, var(--topbar-icon-color, #64748B))" : "var(--topbar-icon-color, #64748B)",
2888
- _hover: {
2889
- bg: "var(--topbar-icon-hover-bg, transparent)",
2890
- color: "var(--topbar-icon-hover-color, var(--topbar-icon-color, #64748B))"
2891
- },
2892
- sx: {
2893
- // Normalize the glyph itself, not just its box. Covers react-icons
2894
- // <svg> and brand <img> alike, and pins stroke weight so one glyph
2895
- // can't read heavier than its neighbours.
2896
- "& svg, & img": {
2897
- width: "var(--topbar-icon-size, 20px)",
2898
- height: "var(--topbar-icon-size, 20px)",
2899
- strokeWidth: "var(--topbar-icon-stroke-width, 2)"
2900
- }
2901
- },
2902
- ...rest
2903
- }
2904
- );
2905
- if (!badgeCount && !children) return button;
2906
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { position: "relative", display: "inline-flex", children: [
2907
- button,
2908
- /* @__PURE__ */ jsxRuntime.jsx(TopBarBadge, { count: badgeCount ?? 0 }),
2909
- children
2910
- ] });
2911
- }
2912
- );
2913
- var SUPPORT_REQUEST_SUCCESS_MESSAGE = "Your support request was sent to Politogy leadership.";
2914
- function splitName(value) {
2915
- const trimmed = String(value ?? "").trim();
2916
- if (!trimmed) return { firstName: "", lastName: "" };
2917
- const [first, ...rest] = trimmed.split(/\s+/);
2918
- return { firstName: first ?? "", lastName: rest.join(" ") };
2919
- }
2920
- function resolveSupportProfile(user) {
2921
- if (!user) {
2922
- return { firstName: "", lastName: "", email: "", phone: "", missingEmail: true, missingPhone: true };
2923
- }
2924
- const meta = user.raw.user_metadata ?? {};
2925
- const resolvedName = String(meta.full_name ?? meta.name ?? "").trim() || String(user.email ?? "").split("@")[0] || "";
2926
- const { firstName, lastName } = splitName(resolvedName);
2927
- const email = String(user.email ?? "").trim();
2928
- const phone = String(meta.phone ?? "").trim();
2929
- return {
2930
- firstName,
2931
- lastName,
2932
- email,
2933
- phone,
2934
- missingEmail: email.length === 0,
2935
- missingPhone: phone.length === 0
2936
- };
2937
- }
2938
- function formatTimestamp(date) {
2939
- return date.toLocaleString("en-US", {
2940
- year: "numeric",
2941
- month: "short",
2942
- day: "numeric",
2943
- hour: "numeric",
2944
- minute: "2-digit",
2945
- second: "2-digit",
2946
- hour12: true
2947
- });
2948
- }
2949
- function buildSupportRequestMessage(input) {
2950
- const submittedAt = input.submittedAt ?? /* @__PURE__ */ new Date();
2951
- const iso = submittedAt.toISOString();
2952
- const display = formatTimestamp(submittedAt);
2953
- const trimmedSearch = String(input.search ?? "").trim();
2954
- return [
2955
- "A user requested support.",
2956
- "",
2957
- `First Name: ${input.firstName.trim() || "Unknown"}`,
2958
- `Last Name: ${input.lastName.trim() || "Unknown"}`,
2959
- `Organization: ${(input.organizationName ?? "").trim() || "Unknown"}`,
2960
- `Email: ${input.email.trim()}`,
2961
- `Phone: ${input.phone.trim()}`,
2962
- `Submitted At (ISO): ${iso}`,
2963
- `Submitted At (Local): ${display}`,
2964
- `Page Path: ${input.pathname.trim() || "/"}`,
2965
- `Page Title: ${(input.pageTitle ?? "").trim() || "Unknown"}`,
2966
- `Query String: ${trimmedSearch || "None"}`
2967
- ].join("\n");
2968
- }
2969
- function SupportRequestButton() {
2970
- const support = useShellSupport();
2971
- const { user } = useShellAuth();
2972
- const org = useShellOrganization();
2973
- const { isOpen, onOpen, onClose } = react.useDisclosure();
2974
- const toast = react.useToast();
2975
- const profile = react$1.useMemo(() => resolveSupportProfile(user), [user]);
2976
- const [email, setEmail] = react$1.useState("");
2977
- const [phone, setPhone] = react$1.useState("");
2978
- const [message, setMessage] = react$1.useState("");
2979
- const [isSubmitting, setIsSubmitting] = react$1.useState(false);
2980
- react$1.useEffect(() => {
2981
- if (!isOpen) return;
2982
- setEmail(profile.email);
2983
- setPhone(profile.phone);
2984
- setMessage("");
2985
- setIsSubmitting(false);
2986
- }, [isOpen, profile.email, profile.phone]);
2987
- const handleClose = react$1.useCallback(() => {
2988
- if (isSubmitting) return;
2989
- onClose();
2990
- }, [isSubmitting, onClose]);
2991
- const handleSubmit = react$1.useCallback(async () => {
2992
- if (!support) return;
2993
- const resolvedEmail = (profile.missingEmail ? email : profile.email).trim();
2994
- const resolvedPhone = (profile.missingPhone ? phone : profile.phone).trim();
2995
- if (!resolvedEmail) {
2996
- toast({ title: "Email required", status: "error", duration: 4e3, isClosable: true });
2997
- return;
2998
- }
2999
- if (!resolvedPhone) {
3000
- toast({ title: "Phone required", status: "error", duration: 4e3, isClosable: true });
3001
- return;
3002
- }
3003
- if (!user?.id) {
3004
- toast({
3005
- title: "Unable to send support request",
3006
- description: "You must be signed in to request support.",
3007
- status: "error",
3008
- duration: 4e3,
3009
- isClosable: true
3010
- });
3011
- return;
3012
- }
3013
- const context = {
3014
- pathname: typeof window !== "undefined" ? window.location.pathname : "/",
3015
- search: typeof window !== "undefined" ? window.location.search : "",
3016
- pageTitle: typeof document !== "undefined" ? document.title : void 0,
3017
- organizationName: org?.currentOrg?.name ?? void 0
3018
- };
3019
- const formatted = buildSupportRequestMessage({
3020
- firstName: profile.firstName,
3021
- lastName: profile.lastName,
3022
- organizationName: context.organizationName,
3023
- email: resolvedEmail,
3024
- phone: resolvedPhone,
3025
- pathname: context.pathname,
3026
- search: context.search,
3027
- pageTitle: context.pageTitle
3028
- });
3029
- try {
3030
- setIsSubmitting(true);
3031
- await support.submitSupportRequest({
3032
- email: resolvedEmail,
3033
- phone: resolvedPhone,
3034
- message: message.trim() ? `${message.trim()}
3035
-
3036
- ---
3037
1337
 
3038
- ${formatted}` : formatted,
3039
- context
3040
- });
3041
- toast({
3042
- title: "Support request sent",
3043
- description: SUPPORT_REQUEST_SUCCESS_MESSAGE,
3044
- status: "success",
3045
- duration: 5e3,
3046
- isClosable: true
3047
- });
3048
- onClose();
3049
- } catch (err) {
3050
- const description = err instanceof Error ? err.message : "Unable to send your support request.";
3051
- toast({
3052
- title: "Failed to request support",
3053
- description,
3054
- status: "error",
3055
- duration: 5e3,
3056
- isClosable: true
3057
- });
3058
- } finally {
3059
- setIsSubmitting(false);
3060
- }
3061
- }, [support, profile, email, phone, message, toast, user?.id, onClose, org?.currentOrg?.name]);
3062
- if (!support) return null;
3063
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3064
- /* @__PURE__ */ jsxRuntime.jsx(
3065
- TopBarIcon,
3066
- {
3067
- slot: "support",
3068
- "aria-label": "Request support",
3069
- fallbackIcon: /* @__PURE__ */ jsxRuntime.jsx(LifebuoyIcon, {}),
3070
- onClick: onOpen
3071
- }
3072
- ),
3073
- /* @__PURE__ */ jsxRuntime.jsxs(react.Modal, { isOpen, onClose: handleClose, isCentered: true, children: [
3074
- /* @__PURE__ */ jsxRuntime.jsx(react.ModalOverlay, {}),
3075
- /* @__PURE__ */ jsxRuntime.jsxs(react.ModalContent, { children: [
3076
- /* @__PURE__ */ jsxRuntime.jsx(react.ModalHeader, { children: "Support" }),
3077
- /* @__PURE__ */ jsxRuntime.jsx(react.ModalCloseButton, { isDisabled: isSubmitting }),
3078
- /* @__PURE__ */ jsxRuntime.jsx(react.ModalBody, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 4, children: [
3079
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "gray.600", children: "A message will be texted and emailed with your contact information and support will reply as soon as possible. If you are a beta customer that is typically within minutes." }),
3080
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { rounded: "md", borderWidth: "1px", borderColor: "gray.200", bg: "gray.50", p: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 1, children: [
3081
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "sm", children: [
3082
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Name:" }),
3083
- " ",
3084
- [profile.firstName, profile.lastName].filter(Boolean).join(" ") || "Unknown"
3085
- ] }),
3086
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "sm", children: [
3087
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Organization:" }),
3088
- " ",
3089
- org?.currentOrg?.name ?? "Unknown"
3090
- ] }),
3091
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "sm", children: [
3092
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Email:" }),
3093
- " ",
3094
- (profile.missingEmail ? email : profile.email) || "Missing"
3095
- ] }),
3096
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "sm", children: [
3097
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Phone:" }),
3098
- " ",
3099
- (profile.missingPhone ? phone : profile.phone) || "Missing"
3100
- ] })
3101
- ] }) }),
3102
- profile.missingEmail && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
3103
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Email" }),
3104
- /* @__PURE__ */ jsxRuntime.jsx(
3105
- "input",
3106
- {
3107
- type: "email",
3108
- "aria-label": "Email",
3109
- value: email,
3110
- onChange: (e) => setEmail(e.target.value),
3111
- placeholder: "name@example.com",
3112
- style: {
3113
- width: "100%",
3114
- padding: "8px 12px",
3115
- border: "1px solid #e2e8f0",
3116
- borderRadius: "6px",
3117
- fontSize: "14px"
3118
- }
3119
- }
3120
- )
3121
- ] }),
3122
- profile.missingPhone && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
3123
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Phone" }),
3124
- /* @__PURE__ */ jsxRuntime.jsx(
3125
- "input",
3126
- {
3127
- "aria-label": "Phone",
3128
- value: phone,
3129
- onChange: (e) => setPhone(e.target.value),
3130
- placeholder: "(503) 555-1212",
3131
- style: {
3132
- width: "100%",
3133
- padding: "8px 12px",
3134
- border: "1px solid #e2e8f0",
3135
- borderRadius: "6px",
3136
- fontSize: "14px"
3137
- }
3138
- }
3139
- )
3140
- ] }),
3141
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
3142
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Optional message" }),
3143
- /* @__PURE__ */ jsxRuntime.jsx(
3144
- react.Textarea,
3145
- {
3146
- value: message,
3147
- onChange: (e) => setMessage(e.target.value),
3148
- placeholder: "What can we help you with?",
3149
- rows: 3
3150
- }
3151
- )
3152
- ] })
3153
- ] }) }),
3154
- /* @__PURE__ */ jsxRuntime.jsxs(react.ModalFooter, { children: [
3155
- /* @__PURE__ */ jsxRuntime.jsx(react.Button, { variant: "ghost", mr: 3, onClick: handleClose, isDisabled: isSubmitting, children: "Cancel" }),
3156
- /* @__PURE__ */ jsxRuntime.jsx(
3157
- react.Button,
3158
- {
3159
- colorScheme: "red",
3160
- onClick: () => void handleSubmit(),
3161
- isLoading: isSubmitting,
3162
- loadingText: "Sending",
3163
- children: "Request Support"
3164
- }
3165
- )
3166
- ] })
3167
- ] })
3168
- ] })
3169
- ] });
3170
- }
3171
- function LifebuoyIcon() {
3172
- return /* @__PURE__ */ jsxRuntime.jsxs(
3173
- "svg",
3174
- {
3175
- width: "18",
3176
- height: "18",
3177
- viewBox: "0 0 24 24",
3178
- fill: "none",
3179
- stroke: "currentColor",
3180
- strokeWidth: "2",
3181
- strokeLinecap: "round",
3182
- strokeLinejoin: "round",
3183
- "aria-hidden": "true",
3184
- children: [
3185
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
3186
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "4" }),
3187
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "4.93", x2: "9.17", y2: "9.17" }),
3188
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "14.83", x2: "19.07", y2: "19.07" }),
3189
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "9.17", x2: "19.07", y2: "4.93" }),
3190
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "9.17", x2: "18.36", y2: "5.64" }),
3191
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "19.07", x2: "9.17", y2: "14.83" })
3192
- ]
3193
- }
3194
- );
3195
- }
3196
- function ShellChrome({
3197
- children,
3198
- organization,
3199
- linkedAccounts,
3200
- support,
3201
- devNotesMenu,
3202
- myProfileHref,
3203
- mode,
3204
- navItems,
3205
- navigationPreference,
3206
- role,
3207
- footer,
3208
- brandLogoSrc,
3209
- headerIconsSlot,
3210
- organizationSettingsHref,
3211
- showContinueOnboarding,
3212
- onContinueOnboarding,
3213
- pageTitle,
3214
- breadcrumbs,
3215
- headerActions,
3216
- userAvatarUrl
3217
- }) {
3218
- const chrome = react$1.useMemo(
3219
- () => ({
3220
- devNotesMenu: devNotesMenu ?? null,
3221
- myProfileHref: myProfileHref ?? "/my-profile",
3222
- brandLogoSrc,
3223
- headerIconsSlot,
3224
- organizationSettingsHref,
3225
- showContinueOnboarding,
3226
- onContinueOnboarding,
3227
- pageTitle,
3228
- breadcrumbs,
3229
- headerActions,
3230
- userAvatarUrl
3231
- }),
3232
- [
3233
- devNotesMenu,
3234
- myProfileHref,
3235
- brandLogoSrc,
3236
- headerIconsSlot,
3237
- organizationSettingsHref,
3238
- showContinueOnboarding,
3239
- onContinueOnboarding,
3240
- pageTitle,
3241
- breadcrumbs,
3242
- headerActions,
3243
- userAvatarUrl
3244
- ]
3245
- );
3246
- const navItemsValue = react$1.useMemo(
3247
- () => navItems ? { items: navItems } : null,
3248
- [navItems]
3249
- );
3250
- return /* @__PURE__ */ jsxRuntime.jsx(ShellOrgContext.Provider, { value: organization ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellLinkedAccountsContext.Provider, { value: linkedAccounts ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellSupportProviderContext.Provider, { value: support ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellModeContext.Provider, { value: mode ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellNavItemsContext.Provider, { value: navItemsValue, children: /* @__PURE__ */ jsxRuntime.jsx(ShellNavigationPreferenceContext.Provider, { value: navigationPreference ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellRoleContext.Provider, { value: role ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellFooterContext.Provider, { value: footer ?? null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellChromeContext.Provider, { value: chrome, children }) }) }) }) }) }) }) }) });
1338
+ // src/lib/userDisplay.ts
1339
+ function getUserDisplayName(user) {
1340
+ if (!user) return "User";
1341
+ return user.profile?.full_name || user.profile?.name || user.user_metadata?.full_name || user.user_metadata?.name || (typeof user.email === "string" ? user.email.split("@")[0] : "") || user.email || "User";
3251
1342
  }
3252
1343
 
3253
1344
  // src/sw/registerShellPrecache.ts
@@ -3280,23 +1371,14 @@ function buildSpeculationRules(currentApp, registry) {
3280
1371
  return JSON.stringify(payload);
3281
1372
  }
3282
1373
 
3283
- exports.AppLayout = AppLayout;
3284
1374
  exports.BrandIcon = BrandIcon;
3285
1375
  exports.BrandLogo = BrandLogo;
3286
1376
  exports.BreadcrumbBar = BreadcrumbBar;
3287
1377
  exports.Card = Card;
3288
- exports.CommitCopyButton = CommitCopyButton;
3289
- exports.Footer = Footer;
3290
1378
  exports.GlobalUiIdProvider = GlobalUiIdProvider;
3291
- exports.Header = Header;
3292
1379
  exports.HeaderTabBar = HeaderTabBar;
3293
- exports.ModePill = ModePill;
3294
- exports.NavItem = NavItem;
3295
- exports.OrgPill = OrgPill;
3296
- exports.OrgSelector = OrgPill;
3297
1380
  exports.PageContainer = PageContainer;
3298
1381
  exports.PageHeader = PageHeader;
3299
- exports.ShellChrome = ShellChrome;
3300
1382
  exports.ShellChromeContext = ShellChromeContext;
3301
1383
  exports.ShellFooterContext = ShellFooterContext;
3302
1384
  exports.ShellLinkedAccountsContext = ShellLinkedAccountsContext;
@@ -3307,11 +1389,6 @@ exports.ShellOrgContext = ShellOrgContext;
3307
1389
  exports.ShellProvider = ShellProvider;
3308
1390
  exports.ShellRoleContext = ShellRoleContext;
3309
1391
  exports.ShellSupportProviderContext = ShellSupportProviderContext;
3310
- exports.Sidebar = Sidebar;
3311
- exports.SupportRequestButton = SupportRequestButton;
3312
- exports.TopBarBadge = TopBarBadge;
3313
- exports.TopBarIcon = TopBarIcon;
3314
- exports.UserMenu = UserMenu;
3315
1392
  exports.buildSpeculationRules = buildSpeculationRules;
3316
1393
  exports.getOrganizationBadgeLetters = getOrganizationBadgeLetters;
3317
1394
  exports.getOrganizationBrandColor = getOrganizationBrandColor;