@the-portland-company/shell 0.52.0 → 0.53.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/app-chrome.cjs +312 -62
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +55 -2
- package/dist/app-chrome.d.ts +55 -2
- package/dist/app-chrome.js +314 -65
- package/dist/app-chrome.js.map +1 -1
- package/package.json +1 -1
package/dist/app-chrome.d.cts
CHANGED
|
@@ -24,6 +24,46 @@ declare function resolveIdentity(input: {
|
|
|
24
24
|
loaded?: boolean;
|
|
25
25
|
}): ResolvedIdentity;
|
|
26
26
|
|
|
27
|
+
/** One stage in the boot pipeline (spec §7). */
|
|
28
|
+
type BootStageId = 's0_session' | 's1_org' | 's2_uvp_connect' | 's3_district' | 's4_universe' | 's5_layers' | 's6_verified' | 's7_tribes' | 's8_intelligence' | 's9_shell';
|
|
29
|
+
type BootPhase = 'booting' | 'slow' | 'error' | 'done';
|
|
30
|
+
/**
|
|
31
|
+
* The progress-reporting contract (spec §14, Shell package integration
|
|
32
|
+
* point). An app with a real pipeline drives this; the shape is intentionally
|
|
33
|
+
* small so a future per-app pipeline can report into it without a shell
|
|
34
|
+
* release.
|
|
35
|
+
*/
|
|
36
|
+
type BootProgress = {
|
|
37
|
+
stageId: BootStageId;
|
|
38
|
+
/** 0–100, real, monotonic — never supplied as a guess. */
|
|
39
|
+
pct: number;
|
|
40
|
+
/** The account's own status line for this stage (spec §8; tokens already
|
|
41
|
+
* filled, fallback already applied by the caller). */
|
|
42
|
+
label: string;
|
|
43
|
+
phase: BootPhase;
|
|
44
|
+
/** Critical-stage failure: Retry / Sign out (spec §9.1). */
|
|
45
|
+
onRetry?: () => void;
|
|
46
|
+
onSignOut?: () => void;
|
|
47
|
+
};
|
|
48
|
+
type LoadingScreenProps = {
|
|
49
|
+
/** Real pipeline progress, when the host app has one wired. */
|
|
50
|
+
bootProgress?: BootProgress;
|
|
51
|
+
/**
|
|
52
|
+
* Fallback completion signal used when `bootProgress` is absent: the
|
|
53
|
+
* screen dismisses once this flips true. Callers pass the same "org list
|
|
54
|
+
* resolved" signal FrameBody already computes, so a consumer that never
|
|
55
|
+
* wires the full contract still gets a truthful (if coarse) boot report
|
|
56
|
+
* instead of a spinner.
|
|
57
|
+
*/
|
|
58
|
+
syntheticReady?: boolean;
|
|
59
|
+
/** True while the screen should be mounted at all (trigger gate lives in
|
|
60
|
+
* frame.tsx; this component only renders its own exit transition). */
|
|
61
|
+
visible: boolean;
|
|
62
|
+
/** Called after the fade-out completes and the DOM node should unmount. */
|
|
63
|
+
onExitComplete: () => void;
|
|
64
|
+
};
|
|
65
|
+
declare function LoadingScreen({ bootProgress, syntheticReady, visible, onExitComplete, }: LoadingScreenProps): react_jsx_runtime.JSX.Element | null;
|
|
66
|
+
|
|
27
67
|
/** Every app that renders the shared chrome. Keyed by its subdomain. */
|
|
28
68
|
type PolitogyAppKey = 'vrm' | 'contacts' | 'forms' | 'surveys' | 'polls' | 'petitions' | 'email' | 'a2p' | 'phonebank' | 'canvass' | 'signs' | 'ctas';
|
|
29
69
|
/**
|
|
@@ -205,7 +245,7 @@ type PolitogyBuildMeta = {
|
|
|
205
245
|
commitDate?: string;
|
|
206
246
|
commitAuthor?: string;
|
|
207
247
|
};
|
|
208
|
-
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, children, }: {
|
|
248
|
+
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, bootProgress, appReady, children, }: {
|
|
209
249
|
app: PolitogyAppKey;
|
|
210
250
|
/** Current path from the app's router (Next: usePathname()). */
|
|
211
251
|
pathname: string;
|
|
@@ -225,6 +265,19 @@ declare function PolitogyAppFrame({ app, pathname, search, linkComponent, sessio
|
|
|
225
265
|
buildMeta?: PolitogyBuildMeta;
|
|
226
266
|
/** Optional DATA plumbing (never styling) — see PolitogyFrameData. */
|
|
227
267
|
data?: PolitogyFrameData;
|
|
268
|
+
/**
|
|
269
|
+
* Real boot-pipeline progress (Specs/Loading-Screen §14 contract). Absent
|
|
270
|
+
* for apps that have not wired their own pipeline yet — the frame then
|
|
271
|
+
* reports a truthful but coarse two-stage boot instead of a spinner.
|
|
272
|
+
*/
|
|
273
|
+
bootProgress?: BootProgress;
|
|
274
|
+
/**
|
|
275
|
+
* Explicit "the host app is ready to be shown" signal. When omitted (every
|
|
276
|
+
* app today), the Loading Screen dismisses itself once the chrome's own
|
|
277
|
+
* org/nav state resolves, so none of the 14 consumer apps has to opt in to
|
|
278
|
+
* keep working.
|
|
279
|
+
*/
|
|
280
|
+
appReady?: boolean;
|
|
228
281
|
children: ReactNode;
|
|
229
282
|
}): react_jsx_runtime.JSX.Element;
|
|
230
283
|
|
|
@@ -375,4 +428,4 @@ type UseChromeModeArg = ModeId | {
|
|
|
375
428
|
*/
|
|
376
429
|
declare function useChromeMode(arg?: UseChromeModeArg): NavMode;
|
|
377
430
|
|
|
378
|
-
export { type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
|
431
|
+
export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
package/dist/app-chrome.d.ts
CHANGED
|
@@ -24,6 +24,46 @@ declare function resolveIdentity(input: {
|
|
|
24
24
|
loaded?: boolean;
|
|
25
25
|
}): ResolvedIdentity;
|
|
26
26
|
|
|
27
|
+
/** One stage in the boot pipeline (spec §7). */
|
|
28
|
+
type BootStageId = 's0_session' | 's1_org' | 's2_uvp_connect' | 's3_district' | 's4_universe' | 's5_layers' | 's6_verified' | 's7_tribes' | 's8_intelligence' | 's9_shell';
|
|
29
|
+
type BootPhase = 'booting' | 'slow' | 'error' | 'done';
|
|
30
|
+
/**
|
|
31
|
+
* The progress-reporting contract (spec §14, Shell package integration
|
|
32
|
+
* point). An app with a real pipeline drives this; the shape is intentionally
|
|
33
|
+
* small so a future per-app pipeline can report into it without a shell
|
|
34
|
+
* release.
|
|
35
|
+
*/
|
|
36
|
+
type BootProgress = {
|
|
37
|
+
stageId: BootStageId;
|
|
38
|
+
/** 0–100, real, monotonic — never supplied as a guess. */
|
|
39
|
+
pct: number;
|
|
40
|
+
/** The account's own status line for this stage (spec §8; tokens already
|
|
41
|
+
* filled, fallback already applied by the caller). */
|
|
42
|
+
label: string;
|
|
43
|
+
phase: BootPhase;
|
|
44
|
+
/** Critical-stage failure: Retry / Sign out (spec §9.1). */
|
|
45
|
+
onRetry?: () => void;
|
|
46
|
+
onSignOut?: () => void;
|
|
47
|
+
};
|
|
48
|
+
type LoadingScreenProps = {
|
|
49
|
+
/** Real pipeline progress, when the host app has one wired. */
|
|
50
|
+
bootProgress?: BootProgress;
|
|
51
|
+
/**
|
|
52
|
+
* Fallback completion signal used when `bootProgress` is absent: the
|
|
53
|
+
* screen dismisses once this flips true. Callers pass the same "org list
|
|
54
|
+
* resolved" signal FrameBody already computes, so a consumer that never
|
|
55
|
+
* wires the full contract still gets a truthful (if coarse) boot report
|
|
56
|
+
* instead of a spinner.
|
|
57
|
+
*/
|
|
58
|
+
syntheticReady?: boolean;
|
|
59
|
+
/** True while the screen should be mounted at all (trigger gate lives in
|
|
60
|
+
* frame.tsx; this component only renders its own exit transition). */
|
|
61
|
+
visible: boolean;
|
|
62
|
+
/** Called after the fade-out completes and the DOM node should unmount. */
|
|
63
|
+
onExitComplete: () => void;
|
|
64
|
+
};
|
|
65
|
+
declare function LoadingScreen({ bootProgress, syntheticReady, visible, onExitComplete, }: LoadingScreenProps): react_jsx_runtime.JSX.Element | null;
|
|
66
|
+
|
|
27
67
|
/** Every app that renders the shared chrome. Keyed by its subdomain. */
|
|
28
68
|
type PolitogyAppKey = 'vrm' | 'contacts' | 'forms' | 'surveys' | 'polls' | 'petitions' | 'email' | 'a2p' | 'phonebank' | 'canvass' | 'signs' | 'ctas';
|
|
29
69
|
/**
|
|
@@ -205,7 +245,7 @@ type PolitogyBuildMeta = {
|
|
|
205
245
|
commitDate?: string;
|
|
206
246
|
commitAuthor?: string;
|
|
207
247
|
};
|
|
208
|
-
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, children, }: {
|
|
248
|
+
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, bootProgress, appReady, children, }: {
|
|
209
249
|
app: PolitogyAppKey;
|
|
210
250
|
/** Current path from the app's router (Next: usePathname()). */
|
|
211
251
|
pathname: string;
|
|
@@ -225,6 +265,19 @@ declare function PolitogyAppFrame({ app, pathname, search, linkComponent, sessio
|
|
|
225
265
|
buildMeta?: PolitogyBuildMeta;
|
|
226
266
|
/** Optional DATA plumbing (never styling) — see PolitogyFrameData. */
|
|
227
267
|
data?: PolitogyFrameData;
|
|
268
|
+
/**
|
|
269
|
+
* Real boot-pipeline progress (Specs/Loading-Screen §14 contract). Absent
|
|
270
|
+
* for apps that have not wired their own pipeline yet — the frame then
|
|
271
|
+
* reports a truthful but coarse two-stage boot instead of a spinner.
|
|
272
|
+
*/
|
|
273
|
+
bootProgress?: BootProgress;
|
|
274
|
+
/**
|
|
275
|
+
* Explicit "the host app is ready to be shown" signal. When omitted (every
|
|
276
|
+
* app today), the Loading Screen dismisses itself once the chrome's own
|
|
277
|
+
* org/nav state resolves, so none of the 14 consumer apps has to opt in to
|
|
278
|
+
* keep working.
|
|
279
|
+
*/
|
|
280
|
+
appReady?: boolean;
|
|
228
281
|
children: ReactNode;
|
|
229
282
|
}): react_jsx_runtime.JSX.Element;
|
|
230
283
|
|
|
@@ -375,4 +428,4 @@ type UseChromeModeArg = ModeId | {
|
|
|
375
428
|
*/
|
|
376
429
|
declare function useChromeMode(arg?: UseChromeModeArg): NavMode;
|
|
377
430
|
|
|
378
|
-
export { type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
|
431
|
+
export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
package/dist/app-chrome.js
CHANGED
|
@@ -4,10 +4,10 @@ import { clearBetaBannerDismissal, standardPolitogyNav, NavAppShell, NavChromePr
|
|
|
4
4
|
import './chunk-U4RZPZET.js';
|
|
5
5
|
import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, isNavDisplayPreference, writeNavDisplayCookie, writeNavDisplayLocal, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-6UHJV2FH.js';
|
|
6
6
|
import { resolveActiveOrg, isOrgId, writeActiveOrgCookie, ACTIVE_ORG_QUERY_PARAM, readActiveOrgCookie } from './chunk-W2HITMV5.js';
|
|
7
|
-
import { createContext,
|
|
7
|
+
import { createContext, useMemo, useRef, useEffect, useState, useCallback, useContext } from 'react';
|
|
8
8
|
import { DevNotesMenu, createDevNotesClient, DevNotesProvider, DevNotesTaskListModal, DevNotesOverlay } from '@the-portland-company/devnotes';
|
|
9
9
|
import { FiFlag, FiTarget, FiMapPin, FiMap, FiPhone, FiShare2, FiDollarSign, FiCreditCard, FiSettings, FiBriefcase, FiBarChart2, FiUser, FiClock, FiSend, FiFileText, FiEdit3, FiUpload, FiList, FiGrid, FiLifeBuoy, FiActivity, FiLayout, FiMessageSquare, FiMail, FiUsers, FiHome } from 'react-icons/fi';
|
|
10
|
-
import {
|
|
10
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
|
|
12
12
|
// src/lib/breadcrumbs.ts
|
|
13
13
|
function deriveBreadcrumbs(items, pathname, opts) {
|
|
@@ -78,6 +78,216 @@ function resolveIdentity(input) {
|
|
|
78
78
|
loaded: input.loaded ?? false
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
var SYNTHETIC_STAGES = [
|
|
82
|
+
{ pct: 20, label: "Connecting to your organization\u2026" },
|
|
83
|
+
{ pct: 100, label: "Ready." }
|
|
84
|
+
];
|
|
85
|
+
var ANNOUNCE_THROTTLE_MS = 1500;
|
|
86
|
+
var MIN_DWELL_MS = 450;
|
|
87
|
+
var FILL_EASE_MS = 380;
|
|
88
|
+
var HOLD_MS = 240;
|
|
89
|
+
var FADE_MS = 300;
|
|
90
|
+
function usePrefersReducedMotion() {
|
|
91
|
+
const [reduced, setReduced] = useState(false);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
94
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
95
|
+
setReduced(mq.matches);
|
|
96
|
+
const onChange = () => setReduced(mq.matches);
|
|
97
|
+
mq.addEventListener("change", onChange);
|
|
98
|
+
return () => mq.removeEventListener("change", onChange);
|
|
99
|
+
}, []);
|
|
100
|
+
return reduced;
|
|
101
|
+
}
|
|
102
|
+
function useThrottledAnnouncement(label, isFinal) {
|
|
103
|
+
const [announced, setAnnounced] = useState(label);
|
|
104
|
+
const lastAtRef = useRef(0);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
if (isFinal || now - lastAtRef.current >= ANNOUNCE_THROTTLE_MS) {
|
|
108
|
+
lastAtRef.current = now;
|
|
109
|
+
setAnnounced(label);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const wait = ANNOUNCE_THROTTLE_MS - (now - lastAtRef.current);
|
|
113
|
+
const t = setTimeout(() => {
|
|
114
|
+
lastAtRef.current = Date.now();
|
|
115
|
+
setAnnounced(label);
|
|
116
|
+
}, wait);
|
|
117
|
+
return () => clearTimeout(t);
|
|
118
|
+
}, [label, isFinal]);
|
|
119
|
+
return announced;
|
|
120
|
+
}
|
|
121
|
+
function useDwelledLabel(label) {
|
|
122
|
+
const [displayed, setDisplayed] = useState(label);
|
|
123
|
+
const shownAtRef = useRef(0);
|
|
124
|
+
const pendingRef = useRef(null);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (label === displayed) return;
|
|
127
|
+
const elapsed = Date.now() - shownAtRef.current;
|
|
128
|
+
if (pendingRef.current) clearTimeout(pendingRef.current);
|
|
129
|
+
if (elapsed >= MIN_DWELL_MS) {
|
|
130
|
+
shownAtRef.current = Date.now();
|
|
131
|
+
setDisplayed(label);
|
|
132
|
+
} else {
|
|
133
|
+
pendingRef.current = setTimeout(() => {
|
|
134
|
+
shownAtRef.current = Date.now();
|
|
135
|
+
setDisplayed(label);
|
|
136
|
+
}, MIN_DWELL_MS - elapsed);
|
|
137
|
+
}
|
|
138
|
+
return () => {
|
|
139
|
+
if (pendingRef.current) clearTimeout(pendingRef.current);
|
|
140
|
+
};
|
|
141
|
+
}, [label]);
|
|
142
|
+
return displayed;
|
|
143
|
+
}
|
|
144
|
+
function LoadingScreen({
|
|
145
|
+
bootProgress,
|
|
146
|
+
syntheticReady,
|
|
147
|
+
visible,
|
|
148
|
+
onExitComplete
|
|
149
|
+
}) {
|
|
150
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
151
|
+
const synthetic = useMemo(() => {
|
|
152
|
+
const stage = syntheticReady ? SYNTHETIC_STAGES[1] : SYNTHETIC_STAGES[0];
|
|
153
|
+
return {
|
|
154
|
+
pct: stage.pct,
|
|
155
|
+
label: stage.label,
|
|
156
|
+
phase: syntheticReady ? "done" : "booting"
|
|
157
|
+
};
|
|
158
|
+
}, [syntheticReady]);
|
|
159
|
+
const pct = bootProgress?.pct ?? synthetic.pct;
|
|
160
|
+
const rawLabel = bootProgress?.label ?? synthetic.label;
|
|
161
|
+
const phase = bootProgress?.phase ?? synthetic.phase;
|
|
162
|
+
const isFinal = phase === "done" && pct >= 100;
|
|
163
|
+
const highWaterRef = useRef(0);
|
|
164
|
+
const clampedPct = Math.max(pct, highWaterRef.current);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
highWaterRef.current = clampedPct;
|
|
167
|
+
}, [clampedPct]);
|
|
168
|
+
const displayedLabel = useDwelledLabel(rawLabel);
|
|
169
|
+
const announced = useThrottledAnnouncement(displayedLabel, isFinal);
|
|
170
|
+
const [fading, setFading] = useState(false);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!isFinal) return;
|
|
173
|
+
const holdMs = reducedMotion ? 0 : HOLD_MS;
|
|
174
|
+
const fadeMs = reducedMotion ? 0 : FADE_MS;
|
|
175
|
+
const holdTimer = setTimeout(() => {
|
|
176
|
+
setFading(true);
|
|
177
|
+
const fadeTimer = setTimeout(onExitComplete, fadeMs);
|
|
178
|
+
return () => clearTimeout(fadeTimer);
|
|
179
|
+
}, holdMs);
|
|
180
|
+
return () => clearTimeout(holdTimer);
|
|
181
|
+
}, [isFinal, reducedMotion]);
|
|
182
|
+
if (!visible) return null;
|
|
183
|
+
const isError = phase === "error";
|
|
184
|
+
return /* @__PURE__ */ jsxs(
|
|
185
|
+
"div",
|
|
186
|
+
{
|
|
187
|
+
"data-testid": "politogy-loading-screen",
|
|
188
|
+
style: {
|
|
189
|
+
position: "fixed",
|
|
190
|
+
inset: 0,
|
|
191
|
+
zIndex: 2147483e3,
|
|
192
|
+
display: "flex",
|
|
193
|
+
flexDirection: "column",
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
justifyContent: "center",
|
|
196
|
+
gap: "2rem",
|
|
197
|
+
background: "var(--color-ink-900, #0a0a0c)",
|
|
198
|
+
color: "#fff",
|
|
199
|
+
opacity: fading ? 0 : 1,
|
|
200
|
+
transition: reducedMotion ? void 0 : `opacity ${FADE_MS}ms ease-out`
|
|
201
|
+
},
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsxs(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
"aria-label": "Politogy",
|
|
207
|
+
style: {
|
|
208
|
+
display: "flex",
|
|
209
|
+
alignItems: "center",
|
|
210
|
+
gap: "0.6rem",
|
|
211
|
+
fontFamily: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)"
|
|
212
|
+
},
|
|
213
|
+
children: [
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"span",
|
|
216
|
+
{
|
|
217
|
+
"aria-hidden": "true",
|
|
218
|
+
style: {
|
|
219
|
+
display: "inline-flex",
|
|
220
|
+
alignItems: "center",
|
|
221
|
+
justifyContent: "center",
|
|
222
|
+
width: 34,
|
|
223
|
+
height: 26,
|
|
224
|
+
borderRadius: 6,
|
|
225
|
+
background: "#fff",
|
|
226
|
+
color: "var(--color-ink-900, #0a0a0c)",
|
|
227
|
+
fontSize: "0.8125rem",
|
|
228
|
+
fontWeight: 800,
|
|
229
|
+
letterSpacing: "-0.02em"
|
|
230
|
+
},
|
|
231
|
+
children: "vrm"
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "1.375rem", fontWeight: 700, letterSpacing: "-0.02em" }, children: "Politogy" })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
),
|
|
238
|
+
/* @__PURE__ */ jsxs("div", { style: { width: 320, maxWidth: "80vw" }, children: [
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
"div",
|
|
241
|
+
{
|
|
242
|
+
role: "progressbar",
|
|
243
|
+
"aria-valuemin": 0,
|
|
244
|
+
"aria-valuemax": 100,
|
|
245
|
+
"aria-valuenow": Math.round(clampedPct),
|
|
246
|
+
"aria-label": "Loading your workspace",
|
|
247
|
+
style: {
|
|
248
|
+
height: 3,
|
|
249
|
+
width: "100%",
|
|
250
|
+
borderRadius: 2,
|
|
251
|
+
background: "rgba(255,255,255,0.12)",
|
|
252
|
+
overflow: "hidden"
|
|
253
|
+
},
|
|
254
|
+
children: /* @__PURE__ */ jsx(
|
|
255
|
+
"div",
|
|
256
|
+
{
|
|
257
|
+
style: {
|
|
258
|
+
height: "100%",
|
|
259
|
+
width: `${clampedPct}%`,
|
|
260
|
+
background: "linear-gradient(90deg, var(--color-race-blue, #1d4ed8), var(--color-race-red, #dc2626))",
|
|
261
|
+
transition: reducedMotion ? void 0 : `width ${FILL_EASE_MS}ms ease-out`
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
/* @__PURE__ */ jsx(
|
|
268
|
+
"div",
|
|
269
|
+
{
|
|
270
|
+
role: "status",
|
|
271
|
+
"aria-live": "polite",
|
|
272
|
+
style: {
|
|
273
|
+
marginTop: "0.75rem",
|
|
274
|
+
fontFamily: "var(--font-mono, ui-monospace, monospace)",
|
|
275
|
+
fontSize: "0.8125rem",
|
|
276
|
+
color: isError ? "var(--color-race-red, #dc2626)" : "rgba(255,255,255,0.72)",
|
|
277
|
+
textAlign: "center"
|
|
278
|
+
},
|
|
279
|
+
children: announced
|
|
280
|
+
}
|
|
281
|
+
),
|
|
282
|
+
isError && (bootProgress?.onRetry || bootProgress?.onSignOut) && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", gap: "0.75rem", justifyContent: "center" }, children: [
|
|
283
|
+
bootProgress?.onRetry && /* @__PURE__ */ jsx("button", { type: "button", onClick: bootProgress.onRetry, children: "Retry" }),
|
|
284
|
+
bootProgress?.onSignOut && /* @__PURE__ */ jsx("button", { type: "button", onClick: bootProgress.onSignOut, children: "Sign out" })
|
|
285
|
+
] })
|
|
286
|
+
] })
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
81
291
|
var CORE_APP = "vrm";
|
|
82
292
|
var DEFAULT_SIGN_OUT_URL = "/auth/signout";
|
|
83
293
|
var DEFAULT_BRAND_LOGO = "/brand/logos/relationship-mode-logo.png";
|
|
@@ -128,6 +338,8 @@ function PolitogyAppFrame({
|
|
|
128
338
|
signOutUrl = DEFAULT_SIGN_OUT_URL,
|
|
129
339
|
buildMeta,
|
|
130
340
|
data,
|
|
341
|
+
bootProgress,
|
|
342
|
+
appReady,
|
|
131
343
|
children
|
|
132
344
|
}) {
|
|
133
345
|
const meta = POLITOGY_APPS[app];
|
|
@@ -156,6 +368,8 @@ function PolitogyAppFrame({
|
|
|
156
368
|
signOutUrl,
|
|
157
369
|
buildMeta,
|
|
158
370
|
data,
|
|
371
|
+
bootProgress,
|
|
372
|
+
appReady,
|
|
159
373
|
children
|
|
160
374
|
}
|
|
161
375
|
)
|
|
@@ -172,10 +386,33 @@ function FrameBody({
|
|
|
172
386
|
signOutUrl,
|
|
173
387
|
buildMeta,
|
|
174
388
|
data,
|
|
389
|
+
bootProgress,
|
|
390
|
+
appReady,
|
|
175
391
|
children
|
|
176
392
|
}) {
|
|
177
393
|
const meta = POLITOGY_APPS[app];
|
|
178
394
|
const chrome = useChromeState();
|
|
395
|
+
const bootAppMode = meta.mode;
|
|
396
|
+
const bootOrgId = chrome?.orgId ?? null;
|
|
397
|
+
const [bootVisible, setBootVisible] = useState(true);
|
|
398
|
+
const [bootGeneration, setBootGeneration] = useState(0);
|
|
399
|
+
const seenOrgRef = useRef(bootOrgId);
|
|
400
|
+
const seenModeRef = useRef(bootAppMode);
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (seenOrgRef.current !== null && seenOrgRef.current !== bootOrgId) {
|
|
403
|
+
setBootVisible(true);
|
|
404
|
+
setBootGeneration((g) => g + 1);
|
|
405
|
+
}
|
|
406
|
+
seenOrgRef.current = bootOrgId;
|
|
407
|
+
}, [bootOrgId]);
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (seenModeRef.current !== void 0 && seenModeRef.current !== bootAppMode) {
|
|
410
|
+
setBootVisible(true);
|
|
411
|
+
setBootGeneration((g) => g + 1);
|
|
412
|
+
}
|
|
413
|
+
seenModeRef.current = bootAppMode;
|
|
414
|
+
}, [bootAppMode]);
|
|
415
|
+
const chromeResolved = chrome == null || !chrome.orgsLoading;
|
|
179
416
|
const signOutRef = useRef(onSignOut);
|
|
180
417
|
signOutRef.current = onSignOut;
|
|
181
418
|
const signingOutRef = useRef(false);
|
|
@@ -250,70 +487,82 @@ function FrameBody({
|
|
|
250
487
|
[navItems, pathname, search, meta.origin]
|
|
251
488
|
);
|
|
252
489
|
const breadcrumbs = core ? core.page?.breadcrumbs : grants?.includes("breadcrumbs") ? granted("breadcrumbs") : derivedBreadcrumbs;
|
|
253
|
-
return /* @__PURE__ */
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
badgeLabel: "P",
|
|
262
|
-
title: "Politogy"
|
|
263
|
-
},
|
|
264
|
-
nav: {
|
|
265
|
-
// A flat rail's sub-menu IS the nav, so the 'tabs only' preference
|
|
266
|
-
// (which strips children) would leave one childless parent row —
|
|
267
|
-
// flat apps ignore the stripping.
|
|
268
|
-
items: navDisplay === "tabs" && meta.navShape !== "flat" ? navItems.map(({ children: _sub, ...item }) => item) : navItems,
|
|
269
|
-
flatten: meta.navShape === "flat"
|
|
270
|
-
},
|
|
271
|
-
// The app's own workspace is NOT the organization — it is a per-app
|
|
272
|
-
// bootstrap account with a different id in every app. Showing it while
|
|
273
|
-
// the chrome API resolves flashed the wrong org on every load ("not
|
|
274
|
-
// connected to the account"). Shimmer while in flight; only fall back
|
|
275
|
-
// to the app label once the chrome lookup has actually failed.
|
|
276
|
-
org: activeCoreOrg ? {
|
|
277
|
-
name: activeCoreOrg.name,
|
|
278
|
-
id: activeCoreOrg.id,
|
|
279
|
-
logoUrl: activeCoreOrg.logoUrl,
|
|
280
|
-
brandColor: activeCoreOrg.brandColor,
|
|
281
|
-
organizations: coreOrgs,
|
|
282
|
-
onSwitch: chrome?.setOrgId
|
|
283
|
-
} : chrome?.orgsLoading ? { name: null, id: null, loading: true } : { name: session.accountName, id: session.accountId },
|
|
284
|
-
mode,
|
|
285
|
-
footer,
|
|
286
|
-
linkComponent
|
|
490
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
491
|
+
/* @__PURE__ */ jsx(
|
|
492
|
+
LoadingScreen,
|
|
493
|
+
{
|
|
494
|
+
visible: bootVisible,
|
|
495
|
+
bootProgress,
|
|
496
|
+
syntheticReady: appReady ?? chromeResolved,
|
|
497
|
+
onExitComplete: () => setBootVisible(false)
|
|
287
498
|
},
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
499
|
+
bootGeneration
|
|
500
|
+
),
|
|
501
|
+
/* @__PURE__ */ jsx(
|
|
502
|
+
NavAppShell,
|
|
503
|
+
{
|
|
504
|
+
sidebar: {
|
|
505
|
+
pathname,
|
|
506
|
+
brand: {
|
|
507
|
+
logoSrc: core?.brandLogoSrc ?? modeBrandLogo(appMode),
|
|
508
|
+
logoAlt: "Politogy",
|
|
509
|
+
badgeLabel: "P",
|
|
510
|
+
title: "Politogy"
|
|
511
|
+
},
|
|
512
|
+
nav: {
|
|
513
|
+
// A flat rail's sub-menu IS the nav, so the 'tabs only' preference
|
|
514
|
+
// (which strips children) would leave one childless parent row —
|
|
515
|
+
// flat apps ignore the stripping.
|
|
516
|
+
items: navDisplay === "tabs" && meta.navShape !== "flat" ? navItems.map(({ children: _sub, ...item }) => item) : navItems,
|
|
517
|
+
flatten: meta.navShape === "flat"
|
|
518
|
+
},
|
|
519
|
+
// The app's own workspace is NOT the organization — it is a per-app
|
|
520
|
+
// bootstrap account with a different id in every app. Showing it while
|
|
521
|
+
// the chrome API resolves flashed the wrong org on every load ("not
|
|
522
|
+
// connected to the account"). Shimmer while in flight; only fall back
|
|
523
|
+
// to the app label once the chrome lookup has actually failed.
|
|
524
|
+
org: activeCoreOrg ? {
|
|
525
|
+
name: activeCoreOrg.name,
|
|
526
|
+
id: activeCoreOrg.id,
|
|
527
|
+
logoUrl: activeCoreOrg.logoUrl,
|
|
528
|
+
brandColor: activeCoreOrg.brandColor,
|
|
529
|
+
organizations: coreOrgs,
|
|
530
|
+
onSwitch: chrome?.setOrgId
|
|
531
|
+
} : chrome?.orgsLoading ? { name: null, id: null, loading: true } : { name: session.accountName, id: session.accountId },
|
|
532
|
+
mode,
|
|
533
|
+
footer,
|
|
534
|
+
linkComponent
|
|
303
535
|
},
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
core?.page?.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
536
|
+
betaBanner: meta.beta === false ? void 0 : {},
|
|
537
|
+
modeId: appMode,
|
|
538
|
+
contentWrapper: core?.contentWrapper ?? granted("contentWrapper"),
|
|
539
|
+
topBar: {
|
|
540
|
+
// Core's pages publish their own identity; every other app is one
|
|
541
|
+
// product surface and takes its registry title.
|
|
542
|
+
title: core?.page?.title ?? (grants?.includes("title") ? data?.frame?.title : void 0) ?? meta.title,
|
|
543
|
+
breadcrumbs,
|
|
544
|
+
user: {
|
|
545
|
+
name: identity.userName,
|
|
546
|
+
email: identity.email,
|
|
547
|
+
// The chrome API's avatar wins; the app may supply a fallback chain
|
|
548
|
+
// (e.g. a stored memoji) for users with no uploaded photo.
|
|
549
|
+
avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,
|
|
550
|
+
role: session.role
|
|
551
|
+
},
|
|
552
|
+
userMenu,
|
|
553
|
+
onSignOut: handleSignOut,
|
|
554
|
+
// The action cluster is IDENTICAL everywhere — core included. Page
|
|
555
|
+
// actions sit BEFORE it, never inside it, so the Alerts bell keeps its
|
|
556
|
+
// required position: farthest right, immediately before My Account.
|
|
557
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
558
|
+
core?.page?.actions,
|
|
559
|
+
/* @__PURE__ */ jsx(ChromeTopBarActions, {})
|
|
560
|
+
] })
|
|
561
|
+
},
|
|
562
|
+
children
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
] });
|
|
317
566
|
}
|
|
318
567
|
var standardNavIcons = {
|
|
319
568
|
home: FiHome,
|
|
@@ -790,6 +1039,6 @@ function useChromeMode(arg = {}) {
|
|
|
790
1039
|
};
|
|
791
1040
|
}
|
|
792
1041
|
|
|
793
|
-
export { ChromeTopBarActions, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
|
1042
|
+
export { ChromeTopBarActions, LoadingScreen, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, deriveBreadcrumbs, looksLikeEmail, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
|
|
794
1043
|
//# sourceMappingURL=app-chrome.js.map
|
|
795
1044
|
//# sourceMappingURL=app-chrome.js.map
|