@eintrek/erp-shell 0.1.60 → 0.1.62
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/http/axios.d.ts +8 -0
- package/dist/http/organization-fallback.d.ts +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.esm.js +287 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +296 -11
- package/dist/index.js.map +1 -1
- package/dist/navigation/nav-user.d.ts +4 -1
- package/dist/navigation/platform-url.d.ts +23 -0
- package/dist/navigation/url-org-gate.d.ts +22 -0
- package/dist/organization/active-organization.d.ts +50 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -324,20 +324,154 @@ function readOrganizationStorage() {
|
|
|
324
324
|
return null;
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* The organisations the signed-in user belongs to, once their list has
|
|
329
|
+
* loaded (AppLayoutShell reports it); null until then.
|
|
330
|
+
*/
|
|
331
|
+
let knownOrganizationIds = null;
|
|
332
|
+
/**
|
|
333
|
+
* Record the user's organisations (browser only). From then on a stored or
|
|
334
|
+
* tab organisation that is not among them loses to one that is: a browser
|
|
335
|
+
* that still remembers a deleted organisation — or one the user has left —
|
|
336
|
+
* must not keep sending it while the cookie names a good one.
|
|
337
|
+
*/
|
|
338
|
+
function setKnownOrganizationIds(ids) {
|
|
339
|
+
if (!isBrowser$1())
|
|
340
|
+
return;
|
|
341
|
+
if (ids === null) {
|
|
342
|
+
knownOrganizationIds = null;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const set = new Set();
|
|
346
|
+
for (const id of ids) {
|
|
347
|
+
const value = normalise(id);
|
|
348
|
+
if (value)
|
|
349
|
+
set.add(value);
|
|
350
|
+
}
|
|
351
|
+
knownOrganizationIds = set;
|
|
352
|
+
}
|
|
353
|
+
/** Whether the loaded list says the user is NOT a member of `id`. */
|
|
354
|
+
function isKnownStaleOrganizationId(id) {
|
|
355
|
+
const value = normalise(id);
|
|
356
|
+
return (value !== null &&
|
|
357
|
+
knownOrganizationIds !== null &&
|
|
358
|
+
!knownOrganizationIds.has(value));
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Marks a request that must not carry X-Organization-ID: the user's own list
|
|
362
|
+
* of organisations is how a stale organisation id is found out, so it cannot
|
|
363
|
+
* depend on one. An app's interceptor honours it by passing the request's
|
|
364
|
+
* config to getRequestOrganizationId(config).
|
|
365
|
+
*
|
|
366
|
+
* axiosInstance.get("/organizations", withoutOrganization({ params }))
|
|
367
|
+
*/
|
|
368
|
+
const WITHOUT_ORGANIZATION_FLAG = "erpWithoutOrganization";
|
|
369
|
+
function withoutOrganization(config) {
|
|
370
|
+
return { ...(config ?? {}), [WITHOUT_ORGANIZATION_FLAG]: true };
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Marks a request for one particular organisation, whatever this tab has
|
|
374
|
+
* active. Used to retry the organisation list under each stored id when an
|
|
375
|
+
* API refuses it without one (see http/organization-fallback.ts).
|
|
376
|
+
*/
|
|
377
|
+
const FOR_ORGANIZATION_FIELD = "erpOrganizationId";
|
|
378
|
+
function isWithoutOrganization(config) {
|
|
379
|
+
return (!!config &&
|
|
380
|
+
typeof config === "object" &&
|
|
381
|
+
config[WITHOUT_ORGANIZATION_FLAG] === true);
|
|
382
|
+
}
|
|
383
|
+
function explicitOrganization(config) {
|
|
384
|
+
if (!config || typeof config !== "object")
|
|
385
|
+
return null;
|
|
386
|
+
const value = config[FOR_ORGANIZATION_FIELD];
|
|
387
|
+
return typeof value === "string" || typeof value === "number"
|
|
388
|
+
? normalise(value)
|
|
389
|
+
: null;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Every organisation id this browser has stored for the tab — the tab's
|
|
393
|
+
* own, localStorage, the cookie — distinct, in that order.
|
|
394
|
+
*/
|
|
395
|
+
function getStoredOrganizationIds() {
|
|
396
|
+
const ids = [
|
|
397
|
+
getTabOrganizationId(),
|
|
398
|
+
readOrganizationStorage(),
|
|
399
|
+
readOrganizationCookie(),
|
|
400
|
+
].filter((id) => id !== null);
|
|
401
|
+
return [...new Set(ids)];
|
|
402
|
+
}
|
|
327
403
|
/**
|
|
328
404
|
* The organisation a request from this tab belongs to: the tab's own, then
|
|
329
405
|
* localStorage, then the cookie. Use it for the X-Organization-ID header —
|
|
330
406
|
* and read it before any `await` in the interceptor, so the header names the
|
|
331
407
|
* organisation that was active when the request was made.
|
|
332
408
|
*
|
|
409
|
+
* Pass the request's config: a request marked withoutOrganization() gets
|
|
410
|
+
* null, so no header.
|
|
411
|
+
*
|
|
333
412
|
* localStorage before the cookie: setOrganizationId writes localStorage
|
|
334
413
|
* synchronously and then awaits a server action for the cookie, so during
|
|
335
414
|
* that round trip the cookie still names the previous organisation.
|
|
415
|
+
*
|
|
416
|
+
* Once the user's organisations are known (setKnownOrganizationIds), the
|
|
417
|
+
* first of those that is one of them wins: 0.1.60 let a deleted
|
|
418
|
+
* organisation's id left in localStorage beat a valid cookie, and every
|
|
419
|
+
* request — the organisation list included — was refused. If none is a
|
|
420
|
+
* member the first is still sent, as before: the list may simply be behind
|
|
421
|
+
* (an organisation created a moment ago).
|
|
422
|
+
*/
|
|
423
|
+
function getRequestOrganizationId(config) {
|
|
424
|
+
const explicit = explicitOrganization(config);
|
|
425
|
+
if (explicit)
|
|
426
|
+
return explicit;
|
|
427
|
+
if (isWithoutOrganization(config))
|
|
428
|
+
return null;
|
|
429
|
+
const candidates = [
|
|
430
|
+
getTabOrganizationId(),
|
|
431
|
+
readOrganizationStorage(),
|
|
432
|
+
readOrganizationCookie(),
|
|
433
|
+
];
|
|
434
|
+
if (knownOrganizationIds !== null) {
|
|
435
|
+
const member = candidates.find(id => id !== null && knownOrganizationIds.has(id));
|
|
436
|
+
if (member)
|
|
437
|
+
return member;
|
|
438
|
+
}
|
|
439
|
+
return candidates.find(id => id !== null) ?? null;
|
|
440
|
+
}
|
|
441
|
+
function expireOrganizationCookie() {
|
|
442
|
+
// The cookie is set by the host's server action, host-only or on a parent
|
|
443
|
+
// domain (AUTH_COOKIE_DOMAIN, e.g. .antniti.com). The browser does not say
|
|
444
|
+
// which, so expire it on the host and on every parent domain.
|
|
445
|
+
const base = `${ORGANIZATION_ID_KEY}=; Max-Age=0; path=/; SameSite=Lax`;
|
|
446
|
+
try {
|
|
447
|
+
document.cookie = base;
|
|
448
|
+
const labels = window.location.hostname.split(".");
|
|
449
|
+
for (let i = 0; i < labels.length - 1; i++) {
|
|
450
|
+
document.cookie = `${base}; domain=.${labels.slice(i).join(".")}`;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
// Cookies unavailable: nothing to clear.
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Forget the active organisation everywhere this browser keeps it: the tab,
|
|
459
|
+
* localStorage and the `erp.organizationId` cookie. Call on sign-out, so the
|
|
460
|
+
* next person to sign in on this browser does not start in the previous
|
|
461
|
+
* person's organisation.
|
|
336
462
|
*/
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
463
|
+
function clearStoredOrganization() {
|
|
464
|
+
if (!isBrowser$1())
|
|
465
|
+
return;
|
|
466
|
+
tabOrganizationId = null;
|
|
467
|
+
knownOrganizationIds = null;
|
|
468
|
+
try {
|
|
469
|
+
localStorage.removeItem(ORGANIZATION_ID_KEY);
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
// Storage unavailable: nothing to clear.
|
|
473
|
+
}
|
|
474
|
+
expireOrganizationCookie();
|
|
341
475
|
}
|
|
342
476
|
|
|
343
477
|
const SELECTED_ORG_STORAGE_KEY = "erp.organizationId";
|
|
@@ -1431,6 +1565,44 @@ const runGlobalRequestPrecheck = (config) => {
|
|
|
1431
1565
|
return validateGoodsReceiptChecks(config.data);
|
|
1432
1566
|
};
|
|
1433
1567
|
|
|
1568
|
+
/**
|
|
1569
|
+
* The organisation list is asked for without an organisation (see
|
|
1570
|
+
* withoutOrganization), but some APIs still refuse it without one: Peoplx's
|
|
1571
|
+
* GET /organizations sits behind its module.hr plan gate, which answers 400
|
|
1572
|
+
* "organization context required" to no header and 403 to an organisation
|
|
1573
|
+
* that does not exist. Until those routes stop needing one, a refused list is
|
|
1574
|
+
* asked again under each organisation this browser has stored — the tab's,
|
|
1575
|
+
* localStorage's, the cookie's — so one good id among stale ones still gets
|
|
1576
|
+
* the user their list, and with it the way back to a working organisation.
|
|
1577
|
+
*
|
|
1578
|
+
* `send` re-sends the request (an axios instance call). Returns the first
|
|
1579
|
+
* answer that succeeds, or undefined when none did or the request is not a
|
|
1580
|
+
* refused organisation-less one. Each retry is marked, so it never retries
|
|
1581
|
+
* itself.
|
|
1582
|
+
*/
|
|
1583
|
+
const RETRIED = "_erpOrganizationFallback";
|
|
1584
|
+
async function retryWithStoredOrganizations(config, status, send) {
|
|
1585
|
+
if (!config ||
|
|
1586
|
+
config[WITHOUT_ORGANIZATION_FLAG] !== true ||
|
|
1587
|
+
config[RETRIED] === true ||
|
|
1588
|
+
(status !== 400 && status !== 403)) {
|
|
1589
|
+
return undefined;
|
|
1590
|
+
}
|
|
1591
|
+
for (const id of getStoredOrganizationIds()) {
|
|
1592
|
+
try {
|
|
1593
|
+
return await send({
|
|
1594
|
+
...config,
|
|
1595
|
+
[RETRIED]: true,
|
|
1596
|
+
[FOR_ORGANIZATION_FIELD]: id,
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
catch {
|
|
1600
|
+
// That one was refused too; try the next.
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return undefined;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1434
1606
|
const isBrowser = () => typeof window !== "undefined";
|
|
1435
1607
|
const isAuthEndpoint = (url) => !!url && url.includes("/api/auth/");
|
|
1436
1608
|
const isAuthPage = () => isBrowser() && window.location.pathname.startsWith("/auth/");
|
|
@@ -1439,7 +1611,7 @@ const redirectToLogout = async () => {
|
|
|
1439
1611
|
return;
|
|
1440
1612
|
localStorage.clear();
|
|
1441
1613
|
sessionStorage.clear();
|
|
1442
|
-
|
|
1614
|
+
clearStoredOrganization();
|
|
1443
1615
|
clearAccessTokenCache();
|
|
1444
1616
|
await react.signOut({ callbackUrl: "/auth/signin" });
|
|
1445
1617
|
};
|
|
@@ -1471,7 +1643,7 @@ instance.interceptors.request.use(async (config) => {
|
|
|
1471
1643
|
// Read before the token await: the header must name the organisation that
|
|
1472
1644
|
// was active when the request was made (and under which its query is
|
|
1473
1645
|
// cached), not whatever is active once the token resolves.
|
|
1474
|
-
const organizationId = getRequestOrganizationId();
|
|
1646
|
+
const organizationId = getRequestOrganizationId(config);
|
|
1475
1647
|
const token = await getAccessToken(false);
|
|
1476
1648
|
if (token) {
|
|
1477
1649
|
config.headers.Authorization = `Bearer ${token}`;
|
|
@@ -1531,6 +1703,9 @@ instance.interceptors.response.use(response => {
|
|
|
1531
1703
|
window.dispatchEvent(new CustomEvent("erp:employee-record-required"));
|
|
1532
1704
|
}
|
|
1533
1705
|
}
|
|
1706
|
+
const listed = await retryWithStoredOrganizations(originalRequest, error.response?.status, config => instance(config));
|
|
1707
|
+
if (listed !== undefined)
|
|
1708
|
+
return listed;
|
|
1534
1709
|
return Promise.reject(toApiError(error));
|
|
1535
1710
|
});
|
|
1536
1711
|
|
|
@@ -1557,7 +1732,7 @@ function platformApiBase(app) {
|
|
|
1557
1732
|
* This package's version, attached to help messages so the back office can
|
|
1558
1733
|
* tell which shell a report came from. Keep in step with package.json.
|
|
1559
1734
|
*/
|
|
1560
|
-
const SHELL_VERSION = "0.1.
|
|
1735
|
+
const SHELL_VERSION = "0.1.62";
|
|
1561
1736
|
|
|
1562
1737
|
const HELP_APP_LABEL = {
|
|
1563
1738
|
platform: "แพลตฟอร์ม",
|
|
@@ -1880,6 +2055,9 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
|
|
|
1880
2055
|
};
|
|
1881
2056
|
const handleSignOut = async () => {
|
|
1882
2057
|
setIsSigningOut(true);
|
|
2058
|
+
// The next person to sign in on this browser must not start in this
|
|
2059
|
+
// person's organisation: forget it in the tab, localStorage and cookie.
|
|
2060
|
+
clearStoredOrganization();
|
|
1883
2061
|
try {
|
|
1884
2062
|
await react.signOut({ callbackUrl: signInRedirectHref, redirect: true });
|
|
1885
2063
|
}
|
|
@@ -1897,8 +2075,15 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
|
|
|
1897
2075
|
};
|
|
1898
2076
|
const handleProfile = () => {
|
|
1899
2077
|
setIsOpen(false);
|
|
1900
|
-
if (profileHref)
|
|
1901
|
-
|
|
2078
|
+
if (!profileHref)
|
|
2079
|
+
return;
|
|
2080
|
+
// Account settings live on Platform, so from any other app this is a
|
|
2081
|
+
// full page load, the same as the apps menu.
|
|
2082
|
+
if (isExternalHref(profileHref)) {
|
|
2083
|
+
window.location.assign(profileHref);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
router.push(profileHref);
|
|
1902
2087
|
};
|
|
1903
2088
|
const handleSystemLink = (href) => {
|
|
1904
2089
|
setIsOpen(false);
|
|
@@ -1965,7 +2150,7 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
|
|
|
1965
2150
|
{ code: "en", label: "English" },
|
|
1966
2151
|
{ code: "zh", label: "中文" },
|
|
1967
2152
|
{ code: "ja", label: "日本語" },
|
|
1968
|
-
].map(({ code, label }) => (jsxRuntime.jsxs("button", { onClick: () => handleLanguageChange(code), className: "flex w-full items-center gap-3 px-3 py-2 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx("span", { className: "flex-1 text-left", children: label }), organization?.language === code && (jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-primary" }))] }, code))) }))] })), profileHref && (jsxRuntime.jsxs("button", { onClick: handleProfile, className: "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx(lucideReact.
|
|
2153
|
+
].map(({ code, label }) => (jsxRuntime.jsxs("button", { onClick: () => handleLanguageChange(code), className: "flex w-full items-center gap-3 px-3 py-2 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx("span", { className: "flex-1 text-left", children: label }), organization?.language === code && (jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-primary" }))] }, code))) }))] })), profileHref && (jsxRuntime.jsxs("button", { onClick: handleProfile, className: "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx(lucideReact.UserCog, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E15\u0E31\u0E49\u0E07\u0E04\u0E48\u0E32\u0E1A\u0E31\u0E0D\u0E0A\u0E35" })] })), showSelectOrganization && (jsxRuntime.jsxs("button", { onClick: handleSelectOrganization, className: "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx(lucideReact.Building2, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E18\u0E38\u0E23\u0E01\u0E34\u0E08" })] })), visibleSystemLinks.length > 0 && (jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { onClick: () => {
|
|
1969
2154
|
setShowAppsMenu(!showAppsMenu);
|
|
1970
2155
|
setShowThemeMenu(false);
|
|
1971
2156
|
setShowLanguageMenu(false);
|
|
@@ -2045,6 +2230,32 @@ function hasPlanFeature(features, feature) {
|
|
|
2045
2230
|
return false;
|
|
2046
2231
|
}
|
|
2047
2232
|
|
|
2233
|
+
/**
|
|
2234
|
+
* Where Platform lives, and the pages every app sends people to on it.
|
|
2235
|
+
*
|
|
2236
|
+
* Personal settings — profile, password, signature, LINE — exist once, on
|
|
2237
|
+
* Platform. Every other app links there instead of keeping its own copy.
|
|
2238
|
+
*
|
|
2239
|
+
* Plain .ts (no JSX) so node --test can import it; app-registry.tsx reads the
|
|
2240
|
+
* base from here too, so the app switcher and the account link never disagree.
|
|
2241
|
+
*/
|
|
2242
|
+
const DEFAULT_PLATFORM_URL = "https://platform.antniti.com";
|
|
2243
|
+
/** Read per call: Next inlines NEXT_PUBLIC_* at the consuming app's build. */
|
|
2244
|
+
function platformBaseUrl() {
|
|
2245
|
+
return (process.env.NEXT_PUBLIC_PLATFORM_URL || DEFAULT_PLATFORM_URL).replace(/\/+$/, "");
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* ตั้งค่าบัญชี for an organisation: `/{code}/account/settings` on Platform.
|
|
2249
|
+
* Undefined without an organisation code — there is no page to open then.
|
|
2250
|
+
*/
|
|
2251
|
+
function accountSettingsHref(orgCode, { activeApp } = {}) {
|
|
2252
|
+
const code = orgCode?.trim();
|
|
2253
|
+
if (!code)
|
|
2254
|
+
return undefined;
|
|
2255
|
+
const path = `/${encodeURIComponent(code)}/account/settings`;
|
|
2256
|
+
return activeApp === "platform" ? path : `${platformBaseUrl()}${path}`;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2048
2259
|
function normaliseBase(value, fallback) {
|
|
2049
2260
|
return (value ?? fallback).replace(/\/$/, "");
|
|
2050
2261
|
}
|
|
@@ -2061,7 +2272,7 @@ const APPS = [
|
|
|
2061
2272
|
label: "แพลตฟอร์ม",
|
|
2062
2273
|
icon: jsxRuntime.jsx(lucideReact.Building2, { className: "h-4 w-4 shrink-0" }),
|
|
2063
2274
|
features: null,
|
|
2064
|
-
baseUrl:
|
|
2275
|
+
baseUrl: platformBaseUrl,
|
|
2065
2276
|
},
|
|
2066
2277
|
{
|
|
2067
2278
|
id: "accounx",
|
|
@@ -3273,6 +3484,31 @@ function resolveUrlOrgGate({ code, organizations, organizationsStatus, organizat
|
|
|
3273
3484
|
return { state: "unverified" };
|
|
3274
3485
|
}
|
|
3275
3486
|
}
|
|
3487
|
+
function resolveStaleOrganization({ code, organizations, organizationsStatus, organizationId, }) {
|
|
3488
|
+
const status = organizationsStatus ?? (organizations.length > 0 ? "success" : undefined);
|
|
3489
|
+
if (status !== "success")
|
|
3490
|
+
return { action: "none" };
|
|
3491
|
+
const activeId = organizationId === null || organizationId === undefined
|
|
3492
|
+
? ""
|
|
3493
|
+
: String(organizationId).trim();
|
|
3494
|
+
if (activeId === "")
|
|
3495
|
+
return { action: "none" };
|
|
3496
|
+
if (organizations.some(org => String(org.id) === activeId)) {
|
|
3497
|
+
return { action: "none" };
|
|
3498
|
+
}
|
|
3499
|
+
const urlOrg = findOrganizationForCode(code, organizations);
|
|
3500
|
+
if (urlOrg) {
|
|
3501
|
+
return { action: "use", targetId: String(urlOrg.id), fromUrl: true };
|
|
3502
|
+
}
|
|
3503
|
+
if (organizations.length === 1) {
|
|
3504
|
+
return {
|
|
3505
|
+
action: "use",
|
|
3506
|
+
targetId: String(organizations[0].id),
|
|
3507
|
+
fromUrl: false,
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
return { action: "forget" };
|
|
3511
|
+
}
|
|
3276
3512
|
|
|
3277
3513
|
/**
|
|
3278
3514
|
* Top-level app chrome: sticky header, sidebar slot, content gutter and the
|
|
@@ -3334,6 +3570,44 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
|
|
|
3334
3570
|
requestedSwitchRef.current = switchTargetId;
|
|
3335
3571
|
void setOrganizationId(switchTargetId);
|
|
3336
3572
|
}, [switchTargetId, setOrganizationId]);
|
|
3573
|
+
// Once the user's organisations are known, requests prefer a stored
|
|
3574
|
+
// organisation that is one of them (getRequestOrganizationId).
|
|
3575
|
+
const organizationsLoaded = (organizationsStatus ??
|
|
3576
|
+
(organizations.length > 0 ? "success" : undefined)) === "success";
|
|
3577
|
+
React.useEffect(() => {
|
|
3578
|
+
if (organizationsLoaded) {
|
|
3579
|
+
setKnownOrganizationIds(organizations.map(org => org.id));
|
|
3580
|
+
}
|
|
3581
|
+
}, [organizationsLoaded, organizations]);
|
|
3582
|
+
// An active organisation that is not one of the user's — deleted, or left —
|
|
3583
|
+
// is replaced, so it stops being sent with every request. The URL's
|
|
3584
|
+
// organisation, when the user is a member, is the gate's switch above;
|
|
3585
|
+
// otherwise the user's only organisation, or none (they pick one: the
|
|
3586
|
+
// refusal below offers it, and a path without a code goes to the picker).
|
|
3587
|
+
// Once per stale id and target, so a setter that does not take cannot loop.
|
|
3588
|
+
const staleRecovery = resolveStaleOrganization({
|
|
3589
|
+
code,
|
|
3590
|
+
organizations,
|
|
3591
|
+
organizationsStatus,
|
|
3592
|
+
organizationId,
|
|
3593
|
+
});
|
|
3594
|
+
const staleRecoveryTarget = staleRecovery.action === "forget"
|
|
3595
|
+
? null
|
|
3596
|
+
: staleRecovery.action === "use" && !staleRecovery.fromUrl
|
|
3597
|
+
? staleRecovery.targetId
|
|
3598
|
+
: undefined;
|
|
3599
|
+
const staleRecoveryKey = staleRecoveryTarget === undefined
|
|
3600
|
+
? null
|
|
3601
|
+
: `${String(organizationId)}>${staleRecoveryTarget ?? ""}`;
|
|
3602
|
+
const handledStaleRecoveryRef = React.useRef(null);
|
|
3603
|
+
React.useEffect(() => {
|
|
3604
|
+
if (staleRecoveryKey === null || staleRecoveryTarget === undefined)
|
|
3605
|
+
return;
|
|
3606
|
+
if (handledStaleRecoveryRef.current === staleRecoveryKey)
|
|
3607
|
+
return;
|
|
3608
|
+
handledStaleRecoveryRef.current = staleRecoveryKey;
|
|
3609
|
+
void setOrganizationId(staleRecoveryTarget);
|
|
3610
|
+
}, [staleRecoveryKey, staleRecoveryTarget, setOrganizationId]);
|
|
3337
3611
|
// Key for the content subtree. It exists so switching organizations throws
|
|
3338
3612
|
// away the previous org's component state — but it must react ONLY to a real
|
|
3339
3613
|
// switch. organizationId legitimately passes through null while the context
|
|
@@ -4057,6 +4331,7 @@ exports.EMPLOYEE_RECORD_OK_EVENT = EMPLOYEE_RECORD_OK_EVENT;
|
|
|
4057
4331
|
exports.EMPLOYEE_RECORD_REQUIRED = EMPLOYEE_RECORD_REQUIRED;
|
|
4058
4332
|
exports.EMPLOYEE_RECORD_REQUIRED_EVENT = EMPLOYEE_RECORD_REQUIRED_EVENT;
|
|
4059
4333
|
exports.EmployeeRequiredGate = EmployeeRequiredGate;
|
|
4334
|
+
exports.FOR_ORGANIZATION_FIELD = FOR_ORGANIZATION_FIELD;
|
|
4060
4335
|
exports.FeatureFlagsProvider = FeatureFlagsProvider;
|
|
4061
4336
|
exports.HeaderBackground = HeaderBackground;
|
|
4062
4337
|
exports.HelpButton = HelpButton;
|
|
@@ -4083,15 +4358,18 @@ exports.SidebarProvider = SidebarProvider;
|
|
|
4083
4358
|
exports.SidebarTrigger = SidebarTrigger;
|
|
4084
4359
|
exports.TapMenu = TapMenu;
|
|
4085
4360
|
exports.ThemeProvider = ThemeProvider;
|
|
4361
|
+
exports.WITHOUT_ORGANIZATION_FLAG = WITHOUT_ORGANIZATION_FLAG;
|
|
4086
4362
|
exports.WORKSPACE_HEADER_VARS = WORKSPACE_HEADER_VARS;
|
|
4087
4363
|
exports.WORKSPACE_LIGHT_HEADER_VARS = WORKSPACE_LIGHT_HEADER_VARS;
|
|
4088
4364
|
exports.WorkspaceBrand = WorkspaceBrand;
|
|
4365
|
+
exports.accountSettingsHref = accountSettingsHref;
|
|
4089
4366
|
exports.axiosInstance = instance;
|
|
4090
4367
|
exports.buildSystemLinks = buildSystemLinks;
|
|
4091
4368
|
exports.buildUrlWithCode = buildUrlWithCode;
|
|
4092
4369
|
exports.canAccessNavPermissionRoute = canAccessNavPermissionRoute;
|
|
4093
4370
|
exports.clearAccessTokenCache = clearAccessTokenCache;
|
|
4094
4371
|
exports.clearOrganizationId = clearOrganizationId;
|
|
4372
|
+
exports.clearStoredOrganization = clearStoredOrganization;
|
|
4095
4373
|
exports.closeHelp = closeHelp;
|
|
4096
4374
|
exports.decodeJWT = decodeJWT;
|
|
4097
4375
|
exports.describeAttached = describeAttached;
|
|
@@ -4108,8 +4386,10 @@ exports.getOrganizationId = getOrganizationId;
|
|
|
4108
4386
|
exports.getQueryClientConfig = getQueryClientConfig;
|
|
4109
4387
|
exports.getRequestOrganizationId = getRequestOrganizationId;
|
|
4110
4388
|
exports.getRequiredChildGroups = getRequiredChildGroups;
|
|
4389
|
+
exports.getStoredOrganizationIds = getStoredOrganizationIds;
|
|
4111
4390
|
exports.getTabOrganizationId = getTabOrganizationId;
|
|
4112
4391
|
exports.hasPlanFeature = hasPlanFeature;
|
|
4392
|
+
exports.isKnownStaleOrganizationId = isKnownStaleOrganizationId;
|
|
4113
4393
|
exports.isLikelyTransientNetworkError = isLikelyTransientNetworkError;
|
|
4114
4394
|
exports.isPathActive = isPathActive;
|
|
4115
4395
|
exports.myRolesQueryKey = myRolesQueryKey;
|
|
@@ -4117,13 +4397,17 @@ exports.normalizePathnameForPermissionRules = normalizePathnameForPermissionRule
|
|
|
4117
4397
|
exports.openHelp = openHelp;
|
|
4118
4398
|
exports.organizationScopedQueryHash = organizationScopedQueryHash;
|
|
4119
4399
|
exports.parseMyRoles = parseMyRoles;
|
|
4400
|
+
exports.platformBaseUrl = platformBaseUrl;
|
|
4120
4401
|
exports.resolveActiveNav = resolveActiveNav;
|
|
4121
4402
|
exports.resolveSidebarNav = resolveSidebarNav;
|
|
4403
|
+
exports.resolveStaleOrganization = resolveStaleOrganization;
|
|
4122
4404
|
exports.resolveUiRoutePermission = resolveUiRoutePermission;
|
|
4123
4405
|
exports.resolveUrlOrgGate = resolveUrlOrgGate;
|
|
4406
|
+
exports.retryWithStoredOrganizations = retryWithStoredOrganizations;
|
|
4124
4407
|
exports.roleSummary = roleSummary;
|
|
4125
4408
|
exports.runGlobalRequestPrecheck = runGlobalRequestPrecheck;
|
|
4126
4409
|
exports.runWithTransientRetry = runWithTransientRetry;
|
|
4410
|
+
exports.setKnownOrganizationIds = setKnownOrganizationIds;
|
|
4127
4411
|
exports.setOrganizationId = setOrganizationId;
|
|
4128
4412
|
exports.setTabOrganizationId = setTabOrganizationId;
|
|
4129
4413
|
exports.shouldRetryQuery = shouldRetryQuery;
|
|
@@ -4141,4 +4425,5 @@ exports.usePermissionRules = usePermissionRules;
|
|
|
4141
4425
|
exports.useSessionRefresh = useSessionRefresh;
|
|
4142
4426
|
exports.useSidebar = useSidebar;
|
|
4143
4427
|
exports.useSidebarToggle = useSidebarToggle;
|
|
4428
|
+
exports.withoutOrganization = withoutOrganization;
|
|
4144
4429
|
//# sourceMappingURL=index.js.map
|