@eintrek/erp-shell 0.1.21 → 0.1.24

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
@@ -711,8 +711,15 @@ function getActiveSubModule(pathname, modules) {
711
711
  }
712
712
  return null;
713
713
  }
714
- /** Prepend `code` (org code) to a relative URL so `/sales` → `/O25.../sales`. */
714
+ /** Prepend `code` (org code) to a relative URL so `/sales` → `/O25.../sales`.
715
+ * Absolute `http(s)://` URLs are left intact; optional `{code}` placeholder is replaced.
716
+ */
715
717
  function buildUrlWithCode(code, url) {
718
+ if (/^https?:\/\//i.test(url)) {
719
+ if (!code)
720
+ return url.split("{code}").join("");
721
+ return url.split("{code}").join(code);
722
+ }
716
723
  if (!code)
717
724
  return url;
718
725
  const cleanUrl = url.startsWith("/") ? url.slice(1) : url;
@@ -752,7 +759,10 @@ function AppBreadcrumb({ labels = {}, homeLabel = "แดชบอร์ด", ro
752
759
  })] }) }));
753
760
  }
754
761
 
755
- function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref = "/select-organization", signInRedirectHref = "/auth/signin", } = {}) {
762
+ function isExternalHref(href) {
763
+ return /^https?:\/\//i.test(href) || href.startsWith("//");
764
+ }
765
+ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref = "/select-organization", signInRedirectHref = "/auth/signin", systemLinks, } = {}) {
756
766
  const { data: session, status } = react.useSession();
757
767
  const router = navigation.useRouter();
758
768
  const { theme, setTheme } = nextThemes.useTheme();
@@ -804,6 +814,14 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
804
814
  if (profileHref)
805
815
  router.push(profileHref);
806
816
  };
817
+ const handleSystemLink = (href) => {
818
+ setIsOpen(false);
819
+ if (isExternalHref(href)) {
820
+ window.location.assign(href);
821
+ return;
822
+ }
823
+ router.push(href);
824
+ };
807
825
  const handleThemeChange = (newTheme) => {
808
826
  setTheme(newTheme);
809
827
  setShowThemeMenu(false);
@@ -836,6 +854,7 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
836
854
  }
837
855
  if (!user)
838
856
  return null;
857
+ const visibleSystemLinks = (systemLinks ?? []).filter(link => link.href && link.label);
839
858
  const dropdownContent = isOpen ? (jsxRuntime.jsxs("div", { "data-dropdown": "nav-user", className: "fixed w-52 sm:w-60 md:w-80 rounded-lg bg-sidebar border border-sidebar-border shadow-xl z-50 overflow-hidden\n -translate-x-[65%] -translate-y-[110%] sm:translate-x-0 sm:-translate-y-full", style: { top: `${position.top}px`, left: `${position.left}px` }, children: [organization && (jsxRuntime.jsx("div", { className: "p-4 border-b border-sidebar-border bg-sidebar-accent/30", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [organization.logoUrl ? (
840
859
  // eslint-disable-next-line @next/next/no-img-element
841
860
  jsxRuntime.jsx("img", { src: organization.logoUrl, alt: organization.name ?? "โลโก้บริษัท", className: "h-8 w-8 flex-shrink-0 rounded object-contain bg-white",
@@ -843,7 +862,7 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
843
862
  // sidebar; drop back to the generic icon's empty slot.
844
863
  onError: e => {
845
864
  e.currentTarget.style.display = "none";
846
- } })) : (jsxRuntime.jsx(lucideReact.Building2, { className: "h-5 w-5 text-sidebar-foreground/70 flex-shrink-0" })), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx("div", { className: "font-medium text-sm sm:truncate", children: organization.name }), organization.description && (jsxRuntime.jsx("div", { className: "text-xs text-sidebar-foreground/70 truncate mt-0.5", children: organization.description }))] })] }) })), jsxRuntime.jsxs("div", { className: "p-2", children: [jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { onClick: () => {
865
+ } })) : (jsxRuntime.jsx(lucideReact.Building2, { className: "h-5 w-5 text-sidebar-foreground/70 flex-shrink-0" })), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx("div", { className: "font-medium text-sm sm:truncate", children: organization.name }), organization.description && (jsxRuntime.jsx("div", { className: "text-xs text-sidebar-foreground/70 truncate mt-0.5", children: organization.description }))] })] }) })), jsxRuntime.jsxs("div", { className: "p-2", children: [visibleSystemLinks.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [visibleSystemLinks.map(link => (jsxRuntime.jsxs("button", { type: "button", onClick: () => handleSystemLink(link.href), 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: [link.icon ?? jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: link.label })] }, `${link.label}:${link.href}`))), jsxRuntime.jsx("div", { className: "my-1 border-t border-sidebar-border" })] })) : null, jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { onClick: () => {
847
866
  setShowThemeMenu(!showThemeMenu);
848
867
  setShowLanguageMenu(false);
849
868
  }, 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.Palette, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E40\u0E1B\u0E25\u0E35\u0E48\u0E22\u0E19\u0E18\u0E35\u0E21" }), jsxRuntime.jsx(lucideReact.ChevronUp, { className: `h-4 w-4 transition-transform ${showThemeMenu ? "rotate-180" : ""}` })] }), showThemeMenu && (jsxRuntime.jsx("div", { className: "mt-1 ml-4 space-y-1", children: [
@@ -859,7 +878,7 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
859
878
  { code: "zh", label: "中文" },
860
879
  { code: "ja", label: "日本語" },
861
880
  ].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" })] })), 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" })] }), jsxRuntime.jsx("button", { onClick: handleSignOut, disabled: isSigningOut, 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 disabled:opacity-50 disabled:cursor-not-allowed mt-1", children: isSigningOut ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E23\u0E30\u0E1A\u0E1A..." })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(lucideReact.LogOut, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E23\u0E30\u0E1A\u0E1A" })] })) })] })] })) : null;
862
- return (jsxRuntime.jsx("div", { className: "p-2", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), className: "flex w-full items-center gap-2 p-2 rounded-md transition-colors", children: [jsxRuntime.jsxs(erpTheme.Avatar, { className: "h-8 w-8 rounded-lg", children: [jsxRuntime.jsx(erpTheme.AvatarImage, { src: user.avatar, alt: user.name }), jsxRuntime.jsx(erpTheme.AvatarFallback, { className: "rounded-lg bg-[#E4525F] text-sidebar-primary-foreground w-8 h-8", children: getInitials(user.name) })] }), jsxRuntime.jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [jsxRuntime.jsx("span", { className: "truncate font-medium", title: user.name, children: user.name }), jsxRuntime.jsx("span", { className: "truncate text-xs text-sidebar-foreground/70", title: user.email, children: user.email })] }), jsxRuntime.jsx(lucideReact.ChevronUp, { className: `ml-auto size-4 transition-transform ${isOpen ? "rotate-180" : ""}` })] }), typeof window !== "undefined" &&
881
+ return (jsxRuntime.jsx("div", { className: "p-2", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), className: "flex w-full items-center gap-2 p-2 rounded-md transition-colors", children: [jsxRuntime.jsxs(erpTheme.Avatar, { className: "h-8 w-8 rounded-lg", children: [user.avatar ? (jsxRuntime.jsx(erpTheme.AvatarImage, { src: user.avatar, alt: user.name })) : null, jsxRuntime.jsx(erpTheme.AvatarFallback, { className: "rounded-lg bg-[#E4525F] text-sidebar-primary-foreground w-8 h-8", children: getInitials(user.name) })] }), jsxRuntime.jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [jsxRuntime.jsx("span", { className: "truncate font-medium", title: user.name, children: user.name }), jsxRuntime.jsx("span", { className: "truncate text-xs text-sidebar-foreground/70", title: user.email, children: user.email })] }), jsxRuntime.jsx(lucideReact.ChevronUp, { className: `ml-auto size-4 transition-transform ${isOpen ? "rotate-180" : ""}` })] }), typeof window !== "undefined" &&
863
882
  reactDom.createPortal(dropdownContent, document.body)] }) }));
864
883
  }
865
884
 
@@ -938,6 +957,17 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, })
938
957
  }) }) }));
939
958
  }
940
959
 
960
+ function OrgMark({ logoUrl, size = "md", }) {
961
+ const [failed, setFailed] = React.useState(false);
962
+ const box = size === "sm"
963
+ ? "flex size-6 items-center justify-center rounded-md border overflow-hidden bg-background"
964
+ : "flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground overflow-hidden";
965
+ const icon = size === "sm" ? "size-3.5 shrink-0" : "size-4";
966
+ if (logoUrl && !failed) {
967
+ return (jsxRuntime.jsx("div", { className: erpTheme.cn(box, size === "md" && "bg-white"), children: jsxRuntime.jsx("img", { src: logoUrl, alt: "", className: "size-full object-contain p-0.5", onError: () => setFailed(true) }) }));
968
+ }
969
+ return (jsxRuntime.jsx("div", { className: box, children: jsxRuntime.jsx(lucideReact.Building2, { className: icon }) }));
970
+ }
941
971
  function OrganizationSwitcher({ activeOrganization, isContextLoading = false, organizations, isLoadingOrgs = false, setOrganizationCookie, registerOrganizationHref = "/register-organization", className, }) {
942
972
  const { isMobile } = useSidebarToggle();
943
973
  const router = navigation.useRouter();
@@ -983,10 +1013,10 @@ function OrganizationSwitcher({ activeOrganization, isContextLoading = false, or
983
1013
  return (jsxRuntime.jsx(SidebarMenu, { className: className, children: jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2", children: [jsxRuntime.jsx(erpTheme.Skeleton, { className: "size-8 rounded-lg" }), jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-4 w-24" })] }) }) }));
984
1014
  }
985
1015
  if (organizations.length <= 1) {
986
- return (jsxRuntime.jsx(SidebarMenu, { className: className, children: jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2", children: [jsxRuntime.jsx("div", { className: "flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground", children: jsxRuntime.jsx(lucideReact.Building2, { className: "size-4" }) }), jsxRuntime.jsx("span", { className: "truncate font-semibold text-sm min-w-0 flex-1", children: activeOrganization?.name || "ไม่มีองค์กร" })] }) }) }));
1016
+ return (jsxRuntime.jsx(SidebarMenu, { className: className, children: jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2", children: [jsxRuntime.jsx(OrgMark, { logoUrl: activeOrganization?.logoUrl }), jsxRuntime.jsx("span", { className: "truncate font-semibold text-sm min-w-0 flex-1", children: activeOrganization?.name || "ไม่มีองค์กร" })] }) }) }));
987
1017
  }
988
- return (jsxRuntime.jsx(SidebarMenu, { className: className, children: jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsxs(erpTheme.DropdownMenu, { children: [jsxRuntime.jsx(erpTheme.DropdownMenuTrigger, { asChild: true, children: jsxRuntime.jsxs(SidebarMenuButton, { size: "lg", className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", children: [jsxRuntime.jsx("div", { className: "flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground", children: jsxRuntime.jsx(lucideReact.Building2, { className: "size-4" }) }), jsxRuntime.jsx("span", { className: "truncate font-semibold text-sm flex-1 min-w-0 text-left", children: activeOrganization?.name || "เลือกองค์กร" }), isPending ? (jsxRuntime.jsx(lucideReact.Loader2, { className: "ml-auto size-4 animate-spin" })) : (jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "ml-auto size-4" }))] }) }), jsxRuntime.jsxs(erpTheme.DropdownMenuContent, { className: "w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg", align: "start", side: isMobile ? "bottom" : "right", sideOffset: 4, children: [jsxRuntime.jsx(erpTheme.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" }), organizations.map(org => (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onClick: () => handleOrganizationChange(org), disabled: isPending, className: erpTheme.cn("gap-2 p-2 cursor-pointer", String(activeOrganization?.id) === String(org.id) &&
989
- "bg-accent"), children: [jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border", children: jsxRuntime.jsx(lucideReact.Building2, { className: "size-3.5 shrink-0" }) }), jsxRuntime.jsx("span", { className: "truncate", children: org.name })] }, org.id))), jsxRuntime.jsx(erpTheme.DropdownMenuSeparator, {}), jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { className: "gap-2 p-2 cursor-pointer", onClick: () => router.push(registerOrganizationHref), children: [jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border bg-transparent", children: jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }) }), jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: "\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" })] })] })] }) }) }));
1018
+ return (jsxRuntime.jsx(SidebarMenu, { className: className, children: jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsxs(erpTheme.DropdownMenu, { children: [jsxRuntime.jsx(erpTheme.DropdownMenuTrigger, { asChild: true, children: jsxRuntime.jsxs(SidebarMenuButton, { size: "lg", className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", children: [jsxRuntime.jsx(OrgMark, { logoUrl: activeOrganization?.logoUrl }), jsxRuntime.jsx("span", { className: "truncate font-semibold text-sm flex-1 min-w-0 text-left", children: activeOrganization?.name || "เลือกองค์กร" }), isPending ? (jsxRuntime.jsx(lucideReact.Loader2, { className: "ml-auto size-4 animate-spin" })) : (jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "ml-auto size-4" }))] }) }), jsxRuntime.jsxs(erpTheme.DropdownMenuContent, { className: "w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg", align: "start", side: isMobile ? "bottom" : "right", sideOffset: 4, children: [jsxRuntime.jsx(erpTheme.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" }), organizations.map(org => (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onClick: () => handleOrganizationChange(org), disabled: isPending, className: erpTheme.cn("gap-2 p-2 cursor-pointer", String(activeOrganization?.id) === String(org.id) &&
1019
+ "bg-accent"), children: [jsxRuntime.jsx(OrgMark, { logoUrl: org.logoUrl, size: "sm" }), jsxRuntime.jsx("span", { className: "truncate", children: org.name })] }, org.id))), jsxRuntime.jsx(erpTheme.DropdownMenuSeparator, {}), jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { className: "gap-2 p-2 cursor-pointer", onClick: () => router.push(registerOrganizationHref), children: [jsxRuntime.jsx("div", { className: "flex size-6 items-center justify-center rounded-md border bg-transparent", children: jsxRuntime.jsx(lucideReact.Plus, { className: "size-4" }) }), jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: "\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" })] })] })] }) }) }));
990
1020
  }
991
1021
 
992
1022
  function SidebarHeader({ children, className }) {
@@ -1266,12 +1296,11 @@ function getRequiredChildGroups(pathname, rulesMap) {
1266
1296
  * into the host's `usePermission` probe, and renders one of three states:
1267
1297
  *
1268
1298
  * 1. while rules or probe are still loading → centered spinner card
1269
- * 2. probe says no access → centered "ไม่มีสิทธิ์" card with required vs.
1270
- * user roles for debugging
1299
+ * 2. probe says no access → centered "ไม่มีสิทธิ์" card
1271
1300
  * 3. otherwise → children
1272
1301
  *
1273
- * The host wraps `<PermissionProvider>` around its page chrome (typically
1274
- * via `AppLayoutShell`'s `permissionProvider` slot).
1302
+ * Role codes are intentionally not shown on the deny screen (they are
1303
+ * internal identifiers like `fin_controller` and confuse end users).
1275
1304
  */
1276
1305
  function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1277
1306
  const pathname = navigation.usePathname();
@@ -1285,7 +1314,7 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1285
1314
  const requiredRoles = React.useMemo(() => getRequiredChildGroups(pathname, mergedMap), [pathname, mergedMap]);
1286
1315
  // Hook must run unconditionally (rules of hooks); we just ignore the
1287
1316
  // result on the fast path below.
1288
- const { hasPermission, isLoading, userRoles } = usePermission({
1317
+ const { hasPermission, isLoading } = usePermission({
1289
1318
  requiredRoles,
1290
1319
  });
1291
1320
  // Fast path — no rule covers this pathname, anyone can see it. Skip the
@@ -1306,13 +1335,83 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1306
1335
  return null;
1307
1336
  }
1308
1337
  if (!hasPermission) {
1309
- return (jsxRuntime.jsx(erpTheme.Card, { className: "p-6", children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center gap-3 text-center", children: [jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-8 w-8 text-destructive" }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-destructive", children: "\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E04\u0E38\u0E13\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E43\u0E19\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49" }), requiredRoles.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-3 space-y-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E17\u0E35\u0E48\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23:" }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: requiredRoles.map((role, idx) => (jsxRuntime.jsx("span", { className: "rounded bg-muted px-2 py-0.5 text-xs font-mono", children: role }, idx))) })] })), userRoles.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-3 space-y-1", children: [jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13:" }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: userRoles.map((role, idx) => (jsxRuntime.jsx("span", { className: "rounded bg-primary/10 px-2 py-0.5 text-xs font-mono text-primary", children: role }, idx))) })] }))] })] }) }));
1338
+ return (jsxRuntime.jsx(erpTheme.Card, { className: "p-6", children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center gap-3 text-center", children: [jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-8 w-8 text-destructive" }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-destructive", children: "\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E04\u0E38\u0E13\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E43\u0E19\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49 \u0E2B\u0E32\u0E01\u0E04\u0E34\u0E14\u0E27\u0E48\u0E32\u0E04\u0E27\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E44\u0E14\u0E49 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E1C\u0E39\u0E49\u0E14\u0E39\u0E41\u0E25\u0E23\u0E30\u0E1A\u0E1A\u0E02\u0E2D\u0E07\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" })] })] }) }));
1310
1339
  }
1311
1340
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
1312
1341
  }
1313
1342
 
1343
+ /** Cookie name for the cross-subdomain theme choice. */
1344
+ const THEME_COOKIE = "erp-theme";
1345
+ /**
1346
+ * Parent domain to scope the cookie to, so every `*.antniti.com` app reads the
1347
+ * same value. Returns `null` on localhost / bare IPs (host-only cookie).
1348
+ */
1349
+ function sharedCookieDomain() {
1350
+ if (typeof window === "undefined")
1351
+ return null;
1352
+ const host = window.location.hostname;
1353
+ if (host === "localhost" || /^[0-9.]+$/.test(host))
1354
+ return null;
1355
+ const parts = host.split(".");
1356
+ if (parts.length < 2)
1357
+ return null;
1358
+ return "." + parts.slice(-2).join(".");
1359
+ }
1360
+ function readThemeCookie() {
1361
+ if (typeof document === "undefined")
1362
+ return null;
1363
+ const match = document.cookie.match(new RegExp("(?:^|; )" + THEME_COOKIE + "=([^;]*)"));
1364
+ return match ? decodeURIComponent(match[1]) : null;
1365
+ }
1366
+ function writeThemeCookie(value) {
1367
+ if (typeof document === "undefined")
1368
+ return;
1369
+ const domain = sharedCookieDomain();
1370
+ const parts = [
1371
+ `${THEME_COOKIE}=${encodeURIComponent(value)}`,
1372
+ "path=/",
1373
+ "max-age=31536000", // 1 year
1374
+ "SameSite=Lax",
1375
+ ];
1376
+ if (domain)
1377
+ parts.push(`domain=${domain}`);
1378
+ document.cookie = parts.join("; ");
1379
+ }
1380
+ /**
1381
+ * Bridges next-themes (per-origin localStorage) with a cookie scoped to the
1382
+ * parent domain, so the light/dark/system choice is shared across the
1383
+ * accounting / HR / platform subdomains. On load it adopts the shared cookie;
1384
+ * on every later change it writes the cookie back.
1385
+ */
1386
+ function ThemeCookieSync() {
1387
+ const { theme, setTheme } = nextThemes.useTheme();
1388
+ const skipFirstWrite = React__namespace.useRef(true);
1389
+ React__namespace.useEffect(() => {
1390
+ const cookie = readThemeCookie();
1391
+ if (cookie) {
1392
+ if (cookie !== theme)
1393
+ setTheme(cookie);
1394
+ }
1395
+ else if (theme) {
1396
+ writeThemeCookie(theme); // seed the shared cookie the first time
1397
+ }
1398
+ // Run once on mount — `theme` is intentionally not a dependency here.
1399
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1400
+ }, []);
1401
+ React__namespace.useEffect(() => {
1402
+ // Skip the initial render; only persist genuine changes (incl. the
1403
+ // setTheme() triggered by adopting the cookie above — harmless re-write).
1404
+ if (skipFirstWrite.current) {
1405
+ skipFirstWrite.current = false;
1406
+ return;
1407
+ }
1408
+ if (theme)
1409
+ writeThemeCookie(theme);
1410
+ }, [theme]);
1411
+ return null;
1412
+ }
1314
1413
  function ThemeProvider({ children, ...props }) {
1315
- return jsxRuntime.jsx(nextThemes.ThemeProvider, { ...props, children: children });
1414
+ return (jsxRuntime.jsxs(nextThemes.ThemeProvider, { ...props, children: [jsxRuntime.jsx(ThemeCookieSync, {}), children] }));
1316
1415
  }
1317
1416
 
1318
1417
  const defaultFlags = {
@@ -1438,7 +1537,9 @@ function SessionBackedProfileCacheSeeder({ queryKey, mapper, }) {
1438
1537
  if (mapped) {
1439
1538
  const existing = queryClient.getQueryData(queryKey);
1440
1539
  if (!existing) {
1441
- queryClient.setQueryData(queryKey, mapped);
1540
+ // updatedAt: 0 → immediately stale so observers still GET /myprofile
1541
+ // for fields the JWT seed omits (avatar/signature paths).
1542
+ queryClient.setQueryData(queryKey, mapped, { updatedAt: 0 });
1442
1543
  }
1443
1544
  else {
1444
1545
  // Only overwrite when the mapped id changed — we don't know T's shape
@@ -1446,7 +1547,7 @@ function SessionBackedProfileCacheSeeder({ queryKey, mapper, }) {
1446
1547
  const existingId = existing?.id;
1447
1548
  const mappedId = mapped?.id;
1448
1549
  if (existingId !== mappedId) {
1449
- queryClient.setQueryData(queryKey, mapped);
1550
+ queryClient.setQueryData(queryKey, mapped, { updatedAt: 0 });
1450
1551
  }
1451
1552
  }
1452
1553
  }