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