@campfire-interactive/shell-header 0.5.3 → 0.6.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/dist/index.d.ts +38 -1
- package/dist/index.js +74 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ interface AppDefinition {
|
|
|
10
10
|
color: string;
|
|
11
11
|
/** Local dev port (e.g., 3000). Omit if the app doesn't run locally. */
|
|
12
12
|
localPort?: number;
|
|
13
|
+
/**
|
|
14
|
+
* If set, the app tile is only visible when the host passes a
|
|
15
|
+
* `currentTenantRole` that matches one of these values. Used to gate
|
|
16
|
+
* admin-style apps (e.g. User Management → ['admin', 'owner']).
|
|
17
|
+
*/
|
|
18
|
+
requiresRole?: string[];
|
|
13
19
|
}
|
|
14
20
|
interface ShellUser {
|
|
15
21
|
name: string;
|
|
@@ -32,6 +38,12 @@ interface LocaleOption {
|
|
|
32
38
|
/** Human-readable label shown in the dropdown, e.g. "English", "Deutsch". */
|
|
33
39
|
label: string;
|
|
34
40
|
}
|
|
41
|
+
interface CurrencyOption {
|
|
42
|
+
/** ISO-4217 code, e.g. "USD", "EUR", "JPY". */
|
|
43
|
+
code: string;
|
|
44
|
+
/** Human-readable label shown in the dropdown, e.g. "US Dollar", "Euro". */
|
|
45
|
+
label: string;
|
|
46
|
+
}
|
|
35
47
|
/**
|
|
36
48
|
* Subset of the platform-notifications `Notification` shape that the bell UI
|
|
37
49
|
* actually renders. Hosts typically map their fetched items (e.g., from
|
|
@@ -53,6 +65,13 @@ interface ShellHeaderProps {
|
|
|
53
65
|
user: ShellUser;
|
|
54
66
|
/** App IDs the user is authorized to access in the current tenant */
|
|
55
67
|
authorizedApps: string[];
|
|
68
|
+
/**
|
|
69
|
+
* The user's role in their currently-active tenant (e.g. "admin", "owner",
|
|
70
|
+
* "member"). Apps in the catalog with `requiresRole` are shown only when
|
|
71
|
+
* this matches one of the allowed roles. Hosts typically derive this from
|
|
72
|
+
* `user.tenants.find(t => t.id === user.tenant.id)?.role`.
|
|
73
|
+
*/
|
|
74
|
+
currentTenantRole?: string;
|
|
56
75
|
/** Unread count for the bell badge. */
|
|
57
76
|
notificationCount?: number;
|
|
58
77
|
/**
|
|
@@ -110,13 +129,31 @@ interface ShellHeaderProps {
|
|
|
110
129
|
* pick up the change.
|
|
111
130
|
*/
|
|
112
131
|
onLocaleChange?: (locale: string) => void | Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Current display currency (ISO-4217, e.g. "USD"). When provided alongside
|
|
134
|
+
* `supportedCurrencies` and `onCurrencyChange`, a Currency section renders
|
|
135
|
+
* inside the dropdown. Omitting any of the three hides it.
|
|
136
|
+
*
|
|
137
|
+
* The picker is a UI-only surface — this package neither knows nor cares
|
|
138
|
+
* about FX rates or conversion. Hosts handle rate tables, conversion, and
|
|
139
|
+
* persistence themselves.
|
|
140
|
+
*/
|
|
141
|
+
currency?: string;
|
|
142
|
+
/** Currencies the user can pick from. */
|
|
143
|
+
supportedCurrencies?: ReadonlyArray<CurrencyOption>;
|
|
144
|
+
/**
|
|
145
|
+
* Called when the user picks a different currency. Consumer is responsible
|
|
146
|
+
* for persisting the choice (typically to localStorage or a user-prefs
|
|
147
|
+
* endpoint) and for propagating it through the rest of the UI.
|
|
148
|
+
*/
|
|
149
|
+
onCurrencyChange?: (currency: string) => void | Promise<void>;
|
|
113
150
|
/** Fixed width for the app brand area (e.g., to align with a sidebar below). */
|
|
114
151
|
brandWidth?: number;
|
|
115
152
|
/** Optional content in the left/center area (filters, search, breadcrumbs, etc.) */
|
|
116
153
|
children?: React.ReactNode;
|
|
117
154
|
}
|
|
118
155
|
|
|
119
|
-
declare function ShellHeader({ appId, user, authorizedApps, notificationCount, onNotificationClick, notifications, onMarkRead, onMarkAllRead, onNotificationItemClick, onLogout, locale, supportedLocales, onLocaleChange, tenant, tenants, onTenantSwitch, brandWidth, children, }: ShellHeaderProps): react_jsx_runtime.JSX.Element;
|
|
156
|
+
declare function ShellHeader({ appId, user, authorizedApps, currentTenantRole, notificationCount, onNotificationClick, notifications, onMarkRead, onMarkAllRead, onNotificationItemClick, onLogout, locale, supportedLocales, onLocaleChange, currency, supportedCurrencies, onCurrencyChange, tenant, tenants, onTenantSwitch, brandWidth, children, }: ShellHeaderProps): react_jsx_runtime.JSX.Element;
|
|
120
157
|
|
|
121
158
|
interface LocaleSwitcherProps {
|
|
122
159
|
currentLocale: string;
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var appCatalog = [
|
|
|
34
34
|
{ id: "pim", name: "Price Index", icon: "BarChart3", letter: "P", color: "#f97316", localPort: 3300 },
|
|
35
35
|
{ id: "cpq", name: "CPQ", icon: "Calculator", letter: "C", color: "#4f46e5", localPort: 3400 },
|
|
36
36
|
{ id: "omsf", name: "OMSF", icon: "FileText", letter: "O", color: "#0ea5e9", localPort: 3500 },
|
|
37
|
-
{ id: "identity", name: "
|
|
37
|
+
{ id: "identity", name: "User Management", icon: "Shield", letter: "U", color: "#8b5cf6", localPort: 3600, requiresRole: ["admin", "owner"] },
|
|
38
38
|
{ id: "bom", name: "BOM", icon: "Network", letter: "B", color: "#0d9488", localPort: 3700 }
|
|
39
39
|
];
|
|
40
40
|
function getAppUrl(app) {
|
|
@@ -66,7 +66,8 @@ import {
|
|
|
66
66
|
LogOut,
|
|
67
67
|
Globe,
|
|
68
68
|
ChevronDown,
|
|
69
|
-
Check
|
|
69
|
+
Check,
|
|
70
|
+
Coins
|
|
70
71
|
} from "lucide-react";
|
|
71
72
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
72
73
|
var iconMap = {
|
|
@@ -97,10 +98,12 @@ function GridIcon({ size = 20 }) {
|
|
|
97
98
|
|
|
98
99
|
// src/AppSwitcher.tsx
|
|
99
100
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
100
|
-
function AppSwitcher({ currentAppId, authorizedApps }) {
|
|
101
|
+
function AppSwitcher({ currentAppId, authorizedApps, currentTenantRole }) {
|
|
101
102
|
const [open, setOpen] = useState(false);
|
|
102
103
|
const ref = useRef(null);
|
|
103
|
-
const visibleApps = appCatalog.filter(
|
|
104
|
+
const visibleApps = appCatalog.filter(
|
|
105
|
+
(a) => authorizedApps.includes(a.id) && (!a.requiresRole || currentTenantRole !== void 0 && a.requiresRole.includes(currentTenantRole))
|
|
106
|
+
);
|
|
104
107
|
useEffect(() => {
|
|
105
108
|
function handleClickOutside(e) {
|
|
106
109
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
@@ -260,12 +263,16 @@ function UserMenu({
|
|
|
260
263
|
locale,
|
|
261
264
|
supportedLocales,
|
|
262
265
|
onLocaleChange,
|
|
266
|
+
currency,
|
|
267
|
+
supportedCurrencies,
|
|
268
|
+
onCurrencyChange,
|
|
263
269
|
tenant,
|
|
264
270
|
tenants,
|
|
265
271
|
onTenantSwitch
|
|
266
272
|
}) {
|
|
267
273
|
const [open, setOpen] = useState3(false);
|
|
268
274
|
const [langExpanded, setLangExpanded] = useState3(false);
|
|
275
|
+
const [currExpanded, setCurrExpanded] = useState3(false);
|
|
269
276
|
const [tenantExpanded, setTenantExpanded] = useState3(false);
|
|
270
277
|
const [updating, setUpdating] = useState3(false);
|
|
271
278
|
const ref = useRef3(null);
|
|
@@ -274,6 +281,7 @@ function UserMenu({
|
|
|
274
281
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
275
282
|
setOpen(false);
|
|
276
283
|
setLangExpanded(false);
|
|
284
|
+
setCurrExpanded(false);
|
|
277
285
|
setTenantExpanded(false);
|
|
278
286
|
}
|
|
279
287
|
}
|
|
@@ -283,6 +291,7 @@ function UserMenu({
|
|
|
283
291
|
useEffect3(() => {
|
|
284
292
|
if (!open) {
|
|
285
293
|
setLangExpanded(false);
|
|
294
|
+
setCurrExpanded(false);
|
|
286
295
|
setTenantExpanded(false);
|
|
287
296
|
}
|
|
288
297
|
}, [open]);
|
|
@@ -303,6 +312,22 @@ function UserMenu({
|
|
|
303
312
|
setOpen(false);
|
|
304
313
|
}
|
|
305
314
|
}
|
|
315
|
+
const showCurrency = currency !== void 0 && supportedCurrencies !== void 0 && supportedCurrencies.length > 0 && onCurrencyChange !== void 0;
|
|
316
|
+
const currentCurrency = showCurrency ? supportedCurrencies.find((c) => c.code === currency) : void 0;
|
|
317
|
+
async function handleCurrencySelect(code) {
|
|
318
|
+
if (code === currency || !onCurrencyChange) {
|
|
319
|
+
setCurrExpanded(false);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
setUpdating(true);
|
|
323
|
+
try {
|
|
324
|
+
await onCurrencyChange(code);
|
|
325
|
+
} finally {
|
|
326
|
+
setUpdating(false);
|
|
327
|
+
setCurrExpanded(false);
|
|
328
|
+
setOpen(false);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
306
331
|
const showTenant = tenant !== void 0;
|
|
307
332
|
const canSwitchTenant = showTenant && onTenantSwitch !== void 0 && Array.isArray(tenants) && tenants.length > 1;
|
|
308
333
|
async function handleTenantSelect(tenantId) {
|
|
@@ -310,15 +335,6 @@ function UserMenu({
|
|
|
310
335
|
setTenantExpanded(false);
|
|
311
336
|
return;
|
|
312
337
|
}
|
|
313
|
-
const target = tenants?.find((t) => t.id === tenantId);
|
|
314
|
-
const targetName = target?.name ?? "this tenant";
|
|
315
|
-
const confirmed = window.confirm(
|
|
316
|
-
`Switch to ${targetName}? Other open suite tabs will reload.`
|
|
317
|
-
);
|
|
318
|
-
if (!confirmed) {
|
|
319
|
-
setTenantExpanded(false);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
338
|
setUpdating(true);
|
|
323
339
|
try {
|
|
324
340
|
await onTenantSwitch(tenantId);
|
|
@@ -424,6 +440,43 @@ function UserMenu({
|
|
|
424
440
|
loc.code
|
|
425
441
|
)) })
|
|
426
442
|
] }),
|
|
443
|
+
showCurrency && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
444
|
+
/* @__PURE__ */ jsx4("div", { className: "cfi-sh-divider" }),
|
|
445
|
+
/* @__PURE__ */ jsxs4(
|
|
446
|
+
"button",
|
|
447
|
+
{
|
|
448
|
+
className: "cfi-sh-menu-item cfi-sh-locale-row",
|
|
449
|
+
onClick: () => setCurrExpanded(!currExpanded),
|
|
450
|
+
"aria-expanded": currExpanded,
|
|
451
|
+
disabled: updating,
|
|
452
|
+
children: [
|
|
453
|
+
/* @__PURE__ */ jsx4(Coins, { size: 14 }),
|
|
454
|
+
/* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-label", children: "Currency" }),
|
|
455
|
+
/* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-row-current", children: currentCurrency?.code ?? currency }),
|
|
456
|
+
/* @__PURE__ */ jsx4(
|
|
457
|
+
ChevronDown,
|
|
458
|
+
{
|
|
459
|
+
size: 14,
|
|
460
|
+
className: currExpanded ? "cfi-sh-locale-chevron cfi-sh-locale-chevron-open" : "cfi-sh-locale-chevron"
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
]
|
|
464
|
+
}
|
|
465
|
+
),
|
|
466
|
+
currExpanded && /* @__PURE__ */ jsx4("div", { className: "cfi-sh-locale-sublist", children: supportedCurrencies.map((cur) => /* @__PURE__ */ jsxs4(
|
|
467
|
+
"button",
|
|
468
|
+
{
|
|
469
|
+
className: cur.code === currency ? "cfi-sh-menu-item cfi-sh-locale-sub-item cfi-sh-locale-item-active" : "cfi-sh-menu-item cfi-sh-locale-sub-item",
|
|
470
|
+
onClick: () => handleCurrencySelect(cur.code),
|
|
471
|
+
disabled: updating,
|
|
472
|
+
children: [
|
|
473
|
+
/* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-label", children: cur.label }),
|
|
474
|
+
/* @__PURE__ */ jsx4("span", { className: "cfi-sh-locale-item-code", children: cur.code })
|
|
475
|
+
]
|
|
476
|
+
},
|
|
477
|
+
cur.code
|
|
478
|
+
)) })
|
|
479
|
+
] }),
|
|
427
480
|
/* @__PURE__ */ jsx4("div", { className: "cfi-sh-divider" }),
|
|
428
481
|
/* @__PURE__ */ jsxs4(
|
|
429
482
|
"button",
|
|
@@ -449,6 +502,7 @@ function ShellHeader({
|
|
|
449
502
|
appId,
|
|
450
503
|
user,
|
|
451
504
|
authorizedApps,
|
|
505
|
+
currentTenantRole,
|
|
452
506
|
notificationCount,
|
|
453
507
|
onNotificationClick,
|
|
454
508
|
notifications,
|
|
@@ -459,6 +513,9 @@ function ShellHeader({
|
|
|
459
513
|
locale,
|
|
460
514
|
supportedLocales,
|
|
461
515
|
onLocaleChange,
|
|
516
|
+
currency,
|
|
517
|
+
supportedCurrencies,
|
|
518
|
+
onCurrencyChange,
|
|
462
519
|
tenant,
|
|
463
520
|
tenants,
|
|
464
521
|
onTenantSwitch,
|
|
@@ -487,7 +544,7 @@ function ShellHeader({
|
|
|
487
544
|
onItemClick: onNotificationItemClick
|
|
488
545
|
}
|
|
489
546
|
),
|
|
490
|
-
/* @__PURE__ */ jsx5(AppSwitcher, { currentAppId: appId, authorizedApps }),
|
|
547
|
+
/* @__PURE__ */ jsx5(AppSwitcher, { currentAppId: appId, authorizedApps, currentTenantRole }),
|
|
491
548
|
/* @__PURE__ */ jsx5(
|
|
492
549
|
UserMenu,
|
|
493
550
|
{
|
|
@@ -497,6 +554,9 @@ function ShellHeader({
|
|
|
497
554
|
locale,
|
|
498
555
|
supportedLocales,
|
|
499
556
|
onLocaleChange,
|
|
557
|
+
currency,
|
|
558
|
+
supportedCurrencies,
|
|
559
|
+
onCurrencyChange,
|
|
500
560
|
tenant,
|
|
501
561
|
tenants,
|
|
502
562
|
onTenantSwitch
|