@the-portland-company/shell 0.1.0 → 0.2.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,8 +1,8 @@
1
- import { extendTheme, Tooltip, ChakraProvider, Flex, Heading, Spacer, Box, Menu, MenuButton, Avatar, MenuList, MenuItem, VStack, Link as Link$1, Text } from '@chakra-ui/react';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { extendTheme, Tooltip, ChakraProvider, Menu, MenuButton, Box, HStack, Text, Portal, MenuList, MenuItem, Flex, MenuDivider, useDisclosure, useToast, IconButton, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, VStack, Textarea, ModalFooter, Button, useColorMode, Avatar, Heading, Spacer, Link as Link$1, Image } from '@chakra-ui/react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import clsx2 from 'clsx';
4
4
  import { Link, useLocation } from 'react-router-dom';
5
- import { createContext, useEffect, useState, useMemo, useContext } from 'react';
5
+ import { createContext, useEffect, useState, useMemo, useContext, useCallback } from 'react';
6
6
 
7
7
  // src/theme/index.ts
8
8
 
@@ -1115,6 +1115,10 @@ function GlobalUiIdProvider() {
1115
1115
  }
1116
1116
  var ShellAuthContext = createContext(null);
1117
1117
  var ShellMetaContext = createContext(null);
1118
+ var ShellOrgContext = createContext(null);
1119
+ var ShellLinkedAccountsContext = createContext(null);
1120
+ var ShellSupportProviderContext = createContext(null);
1121
+ var ShellChromeContext = createContext(null);
1118
1122
  function toShellUser(session) {
1119
1123
  if (!session?.user) return null;
1120
1124
  const su = session.user;
@@ -1131,7 +1135,12 @@ function ShellProvider({
1131
1135
  supabaseClient,
1132
1136
  currentApp,
1133
1137
  appRegistry,
1134
- basename
1138
+ basename,
1139
+ organization,
1140
+ linkedAccounts,
1141
+ support,
1142
+ devNotesMenu,
1143
+ myProfileHref
1135
1144
  }) {
1136
1145
  const [status, setStatus] = useState("loading");
1137
1146
  const [session, setSession] = useState(null);
@@ -1170,7 +1179,14 @@ function ShellProvider({
1170
1179
  }),
1171
1180
  [currentApp, appRegistry, basename]
1172
1181
  );
1173
- return /* @__PURE__ */ jsx(ShellAuthContext.Provider, { value, children: /* @__PURE__ */ jsx(ShellMetaContext.Provider, { value: meta, children: /* @__PURE__ */ jsx(ChakraProvider, { theme: politogyTheme, children }) }) });
1182
+ const chrome = useMemo(
1183
+ () => ({
1184
+ devNotesMenu: devNotesMenu ?? null,
1185
+ myProfileHref: myProfileHref ?? "/my-profile"
1186
+ }),
1187
+ [devNotesMenu, myProfileHref]
1188
+ );
1189
+ return /* @__PURE__ */ jsx(ShellAuthContext.Provider, { value, children: /* @__PURE__ */ jsx(ShellMetaContext.Provider, { value: meta, children: /* @__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(ShellChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ jsx(ChakraProvider, { theme: politogyTheme, children }) }) }) }) }) }) });
1174
1190
  }
1175
1191
  function useShellAuth() {
1176
1192
  const ctx = useContext(ShellAuthContext);
@@ -1193,8 +1209,472 @@ function useCurrentApp() {
1193
1209
  }
1194
1210
  return meta.currentApp;
1195
1211
  }
1212
+ function useShellOrganization() {
1213
+ return useContext(ShellOrgContext);
1214
+ }
1215
+ function useShellLinkedAccounts() {
1216
+ return useContext(ShellLinkedAccountsContext);
1217
+ }
1218
+ function useShellSupport() {
1219
+ return useContext(ShellSupportProviderContext);
1220
+ }
1221
+ function useShellChrome() {
1222
+ const ctx = useContext(ShellChromeContext);
1223
+ if (!ctx) {
1224
+ throw new Error("useShellChrome must be used inside <ShellProvider>");
1225
+ }
1226
+ return ctx;
1227
+ }
1228
+ var HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
1229
+ function normalizeHexColor(value) {
1230
+ const trimmed = value?.trim();
1231
+ if (!trimmed || !HEX_COLOR_PATTERN.test(trimmed)) return null;
1232
+ if (trimmed.length === 7) return trimmed;
1233
+ const [r, g, b] = trimmed.slice(1).split("");
1234
+ return `#${r}${r}${g}${g}${b}${b}`;
1235
+ }
1236
+ function getOrganizationBadgeLetters(name) {
1237
+ const normalizedName = String(name || "").trim().replace(/[^a-zA-Z0-9\s]+/g, " ");
1238
+ const parts = normalizedName.split(/\s+/).filter(Boolean);
1239
+ if (parts.length >= 2) {
1240
+ return `${parts[0][0] || ""}${parts[1][0] || ""}`.toUpperCase();
1241
+ }
1242
+ const compact = parts[0] || normalizedName.replace(/\s+/g, "");
1243
+ return compact.slice(0, 2).toUpperCase() || "OR";
1244
+ }
1245
+ function OrgBadge({ name, logoUrl, brandColor, size }) {
1246
+ const normalizedColor = normalizeHexColor(brandColor);
1247
+ const badgeColor = normalizedColor || "#5a5a5a";
1248
+ const badgeLetters = getOrganizationBadgeLetters(name);
1249
+ return /* @__PURE__ */ jsx(
1250
+ Box,
1251
+ {
1252
+ boxSize: size,
1253
+ borderRadius: "full",
1254
+ overflow: "hidden",
1255
+ border: "1px solid white",
1256
+ flexShrink: 0,
1257
+ bg: logoUrl ? "whiteAlpha.300" : badgeColor,
1258
+ color: "white",
1259
+ display: "inline-flex",
1260
+ alignItems: "center",
1261
+ justifyContent: "center",
1262
+ fontSize: "xs",
1263
+ fontWeight: "bold",
1264
+ letterSpacing: "0.08em",
1265
+ textTransform: "uppercase",
1266
+ children: logoUrl ? /* @__PURE__ */ jsx(Image, { src: logoUrl, alt: `${name ?? "Organization"} logo`, boxSize: "full", objectFit: "cover" }) : badgeLetters
1267
+ }
1268
+ );
1269
+ }
1270
+ function OrgSelector() {
1271
+ const org = useShellOrganization();
1272
+ if (!org) return null;
1273
+ const { currentOrg, organizations, switchOrganization, createOrgHref } = org;
1274
+ const others = organizations.filter((o) => o.id !== currentOrg?.id);
1275
+ return /* @__PURE__ */ jsxs(Menu, { placement: "bottom-start", children: [
1276
+ /* @__PURE__ */ jsx(
1277
+ MenuButton,
1278
+ {
1279
+ as: Box,
1280
+ cursor: "pointer",
1281
+ px: 2,
1282
+ py: 1,
1283
+ borderRadius: "md",
1284
+ _hover: { bg: "gray.100" },
1285
+ _active: { bg: "gray.200" },
1286
+ "aria-label": "Switch organization",
1287
+ children: /* @__PURE__ */ jsxs(HStack, { spacing: 2, align: "center", children: [
1288
+ currentOrg ? /* @__PURE__ */ jsxs(Fragment, { children: [
1289
+ /* @__PURE__ */ jsx(
1290
+ OrgBadge,
1291
+ {
1292
+ name: currentOrg.name,
1293
+ logoUrl: currentOrg.logoUrl,
1294
+ brandColor: currentOrg.brandColor,
1295
+ size: "24px"
1296
+ }
1297
+ ),
1298
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "600", color: "gray.800", noOfLines: 1, children: currentOrg.name })
1299
+ ] }) : /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "gray.500", children: "Select organization" }),
1300
+ /* @__PURE__ */ jsx(Text, { as: "span", fontSize: "xs", color: "gray.500", "aria-hidden": "true", children: "\u25BE" })
1301
+ ] })
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(MenuList, { zIndex: 1500, children: [
1305
+ others.length === 0 && /* @__PURE__ */ jsx(MenuItem, { isDisabled: true, children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "gray.500", children: "No other organizations" }) }),
1306
+ others.map((o) => /* @__PURE__ */ jsx(
1307
+ MenuItem,
1308
+ {
1309
+ onClick: () => {
1310
+ void switchOrganization(o.id);
1311
+ },
1312
+ children: /* @__PURE__ */ jsxs(HStack, { spacing: 2, align: "center", children: [
1313
+ /* @__PURE__ */ jsx(OrgBadge, { name: o.name, logoUrl: o.logoUrl, brandColor: o.brandColor, size: "20px" }),
1314
+ /* @__PURE__ */ jsx(Flex, { direction: "column", children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "600", children: o.name }) })
1315
+ ] })
1316
+ },
1317
+ o.id
1318
+ )),
1319
+ createOrgHref && /* @__PURE__ */ jsxs(Fragment, { children: [
1320
+ /* @__PURE__ */ jsx(MenuDivider, {}),
1321
+ /* @__PURE__ */ jsx(MenuItem, { as: "a", href: createOrgHref, children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "brand.500", fontWeight: "600", children: "Create organization" }) })
1322
+ ] })
1323
+ ] }) })
1324
+ ] });
1325
+ }
1326
+ var SUPPORT_REQUEST_SUCCESS_MESSAGE = "Your support request was sent to Politogy leadership.";
1327
+ function splitName(value) {
1328
+ const trimmed = String(value ?? "").trim();
1329
+ if (!trimmed) return { firstName: "", lastName: "" };
1330
+ const [first, ...rest] = trimmed.split(/\s+/);
1331
+ return { firstName: first ?? "", lastName: rest.join(" ") };
1332
+ }
1333
+ function resolveSupportProfile(user) {
1334
+ if (!user) {
1335
+ return { firstName: "", lastName: "", email: "", phone: "", missingEmail: true, missingPhone: true };
1336
+ }
1337
+ const meta = user.raw.user_metadata ?? {};
1338
+ const resolvedName = String(meta.full_name ?? meta.name ?? "").trim() || String(user.email ?? "").split("@")[0] || "";
1339
+ const { firstName, lastName } = splitName(resolvedName);
1340
+ const email = String(user.email ?? "").trim();
1341
+ const phone = String(meta.phone ?? "").trim();
1342
+ return {
1343
+ firstName,
1344
+ lastName,
1345
+ email,
1346
+ phone,
1347
+ missingEmail: email.length === 0,
1348
+ missingPhone: phone.length === 0
1349
+ };
1350
+ }
1351
+ function formatTimestamp(date) {
1352
+ return date.toLocaleString("en-US", {
1353
+ year: "numeric",
1354
+ month: "short",
1355
+ day: "numeric",
1356
+ hour: "numeric",
1357
+ minute: "2-digit",
1358
+ second: "2-digit",
1359
+ hour12: true
1360
+ });
1361
+ }
1362
+ function buildSupportRequestMessage(input) {
1363
+ const submittedAt = input.submittedAt ?? /* @__PURE__ */ new Date();
1364
+ const iso = submittedAt.toISOString();
1365
+ const display = formatTimestamp(submittedAt);
1366
+ const trimmedSearch = String(input.search ?? "").trim();
1367
+ return [
1368
+ "A user requested support.",
1369
+ "",
1370
+ `First Name: ${input.firstName.trim() || "Unknown"}`,
1371
+ `Last Name: ${input.lastName.trim() || "Unknown"}`,
1372
+ `Organization: ${(input.organizationName ?? "").trim() || "Unknown"}`,
1373
+ `Email: ${input.email.trim()}`,
1374
+ `Phone: ${input.phone.trim()}`,
1375
+ `Submitted At (ISO): ${iso}`,
1376
+ `Submitted At (Local): ${display}`,
1377
+ `Page Path: ${input.pathname.trim() || "/"}`,
1378
+ `Page Title: ${(input.pageTitle ?? "").trim() || "Unknown"}`,
1379
+ `Query String: ${trimmedSearch || "None"}`
1380
+ ].join("\n");
1381
+ }
1382
+ function SupportRequestButton() {
1383
+ const support = useShellSupport();
1384
+ const { user } = useShellAuth();
1385
+ const org = useShellOrganization();
1386
+ const { isOpen, onOpen, onClose } = useDisclosure();
1387
+ const toast = useToast();
1388
+ const profile = useMemo(() => resolveSupportProfile(user), [user]);
1389
+ const [email, setEmail] = useState("");
1390
+ const [phone, setPhone] = useState("");
1391
+ const [message, setMessage] = useState("");
1392
+ const [isSubmitting, setIsSubmitting] = useState(false);
1393
+ useEffect(() => {
1394
+ if (!isOpen) return;
1395
+ setEmail(profile.email);
1396
+ setPhone(profile.phone);
1397
+ setMessage("");
1398
+ setIsSubmitting(false);
1399
+ }, [isOpen, profile.email, profile.phone]);
1400
+ const handleClose = useCallback(() => {
1401
+ if (isSubmitting) return;
1402
+ onClose();
1403
+ }, [isSubmitting, onClose]);
1404
+ const handleSubmit = useCallback(async () => {
1405
+ if (!support) return;
1406
+ const resolvedEmail = (profile.missingEmail ? email : profile.email).trim();
1407
+ const resolvedPhone = (profile.missingPhone ? phone : profile.phone).trim();
1408
+ if (!resolvedEmail) {
1409
+ toast({ title: "Email required", status: "error", duration: 4e3, isClosable: true });
1410
+ return;
1411
+ }
1412
+ if (!resolvedPhone) {
1413
+ toast({ title: "Phone required", status: "error", duration: 4e3, isClosable: true });
1414
+ return;
1415
+ }
1416
+ if (!user?.id) {
1417
+ toast({
1418
+ title: "Unable to send support request",
1419
+ description: "You must be signed in to request support.",
1420
+ status: "error",
1421
+ duration: 4e3,
1422
+ isClosable: true
1423
+ });
1424
+ return;
1425
+ }
1426
+ const context = {
1427
+ pathname: typeof window !== "undefined" ? window.location.pathname : "/",
1428
+ search: typeof window !== "undefined" ? window.location.search : "",
1429
+ pageTitle: typeof document !== "undefined" ? document.title : void 0,
1430
+ organizationName: org?.currentOrg?.name ?? void 0
1431
+ };
1432
+ const formatted = buildSupportRequestMessage({
1433
+ firstName: profile.firstName,
1434
+ lastName: profile.lastName,
1435
+ organizationName: context.organizationName,
1436
+ email: resolvedEmail,
1437
+ phone: resolvedPhone,
1438
+ pathname: context.pathname,
1439
+ search: context.search,
1440
+ pageTitle: context.pageTitle
1441
+ });
1442
+ try {
1443
+ setIsSubmitting(true);
1444
+ await support.submitSupportRequest({
1445
+ email: resolvedEmail,
1446
+ phone: resolvedPhone,
1447
+ message: message.trim() ? `${message.trim()}
1448
+
1449
+ ---
1450
+
1451
+ ${formatted}` : formatted,
1452
+ context
1453
+ });
1454
+ toast({
1455
+ title: "Support request sent",
1456
+ description: SUPPORT_REQUEST_SUCCESS_MESSAGE,
1457
+ status: "success",
1458
+ duration: 5e3,
1459
+ isClosable: true
1460
+ });
1461
+ onClose();
1462
+ } catch (err) {
1463
+ const description = err instanceof Error ? err.message : "Unable to send your support request.";
1464
+ toast({
1465
+ title: "Failed to request support",
1466
+ description,
1467
+ status: "error",
1468
+ duration: 5e3,
1469
+ isClosable: true
1470
+ });
1471
+ } finally {
1472
+ setIsSubmitting(false);
1473
+ }
1474
+ }, [support, profile, email, phone, message, toast, user?.id, onClose, org?.currentOrg?.name]);
1475
+ if (!support) return null;
1476
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1477
+ /* @__PURE__ */ jsx(
1478
+ IconButton,
1479
+ {
1480
+ "aria-label": "Request support",
1481
+ icon: /* @__PURE__ */ jsx(LifebuoyIcon, {}),
1482
+ variant: "ghost",
1483
+ size: "sm",
1484
+ onClick: onOpen
1485
+ }
1486
+ ),
1487
+ /* @__PURE__ */ jsxs(Modal, { isOpen, onClose: handleClose, isCentered: true, children: [
1488
+ /* @__PURE__ */ jsx(ModalOverlay, {}),
1489
+ /* @__PURE__ */ jsxs(ModalContent, { children: [
1490
+ /* @__PURE__ */ jsx(ModalHeader, { children: "Support" }),
1491
+ /* @__PURE__ */ jsx(ModalCloseButton, { isDisabled: isSubmitting }),
1492
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs(VStack, { align: "stretch", spacing: 4, children: [
1493
+ /* @__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." }),
1494
+ /* @__PURE__ */ jsx(Box, { rounded: "md", borderWidth: "1px", borderColor: "gray.200", bg: "gray.50", p: 3, children: /* @__PURE__ */ jsxs(VStack, { align: "stretch", spacing: 1, children: [
1495
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", children: [
1496
+ /* @__PURE__ */ jsx("strong", { children: "Name:" }),
1497
+ " ",
1498
+ [profile.firstName, profile.lastName].filter(Boolean).join(" ") || "Unknown"
1499
+ ] }),
1500
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", children: [
1501
+ /* @__PURE__ */ jsx("strong", { children: "Organization:" }),
1502
+ " ",
1503
+ org?.currentOrg?.name ?? "Unknown"
1504
+ ] }),
1505
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", children: [
1506
+ /* @__PURE__ */ jsx("strong", { children: "Email:" }),
1507
+ " ",
1508
+ (profile.missingEmail ? email : profile.email) || "Missing"
1509
+ ] }),
1510
+ /* @__PURE__ */ jsxs(Text, { fontSize: "sm", children: [
1511
+ /* @__PURE__ */ jsx("strong", { children: "Phone:" }),
1512
+ " ",
1513
+ (profile.missingPhone ? phone : profile.phone) || "Missing"
1514
+ ] })
1515
+ ] }) }),
1516
+ profile.missingEmail && /* @__PURE__ */ jsxs(Box, { children: [
1517
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Email" }),
1518
+ /* @__PURE__ */ jsx(
1519
+ "input",
1520
+ {
1521
+ type: "email",
1522
+ "aria-label": "Email",
1523
+ value: email,
1524
+ onChange: (e) => setEmail(e.target.value),
1525
+ placeholder: "name@example.com",
1526
+ style: {
1527
+ width: "100%",
1528
+ padding: "8px 12px",
1529
+ border: "1px solid #e2e8f0",
1530
+ borderRadius: "6px",
1531
+ fontSize: "14px"
1532
+ }
1533
+ }
1534
+ )
1535
+ ] }),
1536
+ profile.missingPhone && /* @__PURE__ */ jsxs(Box, { children: [
1537
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Phone" }),
1538
+ /* @__PURE__ */ jsx(
1539
+ "input",
1540
+ {
1541
+ "aria-label": "Phone",
1542
+ value: phone,
1543
+ onChange: (e) => setPhone(e.target.value),
1544
+ placeholder: "(503) 555-1212",
1545
+ style: {
1546
+ width: "100%",
1547
+ padding: "8px 12px",
1548
+ border: "1px solid #e2e8f0",
1549
+ borderRadius: "6px",
1550
+ fontSize: "14px"
1551
+ }
1552
+ }
1553
+ )
1554
+ ] }),
1555
+ /* @__PURE__ */ jsxs(Box, { children: [
1556
+ /* @__PURE__ */ jsx(Text, { fontSize: "xs", fontWeight: "600", color: "gray.700", mb: 1, children: "Optional message" }),
1557
+ /* @__PURE__ */ jsx(
1558
+ Textarea,
1559
+ {
1560
+ value: message,
1561
+ onChange: (e) => setMessage(e.target.value),
1562
+ placeholder: "What can we help you with?",
1563
+ rows: 3
1564
+ }
1565
+ )
1566
+ ] })
1567
+ ] }) }),
1568
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1569
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", mr: 3, onClick: handleClose, isDisabled: isSubmitting, children: "Cancel" }),
1570
+ /* @__PURE__ */ jsx(
1571
+ Button,
1572
+ {
1573
+ colorScheme: "orange",
1574
+ onClick: () => void handleSubmit(),
1575
+ isLoading: isSubmitting,
1576
+ loadingText: "Sending",
1577
+ children: "Request Support"
1578
+ }
1579
+ )
1580
+ ] })
1581
+ ] })
1582
+ ] })
1583
+ ] });
1584
+ }
1585
+ function LifebuoyIcon() {
1586
+ return /* @__PURE__ */ jsxs(
1587
+ "svg",
1588
+ {
1589
+ width: "18",
1590
+ height: "18",
1591
+ viewBox: "0 0 24 24",
1592
+ fill: "none",
1593
+ stroke: "currentColor",
1594
+ strokeWidth: "2",
1595
+ strokeLinecap: "round",
1596
+ strokeLinejoin: "round",
1597
+ "aria-hidden": "true",
1598
+ children: [
1599
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1600
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
1601
+ /* @__PURE__ */ jsx("line", { x1: "4.93", y1: "4.93", x2: "9.17", y2: "9.17" }),
1602
+ /* @__PURE__ */ jsx("line", { x1: "14.83", y1: "14.83", x2: "19.07", y2: "19.07" }),
1603
+ /* @__PURE__ */ jsx("line", { x1: "14.83", y1: "9.17", x2: "19.07", y2: "4.93" }),
1604
+ /* @__PURE__ */ jsx("line", { x1: "14.83", y1: "9.17", x2: "18.36", y2: "5.64" }),
1605
+ /* @__PURE__ */ jsx("line", { x1: "4.93", y1: "19.07", x2: "9.17", y2: "14.83" })
1606
+ ]
1607
+ }
1608
+ );
1609
+ }
1610
+ function avatarUrlFromUser(user) {
1611
+ if (!user) return void 0;
1612
+ const meta = user.raw.user_metadata;
1613
+ return meta?.avatar_url ?? void 0;
1614
+ }
1615
+ function UserMenu() {
1616
+ const { user, status, signOut } = useShellAuth();
1617
+ const { myProfileHref } = useShellChrome();
1618
+ const linked = useShellLinkedAccounts();
1619
+ const { colorMode, toggleColorMode } = useColorMode();
1620
+ if (status !== "authenticated" || !user) return null;
1621
+ const displayName = user.displayName ?? user.email ?? "User";
1622
+ const avatarUrl = avatarUrlFromUser(user);
1623
+ return /* @__PURE__ */ jsxs(Menu, { placement: "bottom-end", children: [
1624
+ /* @__PURE__ */ jsx(
1625
+ MenuButton,
1626
+ {
1627
+ "aria-label": "Open account menu",
1628
+ as: Box,
1629
+ cursor: "pointer",
1630
+ borderRadius: "full",
1631
+ _hover: { opacity: 0.85 },
1632
+ children: /* @__PURE__ */ jsx(Avatar, { size: "sm", name: displayName, src: avatarUrl })
1633
+ }
1634
+ ),
1635
+ /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(MenuList, { zIndex: 1500, children: [
1636
+ /* @__PURE__ */ jsxs(Box, { px: 3, py: 2, children: [
1637
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "600", noOfLines: 1, children: displayName }),
1638
+ user.email && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "gray.500", noOfLines: 1, children: user.email })
1639
+ ] }),
1640
+ /* @__PURE__ */ jsx(MenuDivider, {}),
1641
+ /* @__PURE__ */ jsx(MenuItem, { as: "a", href: myProfileHref, children: "My Profile" }),
1642
+ /* @__PURE__ */ jsx(MenuItem, { onClick: toggleColorMode, children: colorMode === "light" ? "Switch to dark mode" : "Switch to light mode" }),
1643
+ /* @__PURE__ */ jsx(MenuItem, { onClick: () => void signOut(), children: "Sign out" }),
1644
+ linked && /* @__PURE__ */ jsxs(Fragment, { children: [
1645
+ /* @__PURE__ */ jsx(MenuDivider, {}),
1646
+ /* @__PURE__ */ jsx(Box, { px: 3, pt: 1, pb: 1, children: /* @__PURE__ */ jsx(Text, { fontSize: "xs", textTransform: "uppercase", letterSpacing: "0.08em", color: "gray.500", children: "Switch account" }) }),
1647
+ linked.linkedAccounts.length === 0 && /* @__PURE__ */ jsx(MenuItem, { isDisabled: true, children: /* @__PURE__ */ jsx(Text, { fontSize: "sm", color: "gray.500", children: "No linked accounts" }) }),
1648
+ linked.linkedAccounts.map((acct) => /* @__PURE__ */ jsx(
1649
+ MenuItem,
1650
+ {
1651
+ onClick: () => {
1652
+ void linked.switchToAccount(acct.id);
1653
+ },
1654
+ children: /* @__PURE__ */ jsxs(HStack, { spacing: 2, align: "center", children: [
1655
+ /* @__PURE__ */ jsx(
1656
+ Avatar,
1657
+ {
1658
+ size: "xs",
1659
+ name: acct.displayName ?? acct.email,
1660
+ src: acct.avatarUrl ?? void 0
1661
+ }
1662
+ ),
1663
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", minW: 0, children: [
1664
+ /* @__PURE__ */ jsx(Text, { fontSize: "sm", fontWeight: "600", noOfLines: 1, children: acct.displayName ?? acct.email }),
1665
+ acct.displayName && /* @__PURE__ */ jsx(Text, { fontSize: "xs", color: "gray.500", noOfLines: 1, children: acct.email })
1666
+ ] })
1667
+ ] })
1668
+ },
1669
+ acct.id
1670
+ )),
1671
+ /* @__PURE__ */ jsx(MenuItem, { onClick: linked.onAddAccount, children: "Add account" })
1672
+ ] })
1673
+ ] }) })
1674
+ ] });
1675
+ }
1196
1676
  function Header({ extrasSlot }) {
1197
- const { user, signOut, status } = useShellAuth();
1677
+ const { devNotesMenu } = useShellChrome();
1198
1678
  return /* @__PURE__ */ jsxs(
1199
1679
  Flex,
1200
1680
  {
@@ -1205,14 +1685,15 @@ function Header({ extrasSlot }) {
1205
1685
  bg: "white",
1206
1686
  borderBottom: "1px solid",
1207
1687
  borderColor: "gray.200",
1688
+ gap: 3,
1208
1689
  children: [
1209
1690
  /* @__PURE__ */ jsx(Heading, { size: "md", color: "brand.500", children: "politogy" }),
1691
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(OrgSelector, {}) }),
1210
1692
  /* @__PURE__ */ jsx(Spacer, {}),
1211
- extrasSlot && /* @__PURE__ */ jsx(Box, { mr: 3, children: extrasSlot }),
1212
- status === "authenticated" && user && /* @__PURE__ */ jsxs(Menu, { children: [
1213
- /* @__PURE__ */ jsx(MenuButton, { children: /* @__PURE__ */ jsx(Avatar, { size: "sm", name: user.displayName ?? user.email ?? "" }) }),
1214
- /* @__PURE__ */ jsx(MenuList, { children: /* @__PURE__ */ jsx(MenuItem, { onClick: () => signOut(), children: "Sign out" }) })
1215
- ] })
1693
+ extrasSlot && /* @__PURE__ */ jsx(Box, { children: extrasSlot }),
1694
+ /* @__PURE__ */ jsx(SupportRequestButton, {}),
1695
+ devNotesMenu,
1696
+ /* @__PURE__ */ jsx(UserMenu, {})
1216
1697
  ]
1217
1698
  }
1218
1699
  );
@@ -1326,6 +1807,6 @@ function buildSpeculationRules(currentApp, registry) {
1326
1807
  return JSON.stringify(payload);
1327
1808
  }
1328
1809
 
1329
- export { AppLayout, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, PageContainer, PageHeader, ShellProvider, Sidebar, buildSpeculationRules, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth };
1810
+ export { AppLayout, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, OrgSelector, PageContainer, PageHeader, ShellProvider, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellLinkedAccounts, useShellOrganization, useShellSupport };
1330
1811
  //# sourceMappingURL=index.js.map
1331
1812
  //# sourceMappingURL=index.js.map