@eintrek/erp-shell 0.1.24 → 0.1.26
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/auth/access-denied.d.ts +22 -0
- package/dist/auth/permission-provider.d.ts +6 -7
- package/dist/auth/permission-utils.d.ts +24 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +90 -45
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +90 -43
- package/dist/index.js.map +1 -1
- package/dist/navigation/navigation-data-helpers.d.ts +6 -2
- package/dist/providers/permission-rules-context.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface AccessDeniedProps {
|
|
2
|
+
/** Bold headline. */
|
|
3
|
+
title?: string;
|
|
4
|
+
/** Secondary explanation — pass the page-specific denied reason here. */
|
|
5
|
+
message?: string;
|
|
6
|
+
/** Where "กลับไปหน้าหลัก" navigates (default "/"). */
|
|
7
|
+
homeHref?: string;
|
|
8
|
+
homeLabel?: string;
|
|
9
|
+
/** When set, shows the "ติดต่อฝ่ายสนับสนุน" button (URL or mailto:). */
|
|
10
|
+
supportHref?: string;
|
|
11
|
+
supportLabel?: string;
|
|
12
|
+
/** Fill the surrounding content area and vertically center (default true). */
|
|
13
|
+
fullScreen?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Central "no access" screen shared by every policy check (route-level
|
|
18
|
+
* PermissionProvider and page-level PermissionGateShell). One look everywhere:
|
|
19
|
+
* an illustration, a 403 headline, the page-specific reason, and recovery
|
|
20
|
+
* actions.
|
|
21
|
+
*/
|
|
22
|
+
export declare function AccessDenied({ title, message, homeHref, homeLabel, supportHref, supportLabel, fullScreen, className, }: AccessDeniedProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -31,13 +31,12 @@ export interface PermissionProviderProps {
|
|
|
31
31
|
* Permission gate for a page subtree. Looks up the current pathname in the
|
|
32
32
|
* permission rules map (live rules from `PermissionRulesProvider`, layered
|
|
33
33
|
* over the optional `fallbackRulesMap`), passes the resolved required roles
|
|
34
|
-
* into the host's `usePermission` probe, and renders one of
|
|
34
|
+
* into the host's `usePermission` probe, and renders one of:
|
|
35
35
|
*
|
|
36
|
-
* 1.
|
|
37
|
-
* 2.
|
|
38
|
-
* 3.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* internal identifiers like `fin_controller` and confuse end users).
|
|
36
|
+
* 1. rules / probe still loading → `null` (let Suspense / loading.tsx show)
|
|
37
|
+
* 2. no rule covers this path → deny (fail closed)
|
|
38
|
+
* 3. rule is explicit public (`[]`) → children
|
|
39
|
+
* 4. probe says no access → deny card
|
|
40
|
+
* 5. otherwise → children
|
|
42
41
|
*/
|
|
43
42
|
export declare function PermissionProvider({ children, usePermission, fallbackRulesMap, }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -9,14 +9,31 @@ import type { PermissionRulesMap } from "../providers/permission-rules-context";
|
|
|
9
9
|
* the same management screen with different filters.
|
|
10
10
|
*/
|
|
11
11
|
export declare function normalizePathnameForPermissionRules(pathname: string): string;
|
|
12
|
+
export interface UiRoutePermissionLookup {
|
|
13
|
+
/** True when an exact, pattern, or restrictive parent rule was found. */
|
|
14
|
+
matched: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Required child-group segments. Empty array means the route is
|
|
17
|
+
* explicitly public (configured as `required_child_groups: []`).
|
|
18
|
+
* When `matched` is false this is always `[]` and must be treated as deny.
|
|
19
|
+
*/
|
|
20
|
+
roles: string[];
|
|
21
|
+
}
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
14
|
-
* JWT must contain at least one matching group). An exact match on the
|
|
15
|
-
* normalized path wins; otherwise the function falls back to a segment-by-
|
|
16
|
-
* segment pattern match where `[param]` placeholders accept any non-empty
|
|
17
|
-
* value. Returns an empty array when no rule covers the path.
|
|
23
|
+
* Resolve UI route permissions for a pathname.
|
|
18
24
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
25
|
+
* - Exact / `[param]` pattern match → matched
|
|
26
|
+
* - Parent-prefix inherit only when the parent rule has a **non-empty**
|
|
27
|
+
* role list (restrictive inherit). Explicitly public parents (`[]`) do
|
|
28
|
+
* not open all children.
|
|
29
|
+
* - No match → `{ matched: false }` (fail closed — never treat as public)
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveUiRoutePermission(pathname: string, rulesMap: PermissionRulesMap): UiRoutePermissionLookup;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the required child-group segments for a page pathname.
|
|
34
|
+
*
|
|
35
|
+
* Prefer {@link resolveUiRoutePermission} when you need to distinguish
|
|
36
|
+
* "explicitly public (`[]`)" from "no rule (deny)". This helper returns
|
|
37
|
+
* `[]` for both cases and is kept for backward compatibility.
|
|
21
38
|
*/
|
|
22
39
|
export declare function getRequiredChildGroups(pathname: string, rulesMap: PermissionRulesMap): string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -19,9 +19,10 @@ export { OrganizationSwitcher, type OrganizationSwitcherProps, type Organization
|
|
|
19
19
|
export { SidebarHeader } from "./navigation/sidebar-header";
|
|
20
20
|
export { AppSidebar, type AppSidebarProps } from "./navigation/app-sidebar";
|
|
21
21
|
export { AppLayoutShell, type AppLayoutShellProps, type AppLayoutShellOrganization, } from "./navigation/app-layout-shell";
|
|
22
|
+
export { AccessDenied, type AccessDeniedProps } from "./auth/access-denied";
|
|
22
23
|
export { PermissionGateShell, type PermissionGateShellProps, type PermissionGateSkeletonVariant, } from "./auth/permission-gate-shell";
|
|
23
24
|
export { PermissionProvider, type PermissionProviderProps, type PermissionProbe, type UsePermissionHook, } from "./auth/permission-provider";
|
|
24
|
-
export { getRequiredChildGroups, normalizePathnameForPermissionRules, } from "./auth/permission-utils";
|
|
25
|
+
export { getRequiredChildGroups, normalizePathnameForPermissionRules, resolveUiRoutePermission, type UiRoutePermissionLookup, } from "./auth/permission-utils";
|
|
25
26
|
export * from "./navigation/navigation-helpers";
|
|
26
27
|
export type { Module, SubPage, SubModule, Tool, Company, NavigationData, } from "./navigation/navigation-types";
|
|
27
28
|
export { canAccessNavPermissionRoute, filterNavigationToolsByPermission, filterNavigationModulesByPermission, getActiveModule, getActiveSubModule, buildUrlWithCode, } from "./navigation/navigation-data-helpers";
|
package/dist/index.esm.js
CHANGED
|
@@ -5,7 +5,7 @@ import { useSession, signOut } from 'next-auth/react';
|
|
|
5
5
|
import { useQueryClient, MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
6
6
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
7
7
|
import { cn, Button, Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage, Avatar, AvatarImage, AvatarFallback, Collapsible, CollapsibleTrigger, CollapsibleContent, Badge, Skeleton, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, Card, CardHeader, CardContent } from '@eintrek/erp-theme';
|
|
8
|
-
import { PanelLeftClose, Menu, ChevronRight, Building2, ExternalLink, Palette, ChevronUp, Sun, Moon, Monitor, Globe, User, Loader2, LogOut, ChevronDown, ChevronsUpDown, Plus,
|
|
8
|
+
import { PanelLeftClose, Menu, ChevronRight, Building2, ExternalLink, Palette, ChevronUp, Sun, Moon, Monitor, Globe, User, Loader2, LogOut, ChevronDown, ChevronsUpDown, Plus, Sparkles, Ban, Home, Headset } from 'lucide-react';
|
|
9
9
|
import { create } from 'zustand';
|
|
10
10
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
11
11
|
import { useRouter, usePathname, useParams } from 'next/navigation';
|
|
@@ -604,12 +604,19 @@ SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
|
|
|
604
604
|
|
|
605
605
|
/**
|
|
606
606
|
* True when the user's child-group segments include at least one of the
|
|
607
|
-
* groups required for `permissionRouteKey`.
|
|
608
|
-
*
|
|
607
|
+
* groups required for `permissionRouteKey`.
|
|
608
|
+
*
|
|
609
|
+
* Fail-closed:
|
|
610
|
+
* - Nav item without `permissionRouteKey` → deny (must declare a key)
|
|
611
|
+
* - Key absent from `rulesMap` → deny (unknown route is not public)
|
|
612
|
+
* - Key present with `required_child_groups: []` → allow (explicit public)
|
|
609
613
|
*/
|
|
610
614
|
function canAccessNavPermissionRoute(permissionRouteKey, rulesMap, userChildSegments) {
|
|
611
615
|
if (!permissionRouteKey)
|
|
612
|
-
return
|
|
616
|
+
return false;
|
|
617
|
+
if (!Object.prototype.hasOwnProperty.call(rulesMap, permissionRouteKey)) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
613
620
|
const required = rulesMap[permissionRouteKey] ?? [];
|
|
614
621
|
if (required.length === 0)
|
|
615
622
|
return true;
|
|
@@ -1144,6 +1151,27 @@ function cx(...parts) {
|
|
|
1144
1151
|
return parts.filter(Boolean).join(" ");
|
|
1145
1152
|
}
|
|
1146
1153
|
|
|
1154
|
+
/**
|
|
1155
|
+
* Central "no access" screen shared by every policy check (route-level
|
|
1156
|
+
* PermissionProvider and page-level PermissionGateShell). One look everywhere:
|
|
1157
|
+
* an illustration, a 403 headline, the page-specific reason, and recovery
|
|
1158
|
+
* actions.
|
|
1159
|
+
*/
|
|
1160
|
+
function AccessDenied({ title = "ขออภัย, คุณไม่มีสิทธิ์เข้าถึงหน้านี้ (403)", message = "คุณไม่มีสิทธิ์เข้าถึงหน้านี้ ขออภัยในความไม่สะดวก", homeHref = "/", homeLabel = "กลับไปหน้าหลัก", supportHref, supportLabel = "ติดต่อฝ่ายสนับสนุน", fullScreen = true, className, }) {
|
|
1161
|
+
const go = (href) => {
|
|
1162
|
+
if (typeof window !== "undefined")
|
|
1163
|
+
window.location.href = href;
|
|
1164
|
+
};
|
|
1165
|
+
const wrapperClass = [
|
|
1166
|
+
"flex min-w-0 flex-1 items-center justify-center p-4",
|
|
1167
|
+
fullScreen ? "min-h-[70vh]" : "",
|
|
1168
|
+
className ?? "",
|
|
1169
|
+
]
|
|
1170
|
+
.filter(Boolean)
|
|
1171
|
+
.join(" ");
|
|
1172
|
+
return (jsx("div", { className: wrapperClass, children: jsxs("div", { className: "relative w-full max-w-3xl overflow-hidden rounded-2xl border border-border/60 bg-card/60 p-8 shadow-sm backdrop-blur sm:p-10", children: [jsx(Sparkles, { className: "pointer-events-none absolute right-6 top-6 h-5 w-5 text-primary/40" }), jsxs("div", { className: "flex flex-col items-center gap-8 text-center sm:flex-row sm:text-left", children: [jsxs("div", { className: "relative shrink-0", children: [jsx("div", { className: "absolute inset-0 rounded-full bg-destructive/20 blur-2xl" }), jsx("div", { className: "relative flex h-28 w-28 items-center justify-center rounded-3xl border border-border/60 bg-gradient-to-br from-muted/40 to-background shadow-inner", children: jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full border border-destructive/40 bg-destructive/10", children: jsx(Ban, { className: "h-8 w-8 text-destructive", strokeWidth: 2.5 }) }) })] }), jsxs("div", { className: "min-w-0 flex-1", children: [jsx("h1", { className: "text-xl font-bold text-foreground sm:text-2xl", children: title }), message ? (jsx("p", { className: "mt-3 whitespace-pre-line text-sm leading-relaxed text-muted-foreground sm:text-base", children: message })) : null, jsxs("div", { className: "mt-6 flex flex-col gap-3 sm:flex-row", children: [jsxs(Button, { type: "button", onClick: () => go(homeHref), className: "w-full sm:w-auto", children: [jsx(Home, { className: "mr-2 h-4 w-4" }), homeLabel] }), supportHref ? (jsxs(Button, { type: "button", variant: "outline", onClick: () => go(supportHref), className: "w-full sm:w-auto", children: [jsx(Headset, { className: "mr-2 h-4 w-4" }), supportLabel] })) : null] })] })] })] }) }));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1147
1175
|
const shellClassName = "flex min-w-0 flex-1 flex-col gap-6 overflow-x-hidden";
|
|
1148
1176
|
/**
|
|
1149
1177
|
* Shared layout for “กำลังตรวจสอบสิทธิ์” (skeleton) and “ไม่มีสิทธิ์” (message).
|
|
@@ -1154,7 +1182,7 @@ function PermissionGateShell({ policyLoading, hasPolicyAccess, deniedMessage, ch
|
|
|
1154
1182
|
return (jsx("div", { className: shellClassName, children: jsx("div", { className: skeletonGridClassName, children: Array.from({ length: skeletonCount }).map((_, i) => (jsx(Card, { children: skeletonVariant === "kpi" ? (jsxs(Fragment$1, { children: [jsxs(CardHeader, { className: "flex flex-row items-center justify-between pb-2", children: [jsx(Skeleton, { className: "h-4 w-24" }), jsx(Skeleton, { className: "h-4 w-4 rounded" })] }), jsxs(CardContent, { children: [jsx(Skeleton, { className: "h-8 w-32 mb-2" }), jsx(Skeleton, { className: "h-3 w-20" })] })] })) : (jsxs(Fragment$1, { children: [jsx(CardHeader, { className: "pb-2", children: jsx(Skeleton, { className: "h-4 w-24" }) }), jsx(CardContent, { children: jsx(Skeleton, { className: "h-8 w-16" }) })] })) }, i))) }) }));
|
|
1155
1183
|
}
|
|
1156
1184
|
if (!hasPolicyAccess) {
|
|
1157
|
-
return (jsx("div", { className: shellClassName, children: jsx(
|
|
1185
|
+
return (jsx("div", { className: shellClassName, children: jsx(AccessDenied, { message: deniedMessage }) }));
|
|
1158
1186
|
}
|
|
1159
1187
|
return jsx(Fragment$1, { children: children });
|
|
1160
1188
|
}
|
|
@@ -1197,8 +1225,8 @@ function PermissionRulesProvider({ children, data, isLoading = false, isError =
|
|
|
1197
1225
|
return (jsx(PermissionRulesContext.Provider, { value: value, children: children }));
|
|
1198
1226
|
}
|
|
1199
1227
|
/**
|
|
1200
|
-
* Read the permission rules.
|
|
1201
|
-
*
|
|
1228
|
+
* Read the permission rules. Outside the provider, report loading so
|
|
1229
|
+
* fail-closed consumers (nav / gates) do not treat empty maps as "allow all".
|
|
1202
1230
|
*/
|
|
1203
1231
|
function usePermissionRules() {
|
|
1204
1232
|
const ctx = useContext(PermissionRulesContext);
|
|
@@ -1206,7 +1234,7 @@ function usePermissionRules() {
|
|
|
1206
1234
|
return {
|
|
1207
1235
|
rulesMap: {},
|
|
1208
1236
|
policiesMap: {},
|
|
1209
|
-
isLoading:
|
|
1237
|
+
isLoading: true,
|
|
1210
1238
|
isError: false,
|
|
1211
1239
|
updatedAt: null,
|
|
1212
1240
|
};
|
|
@@ -1228,20 +1256,22 @@ function normalizePathnameForPermissionRules(pathname) {
|
|
|
1228
1256
|
const path = parts.length <= 1 ? "/" : "/" + parts.slice(1).join("/");
|
|
1229
1257
|
return path.replace(/\/user-role\/(purchase-doc|sales-doc)$/, "/user-role/members");
|
|
1230
1258
|
}
|
|
1259
|
+
function hasRule(rulesMap, routeKey) {
|
|
1260
|
+
return Object.prototype.hasOwnProperty.call(rulesMap, routeKey);
|
|
1261
|
+
}
|
|
1231
1262
|
/**
|
|
1232
|
-
*
|
|
1233
|
-
* JWT must contain at least one matching group). An exact match on the
|
|
1234
|
-
* normalized path wins; otherwise the function falls back to a segment-by-
|
|
1235
|
-
* segment pattern match where `[param]` placeholders accept any non-empty
|
|
1236
|
-
* value. Returns an empty array when no rule covers the path.
|
|
1263
|
+
* Resolve UI route permissions for a pathname.
|
|
1237
1264
|
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1265
|
+
* - Exact / `[param]` pattern match → matched
|
|
1266
|
+
* - Parent-prefix inherit only when the parent rule has a **non-empty**
|
|
1267
|
+
* role list (restrictive inherit). Explicitly public parents (`[]`) do
|
|
1268
|
+
* not open all children.
|
|
1269
|
+
* - No match → `{ matched: false }` (fail closed — never treat as public)
|
|
1240
1270
|
*/
|
|
1241
|
-
function
|
|
1271
|
+
function resolveUiRoutePermission(pathname, rulesMap) {
|
|
1242
1272
|
const normalized = normalizePathnameForPermissionRules(pathname);
|
|
1243
|
-
if (rulesMap
|
|
1244
|
-
return rulesMap[normalized];
|
|
1273
|
+
if (hasRule(rulesMap, normalized)) {
|
|
1274
|
+
return { matched: true, roles: rulesMap[normalized] ?? [] };
|
|
1245
1275
|
}
|
|
1246
1276
|
const pathSegments = normalized.split("/").filter(Boolean);
|
|
1247
1277
|
for (const [pattern, roles] of Object.entries(rulesMap)) {
|
|
@@ -1264,24 +1294,43 @@ function getRequiredChildGroups(pathname, rulesMap) {
|
|
|
1264
1294
|
}
|
|
1265
1295
|
}
|
|
1266
1296
|
if (matches) {
|
|
1267
|
-
return roles;
|
|
1297
|
+
return { matched: true, roles: roles ?? [] };
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// Parent-prefix: /leave/types/abc → /leave/types (non-empty roles only)
|
|
1301
|
+
for (let len = pathSegments.length - 1; len >= 1; len--) {
|
|
1302
|
+
const parent = "/" + pathSegments.slice(0, len).join("/");
|
|
1303
|
+
if (hasRule(rulesMap, parent) && (rulesMap[parent]?.length ?? 0) > 0) {
|
|
1304
|
+
return { matched: true, roles: rulesMap[parent] ?? [] };
|
|
1268
1305
|
}
|
|
1269
1306
|
}
|
|
1270
|
-
return [];
|
|
1307
|
+
return { matched: false, roles: [] };
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Returns the required child-group segments for a page pathname.
|
|
1311
|
+
*
|
|
1312
|
+
* Prefer {@link resolveUiRoutePermission} when you need to distinguish
|
|
1313
|
+
* "explicitly public (`[]`)" from "no rule (deny)". This helper returns
|
|
1314
|
+
* `[]` for both cases and is kept for backward compatibility.
|
|
1315
|
+
*/
|
|
1316
|
+
function getRequiredChildGroups(pathname, rulesMap) {
|
|
1317
|
+
return resolveUiRoutePermission(pathname, rulesMap).roles;
|
|
1271
1318
|
}
|
|
1272
1319
|
|
|
1320
|
+
function DeniedCard() {
|
|
1321
|
+
return (jsx(AccessDenied, { message: "\u0E04\u0E38\u0E13\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\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" }));
|
|
1322
|
+
}
|
|
1273
1323
|
/**
|
|
1274
1324
|
* Permission gate for a page subtree. Looks up the current pathname in the
|
|
1275
1325
|
* permission rules map (live rules from `PermissionRulesProvider`, layered
|
|
1276
1326
|
* over the optional `fallbackRulesMap`), passes the resolved required roles
|
|
1277
|
-
* into the host's `usePermission` probe, and renders one of
|
|
1278
|
-
*
|
|
1279
|
-
* 1. while rules or probe are still loading → centered spinner card
|
|
1280
|
-
* 2. probe says no access → centered "ไม่มีสิทธิ์" card
|
|
1281
|
-
* 3. otherwise → children
|
|
1327
|
+
* into the host's `usePermission` probe, and renders one of:
|
|
1282
1328
|
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1329
|
+
* 1. rules / probe still loading → `null` (let Suspense / loading.tsx show)
|
|
1330
|
+
* 2. no rule covers this path → deny (fail closed)
|
|
1331
|
+
* 3. rule is explicit public (`[]`) → children
|
|
1332
|
+
* 4. probe says no access → deny card
|
|
1333
|
+
* 5. otherwise → children
|
|
1285
1334
|
*/
|
|
1286
1335
|
function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
|
|
1287
1336
|
const pathname = usePathname();
|
|
@@ -1292,31 +1341,27 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
|
|
|
1292
1341
|
// live rules win over fallback when both define the same key
|
|
1293
1342
|
return { ...fallbackRulesMap, ...rulesMap };
|
|
1294
1343
|
}, [rulesMap, fallbackRulesMap]);
|
|
1295
|
-
const
|
|
1296
|
-
// Hook must run unconditionally (rules of hooks)
|
|
1297
|
-
// result on the fast path below.
|
|
1344
|
+
const lookup = useMemo(() => resolveUiRoutePermission(pathname, mergedMap), [pathname, mergedMap]);
|
|
1345
|
+
// Hook must run unconditionally (rules of hooks).
|
|
1298
1346
|
const { hasPermission, isLoading } = usePermission({
|
|
1299
|
-
requiredRoles,
|
|
1347
|
+
requiredRoles: lookup.roles,
|
|
1300
1348
|
});
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1349
|
+
if (rulesLoading) {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
// Unknown route → deny. Only explicit `required_child_groups: []` is public.
|
|
1353
|
+
if (!lookup.matched) {
|
|
1354
|
+
return jsx(DeniedCard, {});
|
|
1355
|
+
}
|
|
1356
|
+
if (lookup.roles.length === 0) {
|
|
1305
1357
|
return jsx(Fragment$1, { children: children });
|
|
1306
1358
|
}
|
|
1307
|
-
|
|
1308
|
-
// Next.js loading.tsx / Suspense fallback show. The old behaviour wrapped
|
|
1309
|
-
// a big "กำลังตรวจสอบสิทธิ์..." card over the top, which both duplicated
|
|
1310
|
-
// the app's own loader and — worse — caused a "ไม่มีสิทธิ์" flash on
|
|
1311
|
-
// fast loads because the probe transiently returned `hasPermission:false,
|
|
1312
|
-
// isLoading:false` before its dependencies (org context, role-segments)
|
|
1313
|
-
// arrived.
|
|
1314
|
-
const isLoadingCombined = rulesLoading || isLoading;
|
|
1359
|
+
const isLoadingCombined = isLoading;
|
|
1315
1360
|
if (isLoadingCombined) {
|
|
1316
1361
|
return null;
|
|
1317
1362
|
}
|
|
1318
1363
|
if (!hasPermission) {
|
|
1319
|
-
return
|
|
1364
|
+
return jsx(DeniedCard, {});
|
|
1320
1365
|
}
|
|
1321
1366
|
return jsx(Fragment$1, { children: children });
|
|
1322
1367
|
}
|
|
@@ -1813,5 +1858,5 @@ const getAxiosErrorMessage = (error) => {
|
|
|
1813
1858
|
return String(error);
|
|
1814
1859
|
};
|
|
1815
1860
|
|
|
1816
|
-
export { ApiError, AppBreadcrumb, AppLayoutShell, AppSidebar, FeatureFlagsProvider, HeaderBackground, NavMain, NavTools, NavUser, OrganizationSwitcher, PermissionGateShell, PermissionProvider, PermissionRulesProvider, ReactQueryProvider, SessionBackedProfileCacheSeeder, SessionRefreshTrigger, SidebarContent, SidebarFooter, SidebarHeader, SidebarProvider, SidebarTrigger, TapMenu, ThemeProvider, instance as axiosInstance, buildUrlWithCode, canAccessNavPermissionRoute, clearAccessTokenCache, clearOrganizationId, decodeJWT, filterNavigationModulesByPermission, filterNavigationToolsByPermission, getAccessToken, getActiveModule, getActiveSubModule, getAxiosErrorMessage, getOrganizationId, getQueryClientConfig, getRequiredChildGroups, isLikelyTransientNetworkError, isPathActive, normalizePathnameForPermissionRules, runGlobalRequestPrecheck, runWithTransientRetry, setOrganizationId, toApiError, unwrapApiResponse, updateOrganizationId, useClearOrgOnUserChange, useFeatureFlag, useFeatureFlags, useInvalidateQueriesOnOrgChange, useKeycloakRoles, useOrganizationIdState, usePermissionRules, useSessionRefresh, useSidebar, useSidebarToggle };
|
|
1861
|
+
export { AccessDenied, ApiError, AppBreadcrumb, AppLayoutShell, AppSidebar, FeatureFlagsProvider, HeaderBackground, NavMain, NavTools, NavUser, OrganizationSwitcher, PermissionGateShell, PermissionProvider, PermissionRulesProvider, ReactQueryProvider, SessionBackedProfileCacheSeeder, SessionRefreshTrigger, SidebarContent, SidebarFooter, SidebarHeader, SidebarProvider, SidebarTrigger, TapMenu, ThemeProvider, instance as axiosInstance, buildUrlWithCode, canAccessNavPermissionRoute, clearAccessTokenCache, clearOrganizationId, decodeJWT, filterNavigationModulesByPermission, filterNavigationToolsByPermission, getAccessToken, getActiveModule, getActiveSubModule, getAxiosErrorMessage, getOrganizationId, getQueryClientConfig, getRequiredChildGroups, isLikelyTransientNetworkError, isPathActive, normalizePathnameForPermissionRules, resolveUiRoutePermission, runGlobalRequestPrecheck, runWithTransientRetry, setOrganizationId, toApiError, unwrapApiResponse, updateOrganizationId, useClearOrgOnUserChange, useFeatureFlag, useFeatureFlags, useInvalidateQueriesOnOrgChange, useKeycloakRoles, useOrganizationIdState, usePermissionRules, useSessionRefresh, useSidebar, useSidebarToggle };
|
|
1817
1862
|
//# sourceMappingURL=index.esm.js.map
|