@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.esm.js CHANGED
@@ -980,6 +980,16 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
980
980
  const params = useParams();
981
981
  const code = params?.code;
982
982
  const extendedSession = session;
983
+ // Track once-authenticated so a transient accessToken drop during the
984
+ // periodic session refresh (SessionRefreshTrigger every 4 min) doesn't
985
+ // blank the whole page. The initial-load gate below still handles cold
986
+ // start; once we've seen a real token we render children optimistically
987
+ // and let the API layer surface a real 401 if the token actually died.
988
+ const hasSeenAccessTokenRef = useRef(false);
989
+ useEffect(() => {
990
+ if (extendedSession?.accessToken)
991
+ hasSeenAccessTokenRef.current = true;
992
+ }, [extendedSession?.accessToken]);
983
993
  // Find the org from the URL `[code]` so we can detect a context mismatch
984
994
  // (the user bookmarked a different org than the cookie remembers).
985
995
  const orgFromCode = React.useMemo(() => {
@@ -1022,9 +1032,15 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1022
1032
  if (status === "loading" && !session) {
1023
1033
  return jsx(Fragment$1, { children: loadingComponent });
1024
1034
  }
1025
- // Session loaded but no accessToken (e.g. token refresh failed) -> bounce
1026
- // through signin so we don't end up at a "No Keycloak Context" error.
1027
- if (status === "authenticated" && !extendedSession?.accessToken) {
1035
+ // Session loaded but no accessToken AND we've never seen one → real auth
1036
+ // failure (token refresh gave nothing back on cold start), bounce through
1037
+ // signin. If we HAVE seen one before, the drop is almost certainly the
1038
+ // in-flight periodic refresh (SessionRefreshTrigger). Don't redirect on
1039
+ // that transient — the JWT callback will settle in a moment and API
1040
+ // calls will keep working with the still-valid cookie.
1041
+ if (status === "authenticated" &&
1042
+ !extendedSession?.accessToken &&
1043
+ !hasSeenAccessTokenRef.current) {
1028
1044
  if (typeof window !== "undefined") {
1029
1045
  const url = new URL(signInUrl, window.location.origin);
1030
1046
  url.searchParams.set("error", "NoAccessToken");
@@ -1033,7 +1049,9 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1033
1049
  }
1034
1050
  return jsx(Fragment$1, { children: loadingComponent });
1035
1051
  }
1036
- if (!extendedSession?.accessToken) {
1052
+ // Only blank the page when we've never seen a token — otherwise the
1053
+ // periodic session refresh would visibly flash the loader every 4 min.
1054
+ if (!extendedSession?.accessToken && !hasSeenAccessTokenRef.current) {
1037
1055
  return jsx(Fragment$1, { children: loadingComponent });
1038
1056
  }
1039
1057
  if (!code) {
@@ -1045,7 +1063,7 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1045
1063
  const contentMargin = open && !isSidebarModalLayout ? "ml-[21.5rem]" : "";
1046
1064
  const needsLeftGutter = !(open && !isSidebarModalLayout);
1047
1065
  const wrap = permissionProvider ?? ((c) => c);
1048
- return (jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (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) })), jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsx(HeaderBackground, {}), jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsx("div", { id: "header-title" })] })] }), jsx(Suspense, { fallback: jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsx("div", { className: "flex h-full items-center justify-center", children: jsxs("div", { className: "space-y-4 text-center", children: [jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsx("div", { className: cx("relative z-10 pr-6 pb-12", needsLeftGutter && "pl-6"), children: wrap(children) }, `org-${organizationId ?? "no-org"}`) })] }) })] }));
1066
+ return (jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (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) })), jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsx(HeaderBackground, {}), jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsx("div", { id: "header-title" })] })] }), jsx(Suspense, { fallback: jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsx("div", { className: "flex h-full items-center justify-center", children: jsxs("div", { className: "space-y-4 text-center", children: [jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsx("div", { className: cx("relative z-10 pr-6", needsLeftGutter && "pl-6"), children: wrap(children) }, `org-${organizationId ?? "no-org"}`) })] }) })] }));
1049
1067
  }
1050
1068
  /** Local tiny classnames helper — avoids pulling clsx/tailwind-merge into
1051
1069
  * the shell just for the layout file. Falsy values are skipped. */