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