@eintrek/erp-shell 0.1.10 → 0.1.11

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
@@ -966,6 +966,261 @@ function AppSidebar({ modules, tools, activeModule, orgCode, approvalCounts, clo
966
966
  return (jsxRuntime.jsxs("div", { className: erpTheme.cn("bg-sidebar shrink-0 flex flex-col overflow-hidden shadow-2xl h-screen w-[var(--sidebar-width-mobile)]", "!w-76 xl:rounded-b-4xl xl:rounded-tl-4xl xl:m-5 xl:h-[calc(100vh-2.5rem)]", className), children: [jsxRuntime.jsx(SidebarHeader, { children: header }), jsxRuntime.jsxs(SidebarContent, { className: "pl-2 pr-2 flex-1 overflow-y-auto", children: [jsxRuntime.jsx(NavMain, { items: modulesWithActiveState, orgCode: orgCode, approvalCounts: approvalCounts, closeOnNavigation: closeOnNavigation }), jsxRuntime.jsx(NavTools, { tools: tools, closeOnNavigation: closeOnNavigation })] }), jsxRuntime.jsx(SidebarFooter, { className: "pl-2 pr-2", children: footer })] }));
967
967
  }
968
968
 
969
+ /**
970
+ * Top-level app chrome: sticky header, sidebar slot, content gutter and the
971
+ * session/org-context plumbing that both Accounting and HR need. Apps wrap
972
+ * it inside their `SidebarProvider` (server-rendered with the cookie-backed
973
+ * `defaultOpen`) and their `ERPProvider`, then pass app-specific bits as
974
+ * props.
975
+ *
976
+ * The padding contract is deliberately owned here so future style tweaks
977
+ * (e.g. the recent left/right/bottom gutter pass) propagate to both apps via
978
+ * an erp-shell bump — domain components must NOT add their own root padding
979
+ * or content gets indented twice.
980
+ */
981
+ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permissionProvider, organizationId, setOrganizationId, organizations, signInUrl, selectOrganizationUrl, }) {
982
+ const { data: session, status } = react.useSession();
983
+ const { open, setOpen } = useSidebar();
984
+ const router = navigation.useRouter();
985
+ const params = navigation.useParams();
986
+ const code = params?.code;
987
+ const extendedSession = session;
988
+ // Find the org from the URL `[code]` so we can detect a context mismatch
989
+ // (the user bookmarked a different org than the cookie remembers).
990
+ const orgFromCode = React__namespace.useMemo(() => {
991
+ if (!code || !organizations.length)
992
+ return null;
993
+ return (organizations.find(org => org.code === code || String(org.id) === code) ?? null);
994
+ }, [code, organizations]);
995
+ // Sync state: when the URL code points at a different org than the cookie,
996
+ // trust the URL and update the cookie. Otherwise do nothing — the user
997
+ // wins ties via the in-app org switcher.
998
+ React.useEffect(() => {
999
+ if (!code)
1000
+ return;
1001
+ if (!organizations.length)
1002
+ return;
1003
+ if (!orgFromCode)
1004
+ return;
1005
+ if (String(organizationId) !== String(orgFromCode.id)) {
1006
+ void setOrganizationId(orgFromCode.id);
1007
+ }
1008
+ }, [
1009
+ orgFromCode,
1010
+ organizationId,
1011
+ setOrganizationId,
1012
+ code,
1013
+ organizations.length,
1014
+ ]);
1015
+ // ≤1024px: sidebar renders as a modal overlay (backdrop dims content). >1024px:
1016
+ // sidebar is docked and the content gets a left margin to clear it.
1017
+ const [isSidebarModalLayout, setIsSidebarModalLayout] = React__namespace.useState(false);
1018
+ React.useEffect(() => {
1019
+ const mq = window.matchMedia("(max-width: 1024px)");
1020
+ const sync = () => setIsSidebarModalLayout(mq.matches);
1021
+ sync();
1022
+ mq.addEventListener("change", sync);
1023
+ return () => mq.removeEventListener("change", sync);
1024
+ }, []);
1025
+ // Wait for session to load (initial load only). When refetching/updating we
1026
+ // already have a session — don't flash the full-screen loader.
1027
+ if (status === "loading" && !session) {
1028
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1029
+ }
1030
+ // Session loaded but no accessToken (e.g. token refresh failed) -> bounce
1031
+ // through signin so we don't end up at a "No Keycloak Context" error.
1032
+ if (status === "authenticated" && !extendedSession?.accessToken) {
1033
+ if (typeof window !== "undefined") {
1034
+ const url = new URL(signInUrl, window.location.origin);
1035
+ url.searchParams.set("error", "NoAccessToken");
1036
+ url.searchParams.set("callbackUrl", window.location.pathname);
1037
+ window.location.href = url.toString();
1038
+ }
1039
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1040
+ }
1041
+ if (!extendedSession?.accessToken) {
1042
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1043
+ }
1044
+ if (!code) {
1045
+ router.push(selectOrganizationUrl);
1046
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent });
1047
+ }
1048
+ // When the sidebar is docked-open it consumes 21.5rem on the left; otherwise
1049
+ // the content takes the full width and we add a left gutter inside.
1050
+ const contentMargin = open && !isSidebarModalLayout ? "ml-[21.5rem]" : "";
1051
+ const needsLeftGutter = !(open && !isSidebarModalLayout);
1052
+ const wrap = permissionProvider ?? ((c) => c);
1053
+ 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-6", 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-[100px] sm:h-[144px] 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"}`) })] }) })] }));
1054
+ }
1055
+ /** Local tiny classnames helper — avoids pulling clsx/tailwind-merge into
1056
+ * the shell just for the layout file. Falsy values are skipped. */
1057
+ function cx(...parts) {
1058
+ return parts.filter(Boolean).join(" ");
1059
+ }
1060
+
1061
+ const shellClassName = "flex min-w-0 flex-1 flex-col gap-6 overflow-x-hidden";
1062
+ /**
1063
+ * Shared layout for “กำลังตรวจสอบสิทธิ์” (skeleton) and “ไม่มีสิทธิ์” (message).
1064
+ * Renders `children` only when policy is loaded and access is granted.
1065
+ */
1066
+ function PermissionGateShell({ policyLoading, hasPolicyAccess, deniedMessage, children, skeletonCount = 6, skeletonGridClassName = "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", skeletonVariant = "kpi", }) {
1067
+ if (policyLoading) {
1068
+ return (jsxRuntime.jsx("div", { className: shellClassName, children: jsxRuntime.jsx("div", { className: skeletonGridClassName, children: Array.from({ length: skeletonCount }).map((_, i) => (jsxRuntime.jsx(erpTheme.Card, { children: skeletonVariant === "kpi" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs(erpTheme.CardHeader, { className: "flex flex-row items-center justify-between pb-2", children: [jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-4 w-24" }), jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-4 w-4 rounded" })] }), jsxRuntime.jsxs(erpTheme.CardContent, { children: [jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-8 w-32 mb-2" }), jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-3 w-20" })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(erpTheme.CardHeader, { className: "pb-2", children: jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-4 w-24" }) }), jsxRuntime.jsx(erpTheme.CardContent, { children: jsxRuntime.jsx(erpTheme.Skeleton, { className: "h-8 w-16" }) })] })) }, i))) }) }));
1069
+ }
1070
+ if (!hasPolicyAccess) {
1071
+ return (jsxRuntime.jsx("div", { className: shellClassName, children: jsxRuntime.jsx(erpTheme.Card, { children: jsxRuntime.jsx(erpTheme.CardContent, { className: "text-sm text-muted-foreground", children: deniedMessage }) }) }));
1072
+ }
1073
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
1074
+ }
1075
+
1076
+ const PermissionRulesContext = React.createContext(undefined);
1077
+ /**
1078
+ * Generic provider — the consumer app passes in `data` (live fetch) and
1079
+ * `fallbackPolicies` (static JSON). All apps share the same mapping logic
1080
+ * (build a flat map of route_key/policy_key → required child groups).
1081
+ */
1082
+ function PermissionRulesProvider({ children, data, isLoading = false, isError = false, fallbackPolicies = [], }) {
1083
+ const rulesMap = React.useMemo(() => {
1084
+ const m = {};
1085
+ if (!data?.routes)
1086
+ return m;
1087
+ for (const row of data.routes) {
1088
+ m[row.route_key] = row.required_child_groups ?? [];
1089
+ }
1090
+ return m;
1091
+ }, [data]);
1092
+ const policiesMap = React.useMemo(() => {
1093
+ const m = {};
1094
+ for (const row of fallbackPolicies) {
1095
+ m[row.policy_key] = row.required_child_groups ?? [];
1096
+ }
1097
+ if (data?.policies?.length) {
1098
+ for (const row of data.policies) {
1099
+ m[row.policy_key] = row.required_child_groups ?? [];
1100
+ }
1101
+ }
1102
+ return m;
1103
+ }, [data, fallbackPolicies]);
1104
+ const value = React.useMemo(() => ({
1105
+ rulesMap,
1106
+ policiesMap,
1107
+ isLoading,
1108
+ isError,
1109
+ updatedAt: data?.updated_at ?? null,
1110
+ }), [rulesMap, policiesMap, isLoading, isError, data?.updated_at]);
1111
+ return (jsxRuntime.jsx(PermissionRulesContext.Provider, { value: value, children: children }));
1112
+ }
1113
+ /**
1114
+ * Read the permission rules. Returns empty maps when used outside the
1115
+ * provider (lets pages render without a hard-fail in tests).
1116
+ */
1117
+ function usePermissionRules() {
1118
+ const ctx = React.useContext(PermissionRulesContext);
1119
+ if (!ctx) {
1120
+ return {
1121
+ rulesMap: {},
1122
+ policiesMap: {},
1123
+ isLoading: false,
1124
+ isError: false,
1125
+ updatedAt: null,
1126
+ };
1127
+ }
1128
+ return ctx;
1129
+ }
1130
+
1131
+ /**
1132
+ * Strip the org `[code]` prefix from a pathname so we can look it up in the
1133
+ * permission rules map. `/O12345/setting/foo` → `/setting/foo`. Single-segment
1134
+ * paths (e.g. `/select-organization`) keep their leading slash.
1135
+ *
1136
+ * Also normalizes the user-role subtab aliases — both `purchase-doc` and
1137
+ * `sales-doc` are gated by the same `members` rule because the UI shows
1138
+ * the same management screen with different filters.
1139
+ */
1140
+ function normalizePathnameForPermissionRules(pathname) {
1141
+ const parts = pathname.split("/").filter(Boolean);
1142
+ const path = parts.length <= 1 ? "/" : "/" + parts.slice(1).join("/");
1143
+ return path.replace(/\/user-role\/(purchase-doc|sales-doc)$/, "/user-role/members");
1144
+ }
1145
+ /**
1146
+ * Returns the required child-group segments for a page pathname (the user's
1147
+ * JWT must contain at least one matching group). An exact match on the
1148
+ * normalized path wins; otherwise the function falls back to a segment-by-
1149
+ * segment pattern match where `[param]` placeholders accept any non-empty
1150
+ * value. Returns an empty array when no rule covers the path.
1151
+ *
1152
+ * `rulesMap` is the merged map — apps that want a fallback should layer it
1153
+ * here (`{ ...fallbackMap, ...liveMap }`) before calling.
1154
+ */
1155
+ function getRequiredChildGroups(pathname, rulesMap) {
1156
+ const normalized = normalizePathnameForPermissionRules(pathname);
1157
+ if (rulesMap[normalized]) {
1158
+ return rulesMap[normalized];
1159
+ }
1160
+ const pathSegments = normalized.split("/").filter(Boolean);
1161
+ for (const [pattern, roles] of Object.entries(rulesMap)) {
1162
+ const patternSegments = pattern.split("/").filter(Boolean);
1163
+ if (patternSegments.length !== pathSegments.length)
1164
+ continue;
1165
+ let matches = true;
1166
+ for (let i = 0; i < patternSegments.length; i++) {
1167
+ const patternSegment = patternSegments[i];
1168
+ const pathSegment = pathSegments[i];
1169
+ if (patternSegment.startsWith("[") && patternSegment.endsWith("]")) {
1170
+ if (!pathSegment) {
1171
+ matches = false;
1172
+ break;
1173
+ }
1174
+ }
1175
+ else if (patternSegment !== pathSegment) {
1176
+ matches = false;
1177
+ break;
1178
+ }
1179
+ }
1180
+ if (matches) {
1181
+ return roles;
1182
+ }
1183
+ }
1184
+ return [];
1185
+ }
1186
+
1187
+ /**
1188
+ * Permission gate for a page subtree. Looks up the current pathname in the
1189
+ * permission rules map (live rules from `PermissionRulesProvider`, layered
1190
+ * over the optional `fallbackRulesMap`), passes the resolved required roles
1191
+ * into the host's `usePermission` probe, and renders one of three states:
1192
+ *
1193
+ * 1. while rules or probe are still loading → centered spinner card
1194
+ * 2. probe says no access → centered "ไม่มีสิทธิ์" card with required vs.
1195
+ * user roles for debugging
1196
+ * 3. otherwise → children
1197
+ *
1198
+ * The host wraps `<PermissionProvider>` around its page chrome (typically
1199
+ * via `AppLayoutShell`'s `permissionProvider` slot).
1200
+ */
1201
+ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1202
+ const pathname = navigation.usePathname();
1203
+ const { rulesMap, isLoading: rulesLoading } = usePermissionRules();
1204
+ const mergedMap = React.useMemo(() => {
1205
+ if (!fallbackRulesMap)
1206
+ return rulesMap;
1207
+ // live rules win over fallback when both define the same key
1208
+ return { ...fallbackRulesMap, ...rulesMap };
1209
+ }, [rulesMap, fallbackRulesMap]);
1210
+ const requiredRoles = React.useMemo(() => getRequiredChildGroups(pathname, mergedMap), [pathname, mergedMap]);
1211
+ const { hasPermission, isLoading, userRoles } = usePermission({
1212
+ requiredRoles,
1213
+ });
1214
+ const isLoadingCombined = rulesLoading || isLoading;
1215
+ if (isLoadingCombined) {
1216
+ return (jsxRuntime.jsx(erpTheme.Card, { className: "p-6", children: jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-3", children: [jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }), jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E15\u0E23\u0E27\u0E08\u0E2A\u0E2D\u0E1A\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07..." })] }) }));
1217
+ }
1218
+ if (!hasPermission) {
1219
+ 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))) })] }))] })] }) }));
1220
+ }
1221
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
1222
+ }
1223
+
969
1224
  function ThemeProvider({ children, ...props }) {
970
1225
  return jsxRuntime.jsx(nextThemes.ThemeProvider, { ...props, children: children });
971
1226
  }
@@ -1067,61 +1322,6 @@ function ReactQueryProvider({ children }) {
1067
1322
  return (jsxRuntime.jsxs(reactQuery.QueryClientProvider, { client: queryClient, children: [children, jsxRuntime.jsx(reactQueryDevtools.ReactQueryDevtools, { client: queryClient, initialIsOpen: false })] }));
1068
1323
  }
1069
1324
 
1070
- const PermissionRulesContext = React.createContext(undefined);
1071
- /**
1072
- * Generic provider — the consumer app passes in `data` (live fetch) and
1073
- * `fallbackPolicies` (static JSON). All apps share the same mapping logic
1074
- * (build a flat map of route_key/policy_key → required child groups).
1075
- */
1076
- function PermissionRulesProvider({ children, data, isLoading = false, isError = false, fallbackPolicies = [], }) {
1077
- const rulesMap = React.useMemo(() => {
1078
- const m = {};
1079
- if (!data?.routes)
1080
- return m;
1081
- for (const row of data.routes) {
1082
- m[row.route_key] = row.required_child_groups ?? [];
1083
- }
1084
- return m;
1085
- }, [data]);
1086
- const policiesMap = React.useMemo(() => {
1087
- const m = {};
1088
- for (const row of fallbackPolicies) {
1089
- m[row.policy_key] = row.required_child_groups ?? [];
1090
- }
1091
- if (data?.policies?.length) {
1092
- for (const row of data.policies) {
1093
- m[row.policy_key] = row.required_child_groups ?? [];
1094
- }
1095
- }
1096
- return m;
1097
- }, [data, fallbackPolicies]);
1098
- const value = React.useMemo(() => ({
1099
- rulesMap,
1100
- policiesMap,
1101
- isLoading,
1102
- isError,
1103
- updatedAt: data?.updated_at ?? null,
1104
- }), [rulesMap, policiesMap, isLoading, isError, data?.updated_at]);
1105
- return (jsxRuntime.jsx(PermissionRulesContext.Provider, { value: value, children: children }));
1106
- }
1107
- /**
1108
- * Read the permission rules. Returns empty maps when used outside the
1109
- * provider (lets pages render without a hard-fail in tests).
1110
- */
1111
- function usePermissionRules() {
1112
- const ctx = React.useContext(PermissionRulesContext);
1113
- if (!ctx) {
1114
- return {
1115
- rulesMap: {},
1116
- policiesMap: {},
1117
- isLoading: false,
1118
- isError: false,
1119
- updatedAt: null,
1120
- };
1121
- }
1122
- return ctx;
1123
- }
1124
-
1125
1325
  /**
1126
1326
  * Renders nothing. Runs during render (after SessionProvider, inside
1127
1327
  * QueryClientProvider) to seed the cached profile slot from the JWT-backed
@@ -1436,6 +1636,7 @@ Object.defineProperty(exports, "isAxiosError", {
1436
1636
  });
1437
1637
  exports.ApiError = ApiError;
1438
1638
  exports.AppBreadcrumb = AppBreadcrumb;
1639
+ exports.AppLayoutShell = AppLayoutShell;
1439
1640
  exports.AppSidebar = AppSidebar;
1440
1641
  exports.FeatureFlagsProvider = FeatureFlagsProvider;
1441
1642
  exports.HeaderBackground = HeaderBackground;
@@ -1443,6 +1644,8 @@ exports.NavMain = NavMain;
1443
1644
  exports.NavTools = NavTools;
1444
1645
  exports.NavUser = NavUser;
1445
1646
  exports.OrganizationSwitcher = OrganizationSwitcher;
1647
+ exports.PermissionGateShell = PermissionGateShell;
1648
+ exports.PermissionProvider = PermissionProvider;
1446
1649
  exports.PermissionRulesProvider = PermissionRulesProvider;
1447
1650
  exports.ReactQueryProvider = ReactQueryProvider;
1448
1651
  exports.SessionBackedProfileCacheSeeder = SessionBackedProfileCacheSeeder;
@@ -1468,8 +1671,10 @@ exports.getActiveSubModule = getActiveSubModule;
1468
1671
  exports.getAxiosErrorMessage = getAxiosErrorMessage;
1469
1672
  exports.getOrganizationId = getOrganizationId;
1470
1673
  exports.getQueryClientConfig = getQueryClientConfig;
1674
+ exports.getRequiredChildGroups = getRequiredChildGroups;
1471
1675
  exports.isLikelyTransientNetworkError = isLikelyTransientNetworkError;
1472
1676
  exports.isPathActive = isPathActive;
1677
+ exports.normalizePathnameForPermissionRules = normalizePathnameForPermissionRules;
1473
1678
  exports.runGlobalRequestPrecheck = runGlobalRequestPrecheck;
1474
1679
  exports.runWithTransientRetry = runWithTransientRetry;
1475
1680
  exports.setOrganizationId = setOrganizationId;