@eintrek/erp-shell 0.1.16 → 0.1.18

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
@@ -999,6 +999,16 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
999
999
  const params = navigation.useParams();
1000
1000
  const code = params?.code;
1001
1001
  const extendedSession = session;
1002
+ // Track once-authenticated so a transient accessToken drop during the
1003
+ // periodic session refresh (SessionRefreshTrigger every 4 min) doesn't
1004
+ // blank the whole page. The initial-load gate below still handles cold
1005
+ // start; once we've seen a real token we render children optimistically
1006
+ // and let the API layer surface a real 401 if the token actually died.
1007
+ const hasSeenAccessTokenRef = React.useRef(false);
1008
+ React.useEffect(() => {
1009
+ if (extendedSession?.accessToken)
1010
+ hasSeenAccessTokenRef.current = true;
1011
+ }, [extendedSession?.accessToken]);
1002
1012
  // Find the org from the URL `[code]` so we can detect a context mismatch
1003
1013
  // (the user bookmarked a different org than the cookie remembers).
1004
1014
  const orgFromCode = React__namespace.useMemo(() => {
@@ -1041,9 +1051,15 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1041
1051
  if (status === "loading" && !session) {
1042
1052
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1043
1053
  }
1044
- // Session loaded but no accessToken (e.g. token refresh failed) -> bounce
1045
- // through signin so we don't end up at a "No Keycloak Context" error.
1046
- if (status === "authenticated" && !extendedSession?.accessToken) {
1054
+ // Session loaded but no accessToken AND we've never seen one → real auth
1055
+ // failure (token refresh gave nothing back on cold start), bounce through
1056
+ // signin. If we HAVE seen one before, the drop is almost certainly the
1057
+ // in-flight periodic refresh (SessionRefreshTrigger). Don't redirect on
1058
+ // that transient — the JWT callback will settle in a moment and API
1059
+ // calls will keep working with the still-valid cookie.
1060
+ if (status === "authenticated" &&
1061
+ !extendedSession?.accessToken &&
1062
+ !hasSeenAccessTokenRef.current) {
1047
1063
  if (typeof window !== "undefined") {
1048
1064
  const url = new URL(signInUrl, window.location.origin);
1049
1065
  url.searchParams.set("error", "NoAccessToken");
@@ -1052,7 +1068,9 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1052
1068
  }
1053
1069
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1054
1070
  }
1055
- if (!extendedSession?.accessToken) {
1071
+ // Only blank the page when we've never seen a token — otherwise the
1072
+ // periodic session refresh would visibly flash the loader every 4 min.
1073
+ if (!extendedSession?.accessToken && !hasSeenAccessTokenRef.current) {
1056
1074
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1057
1075
  }
1058
1076
  if (!code) {
@@ -1064,7 +1082,7 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1064
1082
  const contentMargin = open && !isSidebarModalLayout ? "ml-[21.5rem]" : "";
1065
1083
  const needsLeftGutter = !(open && !isSidebarModalLayout);
1066
1084
  const wrap = permissionProvider ?? ((c) => c);
1067
- return (jsxRuntime.jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsxRuntime.jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (jsxRuntime.jsx("button", { type: "button", "aria-label": "\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39", className: "fixed inset-0 z-40 bg-black/50", onClick: () => setOpen(false) })), jsxRuntime.jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxRuntime.jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxRuntime.jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsxRuntime.jsx(HeaderBackground, {}), jsxRuntime.jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsxRuntime.jsx("div", { id: "header-title" })] })] }), jsxRuntime.jsx(React.Suspense, { fallback: jsxRuntime.jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: jsxRuntime.jsxs("div", { className: "space-y-4 text-center", children: [jsxRuntime.jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsxRuntime.jsx("div", { className: cx("relative z-10 pr-6 pb-12", needsLeftGutter && "pl-6"), children: wrap(children) }, `org-${organizationId ?? "no-org"}`) })] }) })] }));
1085
+ return (jsxRuntime.jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsxRuntime.jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (jsxRuntime.jsx("button", { type: "button", "aria-label": "\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39", className: "fixed inset-0 z-40 bg-black/50", onClick: () => setOpen(false) })), jsxRuntime.jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxRuntime.jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxRuntime.jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsxRuntime.jsx(HeaderBackground, {}), jsxRuntime.jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsxRuntime.jsx("div", { id: "header-title" })] })] }), jsxRuntime.jsx(React.Suspense, { fallback: jsxRuntime.jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: jsxRuntime.jsxs("div", { className: "space-y-4 text-center", children: [jsxRuntime.jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsxRuntime.jsx("div", { className: cx("relative z-10 pr-6", needsLeftGutter && "pl-6"), children: wrap(children) }, `org-${organizationId ?? "no-org"}`) })] }) })] }));
1068
1086
  }
1069
1087
  /** Local tiny classnames helper — avoids pulling clsx/tailwind-merge into
1070
1088
  * the shell just for the layout file. Falsy values are skipped. */