@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/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 getRequestOrganizationId() {
338
- return (getTabOrganizationId() ??
339
- readOrganizationStorage() ??
340
- readOrganizationCookie());
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
- setOrganizationId(null);
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.60";
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
- router.push(profileHref);
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.User, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25\u0E2A\u0E48\u0E27\u0E19\u0E15\u0E31\u0E27" })] })), 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: () => {
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: () => normaliseBase(process.env.NEXT_PUBLIC_PLATFORM_URL, "https://platform.antniti.com"),
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