@the-portland-company/shell 0.24.0 → 0.26.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 +86 -0
- package/dist/app-chrome.cjs +1385 -3
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +111 -3
- package/dist/app-chrome.d.ts +111 -3
- package/dist/app-chrome.js +234 -6
- package/dist/app-chrome.js.map +1 -1
- package/dist/app-icons.cjs +50 -0
- package/dist/app-icons.cjs.map +1 -0
- package/dist/app-icons.d.cts +46 -0
- package/dist/app-icons.d.ts +46 -0
- package/dist/app-icons.js +45 -0
- package/dist/app-icons.js.map +1 -0
- package/dist/chrome.css +41 -144
- package/dist/{chunk-ZZWM7BZZ.js → chunk-LL7RCJQN.js} +31 -3
- package/dist/chunk-LL7RCJQN.js.map +1 -0
- package/dist/{chunk-YWFWSAVT.js → chunk-QCA2PGKA.js} +25 -7
- package/dist/chunk-QCA2PGKA.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/native.cjs +50 -4
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +2 -2
- package/dist/theme-preference.cjs +32 -0
- package/dist/theme-preference.cjs.map +1 -1
- package/dist/theme-preference.d.cts +12 -1
- package/dist/theme-preference.d.ts +12 -1
- package/dist/theme-preference.js +1 -1
- package/package.json +6 -1
- package/dist/chunk-YWFWSAVT.js.map +0 -1
- package/dist/chunk-ZZWM7BZZ.js.map +0 -1
package/dist/app-chrome.cjs
CHANGED
|
@@ -57,6 +57,7 @@ function resolveBrandGlyph(name) {
|
|
|
57
57
|
// src/theme-preference.ts
|
|
58
58
|
var THEME_STORAGE_KEY = "politogy_theme";
|
|
59
59
|
var THEME_COOKIE = "politogy_theme";
|
|
60
|
+
var THEME_PENDING_COOKIE = "politogy_theme_pending";
|
|
60
61
|
var VALID = ["light", "dark", "system"];
|
|
61
62
|
function isThemePreference(v) {
|
|
62
63
|
return typeof v === "string" && VALID.includes(v);
|
|
@@ -83,6 +84,33 @@ function writeThemeCookie(pref) {
|
|
|
83
84
|
pref
|
|
84
85
|
)}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
|
|
85
86
|
}
|
|
87
|
+
function writeSharedCookie(name, value, maxAgeSeconds) {
|
|
88
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
89
|
+
const host = window.location.hostname;
|
|
90
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
91
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
92
|
+
document.cookie = `${name}=${encodeURIComponent(
|
|
93
|
+
value
|
|
94
|
+
)}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax${domainAttr}${secure}`;
|
|
95
|
+
}
|
|
96
|
+
function markThemeChoicePending(pref) {
|
|
97
|
+
writeSharedCookie(THEME_PENDING_COOKIE, pref, 31536e3);
|
|
98
|
+
}
|
|
99
|
+
function readPendingThemeChoice() {
|
|
100
|
+
if (typeof document === "undefined") return null;
|
|
101
|
+
try {
|
|
102
|
+
const m = document.cookie.match(
|
|
103
|
+
new RegExp(`(?:^|;\\s*)${THEME_PENDING_COOKIE}=([^;]+)`)
|
|
104
|
+
);
|
|
105
|
+
const v = m ? decodeURIComponent(m[1]) : null;
|
|
106
|
+
return isThemePreference(v) ? v : null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function clearPendingThemeChoice() {
|
|
112
|
+
writeSharedCookie(THEME_PENDING_COOKIE, "", 0);
|
|
113
|
+
}
|
|
86
114
|
function resolveTheme(pref) {
|
|
87
115
|
if (pref === "system") {
|
|
88
116
|
try {
|
|
@@ -173,6 +201,42 @@ function TopBarWidgetSlot({
|
|
|
173
201
|
/* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title, description })
|
|
174
202
|
] });
|
|
175
203
|
}
|
|
204
|
+
var THEME_OPTIONS = [
|
|
205
|
+
{ value: "light", label: "Light" },
|
|
206
|
+
{ value: "system", label: "System" },
|
|
207
|
+
{ value: "dark", label: "Dark" }
|
|
208
|
+
];
|
|
209
|
+
function NavThemeControl({
|
|
210
|
+
value,
|
|
211
|
+
onChange
|
|
212
|
+
}) {
|
|
213
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
|
|
214
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Appearance" }),
|
|
215
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
216
|
+
"div",
|
|
217
|
+
{
|
|
218
|
+
role: "radiogroup",
|
|
219
|
+
"aria-label": "Appearance",
|
|
220
|
+
className: "flex overflow-hidden rounded-full border border-[color:var(--line)]",
|
|
221
|
+
children: THEME_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
type: "button",
|
|
225
|
+
role: "radio",
|
|
226
|
+
"aria-checked": value === opt.value,
|
|
227
|
+
onClick: () => onChange(opt.value),
|
|
228
|
+
className: [
|
|
229
|
+
"flex-1 px-3 py-1.5 text-xs font-semibold transition",
|
|
230
|
+
value === opt.value ? "bg-[color:var(--color-race-red)] text-white" : "text-[color:var(--muted)] hover:bg-[color:var(--panel)] hover:text-[color:var(--foreground)]"
|
|
231
|
+
].join(" "),
|
|
232
|
+
children: opt.label
|
|
233
|
+
},
|
|
234
|
+
opt.value
|
|
235
|
+
))
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
] });
|
|
239
|
+
}
|
|
176
240
|
var NavChromeContext = react.createContext(null);
|
|
177
241
|
function NavChromeProvider({
|
|
178
242
|
fetchJson,
|
|
@@ -194,6 +258,10 @@ function NavChromeProvider({
|
|
|
194
258
|
}, [fetchJson]);
|
|
195
259
|
return /* @__PURE__ */ jsxRuntime.jsx(NavChromeContext.Provider, { value: { fetchJson, me, meLoaded }, children });
|
|
196
260
|
}
|
|
261
|
+
function useChromeMe() {
|
|
262
|
+
const ctx = react.useContext(NavChromeContext);
|
|
263
|
+
return { me: ctx?.me ?? null, loaded: ctx?.meLoaded ?? false };
|
|
264
|
+
}
|
|
197
265
|
function useNavChromeFetcher() {
|
|
198
266
|
return react.useContext(NavChromeContext)?.fetchJson ?? null;
|
|
199
267
|
}
|
|
@@ -661,6 +729,25 @@ function useThemePreference() {
|
|
|
661
729
|
const meCtx = react.useContext(NavChromeContext);
|
|
662
730
|
react.useEffect(() => {
|
|
663
731
|
if (reconciled.current) return;
|
|
732
|
+
const pending = readPendingThemeChoice();
|
|
733
|
+
if (pending) {
|
|
734
|
+
if (meCtx?.me) reconciled.current = true;
|
|
735
|
+
setValueState(pending);
|
|
736
|
+
applyThemePreference(pending);
|
|
737
|
+
writeThemeCookie(pending);
|
|
738
|
+
try {
|
|
739
|
+
localStorage.setItem(THEME_STORAGE_KEY, pending);
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
if (!meCtx?.me) return;
|
|
743
|
+
fetchJson?.("me/preferences", {
|
|
744
|
+
method: "PATCH",
|
|
745
|
+
headers: { "Content-Type": "application/json" },
|
|
746
|
+
body: JSON.stringify({ themePreference: pending })
|
|
747
|
+
}).then(() => clearPendingThemeChoice()).catch(() => {
|
|
748
|
+
});
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
664
751
|
const pref = meCtx?.me?.themePreference;
|
|
665
752
|
if (isThemePreference(pref)) {
|
|
666
753
|
reconciled.current = true;
|
|
@@ -719,17 +806,1281 @@ function useThemePreference() {
|
|
|
719
806
|
localStorage.setItem(THEME_STORAGE_KEY, next);
|
|
720
807
|
} catch {
|
|
721
808
|
}
|
|
809
|
+
markThemeChoicePending(next);
|
|
722
810
|
fetchJson?.("me/preferences", {
|
|
723
811
|
method: "PATCH",
|
|
724
812
|
headers: { "Content-Type": "application/json" },
|
|
725
813
|
body: JSON.stringify({ themePreference: next })
|
|
726
|
-
}).catch(() => {
|
|
814
|
+
}).then(() => clearPendingThemeChoice()).catch(() => {
|
|
727
815
|
});
|
|
728
816
|
},
|
|
729
817
|
[fetchJson]
|
|
730
818
|
);
|
|
731
819
|
return { value, setValue };
|
|
732
820
|
}
|
|
821
|
+
|
|
822
|
+
// src/native/standardNav.ts
|
|
823
|
+
var CONTACTS_DEFAULT_CHILDREN = [
|
|
824
|
+
{ label: "Dashboard", icon: "grid", path: "/relationship/contacts/dashboard" },
|
|
825
|
+
{ label: "All Contacts", icon: "list", path: "/relationship/contacts/all" },
|
|
826
|
+
{ label: "Tribes", icon: "users", path: "/relationship/contacts/groups" },
|
|
827
|
+
{ label: "Import", icon: "upload", path: "/relationship/contacts/import" },
|
|
828
|
+
{ label: "Import History", icon: "fileText", path: "/relationship/contacts/import-history" },
|
|
829
|
+
{ label: "Merge Rules", icon: "layout", path: "/relationship/contacts/merge-rules" }
|
|
830
|
+
];
|
|
831
|
+
var EMAIL_CHILDREN = [
|
|
832
|
+
{ label: "Dashboard", icon: "grid", path: "/relationship/email/dashboard" },
|
|
833
|
+
{ label: "Create & Edit", icon: "edit", path: "/relationship/email/create-and-edit" },
|
|
834
|
+
{ label: "Sender Information", icon: "mail", path: "/relationship/email/inbox" },
|
|
835
|
+
{ label: "Recipients", icon: "user", path: "/relationship/email/send-to" },
|
|
836
|
+
{ label: "Send & Automate", icon: "clock", path: "/relationship/email/send-and-automate" },
|
|
837
|
+
{ label: "Statistics", icon: "barChart", path: "/relationship/email/statistics" }
|
|
838
|
+
];
|
|
839
|
+
var SMS_CHILDREN = [
|
|
840
|
+
{ label: "Dashboard", icon: "grid", path: "/relationship/sms-mms-blast/dashboard" },
|
|
841
|
+
{ label: "Compose & Send", icon: "send", path: "/relationship/sms-mms-blast/compose-and-send" },
|
|
842
|
+
{ label: "Conversations", icon: "messageSquare", path: "/relationship/sms-mms-blast/conversations" }
|
|
843
|
+
];
|
|
844
|
+
var CTA_CHILDREN = [
|
|
845
|
+
{ label: "Dashboard", icon: "grid", path: "/relationship/cta-database/dashboard" },
|
|
846
|
+
{ label: "Create & Edit", icon: "edit", path: "/relationship/cta-creator/create-and-edit" },
|
|
847
|
+
{ label: "Submissions", icon: "fileText", path: "/relationship/cta-submissions/submissions" }
|
|
848
|
+
];
|
|
849
|
+
var ORG_SETTINGS_CHILDREN = [
|
|
850
|
+
{ label: "General", icon: "settings", path: "/organization-settings" },
|
|
851
|
+
{
|
|
852
|
+
label: "Subscription & Billing",
|
|
853
|
+
icon: "creditCard",
|
|
854
|
+
path: "/organization-settings/subscription-billing",
|
|
855
|
+
children: [
|
|
856
|
+
{ label: "Current Plan", icon: "fileText", path: "/organization-settings/subscription-billing/current-plan" },
|
|
857
|
+
{ label: "SMS, MMS & Email Usage", icon: "activity", path: "/organization-settings/subscription-billing/text-email" },
|
|
858
|
+
{ label: "Wallet", icon: "dollarSign", path: "/organization-settings/subscription-billing/wallet" },
|
|
859
|
+
{ label: "10DLC", icon: "messageSquare", path: "/organization-settings/subscription-billing/10dlc" },
|
|
860
|
+
{ label: "Billing History", icon: "clock", path: "/organization-settings/subscription-billing/billing-history" }
|
|
861
|
+
]
|
|
862
|
+
},
|
|
863
|
+
{ label: "Email", icon: "mail", path: "/organization-settings/email-settings" },
|
|
864
|
+
{ label: "Users", icon: "users", path: "/organization-settings/users" },
|
|
865
|
+
{ label: "Shared Links", icon: "share", path: "/organization-settings/shared-links" }
|
|
866
|
+
];
|
|
867
|
+
var APP_FORMS_CHILDREN = [
|
|
868
|
+
{ label: "Forms", icon: "list", href: "https://forms.politogyvrm.com/?tab=forms" },
|
|
869
|
+
{ label: "Canvas", icon: "edit", href: "https://forms.politogyvrm.com/?tab=canvas" },
|
|
870
|
+
{ label: "Submissions", icon: "fileText", href: "https://forms.politogyvrm.com/?tab=submissions" }
|
|
871
|
+
];
|
|
872
|
+
var APP_SURVEYS_CHILDREN = [
|
|
873
|
+
{ label: "Surveys", icon: "list", href: "https://surveys.politogyvrm.com/surveys" }
|
|
874
|
+
];
|
|
875
|
+
var APP_POLLS_CHILDREN = [
|
|
876
|
+
{ label: "Command Center", icon: "grid", href: "https://polls.politogyvrm.com/" },
|
|
877
|
+
{ label: "New Poll", icon: "edit", href: "https://polls.politogyvrm.com/polls/new" }
|
|
878
|
+
];
|
|
879
|
+
function standardPolitogyNav(opts) {
|
|
880
|
+
const base = opts.coreBaseUrl.replace(/\/+$/, "");
|
|
881
|
+
const abs = (path) => `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
882
|
+
const icon = (key) => opts.icons?.[key];
|
|
883
|
+
const leaves = (children) => children.map((c) => ({ label: c.label, icon: icon(c.icon), href: abs(c.path) }));
|
|
884
|
+
const branches = (children) => children.map((c) => ({
|
|
885
|
+
label: c.label,
|
|
886
|
+
icon: icon(c.icon),
|
|
887
|
+
href: abs(c.path),
|
|
888
|
+
children: c.children ? leaves(c.children) : void 0
|
|
889
|
+
}));
|
|
890
|
+
const contacts = opts.contactsItem ? {
|
|
891
|
+
label: "Contacts",
|
|
892
|
+
icon: icon("users"),
|
|
893
|
+
href: opts.contactsItem.href,
|
|
894
|
+
children: opts.contactsItem.children
|
|
895
|
+
} : {
|
|
896
|
+
label: "Contacts",
|
|
897
|
+
icon: icon("users"),
|
|
898
|
+
href: abs("/relationship/contacts/dashboard"),
|
|
899
|
+
children: leaves(CONTACTS_DEFAULT_CHILDREN)
|
|
900
|
+
};
|
|
901
|
+
const items = [
|
|
902
|
+
{ label: "Dashboard", icon: icon("home"), href: abs("/relationship/dashboard") },
|
|
903
|
+
contacts,
|
|
904
|
+
{
|
|
905
|
+
label: "Email Broadcast",
|
|
906
|
+
icon: icon("mail"),
|
|
907
|
+
href: abs("/relationship/email/dashboard"),
|
|
908
|
+
children: leaves(EMAIL_CHILDREN)
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
label: "SMS/MMS Blast",
|
|
912
|
+
icon: icon("messageSquare"),
|
|
913
|
+
href: abs("/relationship/sms-mms-blast/dashboard"),
|
|
914
|
+
children: leaves(SMS_CHILDREN)
|
|
915
|
+
},
|
|
916
|
+
// These two carry no top-level href in VRM core (parent row only toggles).
|
|
917
|
+
{ label: "Calls to Action", icon: icon("layout"), children: leaves(CTA_CHILDREN) },
|
|
918
|
+
{
|
|
919
|
+
label: "Forms",
|
|
920
|
+
icon: icon("activity"),
|
|
921
|
+
href: "https://forms.politogyvrm.com/",
|
|
922
|
+
children: APP_FORMS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: c.href }))
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
label: "Surveys",
|
|
926
|
+
icon: icon("fileText"),
|
|
927
|
+
href: "https://surveys.politogyvrm.com/surveys",
|
|
928
|
+
children: APP_SURVEYS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: c.href }))
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
label: "Polls",
|
|
932
|
+
icon: icon("barChart"),
|
|
933
|
+
href: "https://polls.politogyvrm.com/",
|
|
934
|
+
children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: c.href }))
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
label: "Organization Settings",
|
|
938
|
+
icon: icon("briefcase"),
|
|
939
|
+
href: abs("/organization-settings"),
|
|
940
|
+
children: branches(ORG_SETTINGS_CHILDREN)
|
|
941
|
+
}
|
|
942
|
+
];
|
|
943
|
+
if (opts.supportItem) {
|
|
944
|
+
items.push({
|
|
945
|
+
label: "Support",
|
|
946
|
+
icon: icon("lifeBuoy"),
|
|
947
|
+
href: opts.supportItem.href,
|
|
948
|
+
onClick: opts.supportItem.onClick
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
return items;
|
|
952
|
+
}
|
|
953
|
+
var safeHref = (h) => h && /^(https?:|mailto:|tel:|\/|#|\.)/i.test(h.trim()) ? h : "#";
|
|
954
|
+
var DefaultLink = ({ href, children, ...rest }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: safeHref(href), ...rest, children });
|
|
955
|
+
function isNavItemActive(pathname, href) {
|
|
956
|
+
let targetPath = href;
|
|
957
|
+
let targetSearch = "";
|
|
958
|
+
if (/^https?:\/\//i.test(href)) {
|
|
959
|
+
if (typeof window === "undefined") return false;
|
|
960
|
+
try {
|
|
961
|
+
const u = new URL(href);
|
|
962
|
+
if (u.host !== window.location.host) return false;
|
|
963
|
+
targetPath = u.pathname;
|
|
964
|
+
targetSearch = u.search;
|
|
965
|
+
} catch {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
} else {
|
|
969
|
+
const qi = href.indexOf("?");
|
|
970
|
+
if (qi >= 0) {
|
|
971
|
+
targetPath = href.slice(0, qi) || "/";
|
|
972
|
+
targetSearch = href.slice(qi);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (targetSearch) {
|
|
976
|
+
if (typeof window === "undefined") return false;
|
|
977
|
+
if (pathname !== targetPath) return false;
|
|
978
|
+
const want = new URLSearchParams(targetSearch);
|
|
979
|
+
const have = new URLSearchParams(window.location.search);
|
|
980
|
+
for (const [k, v] of want) if (have.get(k) !== v) return false;
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
if (targetPath === "/") return pathname === "/";
|
|
984
|
+
return pathname === targetPath || pathname.startsWith(`${targetPath}/`);
|
|
985
|
+
}
|
|
986
|
+
function groupKeyOf(item, index) {
|
|
987
|
+
return item.href ?? `nav-group-${index}-${item.label}`;
|
|
988
|
+
}
|
|
989
|
+
function hasActiveChild(item, pathname, activeFn) {
|
|
990
|
+
return (item.children ?? []).some(
|
|
991
|
+
(child) => (child.href ? activeFn(pathname, child.href) : false) || // Depth-3: a grandchild owning the route also opens the top group.
|
|
992
|
+
(child.children ?? []).some(
|
|
993
|
+
(leaf) => leaf.href ? activeFn(pathname, leaf.href) : false
|
|
994
|
+
)
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
function envChipClasses(label) {
|
|
998
|
+
const l = label.toLowerCase();
|
|
999
|
+
if (l === "production" || l === "prod")
|
|
1000
|
+
return "bg-[#fee2e2] text-[#ef4444]";
|
|
1001
|
+
if (l === "staging" || l === "stage")
|
|
1002
|
+
return "bg-[#ffedd5] text-[#f97316]";
|
|
1003
|
+
if (l === "local" || l === "development" || l === "dev")
|
|
1004
|
+
return "bg-[#dcfce7] text-[#22c55e]";
|
|
1005
|
+
return "bg-[rgba(107,114,128,0.16)] text-[#6b7280]";
|
|
1006
|
+
}
|
|
1007
|
+
var ORG_PILL_DEFAULT = "var(--color-race-red)";
|
|
1008
|
+
function NavSidebar({
|
|
1009
|
+
pathname,
|
|
1010
|
+
brand,
|
|
1011
|
+
nav,
|
|
1012
|
+
org,
|
|
1013
|
+
mode,
|
|
1014
|
+
footer,
|
|
1015
|
+
linkComponent
|
|
1016
|
+
}) {
|
|
1017
|
+
const Link = linkComponent ?? DefaultLink;
|
|
1018
|
+
const activeFn = nav.isActive ?? isNavItemActive;
|
|
1019
|
+
const ModeIcon = mode?.icon;
|
|
1020
|
+
const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
|
|
1021
|
+
const activeGroupKey = react.useMemo(() => {
|
|
1022
|
+
for (let i = 0; i < nav.items.length; i++) {
|
|
1023
|
+
const item = nav.items[i];
|
|
1024
|
+
if (item.children && item.children.length > 0 && hasActiveChild(item, pathname, activeFn)) {
|
|
1025
|
+
return groupKeyOf(item, i);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return null;
|
|
1029
|
+
}, [nav.items, pathname, activeFn]);
|
|
1030
|
+
const [openGroup, setOpenGroup] = react.useState(activeGroupKey);
|
|
1031
|
+
react.useEffect(() => {
|
|
1032
|
+
setOpenGroup(activeGroupKey);
|
|
1033
|
+
}, [activeGroupKey]);
|
|
1034
|
+
const toggleGroup = (key) => setOpenGroup((prev) => prev === key ? null : key);
|
|
1035
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--nav-surface)] lg:flex", children: [
|
|
1036
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1037
|
+
Link,
|
|
1038
|
+
{
|
|
1039
|
+
href: brand.homeHref ?? "/",
|
|
1040
|
+
className: "flex h-[64px] flex-shrink-0 items-center gap-2.5 px-6",
|
|
1041
|
+
children: brand.logoSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1042
|
+
"img",
|
|
1043
|
+
{
|
|
1044
|
+
src: brand.logoSrc,
|
|
1045
|
+
alt: brand.logoAlt ?? brand.title,
|
|
1046
|
+
draggable: false,
|
|
1047
|
+
className: "h-10 w-auto object-contain"
|
|
1048
|
+
}
|
|
1049
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1050
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm", children: brand.badgeLabel }),
|
|
1051
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col leading-tight", children: [
|
|
1052
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-extrabold tracking-[-0.01em] text-[color:var(--color-race-red)]", children: brand.title }),
|
|
1053
|
+
brand.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: brand.subtitle }) : null
|
|
1054
|
+
] })
|
|
1055
|
+
] })
|
|
1056
|
+
}
|
|
1057
|
+
),
|
|
1058
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-y-auto px-4 pb-2", children: [
|
|
1059
|
+
org ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
|
|
1060
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between px-1", children: [
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Organization" }),
|
|
1062
|
+
org.id ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
|
|
1063
|
+
] }),
|
|
1064
|
+
org.organizations && org.organizations.length > 0 && org.onSwitch ? /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group relative", children: [
|
|
1065
|
+
/* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)] [&::-webkit-details-marker]:hidden", children: [
|
|
1066
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
1067
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1068
|
+
"span",
|
|
1069
|
+
{
|
|
1070
|
+
style: { backgroundColor: orgBg },
|
|
1071
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
|
|
1072
|
+
children: org.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1076
|
+
"span",
|
|
1077
|
+
{
|
|
1078
|
+
title: org.name ?? void 0,
|
|
1079
|
+
className: "truncate text-sm font-semibold",
|
|
1080
|
+
children: org.name ?? "No organizations"
|
|
1081
|
+
}
|
|
1082
|
+
)
|
|
1083
|
+
] }),
|
|
1084
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 right-0 z-40 mt-1 max-h-72 overflow-y-auto rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: org.organizations.map((o) => {
|
|
1087
|
+
const selected = o.id === org.id;
|
|
1088
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1089
|
+
"button",
|
|
1090
|
+
{
|
|
1091
|
+
type: "button",
|
|
1092
|
+
onClick: (e) => {
|
|
1093
|
+
e.currentTarget.closest("details")?.removeAttribute("open");
|
|
1094
|
+
if (!selected) org.onSwitch?.(o.id);
|
|
1095
|
+
},
|
|
1096
|
+
"aria-current": selected ? "true" : void 0,
|
|
1097
|
+
className: [
|
|
1098
|
+
"flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
|
|
1099
|
+
selected ? "font-semibold text-[color:var(--color-race-red)]" : "font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]"
|
|
1100
|
+
].join(" "),
|
|
1101
|
+
children: [
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1103
|
+
"span",
|
|
1104
|
+
{
|
|
1105
|
+
style: { backgroundColor: o.brandColor || ORG_PILL_DEFAULT },
|
|
1106
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-[10px] font-bold text-white",
|
|
1107
|
+
children: o.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: o.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (o.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1108
|
+
}
|
|
1109
|
+
),
|
|
1110
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { title: o.name ?? o.id, className: "truncate", children: o.name ?? o.id.slice(0, 8) })
|
|
1111
|
+
]
|
|
1112
|
+
},
|
|
1113
|
+
o.id
|
|
1114
|
+
);
|
|
1115
|
+
}) })
|
|
1116
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1117
|
+
"button",
|
|
1118
|
+
{
|
|
1119
|
+
type: "button",
|
|
1120
|
+
onClick: org.onClick,
|
|
1121
|
+
className: "flex w-full items-center justify-between gap-2 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)]",
|
|
1122
|
+
children: [
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
1124
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1125
|
+
"span",
|
|
1126
|
+
{
|
|
1127
|
+
style: { backgroundColor: orgBg },
|
|
1128
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
|
|
1129
|
+
children: org.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1130
|
+
}
|
|
1131
|
+
),
|
|
1132
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
|
|
1133
|
+
] }),
|
|
1134
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
|
|
1135
|
+
]
|
|
1136
|
+
}
|
|
1137
|
+
)
|
|
1138
|
+
] }) : null,
|
|
1139
|
+
mode ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: mode.options && mode.options.length > 1 && mode.onSelect ? /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group relative", children: [
|
|
1140
|
+
/* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-md bg-[color:var(--color-race-red)] px-2.5 py-2 text-white shadow-sm transition hover:opacity-95 [&::-webkit-details-marker]:hidden", children: [
|
|
1141
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2.5", children: [
|
|
1142
|
+
ModeIcon ? /* @__PURE__ */ jsxRuntime.jsx(ModeIcon, { size: 16, className: "shrink-0" }) : null,
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold tracking-[-0.01em]", children: mode.label })
|
|
1144
|
+
] }),
|
|
1145
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
|
|
1146
|
+
] }),
|
|
1147
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 right-0 z-40 mt-1 overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: mode.options.map((opt) => {
|
|
1148
|
+
const selected = opt.label === mode.label;
|
|
1149
|
+
const OptIcon = opt.icon;
|
|
1150
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1151
|
+
"button",
|
|
1152
|
+
{
|
|
1153
|
+
type: "button",
|
|
1154
|
+
onClick: (e) => {
|
|
1155
|
+
e.currentTarget.closest("details")?.removeAttribute("open");
|
|
1156
|
+
if (!selected) mode.onSelect?.(opt.id);
|
|
1157
|
+
},
|
|
1158
|
+
"aria-current": selected ? "true" : void 0,
|
|
1159
|
+
className: [
|
|
1160
|
+
"flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
|
|
1161
|
+
selected ? "font-semibold text-[color:var(--color-race-red)]" : "font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]"
|
|
1162
|
+
].join(" "),
|
|
1163
|
+
children: [
|
|
1164
|
+
OptIcon ? /* @__PURE__ */ jsxRuntime.jsx(OptIcon, { size: 15, className: "shrink-0 text-current" }) : null,
|
|
1165
|
+
opt.label
|
|
1166
|
+
]
|
|
1167
|
+
},
|
|
1168
|
+
opt.id
|
|
1169
|
+
);
|
|
1170
|
+
}) })
|
|
1171
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1172
|
+
"button",
|
|
1173
|
+
{
|
|
1174
|
+
type: "button",
|
|
1175
|
+
className: "flex w-full items-center justify-between gap-2 rounded-md bg-[color:var(--color-race-red)] px-2.5 py-2 text-white shadow-sm",
|
|
1176
|
+
children: [
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2.5", children: [
|
|
1178
|
+
ModeIcon ? /* @__PURE__ */ jsxRuntime.jsx(ModeIcon, { size: 16, className: "shrink-0" }) : null,
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold tracking-[-0.01em]", children: mode.label })
|
|
1180
|
+
] }),
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
|
|
1182
|
+
]
|
|
1183
|
+
}
|
|
1184
|
+
) }) : null,
|
|
1185
|
+
/* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-col gap-1", children: [
|
|
1186
|
+
nav.groupLabel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-2 pb-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: nav.groupLabel }) : null,
|
|
1187
|
+
nav.items.map((item, idx) => {
|
|
1188
|
+
if (item.children && item.children.length > 0) {
|
|
1189
|
+
const key = groupKeyOf(item, idx);
|
|
1190
|
+
const selfActive = item.href ? activeFn(pathname, item.href) : false;
|
|
1191
|
+
const parentActive = selfActive || hasActiveChild(item, pathname, activeFn);
|
|
1192
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1193
|
+
NavGroup,
|
|
1194
|
+
{
|
|
1195
|
+
item,
|
|
1196
|
+
active: parentActive,
|
|
1197
|
+
open: openGroup === key,
|
|
1198
|
+
onToggle: () => toggleGroup(key),
|
|
1199
|
+
pathname,
|
|
1200
|
+
activeFn,
|
|
1201
|
+
Link
|
|
1202
|
+
},
|
|
1203
|
+
key
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
const active = item.href ? activeFn(pathname, item.href) : false;
|
|
1207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1208
|
+
NavRow,
|
|
1209
|
+
{
|
|
1210
|
+
item,
|
|
1211
|
+
active,
|
|
1212
|
+
Link
|
|
1213
|
+
},
|
|
1214
|
+
item.href ?? `nav-${idx}-${item.label}`
|
|
1215
|
+
);
|
|
1216
|
+
})
|
|
1217
|
+
] })
|
|
1218
|
+
] }),
|
|
1219
|
+
footer ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
1220
|
+
footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center justify-center gap-1.5 text-xs", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
1221
|
+
idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1223
|
+
"a",
|
|
1224
|
+
{
|
|
1225
|
+
href: safeHref(l.href),
|
|
1226
|
+
className: "font-medium text-[color:var(--foreground)] opacity-80 hover:opacity-100 hover:underline",
|
|
1227
|
+
children: l.label
|
|
1228
|
+
}
|
|
1229
|
+
)
|
|
1230
|
+
] }, l.label)) }) : null,
|
|
1231
|
+
footer.envLabel ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]", children: [
|
|
1232
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
1233
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1234
|
+
"span",
|
|
1235
|
+
{
|
|
1236
|
+
className: [
|
|
1237
|
+
"rounded-md px-1.5 py-0.5 text-[10px] font-semibold",
|
|
1238
|
+
envChipClasses(footer.envLabel)
|
|
1239
|
+
].join(" "),
|
|
1240
|
+
children: footer.envLabel
|
|
1241
|
+
}
|
|
1242
|
+
),
|
|
1243
|
+
footer.commit ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1244
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
1245
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commit.slice(-4) })
|
|
1246
|
+
] }) : null,
|
|
1247
|
+
footer.version ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1248
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
1249
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1250
|
+
"v",
|
|
1251
|
+
footer.version
|
|
1252
|
+
] })
|
|
1253
|
+
] }) : null
|
|
1254
|
+
] }),
|
|
1255
|
+
footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
1256
|
+
footer.commitDate ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
|
|
1257
|
+
month: "short",
|
|
1258
|
+
day: "numeric"
|
|
1259
|
+
}) }) : null,
|
|
1260
|
+
footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
|
|
1261
|
+
footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null
|
|
1262
|
+
] }) : null
|
|
1263
|
+
] }) : null
|
|
1264
|
+
] }) : null
|
|
1265
|
+
] });
|
|
1266
|
+
}
|
|
1267
|
+
function ChevronDown({ className } = {}) {
|
|
1268
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1269
|
+
"svg",
|
|
1270
|
+
{
|
|
1271
|
+
width: 16,
|
|
1272
|
+
height: 16,
|
|
1273
|
+
viewBox: "0 0 24 24",
|
|
1274
|
+
fill: "none",
|
|
1275
|
+
stroke: "currentColor",
|
|
1276
|
+
strokeWidth: 2,
|
|
1277
|
+
strokeLinecap: "round",
|
|
1278
|
+
strokeLinejoin: "round",
|
|
1279
|
+
className: className ?? "shrink-0 opacity-80",
|
|
1280
|
+
"aria-hidden": "true",
|
|
1281
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" })
|
|
1282
|
+
}
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
var NAV_HOVER_GRADIENT = "linear-gradient(90deg,#EA4751 0%,#E63946 100%)";
|
|
1286
|
+
function NavRow({
|
|
1287
|
+
item,
|
|
1288
|
+
active,
|
|
1289
|
+
Link,
|
|
1290
|
+
variant = "top"
|
|
1291
|
+
}) {
|
|
1292
|
+
const Icon = item.icon;
|
|
1293
|
+
const isSub = variant === "sub";
|
|
1294
|
+
const activeClasses = isSub ? "bg-[color:var(--panel-elevated)] text-[color:var(--color-race-red)] shadow-sm" : "bg-[color:var(--color-race-red)] text-white shadow-sm";
|
|
1295
|
+
const wrapperClasses = [
|
|
1296
|
+
"group relative block overflow-hidden rounded-md transition",
|
|
1297
|
+
active ? activeClasses : "text-[color:var(--muted)] hover:text-white"
|
|
1298
|
+
].join(" ");
|
|
1299
|
+
const rowPadding = isSub ? "px-3 py-1.5 text-[13px]" : "px-3 py-2 text-sm";
|
|
1300
|
+
const iconSize = isSub ? 16 : 18;
|
|
1301
|
+
const iconClass = active && !isSub ? "text-white" : "text-current";
|
|
1302
|
+
const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1303
|
+
!active ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1304
|
+
"span",
|
|
1305
|
+
{
|
|
1306
|
+
"aria-hidden": "true",
|
|
1307
|
+
className: "pointer-events-none absolute inset-y-0 left-0 w-0 transition-[width] duration-300 group-hover:w-full",
|
|
1308
|
+
style: { background: NAV_HOVER_GRADIENT }
|
|
1309
|
+
}
|
|
1310
|
+
) : null,
|
|
1311
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: `relative z-10 flex items-center gap-3 font-semibold ${rowPadding}`, children: [
|
|
1312
|
+
Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: iconSize, className: iconClass }) : null,
|
|
1313
|
+
item.label
|
|
1314
|
+
] })
|
|
1315
|
+
] });
|
|
1316
|
+
if (item.onClick && !item.href) {
|
|
1317
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: item.onClick, className: `w-full text-left ${wrapperClasses}`, children: inner });
|
|
1318
|
+
}
|
|
1319
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1320
|
+
Link,
|
|
1321
|
+
{
|
|
1322
|
+
href: item.href ?? "#",
|
|
1323
|
+
"aria-current": active ? "page" : void 0,
|
|
1324
|
+
className: wrapperClasses,
|
|
1325
|
+
children: inner
|
|
1326
|
+
}
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
function NavGroup({
|
|
1330
|
+
item,
|
|
1331
|
+
active,
|
|
1332
|
+
open,
|
|
1333
|
+
onToggle,
|
|
1334
|
+
pathname,
|
|
1335
|
+
activeFn,
|
|
1336
|
+
Link
|
|
1337
|
+
}) {
|
|
1338
|
+
const Icon = item.icon;
|
|
1339
|
+
const headerClasses = [
|
|
1340
|
+
"group relative block w-full overflow-hidden text-left transition",
|
|
1341
|
+
open ? "rounded-t-md" : "rounded-md",
|
|
1342
|
+
active ? "bg-[color:var(--color-race-red)] text-white shadow-sm" : open ? "bg-[color:var(--panel)] text-[color:var(--foreground)]" : "text-[color:var(--muted)] hover:text-white"
|
|
1343
|
+
].join(" ");
|
|
1344
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1345
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1346
|
+
"button",
|
|
1347
|
+
{
|
|
1348
|
+
type: "button",
|
|
1349
|
+
onClick: onToggle,
|
|
1350
|
+
"aria-expanded": open,
|
|
1351
|
+
className: headerClasses,
|
|
1352
|
+
children: [
|
|
1353
|
+
!active && !open ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1354
|
+
"span",
|
|
1355
|
+
{
|
|
1356
|
+
"aria-hidden": "true",
|
|
1357
|
+
className: "pointer-events-none absolute inset-y-0 left-0 w-0 transition-[width] duration-300 group-hover:w-full",
|
|
1358
|
+
style: { background: NAV_HOVER_GRADIENT }
|
|
1359
|
+
}
|
|
1360
|
+
) : null,
|
|
1361
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex items-center gap-3 px-3 py-2 text-sm font-semibold", children: [
|
|
1362
|
+
Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 18, className: active ? "text-white" : "text-current" }) : null,
|
|
1363
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: item.label }),
|
|
1364
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1365
|
+
ChevronDown,
|
|
1366
|
+
{
|
|
1367
|
+
className: [
|
|
1368
|
+
"shrink-0 opacity-80 transition-transform duration-200",
|
|
1369
|
+
open ? "rotate-180" : ""
|
|
1370
|
+
].join(" ")
|
|
1371
|
+
}
|
|
1372
|
+
)
|
|
1373
|
+
] })
|
|
1374
|
+
]
|
|
1375
|
+
}
|
|
1376
|
+
),
|
|
1377
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-b-md bg-[color:var(--panel)] p-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children: item.children.map((child, i) => {
|
|
1378
|
+
const childActive = child.href ? activeFn(pathname, child.href) : false;
|
|
1379
|
+
if (child.children && child.children.length > 0) {
|
|
1380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1381
|
+
NavSubGroup,
|
|
1382
|
+
{
|
|
1383
|
+
item: child,
|
|
1384
|
+
pathname,
|
|
1385
|
+
activeFn,
|
|
1386
|
+
Link
|
|
1387
|
+
},
|
|
1388
|
+
child.href ?? `subgrp-${i}-${child.label}`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1392
|
+
NavRow,
|
|
1393
|
+
{
|
|
1394
|
+
item: child,
|
|
1395
|
+
active: childActive,
|
|
1396
|
+
Link,
|
|
1397
|
+
variant: "sub"
|
|
1398
|
+
},
|
|
1399
|
+
child.href ?? `sub-${i}-${child.label}`
|
|
1400
|
+
);
|
|
1401
|
+
}) }) }) : null
|
|
1402
|
+
] });
|
|
1403
|
+
}
|
|
1404
|
+
function NavSubGroup({
|
|
1405
|
+
item,
|
|
1406
|
+
pathname,
|
|
1407
|
+
activeFn,
|
|
1408
|
+
Link
|
|
1409
|
+
}) {
|
|
1410
|
+
const ownsActive = (item.href ? activeFn(pathname, item.href) : false) || (item.children ?? []).some((c) => c.href ? activeFn(pathname, c.href) : false);
|
|
1411
|
+
const [open, setOpen] = react.useState(ownsActive);
|
|
1412
|
+
react.useEffect(() => {
|
|
1413
|
+
if (ownsActive) setOpen(true);
|
|
1414
|
+
}, [ownsActive]);
|
|
1415
|
+
const Icon = item.icon;
|
|
1416
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1417
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1418
|
+
"button",
|
|
1419
|
+
{
|
|
1420
|
+
type: "button",
|
|
1421
|
+
onClick: () => setOpen((v) => !v),
|
|
1422
|
+
"aria-expanded": open,
|
|
1423
|
+
className: [
|
|
1424
|
+
"flex w-full items-center gap-2.5 rounded px-3 py-1.5 text-left text-[13px] font-medium transition",
|
|
1425
|
+
ownsActive ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--panel-elevated)] hover:text-[color:var(--foreground)]"
|
|
1426
|
+
].join(" "),
|
|
1427
|
+
children: [
|
|
1428
|
+
Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 15, className: "shrink-0 text-current" }) : null,
|
|
1429
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: item.label }),
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1431
|
+
ChevronDown,
|
|
1432
|
+
{
|
|
1433
|
+
className: [
|
|
1434
|
+
"shrink-0 opacity-70 transition-transform duration-200",
|
|
1435
|
+
open ? "rotate-180" : ""
|
|
1436
|
+
].join(" ")
|
|
1437
|
+
}
|
|
1438
|
+
)
|
|
1439
|
+
]
|
|
1440
|
+
}
|
|
1441
|
+
),
|
|
1442
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4 flex flex-col gap-0.5 border-l border-[color:var(--line)] pl-2", children: item.children.map((leaf, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1443
|
+
NavRow,
|
|
1444
|
+
{
|
|
1445
|
+
item: leaf,
|
|
1446
|
+
active: leaf.href ? activeFn(pathname, leaf.href) : false,
|
|
1447
|
+
Link,
|
|
1448
|
+
variant: "sub"
|
|
1449
|
+
},
|
|
1450
|
+
leaf.href ?? `subsub-${i}-${leaf.label}`
|
|
1451
|
+
)) }) : null
|
|
1452
|
+
] });
|
|
1453
|
+
}
|
|
1454
|
+
function OrgIdCopyButton({ id }) {
|
|
1455
|
+
const [copied, setCopied] = react.useState(false);
|
|
1456
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1457
|
+
"button",
|
|
1458
|
+
{
|
|
1459
|
+
type: "button",
|
|
1460
|
+
onClick: () => {
|
|
1461
|
+
try {
|
|
1462
|
+
void navigator.clipboard?.writeText(id);
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1465
|
+
setCopied(true);
|
|
1466
|
+
setTimeout(() => setCopied(false), 1500);
|
|
1467
|
+
},
|
|
1468
|
+
title: "Copy organization id",
|
|
1469
|
+
className: "rounded px-1 text-[11px] text-[color:var(--muted)] hover:bg-[color:var(--panel)]",
|
|
1470
|
+
children: copied ? "Copied" : `...${id.slice(-8)}`
|
|
1471
|
+
}
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
function NavTopBar({
|
|
1475
|
+
title,
|
|
1476
|
+
subtitle,
|
|
1477
|
+
breadcrumbs,
|
|
1478
|
+
user,
|
|
1479
|
+
userMenu,
|
|
1480
|
+
onSignOut,
|
|
1481
|
+
actionsList,
|
|
1482
|
+
actions,
|
|
1483
|
+
themeToggle
|
|
1484
|
+
}) {
|
|
1485
|
+
const rawName = user?.name?.trim() || "";
|
|
1486
|
+
const hasRealName = rawName.length > 0 && !rawName.includes("@");
|
|
1487
|
+
const displayName = hasRealName ? formatFirstLast(rawName) : user?.email ?? "Signed in";
|
|
1488
|
+
const initials = (hasRealName ? rawName : user?.email ?? "P").slice(0, 2).toUpperCase();
|
|
1489
|
+
const secondary = hasRealName ? user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member" : [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
|
|
1490
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-20 flex h-20 items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 backdrop-blur-xl", children: [
|
|
1491
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
1492
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1493
|
+
"h1",
|
|
1494
|
+
{
|
|
1495
|
+
className: "truncate text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]",
|
|
1496
|
+
"data-display": "true",
|
|
1497
|
+
children: title
|
|
1498
|
+
}
|
|
1499
|
+
),
|
|
1500
|
+
breadcrumbs && breadcrumbs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Breadcrumb", className: "flex min-w-0 items-center gap-1.5 text-sm text-[color:var(--muted)]", children: breadcrumbs.map((b, i) => {
|
|
1501
|
+
const last = i === breadcrumbs.length - 1;
|
|
1502
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1503
|
+
i > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-[color:var(--muted)] opacity-60", children: "\u203A" }) : null,
|
|
1504
|
+
b.href && !last ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1505
|
+
"a",
|
|
1506
|
+
{
|
|
1507
|
+
href: safeHref(b.href),
|
|
1508
|
+
className: "truncate hover:text-[color:var(--foreground)] hover:underline",
|
|
1509
|
+
children: b.label
|
|
1510
|
+
}
|
|
1511
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1512
|
+
"span",
|
|
1513
|
+
{
|
|
1514
|
+
"aria-current": last ? "page" : void 0,
|
|
1515
|
+
className: ["truncate", last ? "text-[color:var(--foreground)]" : ""].join(" "),
|
|
1516
|
+
children: b.label
|
|
1517
|
+
}
|
|
1518
|
+
)
|
|
1519
|
+
] }, `${b.label}-${i}`);
|
|
1520
|
+
}) }) : subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-[color:var(--muted)]", children: subtitle }) : null
|
|
1521
|
+
] }),
|
|
1522
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex shrink-0 items-center gap-[var(--topbar-icon-gap,8px)]", children: [
|
|
1523
|
+
actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsxRuntime.jsx(NavTopBarActionButton, { action }, action.key)) : null,
|
|
1524
|
+
actions,
|
|
1525
|
+
themeToggle,
|
|
1526
|
+
/* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group/acct relative", children: [
|
|
1527
|
+
/* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "group/tbtip relative flex cursor-pointer list-none items-center gap-2.5 rounded-full py-1 pl-1 pr-2 transition hover:bg-[color:var(--panel-elevated)] [&::-webkit-details-marker]:hidden", children: [
|
|
1528
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-[var(--topbar-icon-button-size,32px)] w-[var(--topbar-icon-button-size,32px)] items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white", children: user?.avatarUrl || user?.memojiUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1529
|
+
"img",
|
|
1530
|
+
{
|
|
1531
|
+
src: user.avatarUrl || user.memojiUrl,
|
|
1532
|
+
alt: "",
|
|
1533
|
+
className: "h-full w-full object-cover"
|
|
1534
|
+
}
|
|
1535
|
+
) : initials }),
|
|
1536
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden flex-col leading-tight md:flex", children: [
|
|
1537
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: displayName }),
|
|
1538
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-[color:var(--muted)]", children: secondary })
|
|
1539
|
+
] }),
|
|
1540
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {}),
|
|
1541
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "group-open/acct:hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1542
|
+
TopBarTooltip,
|
|
1543
|
+
{
|
|
1544
|
+
title: "My Account",
|
|
1545
|
+
description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1546
|
+
hasRealName ? rawName : user?.email ?? "Signed in",
|
|
1547
|
+
user?.email ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1548
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
1549
|
+
user.email
|
|
1550
|
+
] }) : null,
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
1552
|
+
"Profile, organizations, appearance and sign out."
|
|
1553
|
+
] })
|
|
1554
|
+
}
|
|
1555
|
+
) })
|
|
1556
|
+
] }),
|
|
1557
|
+
/* @__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: [
|
|
1558
|
+
/* @__PURE__ */ jsxRuntime.jsx(TopBarPanelArrow, {}),
|
|
1559
|
+
userMenu?.myProfileHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1560
|
+
"a",
|
|
1561
|
+
{
|
|
1562
|
+
href: safeHref(userMenu.myProfileHref),
|
|
1563
|
+
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)]",
|
|
1564
|
+
children: [
|
|
1565
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProfileIcon, {}),
|
|
1566
|
+
"My Profile"
|
|
1567
|
+
]
|
|
1568
|
+
}
|
|
1569
|
+
) : null,
|
|
1570
|
+
userMenu?.organizationSettingsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1571
|
+
"a",
|
|
1572
|
+
{
|
|
1573
|
+
href: safeHref(userMenu.organizationSettingsHref),
|
|
1574
|
+
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)]",
|
|
1575
|
+
children: [
|
|
1576
|
+
/* @__PURE__ */ jsxRuntime.jsx(BriefcaseIcon, {}),
|
|
1577
|
+
"Organization Settings"
|
|
1578
|
+
]
|
|
1579
|
+
}
|
|
1580
|
+
) : null,
|
|
1581
|
+
userMenu?.roles && userMenu.roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
1582
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Roles & Organizations" }),
|
|
1583
|
+
/* @__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: [
|
|
1584
|
+
/* @__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 }),
|
|
1585
|
+
r.orgName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[color:var(--muted)]", children: [
|
|
1586
|
+
"in ",
|
|
1587
|
+
r.orgName
|
|
1588
|
+
] }) : null
|
|
1589
|
+
] }, `${r.role}-${i}`)) })
|
|
1590
|
+
] }) : null,
|
|
1591
|
+
userMenu?.accounts && userMenu.accounts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
1592
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Accounts" }),
|
|
1593
|
+
/* @__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: [
|
|
1594
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1595
|
+
"button",
|
|
1596
|
+
{
|
|
1597
|
+
type: "button",
|
|
1598
|
+
onClick: () => {
|
|
1599
|
+
if (!a.active) userMenu.onSwitchAccount?.(a.id);
|
|
1600
|
+
},
|
|
1601
|
+
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)]",
|
|
1602
|
+
children: [
|
|
1603
|
+
/* @__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() }),
|
|
1604
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col leading-tight", children: [
|
|
1605
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1606
|
+
"span",
|
|
1607
|
+
{
|
|
1608
|
+
className: [
|
|
1609
|
+
"truncate text-sm font-semibold",
|
|
1610
|
+
a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
|
|
1611
|
+
].join(" "),
|
|
1612
|
+
children: a.name ?? a.email ?? a.id.slice(0, 8)
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
|
|
1616
|
+
] })
|
|
1617
|
+
]
|
|
1618
|
+
}
|
|
1619
|
+
),
|
|
1620
|
+
!a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1621
|
+
"button",
|
|
1622
|
+
{
|
|
1623
|
+
type: "button",
|
|
1624
|
+
"aria-label": `Remove ${a.name ?? a.email ?? "account"}`,
|
|
1625
|
+
onClick: () => userMenu.onRemoveAccount?.(a.id),
|
|
1626
|
+
className: "shrink-0 rounded p-1 text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
|
|
1627
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {})
|
|
1628
|
+
}
|
|
1629
|
+
) : null
|
|
1630
|
+
] }, a.id)) }),
|
|
1631
|
+
userMenu.addAccountHref || userMenu.onAddAccount ? userMenu.addAccountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1632
|
+
"a",
|
|
1633
|
+
{
|
|
1634
|
+
href: safeHref(userMenu.addAccountHref),
|
|
1635
|
+
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)]",
|
|
1636
|
+
children: [
|
|
1637
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
1638
|
+
"Add Account"
|
|
1639
|
+
]
|
|
1640
|
+
}
|
|
1641
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1642
|
+
"button",
|
|
1643
|
+
{
|
|
1644
|
+
type: "button",
|
|
1645
|
+
onClick: userMenu.onAddAccount,
|
|
1646
|
+
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)]",
|
|
1647
|
+
children: [
|
|
1648
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
|
|
1649
|
+
"Add Account"
|
|
1650
|
+
]
|
|
1651
|
+
}
|
|
1652
|
+
) : null
|
|
1653
|
+
] }) : null,
|
|
1654
|
+
userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1655
|
+
NavThemeControl,
|
|
1656
|
+
{
|
|
1657
|
+
value: userMenu.theme.value,
|
|
1658
|
+
onChange: userMenu.theme.onChange
|
|
1659
|
+
}
|
|
1660
|
+
) }) : null,
|
|
1661
|
+
onSignOut ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1662
|
+
"button",
|
|
1663
|
+
{
|
|
1664
|
+
type: "button",
|
|
1665
|
+
onClick: onSignOut,
|
|
1666
|
+
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)]",
|
|
1667
|
+
children: [
|
|
1668
|
+
/* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {}),
|
|
1669
|
+
"Sign out"
|
|
1670
|
+
]
|
|
1671
|
+
}
|
|
1672
|
+
) : null
|
|
1673
|
+
] })
|
|
1674
|
+
] }),
|
|
1675
|
+
!hasRealName && user ? /* @__PURE__ */ jsxRuntime.jsx(NamePromptBubble, { profileHref: userMenu?.myProfileHref }) : null
|
|
1676
|
+
] })
|
|
1677
|
+
] });
|
|
1678
|
+
}
|
|
1679
|
+
function NamePromptBubble({ profileHref }) {
|
|
1680
|
+
const KEY = "politogy_name_prompt_dismissed";
|
|
1681
|
+
const [dismissed, setDismissed] = react.useState(true);
|
|
1682
|
+
react.useEffect(() => {
|
|
1683
|
+
try {
|
|
1684
|
+
setDismissed(sessionStorage.getItem(KEY) === "1");
|
|
1685
|
+
} catch {
|
|
1686
|
+
setDismissed(false);
|
|
1687
|
+
}
|
|
1688
|
+
}, []);
|
|
1689
|
+
if (dismissed) return null;
|
|
1690
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-4 top-full z-30 mt-2 w-64 rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-3 shadow-[var(--shadow-soft)]", children: [
|
|
1691
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1692
|
+
"span",
|
|
1693
|
+
{
|
|
1694
|
+
"aria-hidden": true,
|
|
1695
|
+
className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 border-l border-t border-[color:var(--line)] bg-[color:var(--panel-elevated)]"
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: "Add your name" }),
|
|
1699
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs leading-snug text-[color:var(--muted)]", children: "Set your first and last name so teammates see you \u2014 not your email." }),
|
|
1700
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
1701
|
+
profileHref ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1702
|
+
"a",
|
|
1703
|
+
{
|
|
1704
|
+
href: safeHref(profileHref),
|
|
1705
|
+
className: "rounded-full bg-[color:var(--color-race-red)] px-3 py-1 text-xs font-semibold text-white hover:opacity-90",
|
|
1706
|
+
children: "Set name"
|
|
1707
|
+
}
|
|
1708
|
+
) : null,
|
|
1709
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1710
|
+
"button",
|
|
1711
|
+
{
|
|
1712
|
+
type: "button",
|
|
1713
|
+
onClick: () => {
|
|
1714
|
+
setDismissed(true);
|
|
1715
|
+
try {
|
|
1716
|
+
sessionStorage.setItem(KEY, "1");
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
},
|
|
1720
|
+
className: "rounded-full border border-[color:var(--line)] px-3 py-1 text-xs font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
|
|
1721
|
+
children: "Dismiss"
|
|
1722
|
+
}
|
|
1723
|
+
)
|
|
1724
|
+
] })
|
|
1725
|
+
] });
|
|
1726
|
+
}
|
|
1727
|
+
function formatFirstLast(name) {
|
|
1728
|
+
const parts = name.split(/\s+/).filter(Boolean);
|
|
1729
|
+
if (parts.length === 1) return parts[0];
|
|
1730
|
+
return `${parts[0]} ${parts[parts.length - 1][0].toUpperCase()}.`;
|
|
1731
|
+
}
|
|
1732
|
+
function ProfileIcon() {
|
|
1733
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
1734
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
|
|
1735
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "7", r: "4" })
|
|
1736
|
+
] });
|
|
1737
|
+
}
|
|
1738
|
+
function BriefcaseIcon() {
|
|
1739
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
1740
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "7", width: "20", height: "14", rx: "2", ry: "2" }),
|
|
1741
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" })
|
|
1742
|
+
] });
|
|
1743
|
+
}
|
|
1744
|
+
function TrashIcon() {
|
|
1745
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
1746
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 6 5 6 21 6" }),
|
|
1747
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
|
|
1748
|
+
] });
|
|
1749
|
+
}
|
|
1750
|
+
function AddUserIcon() {
|
|
1751
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
1752
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
|
|
1753
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8.5", cy: "7", r: "4" }),
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "20", y1: "8", x2: "20", y2: "14" }),
|
|
1755
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "23", y1: "11", x2: "17", y2: "11" })
|
|
1756
|
+
] });
|
|
1757
|
+
}
|
|
1758
|
+
function SignOutIcon() {
|
|
1759
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1760
|
+
"svg",
|
|
1761
|
+
{
|
|
1762
|
+
width: 15,
|
|
1763
|
+
height: 15,
|
|
1764
|
+
viewBox: "0 0 24 24",
|
|
1765
|
+
fill: "none",
|
|
1766
|
+
stroke: "currentColor",
|
|
1767
|
+
strokeWidth: 2,
|
|
1768
|
+
strokeLinecap: "round",
|
|
1769
|
+
strokeLinejoin: "round",
|
|
1770
|
+
"aria-hidden": "true",
|
|
1771
|
+
children: [
|
|
1772
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
1773
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "16 17 21 12 16 7" }),
|
|
1774
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", x2: "9", y1: "12", y2: "12" })
|
|
1775
|
+
]
|
|
1776
|
+
}
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
function NavTopBarActionButton({ action }) {
|
|
1780
|
+
const Icon = action.icon;
|
|
1781
|
+
const className = topBarIconButtonClass(action.tone ?? "default");
|
|
1782
|
+
const glyphName = useTopBarIconSlotGlyph(action.slot ?? "apps");
|
|
1783
|
+
const BrandGlyph = action.slot ? resolveBrandGlyph(glyphName) : null;
|
|
1784
|
+
const Glyph = BrandGlyph ?? Icon;
|
|
1785
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1786
|
+
/* @__PURE__ */ jsxRuntime.jsx(Glyph, { size: 20, className: "text-current" }),
|
|
1787
|
+
typeof action.badge === "number" && action.badge > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white", children: action.badge }) : null,
|
|
1788
|
+
/* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: action.label, description: action.description })
|
|
1789
|
+
] });
|
|
1790
|
+
const isExternal = action.href ? /^https?:\/\//.test(action.href) : false;
|
|
1791
|
+
if (action.href) {
|
|
1792
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1793
|
+
"a",
|
|
1794
|
+
{
|
|
1795
|
+
href: safeHref(action.href),
|
|
1796
|
+
"aria-label": action.label,
|
|
1797
|
+
className,
|
|
1798
|
+
...isExternal ? { target: "_blank", rel: "noreferrer" } : {},
|
|
1799
|
+
children: content
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1804
|
+
"button",
|
|
1805
|
+
{
|
|
1806
|
+
type: "button",
|
|
1807
|
+
onClick: action.onClick,
|
|
1808
|
+
"aria-label": action.label,
|
|
1809
|
+
className,
|
|
1810
|
+
children: content
|
|
1811
|
+
}
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
var BETA_BANNER_DEFAULTS = {
|
|
1815
|
+
text: "Our product is in Beta. Thank you for participating in the Pilot Program. ",
|
|
1816
|
+
linkLabel: "You can click here to see the Terms & Conditions for Beta users.",
|
|
1817
|
+
href: "https://app.politogyvrm.com/beta-terms-and-conditions"
|
|
1818
|
+
};
|
|
1819
|
+
function NavBetaBanner({ text, linkLabel, href }) {
|
|
1820
|
+
const body = text ?? BETA_BANNER_DEFAULTS.text;
|
|
1821
|
+
const label = linkLabel ?? BETA_BANNER_DEFAULTS.linkLabel;
|
|
1822
|
+
const target = href ?? BETA_BANNER_DEFAULTS.href;
|
|
1823
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1824
|
+
"div",
|
|
1825
|
+
{
|
|
1826
|
+
role: "note",
|
|
1827
|
+
className: "w-full border-y px-4 py-2 text-center text-sm",
|
|
1828
|
+
style: {
|
|
1829
|
+
background: "var(--beta-banner-bg)",
|
|
1830
|
+
borderColor: "var(--beta-banner-border)",
|
|
1831
|
+
color: "var(--beta-banner-text)"
|
|
1832
|
+
},
|
|
1833
|
+
children: [
|
|
1834
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: body }),
|
|
1835
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1836
|
+
"a",
|
|
1837
|
+
{
|
|
1838
|
+
href: safeHref(target),
|
|
1839
|
+
target: "_blank",
|
|
1840
|
+
rel: "noreferrer",
|
|
1841
|
+
className: "font-bold underline",
|
|
1842
|
+
style: { color: "var(--beta-banner-text)" },
|
|
1843
|
+
children: label
|
|
1844
|
+
}
|
|
1845
|
+
)
|
|
1846
|
+
]
|
|
1847
|
+
}
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
function useCloseNavDetailsOnOutsideClick() {
|
|
1851
|
+
react.useEffect(() => {
|
|
1852
|
+
const close = (target) => {
|
|
1853
|
+
document.querySelectorAll("details[data-nav-details][open]").forEach((d) => {
|
|
1854
|
+
if (!target || !d.contains(target)) d.removeAttribute("open");
|
|
1855
|
+
});
|
|
1856
|
+
};
|
|
1857
|
+
const onPointerDown = (e) => close(e.target);
|
|
1858
|
+
const onKey = (e) => {
|
|
1859
|
+
if (e.key === "Escape") close(null);
|
|
1860
|
+
};
|
|
1861
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
1862
|
+
document.addEventListener("keydown", onKey);
|
|
1863
|
+
return () => {
|
|
1864
|
+
document.removeEventListener("mousedown", onPointerDown);
|
|
1865
|
+
document.removeEventListener("keydown", onKey);
|
|
1866
|
+
};
|
|
1867
|
+
}, []);
|
|
1868
|
+
}
|
|
1869
|
+
function NavAppShell({ sidebar, topBar, betaBanner, children }) {
|
|
1870
|
+
useCloseNavDetailsOnOutsideClick();
|
|
1871
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]", children: [
|
|
1872
|
+
/* @__PURE__ */ jsxRuntime.jsx(NavSidebar, { ...sidebar }),
|
|
1873
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lg:pl-[240px]", children: [
|
|
1874
|
+
/* @__PURE__ */ jsxRuntime.jsx(NavTopBar, { ...topBar }),
|
|
1875
|
+
betaBanner ? /* @__PURE__ */ jsxRuntime.jsx(NavBetaBanner, { ...betaBanner }) : null,
|
|
1876
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl", children }) })
|
|
1877
|
+
] })
|
|
1878
|
+
] });
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/lib/breadcrumbs.ts
|
|
1882
|
+
function deriveBreadcrumbs(items, pathname, opts) {
|
|
1883
|
+
const search = opts?.search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
1884
|
+
const origin = opts?.origin ?? (typeof window !== "undefined" ? window.location.origin : "");
|
|
1885
|
+
const matches = (href) => {
|
|
1886
|
+
if (!href) return false;
|
|
1887
|
+
let path = href;
|
|
1888
|
+
let query = "";
|
|
1889
|
+
if (/^https?:\/\//i.test(href)) {
|
|
1890
|
+
try {
|
|
1891
|
+
const u = new URL(href);
|
|
1892
|
+
if (origin && u.origin !== origin) return false;
|
|
1893
|
+
path = u.pathname;
|
|
1894
|
+
query = u.search;
|
|
1895
|
+
} catch {
|
|
1896
|
+
return false;
|
|
1897
|
+
}
|
|
1898
|
+
} else {
|
|
1899
|
+
const qi = href.indexOf("?");
|
|
1900
|
+
if (qi >= 0) {
|
|
1901
|
+
path = href.slice(0, qi) || "/";
|
|
1902
|
+
query = href.slice(qi);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (path !== pathname) return false;
|
|
1906
|
+
if (!query) return true;
|
|
1907
|
+
const want = new URLSearchParams(query);
|
|
1908
|
+
const have = new URLSearchParams(search);
|
|
1909
|
+
for (const [k, v] of want) if (have.get(k) !== v) return false;
|
|
1910
|
+
return true;
|
|
1911
|
+
};
|
|
1912
|
+
const walk = (nodes, trail2) => {
|
|
1913
|
+
for (const node of nodes) {
|
|
1914
|
+
const here = [...trail2, node];
|
|
1915
|
+
if (node.children?.length) {
|
|
1916
|
+
const deeper = walk(node.children, here);
|
|
1917
|
+
if (deeper) return deeper;
|
|
1918
|
+
}
|
|
1919
|
+
if (matches(node.href)) return here;
|
|
1920
|
+
}
|
|
1921
|
+
return null;
|
|
1922
|
+
};
|
|
1923
|
+
const trail = walk(items, []);
|
|
1924
|
+
const dashboard = items.find((i) => i.label === "Dashboard");
|
|
1925
|
+
const crumbs = [{ label: "Dashboard", href: dashboard?.href }];
|
|
1926
|
+
if (!trail) return crumbs;
|
|
1927
|
+
for (const node of trail) {
|
|
1928
|
+
if (node.label === "Dashboard") continue;
|
|
1929
|
+
crumbs.push({ label: node.label, href: node.href });
|
|
1930
|
+
}
|
|
1931
|
+
const last = crumbs[crumbs.length - 1];
|
|
1932
|
+
if (last) delete last.href;
|
|
1933
|
+
return crumbs;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// src/lib/identity.ts
|
|
1937
|
+
function looksLikeEmail(value) {
|
|
1938
|
+
return typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
|
|
1939
|
+
}
|
|
1940
|
+
function resolveIdentity(input) {
|
|
1941
|
+
const apiName = looksLikeEmail(input.apiName) ? null : input.apiName ?? null;
|
|
1942
|
+
const fallbackName = looksLikeEmail(input.fallbackName) ? null : input.fallbackName ?? null;
|
|
1943
|
+
return {
|
|
1944
|
+
userName: apiName ?? fallbackName,
|
|
1945
|
+
email: input.email ?? null,
|
|
1946
|
+
avatarUrl: input.avatarUrl ?? null,
|
|
1947
|
+
loaded: input.loaded ?? false
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
var POLITOGY_APPS = {
|
|
1951
|
+
vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
|
|
1952
|
+
contacts: { title: "Contacts", origin: "https://contacts.politogyvrm.com", storagePrefix: "contacts" },
|
|
1953
|
+
forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms" },
|
|
1954
|
+
surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys" },
|
|
1955
|
+
polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls" },
|
|
1956
|
+
petitions: { title: "Petitions", origin: "https://petitions.politogyvrm.com", storagePrefix: "petitions" }
|
|
1957
|
+
};
|
|
1958
|
+
function PolitogyAppFrame({
|
|
1959
|
+
app,
|
|
1960
|
+
pathname,
|
|
1961
|
+
search,
|
|
1962
|
+
linkComponent,
|
|
1963
|
+
session,
|
|
1964
|
+
onSignOut,
|
|
1965
|
+
buildMeta,
|
|
1966
|
+
data,
|
|
1967
|
+
children
|
|
1968
|
+
}) {
|
|
1969
|
+
const meta = POLITOGY_APPS[app];
|
|
1970
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1971
|
+
PolitogyAppChrome,
|
|
1972
|
+
{
|
|
1973
|
+
storagePrefix: meta.storagePrefix,
|
|
1974
|
+
fetchJson: data?.fetchJson,
|
|
1975
|
+
devNotes: data?.devNotes,
|
|
1976
|
+
org: data?.org,
|
|
1977
|
+
identity: {
|
|
1978
|
+
userId: session.userId,
|
|
1979
|
+
userName: session.userName ?? null,
|
|
1980
|
+
email: session.email,
|
|
1981
|
+
role: session.role
|
|
1982
|
+
},
|
|
1983
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1984
|
+
FrameBody,
|
|
1985
|
+
{
|
|
1986
|
+
app,
|
|
1987
|
+
pathname,
|
|
1988
|
+
search,
|
|
1989
|
+
linkComponent,
|
|
1990
|
+
session,
|
|
1991
|
+
onSignOut,
|
|
1992
|
+
buildMeta,
|
|
1993
|
+
data,
|
|
1994
|
+
children
|
|
1995
|
+
}
|
|
1996
|
+
)
|
|
1997
|
+
}
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
function FrameBody({
|
|
2001
|
+
app,
|
|
2002
|
+
pathname,
|
|
2003
|
+
search,
|
|
2004
|
+
linkComponent,
|
|
2005
|
+
session,
|
|
2006
|
+
onSignOut,
|
|
2007
|
+
buildMeta,
|
|
2008
|
+
data,
|
|
2009
|
+
children
|
|
2010
|
+
}) {
|
|
2011
|
+
const meta = POLITOGY_APPS[app];
|
|
2012
|
+
const chrome = useChromeState();
|
|
2013
|
+
const identity = useChromeIdentity({ userName: session.userName, email: session.email });
|
|
2014
|
+
const userMenu = useChromeUserMenu({
|
|
2015
|
+
userId: session.userId,
|
|
2016
|
+
userName: identity.userName,
|
|
2017
|
+
email: identity.email
|
|
2018
|
+
});
|
|
2019
|
+
const footer = useChromeFooter(buildMeta ?? {});
|
|
2020
|
+
const mode = useChromeMode();
|
|
2021
|
+
const coreOrgs = chrome?.orgs ?? [];
|
|
2022
|
+
const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null;
|
|
2023
|
+
const navDisplay = chrome?.navDisplay ?? "both";
|
|
2024
|
+
const navItems = react.useMemo(
|
|
2025
|
+
() => standardPolitogyNav({
|
|
2026
|
+
coreBaseUrl: POLITOGY_APPS.vrm.origin,
|
|
2027
|
+
icons: standardNavIcons,
|
|
2028
|
+
// An app that OWNS a standard nav entry (Contacts) nests its own routes
|
|
2029
|
+
// under it. This is routing data, not chrome configuration.
|
|
2030
|
+
contactsItem: data?.contactsItem
|
|
2031
|
+
}),
|
|
2032
|
+
[data?.contactsItem]
|
|
2033
|
+
);
|
|
2034
|
+
const breadcrumbs = react.useMemo(
|
|
2035
|
+
() => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),
|
|
2036
|
+
[navItems, pathname, search, meta.origin]
|
|
2037
|
+
);
|
|
2038
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2039
|
+
NavAppShell,
|
|
2040
|
+
{
|
|
2041
|
+
sidebar: {
|
|
2042
|
+
pathname,
|
|
2043
|
+
brand: {
|
|
2044
|
+
logoSrc: "/brand/logos/relationship-mode-logo.png",
|
|
2045
|
+
logoAlt: "Politogy",
|
|
2046
|
+
badgeLabel: "P",
|
|
2047
|
+
title: "Politogy"
|
|
2048
|
+
},
|
|
2049
|
+
nav: {
|
|
2050
|
+
items: navDisplay === "tabs" ? navItems.map(({ children: _sub, ...item }) => item) : navItems
|
|
2051
|
+
},
|
|
2052
|
+
org: activeCoreOrg ? {
|
|
2053
|
+
name: activeCoreOrg.name,
|
|
2054
|
+
id: activeCoreOrg.id,
|
|
2055
|
+
logoUrl: activeCoreOrg.logoUrl,
|
|
2056
|
+
brandColor: activeCoreOrg.brandColor,
|
|
2057
|
+
organizations: coreOrgs,
|
|
2058
|
+
onSwitch: chrome?.setOrgId
|
|
2059
|
+
} : { name: session.accountName, id: session.accountId },
|
|
2060
|
+
mode,
|
|
2061
|
+
footer,
|
|
2062
|
+
linkComponent
|
|
2063
|
+
},
|
|
2064
|
+
betaBanner: {},
|
|
2065
|
+
topBar: {
|
|
2066
|
+
title: meta.title,
|
|
2067
|
+
breadcrumbs,
|
|
2068
|
+
user: {
|
|
2069
|
+
name: identity.userName,
|
|
2070
|
+
email: identity.email,
|
|
2071
|
+
// The chrome API's avatar wins; the app may supply a fallback chain
|
|
2072
|
+
// (e.g. a stored memoji) for users with no uploaded photo.
|
|
2073
|
+
avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,
|
|
2074
|
+
role: session.role
|
|
2075
|
+
},
|
|
2076
|
+
userMenu,
|
|
2077
|
+
onSignOut,
|
|
2078
|
+
actions: /* @__PURE__ */ jsxRuntime.jsx(ChromeTopBarActions, {})
|
|
2079
|
+
},
|
|
2080
|
+
children
|
|
2081
|
+
}
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
733
2084
|
var standardNavIcons = {
|
|
734
2085
|
home: fi.FiHome,
|
|
735
2086
|
users: fi.FiUsers,
|
|
@@ -754,6 +2105,7 @@ var standardNavIcons = {
|
|
|
754
2105
|
share: fi.FiShare2
|
|
755
2106
|
};
|
|
756
2107
|
var ORG_STORAGE_KEY = "vrm.activeOrg";
|
|
2108
|
+
var MODE_STORAGE_KEY = "vrm.activeMode";
|
|
757
2109
|
var NAV_DISPLAY_KEY = "vrm.navDisplay";
|
|
758
2110
|
var CORE_BASE_URL = "https://app.politogyvrm.com";
|
|
759
2111
|
var ChromeStateContext = react.createContext(null);
|
|
@@ -938,6 +2290,16 @@ function useChromeFooter(meta) {
|
|
|
938
2290
|
[isSuperAdmin, meta.envLabel, meta.version, meta.commit, meta.commitDate, meta.commitAuthor]
|
|
939
2291
|
);
|
|
940
2292
|
}
|
|
2293
|
+
function useChromeIdentity(fallback) {
|
|
2294
|
+
const { me, loaded } = useChromeMe();
|
|
2295
|
+
return resolveIdentity({
|
|
2296
|
+
apiName: me?.userName,
|
|
2297
|
+
fallbackName: fallback?.userName,
|
|
2298
|
+
email: fallback?.email,
|
|
2299
|
+
avatarUrl: me?.avatarUrl,
|
|
2300
|
+
loaded
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
941
2303
|
function useChromeUserMenu({
|
|
942
2304
|
userId,
|
|
943
2305
|
userName,
|
|
@@ -981,11 +2343,19 @@ var MODE_DEFS = [
|
|
|
981
2343
|
orgQuery: true
|
|
982
2344
|
}
|
|
983
2345
|
];
|
|
984
|
-
function useChromeMode(
|
|
2346
|
+
function useChromeMode(fallbackModeId = "relationship") {
|
|
985
2347
|
const ctx = react.useContext(ChromeStateContext);
|
|
986
2348
|
const nav = useNavChromeFetcher();
|
|
987
2349
|
const orgId = ctx?.orgId ?? null;
|
|
988
2350
|
const [entitled, setEntitled] = react.useState(null);
|
|
2351
|
+
const [selectedModeId, setSelectedModeId] = react.useState(null);
|
|
2352
|
+
react.useEffect(() => {
|
|
2353
|
+
try {
|
|
2354
|
+
const stored = localStorage.getItem(MODE_STORAGE_KEY);
|
|
2355
|
+
if (stored && MODE_DEFS.some((m) => m.id === stored)) setSelectedModeId(stored);
|
|
2356
|
+
} catch {
|
|
2357
|
+
}
|
|
2358
|
+
}, []);
|
|
989
2359
|
react.useEffect(() => {
|
|
990
2360
|
if (!nav || !orgId) return;
|
|
991
2361
|
let cancelled = false;
|
|
@@ -999,8 +2369,9 @@ function useChromeMode(currentModeId = "relationship") {
|
|
|
999
2369
|
cancelled = true;
|
|
1000
2370
|
};
|
|
1001
2371
|
}, [nav, orgId]);
|
|
1002
|
-
const current = MODE_DEFS.find((m) => m.id === currentModeId) ?? MODE_DEFS[0];
|
|
1003
2372
|
const options = MODE_DEFS.filter((m) => entitled?.[m.id]);
|
|
2373
|
+
const selected = selectedModeId ? options.find((m) => m.id === selectedModeId) ?? null : null;
|
|
2374
|
+
const current = selected ?? options[0] ?? MODE_DEFS.find((m) => m.id === fallbackModeId) ?? MODE_DEFS[0];
|
|
1004
2375
|
const suffix = orgId ? `?org=${orgId.slice(-6)}` : "";
|
|
1005
2376
|
return {
|
|
1006
2377
|
label: current.label,
|
|
@@ -1009,6 +2380,11 @@ function useChromeMode(currentModeId = "relationship") {
|
|
|
1009
2380
|
onSelect: options.length > 1 ? (id) => {
|
|
1010
2381
|
const def = MODE_DEFS.find((m) => m.id === id);
|
|
1011
2382
|
if (!def) return;
|
|
2383
|
+
setSelectedModeId(id);
|
|
2384
|
+
try {
|
|
2385
|
+
localStorage.setItem(MODE_STORAGE_KEY, id);
|
|
2386
|
+
} catch {
|
|
2387
|
+
}
|
|
1012
2388
|
const sep = def.href.includes("?") ? "&" : "?";
|
|
1013
2389
|
window.location.assign(
|
|
1014
2390
|
def.orgQuery && suffix ? `${def.href}${sep}${suffix.slice(1)}` : def.href
|
|
@@ -1018,9 +2394,15 @@ function useChromeMode(currentModeId = "relationship") {
|
|
|
1018
2394
|
}
|
|
1019
2395
|
|
|
1020
2396
|
exports.ChromeTopBarActions = ChromeTopBarActions;
|
|
2397
|
+
exports.POLITOGY_APPS = POLITOGY_APPS;
|
|
1021
2398
|
exports.PolitogyAppChrome = PolitogyAppChrome;
|
|
2399
|
+
exports.PolitogyAppFrame = PolitogyAppFrame;
|
|
2400
|
+
exports.deriveBreadcrumbs = deriveBreadcrumbs;
|
|
2401
|
+
exports.looksLikeEmail = looksLikeEmail;
|
|
2402
|
+
exports.resolveIdentity = resolveIdentity;
|
|
1022
2403
|
exports.standardNavIcons = standardNavIcons;
|
|
1023
2404
|
exports.useChromeFooter = useChromeFooter;
|
|
2405
|
+
exports.useChromeIdentity = useChromeIdentity;
|
|
1024
2406
|
exports.useChromeMode = useChromeMode;
|
|
1025
2407
|
exports.useChromeState = useChromeState;
|
|
1026
2408
|
exports.useChromeUserMenu = useChromeUserMenu;
|