@the-portland-company/shell 0.2.1 → 0.3.1

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,6 +5,8 @@ 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');
8
10
 
9
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
12
 
@@ -1124,6 +1126,11 @@ var ShellMetaContext = react$1.createContext(null);
1124
1126
  var ShellOrgContext = react$1.createContext(null);
1125
1127
  var ShellLinkedAccountsContext = react$1.createContext(null);
1126
1128
  var ShellSupportProviderContext = react$1.createContext(null);
1129
+ var ShellModeContext = react$1.createContext(null);
1130
+ var ShellNavItemsContext = react$1.createContext(null);
1131
+ var ShellNavigationPreferenceContext = react$1.createContext(null);
1132
+ var ShellRoleContext = react$1.createContext(null);
1133
+ var ShellFooterContext = react$1.createContext(null);
1127
1134
  var ShellChromeContext = react$1.createContext(null);
1128
1135
  function toShellUser(session) {
1129
1136
  if (!session?.user) return null;
@@ -1192,7 +1199,7 @@ function ShellProvider({
1192
1199
  }),
1193
1200
  [devNotesMenu, myProfileHref]
1194
1201
  );
1195
- return /* @__PURE__ */ jsxRuntime.jsx(ShellAuthContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(ShellMetaContext.Provider, { value: meta, children: /* @__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(ShellChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ jsxRuntime.jsx(react.ChakraProvider, { theme: politogyTheme, children }) }) }) }) }) }) });
1202
+ return /* @__PURE__ */ jsxRuntime.jsx(ShellAuthContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(ShellMetaContext.Provider, { value: meta, children: /* @__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: null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellNavItemsContext.Provider, { value: null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellNavigationPreferenceContext.Provider, { value: null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellRoleContext.Provider, { value: null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellFooterContext.Provider, { value: null, children: /* @__PURE__ */ jsxRuntime.jsx(ShellChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ jsxRuntime.jsx(react.ChakraProvider, { theme: politogyTheme, children }) }) }) }) }) }) }) }) }) }) }) });
1196
1203
  }
1197
1204
  function useShellAuth() {
1198
1205
  const ctx = react$1.useContext(ShellAuthContext);
@@ -1231,6 +1238,612 @@ function useShellChrome() {
1231
1238
  }
1232
1239
  return ctx;
1233
1240
  }
1241
+ function useShellMode() {
1242
+ return react$1.useContext(ShellModeContext);
1243
+ }
1244
+ function useShellNavItems() {
1245
+ return react$1.useContext(ShellNavItemsContext);
1246
+ }
1247
+ function useShellNavigationPreference() {
1248
+ return react$1.useContext(ShellNavigationPreferenceContext);
1249
+ }
1250
+ function useShellRole() {
1251
+ return react$1.useContext(ShellRoleContext);
1252
+ }
1253
+ function useShellFooter() {
1254
+ return react$1.useContext(ShellFooterContext);
1255
+ }
1256
+ function BrandLogo({ src, alt = "Brand", className, style }) {
1257
+ if (!src) return null;
1258
+ return /* @__PURE__ */ jsxRuntime.jsx(
1259
+ "img",
1260
+ {
1261
+ src,
1262
+ alt,
1263
+ className,
1264
+ style,
1265
+ draggable: false
1266
+ }
1267
+ );
1268
+ }
1269
+ var BrandLogo_default = BrandLogo;
1270
+
1271
+ // src/lib/userDisplay.ts
1272
+ function getUserDisplayName(user) {
1273
+ if (!user) return "User";
1274
+ 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";
1275
+ }
1276
+ function avatarUrlFromUser(user) {
1277
+ if (!user) return void 0;
1278
+ const meta = user.raw.user_metadata;
1279
+ return meta?.avatar_url ?? void 0;
1280
+ }
1281
+ function UserMenu() {
1282
+ const { user, status, signOut } = useShellAuth();
1283
+ const {
1284
+ myProfileHref,
1285
+ organizationSettingsHref,
1286
+ showContinueOnboarding,
1287
+ onContinueOnboarding
1288
+ } = useShellChrome();
1289
+ const linked = useShellLinkedAccounts();
1290
+ const mode = useShellMode();
1291
+ const org = useShellOrganization();
1292
+ const role = useShellRole();
1293
+ const navigate = reactRouterDom.useNavigate();
1294
+ const menuListBg = react.useColorModeValue("white", "gray.900");
1295
+ const menuListBorderColor = react.useColorModeValue("gray.200", "gray.700");
1296
+ if (status !== "authenticated" || !user) return null;
1297
+ const displayName = getUserDisplayName(user.raw);
1298
+ const avatarUrl = avatarUrlFromUser(user);
1299
+ const modeConfig = mode?.getModeConfig() ?? null;
1300
+ const currentOrg = org?.currentOrg ?? null;
1301
+ const linkedAccounts = linked?.linkedAccounts ?? [];
1302
+ const activeRoleConfig = role?.activeRoleConfig ?? null;
1303
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { children: [
1304
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuButton, { py: 2, transition: "all 0.3s", _focus: { boxShadow: "none" }, children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { children: [
1305
+ /* @__PURE__ */ jsxRuntime.jsx(
1306
+ react.Avatar,
1307
+ {
1308
+ size: "sm",
1309
+ src: avatarUrl,
1310
+ name: displayName,
1311
+ bg: modeConfig?.primaryColor || "brand.500"
1312
+ }
1313
+ ),
1314
+ /* @__PURE__ */ jsxRuntime.jsxs(
1315
+ react.VStack,
1316
+ {
1317
+ display: { base: "none", md: "flex" },
1318
+ alignItems: "flex-start",
1319
+ spacing: "1px",
1320
+ ml: "2",
1321
+ children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsx(react.HStack, { spacing: 2, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", children: displayName }) }),
1323
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", children: user.email })
1324
+ ]
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: { base: "none", md: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}) })
1328
+ ] }) }),
1329
+ /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.MenuList, { bg: menuListBg, borderColor: menuListBorderColor, zIndex: 1500, children: [
1330
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiUser, {}), onClick: () => navigate(myProfileHref), children: "My Profile" }),
1331
+ showContinueOnboarding && /* @__PURE__ */ jsxRuntime.jsx(
1332
+ react.MenuItem,
1333
+ {
1334
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiRotateCcw, {}),
1335
+ onClick: () => {
1336
+ if (onContinueOnboarding) onContinueOnboarding();
1337
+ else window.dispatchEvent(new CustomEvent("app:continue-onboarding"));
1338
+ },
1339
+ children: "Continue Onboarding"
1340
+ }
1341
+ ),
1342
+ organizationSettingsHref && /* @__PURE__ */ jsxRuntime.jsx(
1343
+ react.MenuItem,
1344
+ {
1345
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiBriefcase, {}),
1346
+ onClick: () => navigate(organizationSettingsHref),
1347
+ children: "Organization Settings"
1348
+ }
1349
+ ),
1350
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1351
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", px: 3, py: 1, fontWeight: "semibold", children: "ROLES & ORGANIZATIONS" }),
1352
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: 3, py: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 1, children: [
1353
+ currentOrg && currentOrg.role && /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { children: [
1354
+ /* @__PURE__ */ jsxRuntime.jsx(
1355
+ react.Badge,
1356
+ {
1357
+ size: "sm",
1358
+ colorScheme: currentOrg.role === "owner" ? "purple" : currentOrg.role === "admin" ? "blue" : "gray",
1359
+ children: currentOrg.role.charAt(0).toUpperCase() + currentOrg.role.slice(1)
1360
+ }
1361
+ ),
1362
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "gray.500", children: [
1363
+ "in ",
1364
+ currentOrg.name
1365
+ ] })
1366
+ ] }),
1367
+ activeRoleConfig && !currentOrg?.role && /* @__PURE__ */ jsxRuntime.jsx(react.Badge, { colorScheme: activeRoleConfig.color, size: "sm", children: activeRoleConfig.name })
1368
+ ] }) }),
1369
+ linked && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1370
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1371
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", px: 3, py: 1, fontWeight: "semibold", children: "ACCOUNTS" }),
1372
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: 3, py: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, children: [
1373
+ /* @__PURE__ */ jsxRuntime.jsx(react.Avatar, { size: "xs", src: avatarUrl, name: displayName }),
1374
+ /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "start", spacing: 0, flex: 1, children: [
1375
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", color: "brand.600", children: displayName }),
1376
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.400", children: "Active" })
1377
+ ] })
1378
+ ] }) }),
1379
+ linkedAccounts.map((account) => /* @__PURE__ */ jsxRuntime.jsx(
1380
+ react.MenuItem,
1381
+ {
1382
+ onClick: () => {
1383
+ void linked.switchToAccount(account.id);
1384
+ },
1385
+ _hover: { bg: "gray.50" },
1386
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, flex: 1, justify: "space-between", children: [
1387
+ /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, children: [
1388
+ /* @__PURE__ */ jsxRuntime.jsx(
1389
+ react.Avatar,
1390
+ {
1391
+ size: "xs",
1392
+ src: account.avatarUrl ?? void 0,
1393
+ name: account.displayName ?? account.email
1394
+ }
1395
+ ),
1396
+ /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "start", spacing: 0, children: [
1397
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", fontWeight: "medium", noOfLines: 1, children: account.displayName ?? account.email.split("@")[0] }),
1398
+ /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: account.email, placement: "top", hasArrow: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1399
+ react.Text,
1400
+ {
1401
+ fontSize: "xs",
1402
+ color: "gray.500",
1403
+ maxW: "120px",
1404
+ noOfLines: 1,
1405
+ textOverflow: "ellipsis",
1406
+ children: account.email
1407
+ }
1408
+ ) })
1409
+ ] })
1410
+ ] }),
1411
+ /* @__PURE__ */ jsxRuntime.jsx(
1412
+ react.IconButton,
1413
+ {
1414
+ as: "span",
1415
+ role: "button",
1416
+ tabIndex: 0,
1417
+ "aria-label": "Remove account",
1418
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiTrash2, {}),
1419
+ size: "xs",
1420
+ variant: "ghost",
1421
+ colorScheme: "red",
1422
+ onClick: (e) => {
1423
+ e.stopPropagation();
1424
+ linked.onRemoveAccount(account.id);
1425
+ }
1426
+ }
1427
+ )
1428
+ ] })
1429
+ },
1430
+ account.id
1431
+ )),
1432
+ /* @__PURE__ */ jsxRuntime.jsx(
1433
+ react.MenuItem,
1434
+ {
1435
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiUserPlus, {}),
1436
+ onClick: linked.onAddAccount,
1437
+ color: "brand.500",
1438
+ children: "Add Account"
1439
+ }
1440
+ )
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1443
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiLogOut, {}), onClick: () => void signOut(), children: "Sign out" })
1444
+ ] }) })
1445
+ ] }) });
1446
+ }
1447
+ function Header({ onOpen, id }) {
1448
+ const {
1449
+ pageTitle,
1450
+ breadcrumbs,
1451
+ headerActions,
1452
+ headerIconsSlot,
1453
+ brandLogoSrc
1454
+ } = useShellChrome();
1455
+ const mobileNavBg = react.useColorModeValue("white", "gray.900");
1456
+ const mobileNavBorderColor = react.useColorModeValue("gray.200", "gray.700");
1457
+ return /* @__PURE__ */ jsxRuntime.jsx(
1458
+ react.Flex,
1459
+ {
1460
+ id: id || "mobile-nav-container",
1461
+ ml: { base: 0, md: 60 },
1462
+ px: { base: 4, md: 4 },
1463
+ height: "20",
1464
+ alignItems: "center",
1465
+ bg: mobileNavBg,
1466
+ borderBottomWidth: "1px",
1467
+ borderBottomColor: mobileNavBorderColor,
1468
+ justifyContent: { base: "space-between", md: "space-between" },
1469
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { w: "full", mx: "auto", align: "center", justify: "space-between", children: [
1470
+ /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 4, minW: "0", children: [
1471
+ onOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1472
+ react.IconButton,
1473
+ {
1474
+ display: { base: "flex", md: "none" },
1475
+ onClick: onOpen,
1476
+ variant: "outline",
1477
+ "aria-label": "open menu",
1478
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiMenu, {})
1479
+ }
1480
+ ) : null,
1481
+ 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,
1482
+ /* @__PURE__ */ jsxRuntime.jsxs(
1483
+ react.VStack,
1484
+ {
1485
+ align: "flex-start",
1486
+ spacing: 0,
1487
+ minW: "0",
1488
+ display: { base: "none", md: "flex" },
1489
+ children: [
1490
+ pageTitle ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "lg", fontWeight: "bold", noOfLines: 1, children: pageTitle }) : null,
1491
+ 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: [
1492
+ idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { color: "gray.400", mr: 2, children: " > " }) : null,
1493
+ b.href ? /* @__PURE__ */ jsxRuntime.jsx(
1494
+ react.Link,
1495
+ {
1496
+ as: reactRouterDom.NavLink,
1497
+ to: b.href,
1498
+ _hover: { textDecoration: "underline" },
1499
+ noOfLines: 1,
1500
+ children: b.label
1501
+ }
1502
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { noOfLines: 1, children: b.label })
1503
+ ] }, `${b.label}-${idx}`)) }) : null
1504
+ ]
1505
+ }
1506
+ )
1507
+ ] }),
1508
+ /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 0, align: "center", children: [
1509
+ /* @__PURE__ */ jsxRuntime.jsxs(
1510
+ react.HStack,
1511
+ {
1512
+ spacing: 2,
1513
+ mr: "15px",
1514
+ color: "gray.600",
1515
+ sx: {
1516
+ '& button:not([role="menuitem"]):not([data-menu-item])': {
1517
+ width: "32px",
1518
+ height: "32px",
1519
+ minWidth: "32px",
1520
+ padding: 0
1521
+ },
1522
+ '& button:not([role="menuitem"]):not([data-menu-item]) svg': {
1523
+ width: "20px",
1524
+ height: "20px"
1525
+ }
1526
+ },
1527
+ children: [
1528
+ headerActions,
1529
+ headerIconsSlot
1530
+ ]
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ jsxRuntime.jsx(UserMenu, {})
1534
+ ] })
1535
+ ] })
1536
+ }
1537
+ );
1538
+ }
1539
+ function BrandIcon({
1540
+ name,
1541
+ size,
1542
+ className,
1543
+ style,
1544
+ alt,
1545
+ basePath = "/brand/icons/"
1546
+ }) {
1547
+ const a11yLabel = alt ?? "";
1548
+ const isDecorative = a11yLabel === "";
1549
+ const inlineSize = size ? { width: size, height: size, ...style } : style;
1550
+ return /* @__PURE__ */ jsxRuntime.jsx(
1551
+ "img",
1552
+ {
1553
+ src: `${basePath}${name}.svg`,
1554
+ alt: a11yLabel,
1555
+ role: isDecorative ? "presentation" : void 0,
1556
+ "aria-hidden": isDecorative ? true : void 0,
1557
+ className,
1558
+ style: inlineSize,
1559
+ draggable: false
1560
+ }
1561
+ );
1562
+ }
1563
+ var BrandIcon_default = BrandIcon;
1564
+ var MODE_CAMPAIGN = "campaign";
1565
+ var MODE_PETITION = "petition";
1566
+ function NavItem({
1567
+ icon,
1568
+ brandIcon,
1569
+ children,
1570
+ href,
1571
+ action,
1572
+ subItems,
1573
+ badge,
1574
+ subLabel,
1575
+ isSubItem = false,
1576
+ isExpanded,
1577
+ onToggle,
1578
+ menuKey,
1579
+ isDisabled: navIsDisabled,
1580
+ disabled,
1581
+ ...rest
1582
+ }) {
1583
+ const location = reactRouterDom.useLocation();
1584
+ const navigate = reactRouterDom.useNavigate();
1585
+ const mode = useShellMode();
1586
+ const currentMode = mode?.currentMode ?? null;
1587
+ const modeConfig = mode?.getModeConfig() ?? null;
1588
+ const navPref = useShellNavigationPreference();
1589
+ const showSubMenu = navPref?.showSubMenu ?? true;
1590
+ const navHoverGradient = currentMode === MODE_CAMPAIGN ? "linear-gradient(90deg, #F2C94C 0%, #E63946 100%)" : currentMode === MODE_PETITION ? "linear-gradient(90deg, #F2C94C 0%, #007BFF 100%)" : "linear-gradient(90deg, #007BFF 0%, #E63946 100%)";
1591
+ const gradientSweepBefore = {
1592
+ content: '""',
1593
+ position: "absolute",
1594
+ top: 0,
1595
+ left: 0,
1596
+ bottom: 0,
1597
+ width: "0%",
1598
+ background: navHoverGradient,
1599
+ transition: "width 300ms ease",
1600
+ pointerEvents: "none",
1601
+ zIndex: 0
1602
+ };
1603
+ const isDisabled = Boolean(navIsDisabled || disabled);
1604
+ const effectiveHref = href || (Array.isArray(subItems) && subItems.length > 0 ? subItems[0].href : void 0);
1605
+ const effectivePathname = effectiveHref?.split("?")[0];
1606
+ const currentFullPath = location.pathname + location.search;
1607
+ const currentSearchParams = react$1.useMemo(
1608
+ () => new URLSearchParams(location.search),
1609
+ [location.search]
1610
+ );
1611
+ const matchesSubItem = (itemHref) => {
1612
+ if (itemHref === currentFullPath) return true;
1613
+ const itemPath = itemHref.split("?")[0];
1614
+ const itemSearch = itemHref.includes("?") ? itemHref.split("?")[1] : "";
1615
+ if (itemPath === location.pathname && !itemSearch) {
1616
+ return true;
1617
+ }
1618
+ if (location.pathname.startsWith(`${itemPath}/`)) {
1619
+ const itemSegments = itemPath.split("/").filter(Boolean);
1620
+ const locationSegments = location.pathname.split("/").filter(Boolean);
1621
+ const itemLastSegment = itemSegments[itemSegments.length - 1];
1622
+ const locationLastSegment = locationSegments[locationSegments.length - 1];
1623
+ if (itemLastSegment && itemLastSegment === locationLastSegment) {
1624
+ return true;
1625
+ }
1626
+ }
1627
+ if (itemPath === location.pathname && itemSearch) {
1628
+ const itemSearchParams = new URLSearchParams(itemSearch);
1629
+ const itemTab = itemSearchParams.get("tab") || itemSearchParams.get("view");
1630
+ if (!itemTab) return true;
1631
+ return itemTab === currentSearchParams.get("tab");
1632
+ }
1633
+ if (itemPath && !itemHref.includes("?") && itemPath !== effectivePathname) {
1634
+ const segment = itemPath.split("/").pop();
1635
+ if (segment && location.pathname.endsWith("/" + segment) && location.pathname.startsWith(itemPath.split("/").slice(0, -1).join("/"))) {
1636
+ return true;
1637
+ }
1638
+ }
1639
+ return false;
1640
+ };
1641
+ const isActive = isSubItem ? matchesSubItem(effectiveHref || "") : (() => {
1642
+ if (!effectivePathname) return false;
1643
+ if (location.pathname === effectivePathname) return true;
1644
+ if (location.pathname.startsWith(`${effectivePathname}/`)) return true;
1645
+ const dashboardOverviewMatch = effectivePathname.match(
1646
+ /^\/[^/]+\/[^/]*\/dashboard\/overview$/
1647
+ );
1648
+ if (dashboardOverviewMatch) {
1649
+ const dashboardRoot = effectivePathname.replace(/\/overview$/, "");
1650
+ return location.pathname === dashboardRoot || location.pathname.startsWith(`${dashboardRoot}/`) || location.pathname.startsWith(`${dashboardRoot}&`);
1651
+ }
1652
+ return effectivePathname !== `/${location.pathname.split("/")[1]}/dashboard` && location.pathname.startsWith(effectivePathname + "/");
1653
+ })();
1654
+ const hasSubItems = showSubMenu && Array.isArray(subItems) && subItems.length > 0;
1655
+ const hasActiveChild = hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1656
+ const isParentActive = isActive || hasActiveChild;
1657
+ const isSubMenuOpen = isExpanded !== void 0 ? isExpanded : hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1658
+ const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? "gray.100" : "transparent";
1659
+ const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "gray.900" : "gray.600";
1660
+ const disabledRowBg = isSubItem ? "gray.50" : "transparent";
1661
+ const disabledRowTextColor = "gray.400";
1662
+ if (hasSubItems) {
1663
+ const headerProps = {
1664
+ align: "center",
1665
+ px: "4",
1666
+ py: "2.5",
1667
+ mx: "4",
1668
+ w: "auto",
1669
+ borderTopRadius: "lg",
1670
+ borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1671
+ role: "group",
1672
+ cursor: isDisabled ? "not-allowed" : "pointer",
1673
+ opacity: 1,
1674
+ pointerEvents: isDisabled ? "none" : "auto",
1675
+ "data-nav-item-active": String(isParentActive),
1676
+ position: "relative",
1677
+ overflow: "hidden",
1678
+ bg: isDisabled ? disabledRowBg : expandedRowBg,
1679
+ color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1680
+ _before: isDisabled ? void 0 : gradientSweepBefore,
1681
+ _hover: {
1682
+ color: isDisabled ? disabledRowTextColor : "white",
1683
+ _before: isDisabled ? void 0 : { width: "100%" }
1684
+ },
1685
+ "aria-disabled": isDisabled ? "true" : "false",
1686
+ onClick: (e) => {
1687
+ if (isDisabled) return;
1688
+ if (effectiveHref) e.preventDefault();
1689
+ e.stopPropagation();
1690
+ onToggle?.(menuKey || null);
1691
+ }
1692
+ };
1693
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
1694
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { ...headerProps, ...rest, children: [
1695
+ brandIcon ? /* @__PURE__ */ jsxRuntime.jsx(
1696
+ react.Box,
1697
+ {
1698
+ mr: "4",
1699
+ display: "inline-flex",
1700
+ alignItems: "center",
1701
+ position: "relative",
1702
+ zIndex: 1,
1703
+ filter: isParentActive ? "brightness(0) invert(1)" : void 0,
1704
+ _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1705
+ children: /* @__PURE__ */ jsxRuntime.jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1706
+ }
1707
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx(
1708
+ react.Box,
1709
+ {
1710
+ mr: "4",
1711
+ fontSize: "16",
1712
+ position: "relative",
1713
+ zIndex: 1,
1714
+ _groupHover: {
1715
+ color: isDisabled ? disabledRowTextColor : "white"
1716
+ },
1717
+ as: icon
1718
+ }
1719
+ ) : null,
1720
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1", position: "relative", zIndex: 1, children }),
1721
+ /* @__PURE__ */ jsxRuntime.jsx(
1722
+ react.Box,
1723
+ {
1724
+ as: fi.FiChevronDown,
1725
+ position: "relative",
1726
+ zIndex: 1,
1727
+ transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1728
+ transition: "transform 0.2s"
1729
+ }
1730
+ )
1731
+ ] }),
1732
+ /* @__PURE__ */ jsxRuntime.jsx(react.Collapse, { in: isSubMenuOpen, animateOpacity: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1733
+ react.Box,
1734
+ {
1735
+ px: "0",
1736
+ mx: "4",
1737
+ w: "auto",
1738
+ mb: "2",
1739
+ py: "1",
1740
+ borderTopRadius: 0,
1741
+ borderBottomRadius: "md",
1742
+ bg: isDisabled ? "gray.100" : "gray.200",
1743
+ children: subItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1744
+ NavItem,
1745
+ {
1746
+ icon: item.icon,
1747
+ brandIcon: item.brandIcon,
1748
+ href: item.href,
1749
+ isSubItem: true,
1750
+ isDisabled: item.disabled,
1751
+ subLabel: item.subLabel,
1752
+ children: item.name
1753
+ },
1754
+ item.name
1755
+ ))
1756
+ }
1757
+ ) })
1758
+ ] });
1759
+ }
1760
+ const handleClick = (e) => {
1761
+ if (isDisabled) {
1762
+ e.preventDefault();
1763
+ return;
1764
+ }
1765
+ if (action) {
1766
+ e.preventDefault();
1767
+ action();
1768
+ return;
1769
+ }
1770
+ if (effectiveHref) {
1771
+ e.preventDefault();
1772
+ navigate(effectiveHref);
1773
+ }
1774
+ };
1775
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "a", href: effectiveHref || "#", onClick: handleClick, style: { textDecoration: "none" }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1776
+ react.Flex,
1777
+ {
1778
+ align: "center",
1779
+ px: "4",
1780
+ py: "2.5",
1781
+ mx: isSubItem ? "0" : "4",
1782
+ w: isSubItem ? "full" : "auto",
1783
+ borderRadius: isSubItem ? "0" : "lg",
1784
+ role: "group",
1785
+ cursor: isDisabled ? "not-allowed" : "pointer",
1786
+ opacity: 1,
1787
+ pointerEvents: isDisabled ? "none" : "auto",
1788
+ "data-nav-item-active": String(isActive),
1789
+ position: "relative",
1790
+ overflow: "hidden",
1791
+ bg: isDisabled ? disabledRowBg : isActive ? isSubItem ? "gray.100" : modeConfig?.primaryColor || "brand.500" : "transparent",
1792
+ color: isDisabled ? disabledRowTextColor : isActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1793
+ _before: isDisabled ? void 0 : gradientSweepBefore,
1794
+ _hover: {
1795
+ color: isDisabled ? disabledRowTextColor : "white",
1796
+ _before: isDisabled ? void 0 : { width: "100%" }
1797
+ },
1798
+ "aria-disabled": isDisabled ? "true" : "false",
1799
+ onClick: handleClick,
1800
+ children: [
1801
+ brandIcon ? /* @__PURE__ */ jsxRuntime.jsx(
1802
+ react.Box,
1803
+ {
1804
+ mr: "4",
1805
+ display: "inline-flex",
1806
+ alignItems: "center",
1807
+ position: "relative",
1808
+ zIndex: 1,
1809
+ filter: isActive && !isSubItem ? "brightness(0) invert(1)" : void 0,
1810
+ _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1811
+ children: /* @__PURE__ */ jsxRuntime.jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1812
+ }
1813
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx(
1814
+ react.Box,
1815
+ {
1816
+ mr: "4",
1817
+ fontSize: "16",
1818
+ position: "relative",
1819
+ zIndex: 1,
1820
+ _groupHover: {
1821
+ color: isDisabled ? disabledRowTextColor : "white"
1822
+ },
1823
+ as: icon
1824
+ }
1825
+ ) : null,
1826
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { flex: "1", position: "relative", zIndex: 1, children: [
1827
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { children }),
1828
+ subLabel ? /* @__PURE__ */ jsxRuntime.jsx(
1829
+ react.Text,
1830
+ {
1831
+ mt: 0.5,
1832
+ fontSize: "10px",
1833
+ lineHeight: "1.3",
1834
+ color: isDisabled ? "gray.400" : "gray.400",
1835
+ children: subLabel
1836
+ }
1837
+ ) : null,
1838
+ badge && /* @__PURE__ */ jsxRuntime.jsx(react.Badge, { colorScheme: "orange", size: "xs", mt: 1, fontSize: "xs", display: "block", children: badge })
1839
+ ] })
1840
+ ]
1841
+ }
1842
+ ) });
1843
+ }
1844
+ var NavItem_default = NavItem;
1845
+
1846
+ // src/lib/orgColors.ts
1234
1847
  var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
1235
1848
  function normalizeHexColor(value) {
1236
1849
  const trimmed = value?.trim();
@@ -1239,6 +1852,34 @@ function normalizeHexColor(value) {
1239
1852
  const [r, g, b] = trimmed.slice(1).split("");
1240
1853
  return `#${r}${r}${g}${g}${b}${b}`;
1241
1854
  }
1855
+ function hexToRgba(hex, alpha) {
1856
+ const normalized = normalizeHexColor(hex);
1857
+ if (!normalized) return null;
1858
+ const red = parseInt(normalized.slice(1, 3), 16);
1859
+ const green = parseInt(normalized.slice(3, 5), 16);
1860
+ const blue = parseInt(normalized.slice(5, 7), 16);
1861
+ return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
1862
+ }
1863
+ function lightenHexColor(hex, amount) {
1864
+ const normalized = normalizeHexColor(hex);
1865
+ if (!normalized) return null;
1866
+ const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
1867
+ const blend = (channel) => clamp(channel + (255 - channel) * amount);
1868
+ const red = parseInt(normalized.slice(1, 3), 16);
1869
+ const green = parseInt(normalized.slice(3, 5), 16);
1870
+ const blue = parseInt(normalized.slice(5, 7), 16);
1871
+ return `#${blend(red).toString(16).padStart(2, "0")}${blend(green).toString(16).padStart(2, "0")}${blend(blue).toString(16).padStart(2, "0")}`;
1872
+ }
1873
+ function getOrganizationBrandColor(organization) {
1874
+ if (!organization) return null;
1875
+ const direct = normalizeHexColor(organization.brandColor);
1876
+ if (direct) return direct;
1877
+ const brandColors = Array.isArray(organization.settings?.brand_colors) ? organization.settings?.brand_colors : [];
1878
+ const firstBrandColor = brandColors?.find(
1879
+ (entry) => typeof entry?.value === "string" && normalizeHexColor(entry.value)
1880
+ );
1881
+ return normalizeHexColor(firstBrandColor?.value ?? null);
1882
+ }
1242
1883
  function getOrganizationBadgeLetters(name) {
1243
1884
  const normalizedName = String(name || "").trim().replace(/[^a-zA-Z0-9\s]+/g, " ");
1244
1885
  const parts = normalizedName.split(/\s+/).filter(Boolean);
@@ -1273,60 +1914,698 @@ function OrgBadge({ name, logoUrl, brandColor, size }) {
1273
1914
  }
1274
1915
  );
1275
1916
  }
1276
- function OrgSelector() {
1917
+ function OrganizationIdCopy({ orgId }) {
1918
+ const { hasCopied, onCopy } = react.useClipboard(orgId);
1919
+ const copiedAnimation = react$2.keyframes`
1920
+ 0% { opacity: 0; transform: translateY(6px); }
1921
+ 20% { opacity: 1; transform: translateY(0); }
1922
+ 100% { opacity: 0; transform: translateY(-8px); }
1923
+ `;
1924
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: orgId, placement: "top", hasArrow: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
1925
+ react.Flex,
1926
+ {
1927
+ align: "center",
1928
+ cursor: "pointer",
1929
+ onClick: onCopy,
1930
+ _hover: { bg: "gray.100" },
1931
+ borderRadius: "md",
1932
+ px: 2,
1933
+ py: 1,
1934
+ position: "relative",
1935
+ transition: "all 0.2s",
1936
+ children: [
1937
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: hasCopied ? "green.500" : "gray.600", fontWeight: "normal", children: [
1938
+ "...",
1939
+ orgId.slice(-8)
1940
+ ] }),
1941
+ hasCopied && /* @__PURE__ */ jsxRuntime.jsx(
1942
+ react.Box,
1943
+ {
1944
+ position: "absolute",
1945
+ bottom: "100%",
1946
+ mb: "6px",
1947
+ px: "2",
1948
+ py: "1",
1949
+ fontSize: "xs",
1950
+ bg: "gray.700",
1951
+ color: "white",
1952
+ borderRadius: "md",
1953
+ animation: `${copiedAnimation} 0.9s ease-out`,
1954
+ pointerEvents: "none",
1955
+ children: "Copied!"
1956
+ }
1957
+ )
1958
+ ]
1959
+ }
1960
+ ) });
1961
+ }
1962
+ function OrgPill() {
1277
1963
  const org = useShellOrganization();
1964
+ const { user } = useShellAuth();
1965
+ const currentOrg = org?.currentOrg ?? null;
1966
+ const organizations = org?.organizations ?? [];
1967
+ const switchOrganization = org?.switchOrganization;
1968
+ const loadingOrgs = org?.loadingOrgs ?? false;
1969
+ const unselectedOrganizations = react$1.useMemo(
1970
+ () => organizations.filter((o) => o.id !== currentOrg?.id),
1971
+ [organizations, currentOrg?.id]
1972
+ );
1973
+ const selectedOrgBrandColor = react$1.useMemo(
1974
+ () => currentOrg ? getOrganizationBrandColor(currentOrg) : null,
1975
+ [currentOrg]
1976
+ );
1977
+ const selectedOrgColor = selectedOrgBrandColor || "var(--mode-primary-color, #ea6a3a)";
1978
+ const selectedOrgMenuBg = selectedOrgBrandColor && lightenHexColor(selectedOrgBrandColor, 0.12) || "#4a4a4a";
1278
1979
  if (!org) return null;
1279
- const { currentOrg, organizations, switchOrganization, createOrgHref } = org;
1280
- const others = organizations.filter((o) => o.id !== currentOrg?.id);
1281
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { placement: "bottom-start", children: [
1282
- /* @__PURE__ */ jsxRuntime.jsx(
1283
- react.MenuButton,
1284
- {
1285
- as: react.Box,
1286
- cursor: "pointer",
1287
- px: 2,
1288
- py: 1,
1289
- borderRadius: "md",
1290
- _hover: { bg: "gray.100" },
1291
- _active: { bg: "gray.200" },
1292
- "aria-label": "Switch organization",
1293
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, align: "center", children: [
1294
- currentOrg ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1980
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { mx: "4", mb: "3", children: [
1981
+ /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { justify: "space-between", align: "center", mb: "2", children: [
1982
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", fontWeight: "semibold", children: "ORGANIZATION" }),
1983
+ currentOrg?.id && currentOrg.id !== user?.id && /* @__PURE__ */ jsxRuntime.jsx(OrganizationIdCopy, { orgId: currentOrg.id })
1984
+ ] }),
1985
+ loadingOrgs && !currentOrg ? /* @__PURE__ */ jsxRuntime.jsx(react.Skeleton, { height: "32px", width: "full", borderRadius: "md" }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { gutter: 0, children: [
1986
+ /* @__PURE__ */ jsxRuntime.jsx(
1987
+ react.MenuButton,
1988
+ {
1989
+ as: react.Button,
1990
+ rightIcon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}),
1991
+ size: "sm",
1992
+ width: "full",
1993
+ bg: currentOrg?.id ? selectedOrgColor : "gray.100",
1994
+ borderColor: currentOrg?.id ? selectedOrgColor : "gray.300",
1995
+ color: currentOrg?.id ? "white" : "gray.700",
1996
+ _hover: {
1997
+ bg: currentOrg?.id ? selectedOrgColor : "gray.200",
1998
+ color: currentOrg?.id ? "white" : "gray.900"
1999
+ },
2000
+ _active: {
2001
+ bg: currentOrg?.id ? selectedOrgColor : "gray.200",
2002
+ color: currentOrg?.id ? "white" : "gray.900"
2003
+ },
2004
+ _expanded: {
2005
+ bg: currentOrg?.id ? selectedOrgColor : "gray.200",
2006
+ color: currentOrg?.id ? "white" : "gray.900"
2007
+ },
2008
+ textAlign: "left",
2009
+ justifyContent: "flex-start",
2010
+ isDisabled: loadingOrgs && organizations.length === 0,
2011
+ sx: currentOrg?.id ? {
2012
+ color: "white",
2013
+ ".chakra-button__icon": { color: "white" }
2014
+ } : void 0,
2015
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: "2.5", minW: 0, align: "center", children: [
1295
2016
  /* @__PURE__ */ jsxRuntime.jsx(
1296
2017
  OrgBadge,
1297
2018
  {
1298
- name: currentOrg.name,
1299
- logoUrl: currentOrg.logoUrl,
1300
- brandColor: currentOrg.brandColor,
1301
- size: "24px"
2019
+ name: currentOrg?.name,
2020
+ logoUrl: currentOrg?.logoUrl,
2021
+ brandColor: selectedOrgBrandColor,
2022
+ size: "28px"
1302
2023
  }
1303
2024
  ),
1304
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "600", color: "gray.800", noOfLines: 1, children: currentOrg.name })
1305
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "gray.500", children: "Select organization" }),
1306
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontSize: "xs", color: "gray.500", "aria-hidden": "true", children: "\u25BE" })
1307
- ] })
1308
- }
1309
- ),
1310
- /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.MenuList, { zIndex: 1500, children: [
1311
- others.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { isDisabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "gray.500", children: "No other organizations" }) }),
1312
- others.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
1313
- react.MenuItem,
1314
- {
1315
- onClick: () => {
1316
- void switchOrganization(o.id);
1317
- },
1318
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, align: "center", children: [
1319
- /* @__PURE__ */ jsxRuntime.jsx(OrgBadge, { name: o.name, logoUrl: o.logoUrl, brandColor: o.brandColor, size: "20px" }),
1320
- /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { direction: "column", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "600", children: o.name }) })
2025
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: currentOrg?.name || "No organizations" })
1321
2026
  ] })
2027
+ }
2028
+ ),
2029
+ /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2030
+ react.MenuList,
2031
+ {
2032
+ bg: currentOrg?.id ? selectedOrgMenuBg : "gray.100",
2033
+ borderColor: currentOrg?.id ? selectedOrgColor : "gray.300",
2034
+ color: currentOrg?.id ? "white" : "gray.800",
2035
+ mt: "1",
2036
+ minW: "13rem",
2037
+ maxH: "60vh",
2038
+ overflowY: "auto",
2039
+ zIndex: "modal",
2040
+ boxShadow: "2xl",
2041
+ children: [
2042
+ unselectedOrganizations.map((o) => {
2043
+ const orgBrandColor = getOrganizationBrandColor(o);
2044
+ return /* @__PURE__ */ jsxRuntime.jsx(
2045
+ react.MenuItem,
2046
+ {
2047
+ onClick: () => switchOrganization?.(o.id),
2048
+ bg: "transparent",
2049
+ color: currentOrg?.id ? "white" : "inherit",
2050
+ _hover: {
2051
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.2) || "#555555" : "gray.100",
2052
+ color: currentOrg?.id ? "white" : "gray.900"
2053
+ },
2054
+ _focus: {
2055
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.2) || "#555555" : "gray.100",
2056
+ color: currentOrg?.id ? "white" : "gray.900"
2057
+ },
2058
+ _active: {
2059
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.24) || "#5d5d5d" : "gray.200",
2060
+ color: currentOrg?.id ? "white" : "gray.900"
2061
+ },
2062
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { justify: "space-between", align: "center", width: "100%", overflow: "hidden", children: [
2063
+ /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { minW: 0, flex: "1", spacing: "2.5", children: [
2064
+ /* @__PURE__ */ jsxRuntime.jsx(
2065
+ OrgBadge,
2066
+ {
2067
+ name: o.name,
2068
+ logoUrl: o.logoUrl,
2069
+ brandColor: orgBrandColor,
2070
+ size: "24px"
2071
+ }
2072
+ ),
2073
+ /* @__PURE__ */ jsxRuntime.jsx(
2074
+ react.Text,
2075
+ {
2076
+ overflow: "hidden",
2077
+ textOverflow: "ellipsis",
2078
+ whiteSpace: "nowrap",
2079
+ flex: "1",
2080
+ children: o.name
2081
+ }
2082
+ )
2083
+ ] }),
2084
+ /* @__PURE__ */ jsxRuntime.jsx(
2085
+ react.Text,
2086
+ {
2087
+ fontSize: "xs",
2088
+ color: currentOrg?.id ? "whiteAlpha.700" : "gray.500",
2089
+ ml: 2,
2090
+ flexShrink: 0,
2091
+ children: o.id.slice(-8)
2092
+ }
2093
+ )
2094
+ ] })
2095
+ },
2096
+ o.id
2097
+ );
2098
+ }),
2099
+ /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, { my: 0, borderColor: currentOrg?.id ? "whiteAlpha.300" : void 0 }),
2100
+ /* @__PURE__ */ jsxRuntime.jsx(
2101
+ react.MenuItem,
2102
+ {
2103
+ onClick: () => window.dispatchEvent(new CustomEvent("open-create-org")),
2104
+ fontWeight: "bold",
2105
+ bg: "transparent",
2106
+ color: currentOrg?.id ? "white" : "inherit",
2107
+ my: 0,
2108
+ _hover: {
2109
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.2) || "#555555" : "gray.100",
2110
+ color: currentOrg?.id ? "white" : "gray.900"
2111
+ },
2112
+ _focus: {
2113
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.2) || "#555555" : "gray.100",
2114
+ color: currentOrg?.id ? "white" : "gray.900"
2115
+ },
2116
+ _active: {
2117
+ bg: currentOrg?.id ? lightenHexColor(selectedOrgBrandColor || "#ea6a3a", 0.24) || "#5d5d5d" : "gray.200",
2118
+ color: currentOrg?.id ? "white" : "gray.900"
2119
+ },
2120
+ fontSize: "sm",
2121
+ children: "+ New Organization"
2122
+ }
2123
+ )
2124
+ ]
2125
+ }
2126
+ ) })
2127
+ ] })
2128
+ ] });
2129
+ }
2130
+ var OrgPill_default = OrgPill;
2131
+ function ModePill() {
2132
+ const mode = useShellMode();
2133
+ if (!mode) return null;
2134
+ const { currentMode, modes, switchMode, getModeConfig } = mode;
2135
+ const modeConfig = getModeConfig();
2136
+ if (!modeConfig) return null;
2137
+ const hasMultipleModes = modes.length > 1;
2138
+ const isDisabled = !hasMultipleModes;
2139
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mx: "4", mb: "4", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { gutter: 0, children: [
2140
+ /* @__PURE__ */ jsxRuntime.jsx(
2141
+ react.MenuButton,
2142
+ {
2143
+ as: react.Button,
2144
+ leftIcon: modeConfig.icon,
2145
+ rightIcon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiChevronDown, {}),
2146
+ size: "sm",
2147
+ width: "full",
2148
+ bg: modeConfig.primaryColor,
2149
+ borderColor: modeConfig.primaryColor,
2150
+ color: modeConfig.white,
2151
+ _disabled: {
2152
+ bg: modeConfig.primaryColor,
2153
+ borderColor: modeConfig.primaryColor,
2154
+ color: modeConfig.white,
2155
+ cursor: "not-allowed",
2156
+ opacity: 1
1322
2157
  },
1323
- o.id
1324
- )),
1325
- createOrgHref && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1326
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1327
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { as: "a", href: createOrgHref, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "brand.500", fontWeight: "600", children: "Create organization" }) })
2158
+ isDisabled,
2159
+ _hover: {
2160
+ bg: modeConfig.primaryColor,
2161
+ color: modeConfig.white
2162
+ },
2163
+ _active: {
2164
+ bg: modeConfig.primaryColor,
2165
+ color: modeConfig.white
2166
+ },
2167
+ children: modeConfig.name
2168
+ }
2169
+ ),
2170
+ hasMultipleModes && /* @__PURE__ */ jsxRuntime.jsx(react.MenuList, { children: modes.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
2171
+ react.MenuItem,
2172
+ {
2173
+ onClick: () => switchMode(m.id),
2174
+ bg: "transparent",
2175
+ color: currentMode === m.id ? m.primaryColor : "inherit",
2176
+ _hover: { bg: m.bgColor, color: m.primaryColor },
2177
+ icon: m.icon,
2178
+ children: m.name
2179
+ },
2180
+ m.id
2181
+ )) })
2182
+ ] }) });
2183
+ }
2184
+ var ModePill_default = ModePill;
2185
+ function ActivityIcon() {
2186
+ return /* @__PURE__ */ jsxRuntime.jsx(
2187
+ "svg",
2188
+ {
2189
+ width: "12",
2190
+ height: "12",
2191
+ viewBox: "0 0 24 24",
2192
+ fill: "none",
2193
+ stroke: "currentColor",
2194
+ strokeWidth: "2",
2195
+ strokeLinecap: "round",
2196
+ strokeLinejoin: "round",
2197
+ "aria-hidden": "true",
2198
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" })
2199
+ }
2200
+ );
2201
+ }
2202
+ function defaultEnvChip(label) {
2203
+ if (!label) return null;
2204
+ const lower = label.toLowerCase();
2205
+ if (lower.includes("prod")) {
2206
+ return { color: "#ef4444", bg: "#fee2e2", border: "#fecaca", name: label };
2207
+ }
2208
+ if (lower.includes("stag")) {
2209
+ return { color: "#f97316", bg: "#ffedd5", border: "#fed7aa", name: label };
2210
+ }
2211
+ if (lower.includes("local") || lower.includes("dev")) {
2212
+ return { color: "#22c55e", bg: "#dcfce7", border: "#bbf7d0", name: label };
2213
+ }
2214
+ return { color: "#6b7280", bg: "#f3f4f6", border: "#e5e7eb", name: label };
2215
+ }
2216
+ var PLACEHOLDER_SHA_VALUES = /* @__PURE__ */ new Set(["unknown", "n/a", "null", "undefined", ""]);
2217
+ function isMeaningfulCommit(value) {
2218
+ if (typeof value !== "string") return false;
2219
+ const trimmed = value.trim();
2220
+ if (!trimmed) return false;
2221
+ return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2222
+ }
2223
+ function isMeaningfulAuthor(value) {
2224
+ if (typeof value !== "string") return false;
2225
+ const trimmed = value.trim();
2226
+ if (!trimmed) return false;
2227
+ return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2228
+ }
2229
+ function getFirstName(value) {
2230
+ if (!value) return "";
2231
+ const stripped = value.replace(/<[^>]+>/g, "").trim();
2232
+ if (!stripped) return "";
2233
+ const [firstToken] = stripped.split(/\s+/);
2234
+ return firstToken || stripped;
2235
+ }
2236
+ function CommitCopyButton({
2237
+ envNode,
2238
+ environmentLabel,
2239
+ commit,
2240
+ version = "",
2241
+ commitDate = "",
2242
+ commitAuthor = "",
2243
+ commitLog = "",
2244
+ onDiagnosticsClick
2245
+ }) {
2246
+ const safeCommit = isMeaningfulCommit(commit) ? commit.trim() : "";
2247
+ const { hasCopied, onCopy } = react.useClipboard(safeCommit);
2248
+ const shortCommit = safeCommit ? safeCommit.slice(-4) : "";
2249
+ const copiedAnimation = react$2.keyframes`
2250
+ 0% { opacity: 0; transform: translateY(6px); }
2251
+ 20% { opacity: 1; transform: translateY(0); }
2252
+ 100% { opacity: 0; transform: translateY(-8px); }
2253
+ `;
2254
+ const formattedDate = react$1.useMemo(() => {
2255
+ if (!commitDate) return "";
2256
+ const parsed = new Date(commitDate);
2257
+ if (Number.isNaN(parsed.getTime())) return commitDate;
2258
+ return parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2259
+ }, [commitDate]);
2260
+ const metaLine = react$1.useMemo(() => {
2261
+ const firstName = isMeaningfulAuthor(commitAuthor) ? getFirstName(commitAuthor) : "";
2262
+ return [formattedDate, firstName].filter(Boolean).join(" | ");
2263
+ }, [formattedDate, commitAuthor]);
2264
+ const normalizedCommitLog = react$1.useMemo(() => {
2265
+ if (commitLog?.trim()) {
2266
+ return commitLog.replace(/\r\n/g, "\n").replace(/\\n/g, "\n");
2267
+ }
2268
+ return "";
2269
+ }, [commitLog]);
2270
+ const tooltipContent = normalizedCommitLog.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2271
+ react.Box,
2272
+ {
2273
+ maxW: "600px",
2274
+ width: "min(600px, 90vw)",
2275
+ maxH: "80vh",
2276
+ whiteSpace: "pre-wrap",
2277
+ overflowY: "auto",
2278
+ overflowX: "auto",
2279
+ fontSize: "xs",
2280
+ fontFamily: "mono",
2281
+ wordBreak: "break-word",
2282
+ children: normalizedCommitLog
2283
+ }
2284
+ ) : void 0;
2285
+ const envChip = react$1.useMemo(() => {
2286
+ if (envNode) return envNode;
2287
+ const chip = defaultEnvChip(environmentLabel);
2288
+ if (!chip) return null;
2289
+ return /* @__PURE__ */ jsxRuntime.jsx(
2290
+ react.Box,
2291
+ {
2292
+ as: "span",
2293
+ display: "inline-flex",
2294
+ alignItems: "center",
2295
+ px: "1.5",
2296
+ py: "0.5",
2297
+ borderWidth: "1px",
2298
+ borderColor: chip.border,
2299
+ bg: chip.bg,
2300
+ color: chip.color,
2301
+ borderRadius: "md",
2302
+ fontSize: "2xs",
2303
+ fontWeight: "600",
2304
+ lineHeight: "1",
2305
+ textTransform: "capitalize",
2306
+ "aria-label": `Environment: ${chip.name}`,
2307
+ children: chip.name
2308
+ }
2309
+ );
2310
+ }, [envNode, environmentLabel]);
2311
+ const showEnv = Boolean(envChip);
2312
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2313
+ react.VStack,
2314
+ {
2315
+ justify: "center",
2316
+ align: "center",
2317
+ borderRadius: "md",
2318
+ py: 0.5,
2319
+ px: 1,
2320
+ position: "relative",
2321
+ spacing: 0,
2322
+ textAlign: "center",
2323
+ w: "full",
2324
+ children: [
2325
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { justify: "center", align: "center", gap: 2, whiteSpace: "nowrap", children: [
2326
+ showEnv ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2327
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(
2328
+ react.Tooltip,
2329
+ {
2330
+ label: tooltipContent,
2331
+ isDisabled: !tooltipContent,
2332
+ hasArrow: true,
2333
+ placement: "top-start",
2334
+ openDelay: 150,
2335
+ closeOnClick: false,
2336
+ shouldWrapChildren: true,
2337
+ modifiers: [
2338
+ { name: "preventOverflow", options: { padding: 16 } },
2339
+ { name: "offset", options: { offset: [0, 12] } }
2340
+ ],
2341
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2342
+ react.Button,
2343
+ {
2344
+ variant: "unstyled",
2345
+ minW: "auto",
2346
+ h: "auto",
2347
+ p: 0,
2348
+ lineHeight: "1",
2349
+ color: "inherit",
2350
+ onClick: onCopy,
2351
+ "aria-label": "Copy commit ID",
2352
+ isDisabled: !safeCommit,
2353
+ children: envChip
2354
+ }
2355
+ )
2356
+ }
2357
+ ) }),
2358
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" })
2359
+ ] }) : null,
2360
+ shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: hasCopied ? "green.500" : "gray.400", flexShrink: 0, children: shortCommit }) : null,
2361
+ version ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2362
+ shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }) : null,
2363
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "2xs", color: "gray.400", flexShrink: 0, children: [
2364
+ "v",
2365
+ version
2366
+ ] })
2367
+ ] }) : null,
2368
+ onDiagnosticsClick ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2369
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }),
2370
+ /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: "Diagnostics", hasArrow: true, placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(
2371
+ react.Button,
2372
+ {
2373
+ variant: "unstyled",
2374
+ minW: "auto",
2375
+ h: "auto",
2376
+ p: 0,
2377
+ lineHeight: "1",
2378
+ color: "gray.400",
2379
+ _hover: { color: "gray.600" },
2380
+ onClick: (e) => {
2381
+ e.stopPropagation();
2382
+ onDiagnosticsClick();
2383
+ },
2384
+ "aria-label": "Diagnostics",
2385
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.Icon, { as: ActivityIcon, boxSize: 3 })
2386
+ }
2387
+ ) })
2388
+ ] }) : null
2389
+ ] }),
2390
+ metaLine ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "2xs", color: "gray.500", textAlign: "center", whiteSpace: "nowrap", children: [
2391
+ "| ",
2392
+ metaLine,
2393
+ " |"
2394
+ ] }) : null,
2395
+ hasCopied && /* @__PURE__ */ jsxRuntime.jsx(
2396
+ react.Box,
2397
+ {
2398
+ position: "absolute",
2399
+ bottom: "100%",
2400
+ mb: "6px",
2401
+ px: "2",
2402
+ py: "1",
2403
+ fontSize: "xs",
2404
+ bg: "gray.700",
2405
+ color: "white",
2406
+ borderRadius: "md",
2407
+ animation: `${copiedAnimation} 0.9s ease-out`,
2408
+ pointerEvents: "none",
2409
+ children: "Copied"
2410
+ }
2411
+ )
2412
+ ]
2413
+ }
2414
+ );
2415
+ }
2416
+ function Sidebar({ onClose, id }) {
2417
+ const { brandLogoSrc } = useShellChrome();
2418
+ const navItems = useShellNavItems()?.items ?? [];
2419
+ const footer = useShellFooter();
2420
+ const org = useShellOrganization();
2421
+ const currentOrg = org?.currentOrg ?? null;
2422
+ const location = reactRouterDom.useLocation();
2423
+ const sidebarBg = react.useColorModeValue("white", "gray.900");
2424
+ const sidebarBorderColor = react.useColorModeValue("gray.200", "gray.700");
2425
+ const [expandedMenu, setExpandedMenu] = react$1.useState(null);
2426
+ react$1.useEffect(() => {
2427
+ const activeMenu = navItems.find(
2428
+ (item) => item.children && item.children.some((child) => {
2429
+ if (!child.href) return false;
2430
+ const childPath = child.href.split("?")[0];
2431
+ if (!childPath) return false;
2432
+ return location.pathname === childPath || location.pathname.startsWith(`${childPath}/`);
2433
+ })
2434
+ )?.name || null;
2435
+ setExpandedMenu(activeMenu);
2436
+ }, [location.pathname, navItems]);
2437
+ const handleMenuToggle = (menuKey) => {
2438
+ if (!menuKey) {
2439
+ setExpandedMenu(null);
2440
+ return;
2441
+ }
2442
+ setExpandedMenu((prev) => prev === menuKey ? null : menuKey);
2443
+ };
2444
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2445
+ react.Flex,
2446
+ {
2447
+ id: id || "sidebar-container",
2448
+ transition: "3s ease",
2449
+ bg: sidebarBg,
2450
+ borderRight: "1px",
2451
+ borderRightColor: sidebarBorderColor,
2452
+ w: { base: "full", md: 60 },
2453
+ pos: "fixed",
2454
+ h: "100vh",
2455
+ direction: "column",
2456
+ overflow: "hidden",
2457
+ children: [
2458
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { alignItems: "center", mx: "8", mt: "3", mb: "2", justifyContent: "space-between", flexShrink: 0, children: [
2459
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { h: "40px", children: brandLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx(BrandLogo_default, { src: brandLogoSrc, className: "h-full w-auto object-contain" }) : null }),
2460
+ onClose ? /* @__PURE__ */ jsxRuntime.jsx(
2461
+ react.IconButton,
2462
+ {
2463
+ display: { base: "flex", md: "none" },
2464
+ onClick: onClose,
2465
+ variant: "outline",
2466
+ "aria-label": "close menu",
2467
+ icon: /* @__PURE__ */ jsxRuntime.jsx(fi.FiX, {})
2468
+ }
2469
+ ) : null
2470
+ ] }),
2471
+ /* @__PURE__ */ jsxRuntime.jsxs(
2472
+ react.Box,
2473
+ {
2474
+ flex: "1",
2475
+ overflowY: "auto",
2476
+ css: {
2477
+ "&::-webkit-scrollbar": { width: "8px" },
2478
+ "&::-webkit-scrollbar-track": { background: "transparent" },
2479
+ "&::-webkit-scrollbar-thumb": { background: "#CBD5E0", borderRadius: "4px" },
2480
+ "&::-webkit-scrollbar-thumb:hover": { background: "#A0AEC0" }
2481
+ },
2482
+ children: [
2483
+ currentOrg?.logoUrl && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mx: "4", mb: "3", children: /* @__PURE__ */ jsxRuntime.jsx(
2484
+ react.Image,
2485
+ {
2486
+ src: currentOrg.logoUrl,
2487
+ alt: `${currentOrg.name} logo`,
2488
+ maxH: "70px",
2489
+ w: "full",
2490
+ objectFit: "contain",
2491
+ borderRadius: "md"
2492
+ }
2493
+ ) }),
2494
+ /* @__PURE__ */ jsxRuntime.jsx(OrgPill_default, {}),
2495
+ /* @__PURE__ */ jsxRuntime.jsx(ModePill_default, {}),
2496
+ navItems.map((link) => /* @__PURE__ */ jsxRuntime.jsx(
2497
+ NavItem_default,
2498
+ {
2499
+ icon: link.icon,
2500
+ brandIcon: link.brandIcon,
2501
+ href: link.href,
2502
+ subItems: link.children,
2503
+ badge: link.badge,
2504
+ subLabel: link.subLabel,
2505
+ action: link.action,
2506
+ menuKey: String(link.name),
2507
+ isExpanded: expandedMenu === link.name,
2508
+ onToggle: handleMenuToggle,
2509
+ isDisabled: Boolean(link.disabled),
2510
+ children: link.name
2511
+ },
2512
+ link.name
2513
+ ))
2514
+ ]
2515
+ }
2516
+ ),
2517
+ footer && /* @__PURE__ */ jsxRuntime.jsx(
2518
+ react.Box,
2519
+ {
2520
+ px: "1",
2521
+ py: "1",
2522
+ borderTop: "1px",
2523
+ borderColor: "gray.200",
2524
+ flexShrink: 0,
2525
+ bg: sidebarBg,
2526
+ zIndex: 1,
2527
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.VStack, { align: "stretch", spacing: 2, px: "3", py: "2", children: [
2528
+ /* @__PURE__ */ jsxRuntime.jsx(react.HStack, { spacing: 1, fontSize: "xs", color: "gray.600", wrap: "wrap", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", children: [
2529
+ idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { color: "gray.400", mx: 1, children: "|" }) : null,
2530
+ l.external ? /* @__PURE__ */ jsxRuntime.jsx(
2531
+ react.Link,
2532
+ {
2533
+ href: l.href,
2534
+ isExternal: true,
2535
+ _hover: { textDecoration: "underline", opacity: 1 },
2536
+ children: l.label
2537
+ }
2538
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2539
+ react.Link,
2540
+ {
2541
+ as: reactRouterDom.NavLink,
2542
+ to: l.href,
2543
+ _hover: { textDecoration: "underline", opacity: 1 },
2544
+ children: l.label
2545
+ }
2546
+ )
2547
+ ] }, `${l.label}-${idx}`)) }),
2548
+ (footer.version || footer.commit || footer.commitFull || footer.environmentLabel || footer.envNode) && /* @__PURE__ */ jsxRuntime.jsx(
2549
+ CommitCopyButton,
2550
+ {
2551
+ envNode: footer.envNode,
2552
+ environmentLabel: footer.environmentLabel,
2553
+ commit: footer.commitFull ?? footer.commit,
2554
+ version: footer.version,
2555
+ commitDate: footer.commitDate,
2556
+ commitAuthor: footer.commitAuthor,
2557
+ commitLog: footer.commitLog,
2558
+ onDiagnosticsClick: footer.onDiagnosticsClick
2559
+ }
2560
+ )
2561
+ ] })
2562
+ }
2563
+ )
2564
+ ]
2565
+ }
2566
+ );
2567
+ }
2568
+ function Footer({ buildInfo }) {
2569
+ return /* @__PURE__ */ jsxRuntime.jsx(
2570
+ react.Box,
2571
+ {
2572
+ as: "footer",
2573
+ px: 4,
2574
+ py: 2,
2575
+ borderTop: "1px solid",
2576
+ borderColor: "gray.200",
2577
+ fontSize: "xs",
2578
+ color: "gray.500",
2579
+ children: /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { children: [
2580
+ "politogy ",
2581
+ buildInfo ? `\xB7 ${buildInfo}` : ""
1328
2582
  ] })
1329
- ] }) })
2583
+ }
2584
+ );
2585
+ }
2586
+ function AppLayout({
2587
+ children,
2588
+ buildInfo,
2589
+ modalsSlot,
2590
+ notificationsSlot,
2591
+ overlaySlot
2592
+ }) {
2593
+ const [mobileOpen, setMobileOpen] = react$1.useState(false);
2594
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", minH: "100vh", children: [
2595
+ /* @__PURE__ */ jsxRuntime.jsx(
2596
+ Sidebar,
2597
+ {
2598
+ onClose: mobileOpen ? () => setMobileOpen(false) : void 0
2599
+ }
2600
+ ),
2601
+ /* @__PURE__ */ jsxRuntime.jsx(Header, { onOpen: () => setMobileOpen(true) }),
2602
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "main", flex: "1", ml: { base: 0, md: 60 }, position: "relative", overflow: "auto", children: [
2603
+ children,
2604
+ notificationsSlot
2605
+ ] }),
2606
+ buildInfo ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { buildInfo }) : null,
2607
+ modalsSlot,
2608
+ overlaySlot
1330
2609
  ] });
1331
2610
  }
1332
2611
  var SUPPORT_REQUEST_SUCCESS_MESSAGE = "Your support request was sent to Politogy leadership.";
@@ -1613,191 +2892,58 @@ function LifebuoyIcon() {
1613
2892
  }
1614
2893
  );
1615
2894
  }
1616
- function avatarUrlFromUser(user) {
1617
- if (!user) return void 0;
1618
- const meta = user.raw.user_metadata;
1619
- return meta?.avatar_url ?? void 0;
1620
- }
1621
- function UserMenu() {
1622
- const { user, status, signOut } = useShellAuth();
1623
- const { myProfileHref } = useShellChrome();
1624
- const linked = useShellLinkedAccounts();
1625
- const { colorMode, toggleColorMode } = react.useColorMode();
1626
- if (status !== "authenticated" || !user) return null;
1627
- const displayName = user.displayName ?? user.email ?? "User";
1628
- const avatarUrl = avatarUrlFromUser(user);
1629
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Menu, { placement: "bottom-end", children: [
1630
- /* @__PURE__ */ jsxRuntime.jsx(
1631
- react.MenuButton,
1632
- {
1633
- "aria-label": "Open account menu",
1634
- as: react.Box,
1635
- cursor: "pointer",
1636
- borderRadius: "full",
1637
- _hover: { opacity: 0.85 },
1638
- children: /* @__PURE__ */ jsxRuntime.jsx(react.Avatar, { size: "sm", name: displayName, src: avatarUrl })
1639
- }
1640
- ),
1641
- /* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.MenuList, { zIndex: 1500, children: [
1642
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: 3, py: 2, children: [
1643
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "600", noOfLines: 1, children: displayName }),
1644
- user.email && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", noOfLines: 1, children: user.email })
1645
- ] }),
1646
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1647
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { as: "a", href: myProfileHref, children: "My Profile" }),
1648
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { onClick: toggleColorMode, children: colorMode === "light" ? "Switch to dark mode" : "Switch to light mode" }),
1649
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { onClick: () => void signOut(), children: "Sign out" }),
1650
- linked && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1651
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuDivider, {}),
1652
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: 3, pt: 1, pb: 1, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", textTransform: "uppercase", letterSpacing: "0.08em", color: "gray.500", children: "Switch account" }) }),
1653
- linked.linkedAccounts.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { isDisabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", color: "gray.500", children: "No linked accounts" }) }),
1654
- linked.linkedAccounts.map((acct) => /* @__PURE__ */ jsxRuntime.jsx(
1655
- react.MenuItem,
1656
- {
1657
- onClick: () => {
1658
- void linked.switchToAccount(acct.id);
1659
- },
1660
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, align: "center", children: [
1661
- /* @__PURE__ */ jsxRuntime.jsx(
1662
- react.Avatar,
1663
- {
1664
- size: "xs",
1665
- name: acct.displayName ?? acct.email,
1666
- src: acct.avatarUrl ?? void 0
1667
- }
1668
- ),
1669
- /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", minW: 0, children: [
1670
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "600", noOfLines: 1, children: acct.displayName ?? acct.email }),
1671
- acct.displayName && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "gray.500", noOfLines: 1, children: acct.email })
1672
- ] })
1673
- ] })
1674
- },
1675
- acct.id
1676
- )),
1677
- /* @__PURE__ */ jsxRuntime.jsx(react.MenuItem, { onClick: linked.onAddAccount, children: "Add account" })
1678
- ] })
1679
- ] }) })
1680
- ] });
1681
- }
1682
- function Header({ extrasSlot }) {
1683
- const { devNotesMenu } = useShellChrome();
1684
- return /* @__PURE__ */ jsxRuntime.jsxs(
1685
- react.Flex,
1686
- {
1687
- as: "header",
1688
- align: "center",
1689
- px: 4,
1690
- h: "56px",
1691
- bg: "white",
1692
- borderBottom: "1px solid",
1693
- borderColor: "gray.200",
1694
- gap: 3,
1695
- children: [
1696
- /* @__PURE__ */ jsxRuntime.jsx(react.Heading, { size: "md", color: "brand.500", children: "politogy" }),
1697
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(OrgSelector, {}) }),
1698
- /* @__PURE__ */ jsxRuntime.jsx(react.Spacer, {}),
1699
- extrasSlot && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { children: extrasSlot }),
1700
- /* @__PURE__ */ jsxRuntime.jsx(SupportRequestButton, {}),
1701
- devNotesMenu,
1702
- /* @__PURE__ */ jsxRuntime.jsx(UserMenu, {})
1703
- ]
1704
- }
1705
- );
1706
- }
1707
- function Sidebar({ entries, currentApp, footerSlot }) {
1708
- return /* @__PURE__ */ jsxRuntime.jsxs(
1709
- react.Box,
1710
- {
1711
- as: "nav",
1712
- w: "200px",
1713
- minH: "100%",
1714
- bg: "gray.50",
1715
- borderRight: "1px solid",
1716
- borderColor: "gray.200",
1717
- p: 3,
1718
- display: "flex",
1719
- flexDirection: "column",
1720
- children: [
1721
- /* @__PURE__ */ jsxRuntime.jsx(react.VStack, { align: "stretch", spacing: 1, children: entries.filter((e) => e.visible !== false).map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
1722
- react.Link,
1723
- {
1724
- href: entry.path,
1725
- "data-current": entry.id === currentApp,
1726
- px: 3,
1727
- py: 2,
1728
- borderRadius: "md",
1729
- fontWeight: entry.id === currentApp ? "bold" : "normal",
1730
- bg: entry.id === currentApp ? "brand.50" : "transparent",
1731
- _hover: { bg: "gray.100" },
1732
- children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { children: entry.label })
1733
- },
1734
- entry.id
1735
- )) }),
1736
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1" }),
1737
- footerSlot && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { pt: 3, children: footerSlot })
1738
- ]
1739
- }
1740
- );
1741
- }
1742
- function Footer({ buildInfo }) {
1743
- return /* @__PURE__ */ jsxRuntime.jsx(
1744
- react.Box,
1745
- {
1746
- as: "footer",
1747
- px: 4,
1748
- py: 2,
1749
- borderTop: "1px solid",
1750
- borderColor: "gray.200",
1751
- fontSize: "xs",
1752
- color: "gray.500",
1753
- children: /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { children: [
1754
- "politogy ",
1755
- buildInfo ? `\xB7 ${buildInfo}` : ""
1756
- ] })
1757
- }
1758
- );
1759
- }
1760
- function AppLayout({
1761
- children,
1762
- buildInfo,
1763
- headerExtrasSlot,
1764
- modalsSlot,
1765
- notificationsSlot,
1766
- sidebarFooterSlot,
1767
- overlaySlot
1768
- }) {
1769
- const currentApp = useCurrentApp();
1770
- const entries = useAppRegistry();
1771
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", minH: "100vh", children: [
1772
- /* @__PURE__ */ jsxRuntime.jsx(Header, { extrasSlot: headerExtrasSlot }),
1773
- /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { flex: "1", minH: 0, children: [
1774
- /* @__PURE__ */ jsxRuntime.jsx(Sidebar, { entries, currentApp, footerSlot: sidebarFooterSlot }),
1775
- /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "main", flex: "1", overflow: "auto", position: "relative", children: [
1776
- children,
1777
- notificationsSlot
1778
- ] })
1779
- ] }),
1780
- /* @__PURE__ */ jsxRuntime.jsx(Footer, { buildInfo }),
1781
- modalsSlot,
1782
- overlaySlot
1783
- ] });
1784
- }
1785
2895
  function ShellChrome({
1786
2896
  children,
1787
2897
  organization,
1788
2898
  linkedAccounts,
1789
2899
  support,
1790
2900
  devNotesMenu,
1791
- myProfileHref
2901
+ myProfileHref,
2902
+ mode,
2903
+ navItems,
2904
+ navigationPreference,
2905
+ role,
2906
+ footer,
2907
+ brandLogoSrc,
2908
+ headerIconsSlot,
2909
+ organizationSettingsHref,
2910
+ showContinueOnboarding,
2911
+ onContinueOnboarding,
2912
+ pageTitle,
2913
+ breadcrumbs,
2914
+ headerActions
1792
2915
  }) {
1793
2916
  const chrome = react$1.useMemo(
1794
2917
  () => ({
1795
2918
  devNotesMenu: devNotesMenu ?? null,
1796
- myProfileHref: myProfileHref ?? "/my-profile"
2919
+ myProfileHref: myProfileHref ?? "/my-profile",
2920
+ brandLogoSrc,
2921
+ headerIconsSlot,
2922
+ organizationSettingsHref,
2923
+ showContinueOnboarding,
2924
+ onContinueOnboarding,
2925
+ pageTitle,
2926
+ breadcrumbs,
2927
+ headerActions
1797
2928
  }),
1798
- [devNotesMenu, myProfileHref]
2929
+ [
2930
+ devNotesMenu,
2931
+ myProfileHref,
2932
+ brandLogoSrc,
2933
+ headerIconsSlot,
2934
+ organizationSettingsHref,
2935
+ showContinueOnboarding,
2936
+ onContinueOnboarding,
2937
+ pageTitle,
2938
+ breadcrumbs,
2939
+ headerActions
2940
+ ]
2941
+ );
2942
+ const navItemsValue = react$1.useMemo(
2943
+ () => navItems ? { items: navItems } : null,
2944
+ [navItems]
1799
2945
  );
1800
- 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(ShellChromeContext.Provider, { value: chrome, children }) }) }) });
2946
+ 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 }) }) }) }) }) }) }) }) });
1801
2947
  }
1802
2948
 
1803
2949
  // src/sw/registerShellPrecache.ts
@@ -1831,33 +2977,55 @@ function buildSpeculationRules(currentApp, registry) {
1831
2977
  }
1832
2978
 
1833
2979
  exports.AppLayout = AppLayout;
2980
+ exports.BrandIcon = BrandIcon;
2981
+ exports.BrandLogo = BrandLogo;
1834
2982
  exports.BreadcrumbBar = BreadcrumbBar;
1835
2983
  exports.Card = Card;
2984
+ exports.CommitCopyButton = CommitCopyButton;
1836
2985
  exports.Footer = Footer;
1837
2986
  exports.GlobalUiIdProvider = GlobalUiIdProvider;
1838
2987
  exports.Header = Header;
1839
2988
  exports.HeaderTabBar = HeaderTabBar;
1840
- exports.OrgSelector = OrgSelector;
2989
+ exports.ModePill = ModePill;
2990
+ exports.NavItem = NavItem;
2991
+ exports.OrgPill = OrgPill;
2992
+ exports.OrgSelector = OrgPill;
1841
2993
  exports.PageContainer = PageContainer;
1842
2994
  exports.PageHeader = PageHeader;
1843
2995
  exports.ShellChrome = ShellChrome;
1844
2996
  exports.ShellChromeContext = ShellChromeContext;
2997
+ exports.ShellFooterContext = ShellFooterContext;
1845
2998
  exports.ShellLinkedAccountsContext = ShellLinkedAccountsContext;
2999
+ exports.ShellModeContext = ShellModeContext;
3000
+ exports.ShellNavItemsContext = ShellNavItemsContext;
3001
+ exports.ShellNavigationPreferenceContext = ShellNavigationPreferenceContext;
1846
3002
  exports.ShellOrgContext = ShellOrgContext;
1847
3003
  exports.ShellProvider = ShellProvider;
3004
+ exports.ShellRoleContext = ShellRoleContext;
1848
3005
  exports.ShellSupportProviderContext = ShellSupportProviderContext;
1849
3006
  exports.Sidebar = Sidebar;
1850
3007
  exports.SupportRequestButton = SupportRequestButton;
1851
3008
  exports.UserMenu = UserMenu;
1852
3009
  exports.buildSpeculationRules = buildSpeculationRules;
3010
+ exports.getOrganizationBadgeLetters = getOrganizationBadgeLetters;
3011
+ exports.getOrganizationBrandColor = getOrganizationBrandColor;
3012
+ exports.getUserDisplayName = getUserDisplayName;
3013
+ exports.hexToRgba = hexToRgba;
3014
+ exports.lightenHexColor = lightenHexColor;
3015
+ exports.normalizeHexColor = normalizeHexColor;
1853
3016
  exports.politogyTheme = politogyTheme;
1854
3017
  exports.registerShellPrecache = registerShellPrecache;
1855
3018
  exports.useAppRegistry = useAppRegistry;
1856
3019
  exports.useCurrentApp = useCurrentApp;
1857
3020
  exports.useShellAuth = useShellAuth;
1858
3021
  exports.useShellChrome = useShellChrome;
3022
+ exports.useShellFooter = useShellFooter;
1859
3023
  exports.useShellLinkedAccounts = useShellLinkedAccounts;
3024
+ exports.useShellMode = useShellMode;
3025
+ exports.useShellNavItems = useShellNavItems;
3026
+ exports.useShellNavigationPreference = useShellNavigationPreference;
1860
3027
  exports.useShellOrganization = useShellOrganization;
3028
+ exports.useShellRole = useShellRole;
1861
3029
  exports.useShellSupport = useShellSupport;
1862
3030
  //# sourceMappingURL=index.cjs.map
1863
3031
  //# sourceMappingURL=index.cjs.map