@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.
package/dist/index.js CHANGED
@@ -351,9 +351,15 @@ function useOrganizationIdState(initialOrganizationId, actions) {
351
351
  }
352
352
 
353
353
  /**
354
- * Invalidate ALL React Query queries when `organizationId` changes
355
- * (skips the initial mount). Use inside the ERP provider so org-scoped
356
- * data is refetched cleanly when the user switches org.
354
+ * Invalidate ALL React Query queries when `organizationId` changes between
355
+ * two real orgs (e.g. the user switches org in the picker). Skips the
356
+ * initial mount and the first-resolve transition (undefined/null/"" x)
357
+ * — that's the URL `[code]` segment finally syncing into the ERP context,
358
+ * not a real switch, and invalidating there triggers a cascade of refetches
359
+ * on top of the queries that already just fired.
360
+ *
361
+ * Use inside the ERP provider so org-scoped data is wiped cleanly when the
362
+ * user really does switch between two orgs.
357
363
  */
358
364
  function useInvalidateQueriesOnOrgChange(organizationId) {
359
365
  const queryClient = reactQuery.useQueryClient();
@@ -367,7 +373,15 @@ function useInvalidateQueriesOnOrgChange(organizationId) {
367
373
  }
368
374
  if (previousRef.current === organizationId)
369
375
  return;
376
+ const wasEmpty = previousRef.current === undefined ||
377
+ previousRef.current === null ||
378
+ previousRef.current === "";
370
379
  previousRef.current = organizationId;
380
+ // First-resolve: nothing to invalidate yet (queries either haven't run
381
+ // or are running with the right key already). Real org switches (a → b)
382
+ // still hit the invalidate path below.
383
+ if (wasEmpty)
384
+ return;
371
385
  queryClient.invalidateQueries();
372
386
  }, [organizationId, queryClient]);
373
387
  }
@@ -1208,12 +1222,27 @@ function PermissionProvider({ children, usePermission, fallbackRulesMap, }) {
1208
1222
  return { ...fallbackRulesMap, ...rulesMap };
1209
1223
  }, [rulesMap, fallbackRulesMap]);
1210
1224
  const requiredRoles = React.useMemo(() => getRequiredChildGroups(pathname, mergedMap), [pathname, mergedMap]);
1225
+ // Hook must run unconditionally (rules of hooks); we just ignore the
1226
+ // result on the fast path below.
1211
1227
  const { hasPermission, isLoading, userRoles } = usePermission({
1212
1228
  requiredRoles,
1213
1229
  });
1230
+ // Fast path — no rule covers this pathname, anyone can see it. Skip the
1231
+ // probe UI entirely; flashing "checking permission" or "no access" on an
1232
+ // unrestricted page is pure noise.
1233
+ if (requiredRoles.length === 0) {
1234
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
1235
+ }
1236
+ // While the answer hasn't settled, render nothing and let the surrounding
1237
+ // Next.js loading.tsx / Suspense fallback show. The old behaviour wrapped
1238
+ // a big "กำลังตรวจสอบสิทธิ์..." card over the top, which both duplicated
1239
+ // the app's own loader and — worse — caused a "ไม่มีสิทธิ์" flash on
1240
+ // fast loads because the probe transiently returned `hasPermission:false,
1241
+ // isLoading:false` before its dependencies (org context, role-segments)
1242
+ // arrived.
1214
1243
  const isLoadingCombined = rulesLoading || isLoading;
1215
1244
  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..." })] }) }));
1245
+ return null;
1217
1246
  }
1218
1247
  if (!hasPermission) {
1219
1248
  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))) })] }))] })] }) }));