@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/README.md +3 -2
- package/dist/auth/permission-provider.d.ts +3 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +118 -17
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +117 -16
- package/dist/index.js.map +1 -1
- package/dist/navigation/nav-user.d.ts +13 -1
- package/dist/navigation/navigation-data-helpers.d.ts +3 -1
- package/dist/navigation/organization-switcher.d.ts +5 -0
- package/package.json +1 -1
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
|
|
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(
|
|
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(
|
|
989
|
-
"bg-accent"), children: [jsxRuntime.jsx(
|
|
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
|
|
1270
|
-
* user roles for debugging
|
|
1299
|
+
* 2. probe says no access → centered "ไม่มีสิทธิ์" card
|
|
1271
1300
|
* 3. otherwise → children
|
|
1272
1301
|
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
}
|