@eintrek/erp-shell 0.1.11 → 0.1.13

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.
@@ -41,4 +41,4 @@ export interface PermissionProviderProps {
41
41
  * The host wraps `<PermissionProvider>` around its page chrome (typically
42
42
  * via `AppLayoutShell`'s `permissionProvider` slot).
43
43
  */
44
- export declare function PermissionProvider({ children, usePermission, fallbackRulesMap, }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element;
44
+ export declare function PermissionProvider({ children, usePermission, fallbackRulesMap, }: PermissionProviderProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,7 +1,13 @@
1
1
  /**
2
- * Invalidate ALL React Query queries when `organizationId` changes
3
- * (skips the initial mount). Use inside the ERP provider so org-scoped
4
- * data is refetched cleanly when the user switches org.
2
+ * Invalidate ALL React Query queries when `organizationId` changes between
3
+ * two real orgs (e.g. the user switches org in the picker). Skips the
4
+ * initial mount and the first-resolve transition (undefined/null/"" x)
5
+ * — that's the URL `[code]` segment finally syncing into the ERP context,
6
+ * not a real switch, and invalidating there triggers a cascade of refetches
7
+ * on top of the queries that already just fired.
8
+ *
9
+ * Use inside the ERP provider so org-scoped data is wiped cleanly when the
10
+ * user really does switch between two orgs.
5
11
  */
6
12
  export declare function useInvalidateQueriesOnOrgChange(organizationId: number | string | null | undefined): void;
7
13
  /**
package/dist/index.esm.js CHANGED
@@ -332,9 +332,15 @@ function useOrganizationIdState(initialOrganizationId, actions) {
332
332
  }
333
333
 
334
334
  /**
335
- * Invalidate ALL React Query queries when `organizationId` changes
336
- * (skips the initial mount). Use inside the ERP provider so org-scoped
337
- * data is refetched cleanly when the user switches org.
335
+ * Invalidate ALL React Query queries when `organizationId` changes between
336
+ * two real orgs (e.g. the user switches org in the picker). Skips the
337
+ * initial mount and the first-resolve transition (undefined/null/"" x)
338
+ * — that's the URL `[code]` segment finally syncing into the ERP context,
339
+ * not a real switch, and invalidating there triggers a cascade of refetches
340
+ * on top of the queries that already just fired.
341
+ *
342
+ * Use inside the ERP provider so org-scoped data is wiped cleanly when the
343
+ * user really does switch between two orgs.
338
344
  */
339
345
  function useInvalidateQueriesOnOrgChange(organizationId) {
340
346
  const queryClient = useQueryClient();
@@ -348,7 +354,15 @@ function useInvalidateQueriesOnOrgChange(organizationId) {
348
354
  }
349
355
  if (previousRef.current === organizationId)
350
356
  return;
357
+ const wasEmpty = previousRef.current === undefined ||
358
+ previousRef.current === null ||
359
+ previousRef.current === "";
351
360
  previousRef.current = organizationId;
361
+ // First-resolve: nothing to invalidate yet (queries either haven't run
362
+ // or are running with the right key already). Real org switches (a → b)
363
+ // still hit the invalidate path below.
364
+ if (wasEmpty)
365
+ return;
352
366
  queryClient.invalidateQueries();
353
367
  }, [organizationId, queryClient]);
354
368
  }
@@ -1189,12 +1203,27 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1189
1203
  return { ...fallbackRulesMap, ...rulesMap };
1190
1204
  }, [rulesMap, fallbackRulesMap]);
1191
1205
  const requiredRoles = useMemo(() => getRequiredChildGroups(pathname, mergedMap), [pathname, mergedMap]);
1206
+ // Hook must run unconditionally (rules of hooks); we just ignore the
1207
+ // result on the fast path below.
1192
1208
  const { hasPermission, isLoading, userRoles } = usePermission({
1193
1209
  requiredRoles,
1194
1210
  });
1211
+ // Fast path — no rule covers this pathname, anyone can see it. Skip the
1212
+ // probe UI entirely; flashing "checking permission" or "no access" on an
1213
+ // unrestricted page is pure noise.
1214
+ if (requiredRoles.length === 0) {
1215
+ return jsx(Fragment$1, { children: children });
1216
+ }
1217
+ // While the answer hasn't settled, render nothing and let the surrounding
1218
+ // Next.js loading.tsx / Suspense fallback show. The old behaviour wrapped
1219
+ // a big "กำลังตรวจสอบสิทธิ์..." card over the top, which both duplicated
1220
+ // the app's own loader and — worse — caused a "ไม่มีสิทธิ์" flash on
1221
+ // fast loads because the probe transiently returned `hasPermission:false,
1222
+ // isLoading:false` before its dependencies (org context, role-segments)
1223
+ // arrived.
1195
1224
  const isLoadingCombined = rulesLoading || isLoading;
1196
1225
  if (isLoadingCombined) {
1197
- return (jsx(Card, { className: "p-6", children: jsxs("div", { className: "flex items-center justify-center gap-3", children: [jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }), 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..." })] }) }));
1226
+ return null;
1198
1227
  }
1199
1228
  if (!hasPermission) {
1200
1229
  return (jsx(Card, { className: "p-6", children: jsxs("div", { className: "flex flex-col items-center justify-center gap-3 text-center", children: [jsx(AlertCircle, { className: "h-8 w-8 text-destructive" }), jsxs("div", { className: "space-y-1", children: [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" }), 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 && (jsxs("div", { className: "mt-3 space-y-1", children: [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:" }), jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: requiredRoles.map((role, idx) => (jsx("span", { className: "rounded bg-muted px-2 py-0.5 text-xs font-mono", children: role }, idx))) })] })), userRoles.length > 0 && (jsxs("div", { className: "mt-3 space-y-1", children: [jsx("p", { className: "text-xs text-muted-foreground", children: "\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13:" }), jsx("div", { className: "flex flex-wrap gap-1 justify-center", children: userRoles.map((role, idx) => (jsx("span", { className: "rounded bg-primary/10 px-2 py-0.5 text-xs font-mono text-primary", children: role }, idx))) })] }))] })] }) }));