@the-portland-company/shell 0.35.4 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/active-org.cjs +74 -0
- package/dist/active-org.cjs.map +1 -0
- package/dist/active-org.d.cts +30 -0
- package/dist/active-org.d.ts +30 -0
- package/dist/active-org.js +3 -0
- package/dist/active-org.js.map +1 -0
- package/dist/app-chrome.cjs +266 -98
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.js +44 -6
- package/dist/app-chrome.js.map +1 -1
- package/dist/app-icons.cjs +9 -3
- package/dist/app-icons.cjs.map +1 -1
- package/dist/app-icons.js +9 -3
- package/dist/app-icons.js.map +1 -1
- package/dist/{chunk-HZXYGEEA.js → chunk-LIGKGXFT.js} +98 -96
- package/dist/chunk-LIGKGXFT.js.map +1 -0
- package/dist/chunk-LL6LSJAH.js +92 -0
- package/dist/chunk-LL6LSJAH.js.map +1 -0
- package/dist/chunk-W2HITMV5.js +64 -0
- package/dist/chunk-W2HITMV5.js.map +1 -0
- package/dist/logout-signal.cjs +99 -0
- package/dist/logout-signal.cjs.map +1 -0
- package/dist/logout-signal.d.cts +56 -0
- package/dist/logout-signal.d.ts +56 -0
- package/dist/logout-signal.js +3 -0
- package/dist/logout-signal.js.map +1 -0
- package/dist/native.cjs +96 -94
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +1 -1
- package/package.json +11 -1
- package/dist/chunk-HZXYGEEA.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.36.0 — 2026-07-24
|
|
2
|
+
|
|
3
|
+
### Added — cross-subdomain active-org + favicon cache-busting
|
|
4
|
+
|
|
5
|
+
`ChromeStateProvider` now seeds the active org from a shared cross-subdomain
|
|
6
|
+
signal (`active-org` module: URL `?org=<uuid>` → `.politogyvrm.com` cookie
|
|
7
|
+
`politogy_active_org`) before falling back to host-only localStorage, and stamps
|
|
8
|
+
`?org=<full-uuid>` into the address bar on every page. Previously each app +
|
|
9
|
+
subdomain restored from its own host-only localStorage and otherwise defaulted
|
|
10
|
+
to the earliest-joined org, so apps diverged onto different orgs and campaign
|
|
11
|
+
apps could land on a Relationship-only org and lock themselves. `setOrgId` now
|
|
12
|
+
writes the shared cookie so the last-used org follows the user across every app,
|
|
13
|
+
and cross-app/core links carry the FULL org uuid (not the lossy last-6 suffix),
|
|
14
|
+
the only collision-free selector for contacts-api membership matching.
|
|
15
|
+
|
|
16
|
+
`toMetadataIcons` stamps the Brand revision onto every favicon URL as
|
|
17
|
+
`?v=<version>` (falling back to `updatedAt`) so an aggressively-cached browser
|
|
18
|
+
picks up a new Brand publish even when the stored filename is unchanged.
|
|
19
|
+
|
|
1
20
|
## 0.32.0 — 2026-07-23
|
|
2
21
|
|
|
3
22
|
### Added — mode-aware navigation (Campaign Mode) with zero new chrome config
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/active-org.ts
|
|
4
|
+
var ACTIVE_ORG_COOKIE = "politogy_active_org";
|
|
5
|
+
var ACTIVE_ORG_QUERY_PARAM = "org";
|
|
6
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7
|
+
function isOrgId(v) {
|
|
8
|
+
return typeof v === "string" && UUID_RE.test(v.trim());
|
|
9
|
+
}
|
|
10
|
+
function readActiveOrgCookie() {
|
|
11
|
+
if (typeof document === "undefined") return null;
|
|
12
|
+
try {
|
|
13
|
+
const m = document.cookie.match(
|
|
14
|
+
new RegExp(`(?:^|;\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`)
|
|
15
|
+
);
|
|
16
|
+
const v = m ? decodeURIComponent(m[1]) : null;
|
|
17
|
+
return isOrgId(v) ? v : null;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function writeActiveOrgCookie(orgId) {
|
|
23
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
24
|
+
if (!isOrgId(orgId)) return;
|
|
25
|
+
const host = window.location.hostname;
|
|
26
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
27
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
28
|
+
document.cookie = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(
|
|
29
|
+
orgId
|
|
30
|
+
)}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
|
|
31
|
+
}
|
|
32
|
+
function readActiveOrgFromUrl(search) {
|
|
33
|
+
if (!search) return null;
|
|
34
|
+
try {
|
|
35
|
+
const qs = search.includes("?") ? search.slice(search.indexOf("?") + 1) : search;
|
|
36
|
+
const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM);
|
|
37
|
+
return isOrgId(v) ? v : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function resolveActiveOrg(search) {
|
|
43
|
+
const fromUrl = readActiveOrgFromUrl(
|
|
44
|
+
search ?? (typeof window !== "undefined" ? window.location.search : null)
|
|
45
|
+
);
|
|
46
|
+
if (fromUrl) return fromUrl;
|
|
47
|
+
return readActiveOrgCookie();
|
|
48
|
+
}
|
|
49
|
+
function activeOrgParam(orgId) {
|
|
50
|
+
return isOrgId(orgId) ? `${ACTIVE_ORG_QUERY_PARAM}=${orgId}` : "";
|
|
51
|
+
}
|
|
52
|
+
function withActiveOrgParam(url, orgId) {
|
|
53
|
+
if (!isOrgId(orgId)) return url;
|
|
54
|
+
try {
|
|
55
|
+
const base = typeof window !== "undefined" ? window.location.origin : "https://politogyvrm.com";
|
|
56
|
+
const u = new URL(url, base);
|
|
57
|
+
u.searchParams.set(ACTIVE_ORG_QUERY_PARAM, orgId);
|
|
58
|
+
return /^https?:\/\//i.test(url) ? u.toString() : `${u.pathname}${u.search}${u.hash}`;
|
|
59
|
+
} catch {
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.ACTIVE_ORG_COOKIE = ACTIVE_ORG_COOKIE;
|
|
65
|
+
exports.ACTIVE_ORG_QUERY_PARAM = ACTIVE_ORG_QUERY_PARAM;
|
|
66
|
+
exports.activeOrgParam = activeOrgParam;
|
|
67
|
+
exports.isOrgId = isOrgId;
|
|
68
|
+
exports.readActiveOrgCookie = readActiveOrgCookie;
|
|
69
|
+
exports.readActiveOrgFromUrl = readActiveOrgFromUrl;
|
|
70
|
+
exports.resolveActiveOrg = resolveActiveOrg;
|
|
71
|
+
exports.withActiveOrgParam = withActiveOrgParam;
|
|
72
|
+
exports.writeActiveOrgCookie = writeActiveOrgCookie;
|
|
73
|
+
//# sourceMappingURL=active-org.cjs.map
|
|
74
|
+
//# sourceMappingURL=active-org.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/active-org.ts"],"names":[],"mappings":"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","file":"active-org.cjs","sourcesContent":["// Universal active-organization selection — the ONE cross-app contract.\n//\n// A user belongs to many orgs; ONE active-org choice must follow them across\n// every Politogy app + subdomain so all apps resolve the SAME org (and campaign\n// apps don't land on a rel-only org and lock themselves). Layers cooperate,\n// mirroring the theme-preference contract:\n// 1. URL `?org=<uuid>` — the authoritative per-request selector. The shared\n// chrome stamps it onto every route so it is bookmarkable + shareable, and\n// each app forwards it to the chrome API as `x-contacts-org`.\n// 2. `.politogyvrm.com` cookie `politogy_active_org` — cross-subdomain vehicle\n// (auth session cookies are host-only) that remembers the LAST USED org so\n// a bare URL in the next app opens on the same org.\n//\n// The stored/most-authoritative value is the FULL org UUID: contacts-api\n// (auth/vrmMembership.selectRequestedMembership) matches the selector by EXACT\n// id first, then falls back to a short suffix that can COLLIDE across a user's\n// orgs — so the full UUID is the only collision-free selector.\n//\n// Server-safe (no 'use client'): constants/resolve helpers are importable from\n// server layouts; the cookie read/write helpers no-op on the server.\n\nexport const ACTIVE_ORG_COOKIE = 'politogy_active_org'\nexport const ACTIVE_ORG_QUERY_PARAM = 'org'\n\n// Loose UUID shape check — we only ever store ids that came from the user's own\n// authoritative membership list, so this just rejects obvious garbage.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport function isOrgId(v: unknown): v is string {\n return typeof v === 'string' && UUID_RE.test(v.trim())\n}\n\n/** Read the shared active-org cookie (browser only; null on server or unset). */\nexport function readActiveOrgCookie(): string | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isOrgId(v) ? v : null\n } catch {\n return null\n }\n}\n\n// Write the cross-subdomain cookie with the SAME scoping rules as the theme\n// cookie: on a *.politogyvrm.com host scope to the parent domain so every app\n// subdomain reads it; on any other host (localhost, *.pages.dev preview) fall\n// back to a host-only cookie. 1-year, SameSite=Lax, Secure on https.\nexport function writeActiveOrgCookie(orgId: string): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n if (!isOrgId(orgId)) return\n const host = window.location.hostname\n const domainAttr = /(^|\\.)politogyvrm\\.com$/i.test(host) ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n document.cookie = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(\n orgId,\n )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n}\n\n/**\n * Read the `?org=` selector from a URL search string (full UUID only). Accepts a\n * `location.search`-style string or a full URL.\n */\nexport function readActiveOrgFromUrl(search: string | null | undefined): string | null {\n if (!search) return null\n try {\n const qs = search.includes('?') ? search.slice(search.indexOf('?') + 1) : search\n const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM)\n return isOrgId(v) ? v : null\n } catch {\n return null\n }\n}\n\n/**\n * Resolve the active org with the cross-app precedence:\n * 1. `?org=<uuid>` in the current URL (explicit per-request selector)\n * 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)\n * 3. null → let the app fall back to the chrome-API default (earliest-joined)\n * Pass `search` on the server (from the request URL); it defaults to\n * `window.location.search` in the browser.\n */\nexport function resolveActiveOrg(search?: string | null): string | null {\n const fromUrl = readActiveOrgFromUrl(\n search ?? (typeof window !== 'undefined' ? window.location.search : null),\n )\n if (fromUrl) return fromUrl\n return readActiveOrgCookie()\n}\n\n/** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */\nexport function activeOrgParam(orgId: string | null | undefined): string {\n return isOrgId(orgId) ? `${ACTIVE_ORG_QUERY_PARAM}=${orgId}` : ''\n}\n\n/**\n * Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to\n * stamp the active org onto every route so it is always present in the address\n * bar. Leaves the URL unchanged when `orgId` is not a valid id.\n */\nexport function withActiveOrgParam(url: string, orgId: string | null | undefined): string {\n if (!isOrgId(orgId)) return url\n try {\n // Support both absolute and relative URLs.\n const base = typeof window !== 'undefined' ? window.location.origin : 'https://politogyvrm.com'\n const u = new URL(url, base)\n u.searchParams.set(ACTIVE_ORG_QUERY_PARAM, orgId as string)\n // Preserve relative form when the input was relative.\n return /^https?:\\/\\//i.test(url) ? u.toString() : `${u.pathname}${u.search}${u.hash}`\n } catch {\n return url\n }\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare const ACTIVE_ORG_COOKIE = "politogy_active_org";
|
|
2
|
+
declare const ACTIVE_ORG_QUERY_PARAM = "org";
|
|
3
|
+
declare function isOrgId(v: unknown): v is string;
|
|
4
|
+
/** Read the shared active-org cookie (browser only; null on server or unset). */
|
|
5
|
+
declare function readActiveOrgCookie(): string | null;
|
|
6
|
+
declare function writeActiveOrgCookie(orgId: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Read the `?org=` selector from a URL search string (full UUID only). Accepts a
|
|
9
|
+
* `location.search`-style string or a full URL.
|
|
10
|
+
*/
|
|
11
|
+
declare function readActiveOrgFromUrl(search: string | null | undefined): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the active org with the cross-app precedence:
|
|
14
|
+
* 1. `?org=<uuid>` in the current URL (explicit per-request selector)
|
|
15
|
+
* 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)
|
|
16
|
+
* 3. null → let the app fall back to the chrome-API default (earliest-joined)
|
|
17
|
+
* Pass `search` on the server (from the request URL); it defaults to
|
|
18
|
+
* `window.location.search` in the browser.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolveActiveOrg(search?: string | null): string | null;
|
|
21
|
+
/** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */
|
|
22
|
+
declare function activeOrgParam(orgId: string | null | undefined): string;
|
|
23
|
+
/**
|
|
24
|
+
* Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to
|
|
25
|
+
* stamp the active org onto every route so it is always present in the address
|
|
26
|
+
* bar. Leaves the URL unchanged when `orgId` is not a valid id.
|
|
27
|
+
*/
|
|
28
|
+
declare function withActiveOrgParam(url: string, orgId: string | null | undefined): string;
|
|
29
|
+
|
|
30
|
+
export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare const ACTIVE_ORG_COOKIE = "politogy_active_org";
|
|
2
|
+
declare const ACTIVE_ORG_QUERY_PARAM = "org";
|
|
3
|
+
declare function isOrgId(v: unknown): v is string;
|
|
4
|
+
/** Read the shared active-org cookie (browser only; null on server or unset). */
|
|
5
|
+
declare function readActiveOrgCookie(): string | null;
|
|
6
|
+
declare function writeActiveOrgCookie(orgId: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Read the `?org=` selector from a URL search string (full UUID only). Accepts a
|
|
9
|
+
* `location.search`-style string or a full URL.
|
|
10
|
+
*/
|
|
11
|
+
declare function readActiveOrgFromUrl(search: string | null | undefined): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the active org with the cross-app precedence:
|
|
14
|
+
* 1. `?org=<uuid>` in the current URL (explicit per-request selector)
|
|
15
|
+
* 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)
|
|
16
|
+
* 3. null → let the app fall back to the chrome-API default (earliest-joined)
|
|
17
|
+
* Pass `search` on the server (from the request URL); it defaults to
|
|
18
|
+
* `window.location.search` in the browser.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolveActiveOrg(search?: string | null): string | null;
|
|
21
|
+
/** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */
|
|
22
|
+
declare function activeOrgParam(orgId: string | null | undefined): string;
|
|
23
|
+
/**
|
|
24
|
+
* Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to
|
|
25
|
+
* stamp the active org onto every route so it is always present in the address
|
|
26
|
+
* bar. Leaves the URL unchanged when `orgId` is not a valid id.
|
|
27
|
+
*/
|
|
28
|
+
declare function withActiveOrgParam(url: string, orgId: string | null | undefined): string;
|
|
29
|
+
|
|
30
|
+
export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie } from './chunk-W2HITMV5.js';
|
|
2
|
+
//# sourceMappingURL=active-org.js.map
|
|
3
|
+
//# sourceMappingURL=active-org.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"active-org.js"}
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -1397,7 +1397,7 @@ function standardPolitogyNav(opts) {
|
|
|
1397
1397
|
href: abs(c.path),
|
|
1398
1398
|
children: c.children ? leaves(c.children) : void 0
|
|
1399
1399
|
}));
|
|
1400
|
-
const orgParam = opts.activeOrgId ? `org=${opts.activeOrgId
|
|
1400
|
+
const orgParam = opts.activeOrgId ? `org=${opts.activeOrgId}` : "";
|
|
1401
1401
|
const withOrg = (url) => orgParam ? `${url}${url.includes("?") ? "&" : "?"}${orgParam}` : url;
|
|
1402
1402
|
const buildRelationship = () => {
|
|
1403
1403
|
const contacts = opts.contactsItem ? {
|
|
@@ -2154,116 +2154,118 @@ function NavTopBar({
|
|
|
2154
2154
|
}
|
|
2155
2155
|
) })
|
|
2156
2156
|
] }),
|
|
2157
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 w-72 rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
|
|
2157
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 flex max-h-[calc(100vh-5rem)] w-72 flex-col overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
|
|
2158
2158
|
/* @__PURE__ */ jsxRuntime.jsx(TopBarPanelArrow, {}),
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2183
|
-
|
|
2184
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2185
|
-
|
|
2186
|
-
"
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2193
|
-
|
|
2194
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2195
|
-
|
|
2159
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: [
|
|
2160
|
+
userMenu?.myProfileHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2161
|
+
"a",
|
|
2162
|
+
{
|
|
2163
|
+
href: safeHref(userMenu.myProfileHref),
|
|
2164
|
+
className: "flex w-full items-center gap-2.5 px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
|
|
2165
|
+
children: [
|
|
2166
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProfileIcon, {}),
|
|
2167
|
+
"My Profile"
|
|
2168
|
+
]
|
|
2169
|
+
}
|
|
2170
|
+
) : null,
|
|
2171
|
+
userMenu?.organizationSettingsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2172
|
+
"a",
|
|
2173
|
+
{
|
|
2174
|
+
href: safeHref(userMenu.organizationSettingsHref),
|
|
2175
|
+
className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
|
|
2176
|
+
children: [
|
|
2177
|
+
/* @__PURE__ */ jsxRuntime.jsx(BriefcaseIcon, {}),
|
|
2178
|
+
"Organization Settings"
|
|
2179
|
+
]
|
|
2180
|
+
}
|
|
2181
|
+
) : null,
|
|
2182
|
+
userMenu?.roles && userMenu.roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
2183
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Roles & Organizations" }),
|
|
2184
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1.5", children: userMenu.roles.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-wrap items-center gap-1.5 text-sm", children: [
|
|
2185
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-md bg-[color:var(--panel)] px-2 py-0.5 text-xs font-bold uppercase tracking-wide text-[color:var(--foreground)]", children: r.role }),
|
|
2186
|
+
r.orgName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[color:var(--muted)]", children: [
|
|
2187
|
+
"in ",
|
|
2188
|
+
r.orgName
|
|
2189
|
+
] }) : null
|
|
2190
|
+
] }, `${r.role}-${i}`)) })
|
|
2191
|
+
] }) : null,
|
|
2192
|
+
userMenu?.accounts && userMenu.accounts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
2193
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Accounts" }),
|
|
2194
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: userMenu.accounts.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2.5", children: [
|
|
2195
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2196
|
+
"button",
|
|
2197
|
+
{
|
|
2198
|
+
type: "button",
|
|
2199
|
+
onClick: () => {
|
|
2200
|
+
if (!a.active) userMenu.onSwitchAccount?.(a.id);
|
|
2201
|
+
},
|
|
2202
|
+
className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
|
|
2203
|
+
children: [
|
|
2204
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-[11px] font-bold text-white", children: a.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: a.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : (a.name ?? a.email ?? "?").slice(0, 2).toUpperCase() }),
|
|
2205
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col leading-tight", children: [
|
|
2206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2207
|
+
"span",
|
|
2208
|
+
{
|
|
2209
|
+
className: [
|
|
2210
|
+
"truncate text-sm font-semibold",
|
|
2211
|
+
a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
|
|
2212
|
+
].join(" "),
|
|
2213
|
+
children: a.name ?? a.email ?? a.id.slice(0, 8)
|
|
2214
|
+
}
|
|
2215
|
+
),
|
|
2216
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
|
|
2217
|
+
] })
|
|
2218
|
+
]
|
|
2219
|
+
}
|
|
2220
|
+
),
|
|
2221
|
+
!a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2222
|
+
"button",
|
|
2223
|
+
{
|
|
2224
|
+
type: "button",
|
|
2225
|
+
"aria-label": `Remove ${a.name ?? a.email ?? "account"}`,
|
|
2226
|
+
onClick: () => userMenu.onRemoveAccount?.(a.id),
|
|
2227
|
+
className: "shrink-0 rounded p-1 text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2228
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {})
|
|
2229
|
+
}
|
|
2230
|
+
) : null
|
|
2231
|
+
] }, a.id)) }),
|
|
2232
|
+
userMenu.addAccountHref || userMenu.onAddAccount ? userMenu.addAccountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2233
|
+
"a",
|
|
2196
2234
|
{
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (!a.active) userMenu.onSwitchAccount?.(a.id);
|
|
2200
|
-
},
|
|
2201
|
-
className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
|
|
2235
|
+
href: safeHref(userMenu.addAccountHref),
|
|
2236
|
+
className: "mt-1.5 flex items-center gap-2.5 rounded-lg px-1 py-1.5 text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2202
2237
|
children: [
|
|
2203
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2204
|
-
|
|
2205
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2206
|
-
"span",
|
|
2207
|
-
{
|
|
2208
|
-
className: [
|
|
2209
|
-
"truncate text-sm font-semibold",
|
|
2210
|
-
a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
|
|
2211
|
-
].join(" "),
|
|
2212
|
-
children: a.name ?? a.email ?? a.id.slice(0, 8)
|
|
2213
|
-
}
|
|
2214
|
-
),
|
|
2215
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
|
|
2216
|
-
] })
|
|
2238
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2239
|
+
"Add Account"
|
|
2217
2240
|
]
|
|
2218
2241
|
}
|
|
2219
|
-
)
|
|
2220
|
-
!a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2242
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2221
2243
|
"button",
|
|
2222
2244
|
{
|
|
2223
2245
|
type: "button",
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2246
|
+
onClick: userMenu.onAddAccount,
|
|
2247
|
+
className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2248
|
+
children: [
|
|
2249
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2250
|
+
"Add Account"
|
|
2251
|
+
]
|
|
2228
2252
|
}
|
|
2229
2253
|
) : null
|
|
2230
|
-
] }
|
|
2231
|
-
userMenu
|
|
2232
|
-
|
|
2233
|
-
{
|
|
2234
|
-
href: safeHref(userMenu.addAccountHref),
|
|
2235
|
-
className: "mt-1.5 flex items-center gap-2.5 rounded-lg px-1 py-1.5 text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2236
|
-
children: [
|
|
2237
|
-
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2238
|
-
"Add Account"
|
|
2239
|
-
]
|
|
2240
|
-
}
|
|
2241
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2242
|
-
"button",
|
|
2254
|
+
] }) : null,
|
|
2255
|
+
userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2256
|
+
NavThemeControl,
|
|
2243
2257
|
{
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
2247
|
-
children: [
|
|
2248
|
-
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
2249
|
-
"Add Account"
|
|
2250
|
-
]
|
|
2258
|
+
value: userMenu.theme.value,
|
|
2259
|
+
onChange: userMenu.theme.onChange
|
|
2251
2260
|
}
|
|
2252
|
-
) : null
|
|
2253
|
-
] })
|
|
2254
|
-
userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2255
|
-
NavThemeControl,
|
|
2256
|
-
{
|
|
2257
|
-
value: userMenu.theme.value,
|
|
2258
|
-
onChange: userMenu.theme.onChange
|
|
2259
|
-
}
|
|
2260
|
-
) }) : null,
|
|
2261
|
+
) }) : null
|
|
2262
|
+
] }),
|
|
2261
2263
|
onSignOut ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2262
2264
|
"button",
|
|
2263
2265
|
{
|
|
2264
2266
|
type: "button",
|
|
2265
2267
|
onClick: onSignOut,
|
|
2266
|
-
className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
|
|
2268
|
+
className: "flex w-full shrink-0 items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
|
|
2267
2269
|
children: [
|
|
2268
2270
|
/* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {}),
|
|
2269
2271
|
"Sign out"
|
|
@@ -2486,6 +2488,53 @@ function NavAppShell({
|
|
|
2486
2488
|
] });
|
|
2487
2489
|
}
|
|
2488
2490
|
|
|
2491
|
+
// src/active-org.ts
|
|
2492
|
+
var ACTIVE_ORG_COOKIE = "politogy_active_org";
|
|
2493
|
+
var ACTIVE_ORG_QUERY_PARAM = "org";
|
|
2494
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2495
|
+
function isOrgId(v) {
|
|
2496
|
+
return typeof v === "string" && UUID_RE.test(v.trim());
|
|
2497
|
+
}
|
|
2498
|
+
function readActiveOrgCookie() {
|
|
2499
|
+
if (typeof document === "undefined") return null;
|
|
2500
|
+
try {
|
|
2501
|
+
const m = document.cookie.match(
|
|
2502
|
+
new RegExp(`(?:^|;\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`)
|
|
2503
|
+
);
|
|
2504
|
+
const v = m ? decodeURIComponent(m[1]) : null;
|
|
2505
|
+
return isOrgId(v) ? v : null;
|
|
2506
|
+
} catch {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function writeActiveOrgCookie(orgId) {
|
|
2511
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2512
|
+
if (!isOrgId(orgId)) return;
|
|
2513
|
+
const host = window.location.hostname;
|
|
2514
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
2515
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
2516
|
+
document.cookie = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(
|
|
2517
|
+
orgId
|
|
2518
|
+
)}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
|
|
2519
|
+
}
|
|
2520
|
+
function readActiveOrgFromUrl(search) {
|
|
2521
|
+
if (!search) return null;
|
|
2522
|
+
try {
|
|
2523
|
+
const qs = search.includes("?") ? search.slice(search.indexOf("?") + 1) : search;
|
|
2524
|
+
const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM);
|
|
2525
|
+
return isOrgId(v) ? v : null;
|
|
2526
|
+
} catch {
|
|
2527
|
+
return null;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
function resolveActiveOrg(search) {
|
|
2531
|
+
const fromUrl = readActiveOrgFromUrl(
|
|
2532
|
+
(typeof window !== "undefined" ? window.location.search : null)
|
|
2533
|
+
);
|
|
2534
|
+
if (fromUrl) return fromUrl;
|
|
2535
|
+
return readActiveOrgCookie();
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2489
2538
|
// src/lib/breadcrumbs.ts
|
|
2490
2539
|
function deriveBreadcrumbs(items, pathname, opts) {
|
|
2491
2540
|
const search = opts?.search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
@@ -2555,6 +2604,89 @@ function resolveIdentity(input) {
|
|
|
2555
2604
|
loaded: input.loaded ?? false
|
|
2556
2605
|
};
|
|
2557
2606
|
}
|
|
2607
|
+
|
|
2608
|
+
// src/logout-signal.ts
|
|
2609
|
+
var LOGOUT_COOKIE = "politogy_logout_at";
|
|
2610
|
+
var LOGOUT_CHANNEL = "politogy-logout";
|
|
2611
|
+
var POLL_MS = 1e3;
|
|
2612
|
+
function parseTimestamp(value) {
|
|
2613
|
+
if (!value) return null;
|
|
2614
|
+
const n = Number(value);
|
|
2615
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
2616
|
+
}
|
|
2617
|
+
function isSessionRevoked(logoutCookieValue, authTime) {
|
|
2618
|
+
const loggedOutAt = parseTimestamp(logoutCookieValue);
|
|
2619
|
+
if (loggedOutAt === null) return false;
|
|
2620
|
+
if (authTime == null || !Number.isFinite(authTime) || authTime <= 0) return true;
|
|
2621
|
+
const authMs = authTime < 1e12 ? authTime * 1e3 : authTime;
|
|
2622
|
+
return authMs < loggedOutAt;
|
|
2623
|
+
}
|
|
2624
|
+
function readLogoutCookie() {
|
|
2625
|
+
if (typeof document === "undefined") return null;
|
|
2626
|
+
try {
|
|
2627
|
+
const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${LOGOUT_COOKIE}=([^;]+)`));
|
|
2628
|
+
return parseTimestamp(m ? decodeURIComponent(m[1]) : null);
|
|
2629
|
+
} catch {
|
|
2630
|
+
return null;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
function broadcastLogout(at = Date.now()) {
|
|
2634
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2635
|
+
const host = window.location.hostname;
|
|
2636
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
2637
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
2638
|
+
document.cookie = `${LOGOUT_COOKIE}=${at}; Path=/; Max-Age=2592000; SameSite=Lax${domainAttr}${secure}`;
|
|
2639
|
+
try {
|
|
2640
|
+
new BroadcastChannel(LOGOUT_CHANNEL).postMessage(at);
|
|
2641
|
+
} catch {
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
function watchLogout(onLogout, options = {}) {
|
|
2645
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
2646
|
+
return () => {
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
const { authTime = null, intervalMs = POLL_MS } = options;
|
|
2650
|
+
const baseline = readLogoutCookie() ?? 0;
|
|
2651
|
+
let done = false;
|
|
2652
|
+
const fire = (at) => {
|
|
2653
|
+
if (done) return;
|
|
2654
|
+
if (at <= baseline) return;
|
|
2655
|
+
if (authTime != null && !isSessionRevoked(String(at), authTime)) return;
|
|
2656
|
+
done = true;
|
|
2657
|
+
cleanup();
|
|
2658
|
+
onLogout();
|
|
2659
|
+
};
|
|
2660
|
+
const check = () => {
|
|
2661
|
+
const at = readLogoutCookie();
|
|
2662
|
+
if (at !== null) fire(at);
|
|
2663
|
+
};
|
|
2664
|
+
let channel = null;
|
|
2665
|
+
try {
|
|
2666
|
+
channel = new BroadcastChannel(LOGOUT_CHANNEL);
|
|
2667
|
+
channel.onmessage = (event) => {
|
|
2668
|
+
const at = typeof event.data === "number" ? event.data : readLogoutCookie();
|
|
2669
|
+
if (at !== null) fire(at);
|
|
2670
|
+
};
|
|
2671
|
+
} catch {
|
|
2672
|
+
channel = null;
|
|
2673
|
+
}
|
|
2674
|
+
const timer = window.setInterval(check, intervalMs);
|
|
2675
|
+
window.addEventListener("visibilitychange", check);
|
|
2676
|
+
window.addEventListener("focus", check);
|
|
2677
|
+
window.addEventListener("pageshow", check);
|
|
2678
|
+
function cleanup() {
|
|
2679
|
+
window.clearInterval(timer);
|
|
2680
|
+
window.removeEventListener("visibilitychange", check);
|
|
2681
|
+
window.removeEventListener("focus", check);
|
|
2682
|
+
window.removeEventListener("pageshow", check);
|
|
2683
|
+
try {
|
|
2684
|
+
channel?.close();
|
|
2685
|
+
} catch {
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return cleanup;
|
|
2689
|
+
}
|
|
2558
2690
|
var CORE_APP = "vrm";
|
|
2559
2691
|
var POLITOGY_APPS = {
|
|
2560
2692
|
vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
|
|
@@ -2631,6 +2763,28 @@ function FrameBody({
|
|
|
2631
2763
|
}) {
|
|
2632
2764
|
const meta = POLITOGY_APPS[app];
|
|
2633
2765
|
const chrome = useChromeState();
|
|
2766
|
+
const signOutRef = react.useRef(onSignOut);
|
|
2767
|
+
signOutRef.current = onSignOut;
|
|
2768
|
+
const signingOutRef = react.useRef(false);
|
|
2769
|
+
const handleSignOut = react.useCallback(() => {
|
|
2770
|
+
signingOutRef.current = true;
|
|
2771
|
+
broadcastLogout();
|
|
2772
|
+
signOutRef.current();
|
|
2773
|
+
}, []);
|
|
2774
|
+
react.useEffect(
|
|
2775
|
+
() => (
|
|
2776
|
+
// A tab that learns of a sign-out elsewhere ends its OWN session properly
|
|
2777
|
+
// (clear cookie, RP-initiated logout at the IdP) rather than merely
|
|
2778
|
+
// navigating away — otherwise its session cookie would outlive the
|
|
2779
|
+
// sign-out and a later visit would silently resume it. No re-broadcast:
|
|
2780
|
+
// the signal that woke us is already on record for everyone.
|
|
2781
|
+
watchLogout(() => {
|
|
2782
|
+
if (signingOutRef.current) return;
|
|
2783
|
+
signOutRef.current();
|
|
2784
|
+
})
|
|
2785
|
+
),
|
|
2786
|
+
[]
|
|
2787
|
+
);
|
|
2634
2788
|
const core = app === CORE_APP ? data?.core : void 0;
|
|
2635
2789
|
const identity = useChromeIdentity({ userName: session.userName, email: session.email });
|
|
2636
2790
|
const derivedUserMenu = useChromeUserMenu({
|
|
@@ -2713,7 +2867,7 @@ function FrameBody({
|
|
|
2713
2867
|
role: session.role
|
|
2714
2868
|
},
|
|
2715
2869
|
userMenu,
|
|
2716
|
-
onSignOut,
|
|
2870
|
+
onSignOut: handleSignOut,
|
|
2717
2871
|
// The action cluster is IDENTICAL everywhere — core included. Page
|
|
2718
2872
|
// actions sit BEFORE it, never inside it, so the Alerts bell keeps its
|
|
2719
2873
|
// required position: farthest right, immediately before My Account.
|
|
@@ -2772,7 +2926,7 @@ function ChromeStateProvider({
|
|
|
2772
2926
|
const [navDisplay, setNavDisplayState] = react.useState("both");
|
|
2773
2927
|
react.useEffect(() => {
|
|
2774
2928
|
try {
|
|
2775
|
-
const storedOrg = localStorage.getItem(ORG_STORAGE_KEY);
|
|
2929
|
+
const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY);
|
|
2776
2930
|
if (storedOrg) setOrgIdState(storedOrg);
|
|
2777
2931
|
const storedNav = localStorage.getItem(NAV_DISPLAY_KEY);
|
|
2778
2932
|
if (storedNav === "menu" || storedNav === "tabs" || storedNav === "both") {
|
|
@@ -2791,6 +2945,7 @@ function ChromeStateProvider({
|
|
|
2791
2945
|
localStorage.setItem(ORG_STORAGE_KEY, id);
|
|
2792
2946
|
} catch {
|
|
2793
2947
|
}
|
|
2948
|
+
writeActiveOrgCookie(id);
|
|
2794
2949
|
}, []);
|
|
2795
2950
|
const setNavDisplay = react.useCallback((m) => {
|
|
2796
2951
|
setNavDisplayState(m);
|
|
@@ -2835,6 +2990,19 @@ function ChromeStateProvider({
|
|
|
2835
2990
|
setNavDisplay
|
|
2836
2991
|
};
|
|
2837
2992
|
}, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay]);
|
|
2993
|
+
const activeOrgId = value.orgId;
|
|
2994
|
+
react.useEffect(() => {
|
|
2995
|
+
if (!isOrgId(activeOrgId)) return;
|
|
2996
|
+
writeActiveOrgCookie(activeOrgId);
|
|
2997
|
+
try {
|
|
2998
|
+
const url = new URL(window.location.href);
|
|
2999
|
+
if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {
|
|
3000
|
+
url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId);
|
|
3001
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
3002
|
+
}
|
|
3003
|
+
} catch {
|
|
3004
|
+
}
|
|
3005
|
+
}, [activeOrgId]);
|
|
2838
3006
|
return /* @__PURE__ */ jsxRuntime.jsx(ChromeStateContext.Provider, { value, children });
|
|
2839
3007
|
}
|
|
2840
3008
|
function AppDevNotes({
|
|
@@ -2984,7 +3152,7 @@ function useChromeUserMenu({
|
|
|
2984
3152
|
const state = useChromeState();
|
|
2985
3153
|
const theme = useThemePreference();
|
|
2986
3154
|
const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null;
|
|
2987
|
-
const orgSuffix = activeOrg ? `?org=${activeOrg.id
|
|
3155
|
+
const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : "";
|
|
2988
3156
|
return {
|
|
2989
3157
|
// My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
|
|
2990
3158
|
myProfileHref: `${CORE_BASE_URL}/my-profile`,
|
|
@@ -3051,7 +3219,7 @@ function useChromeMode(arg = {}) {
|
|
|
3051
3219
|
const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null;
|
|
3052
3220
|
const selected = selectedModeId ? options.find((m) => m.id === selectedModeId) ?? null : null;
|
|
3053
3221
|
const current = pinned ?? selected ?? options[0] ?? MODE_DEFS.find((m) => m.id === fallbackModeId) ?? MODE_DEFS[0];
|
|
3054
|
-
const suffix = orgId ? `?org=${orgId
|
|
3222
|
+
const suffix = orgId ? `?org=${orgId}` : "";
|
|
3055
3223
|
return {
|
|
3056
3224
|
label: current.label,
|
|
3057
3225
|
icon: current.icon,
|