@geomak/ui 6.21.0 → 6.22.0
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.cjs +30 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -5
- package/dist/index.d.ts +24 -5
- package/dist/index.js +30 -18
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -3
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2869,11 +2869,15 @@ interface SecureLayoutProps {
|
|
|
2869
2869
|
requireAllPermissions?: boolean;
|
|
2870
2870
|
/** Final say. Runs after the built-in checks; may be async. Return false to deny. */
|
|
2871
2871
|
canAccess?: () => boolean | Promise<boolean>;
|
|
2872
|
-
/** Shown while the (possibly async) check resolves. */
|
|
2872
|
+
/** Shown while the (possibly async) check resolves. Pass `null` to render nothing. */
|
|
2873
2873
|
loadingFallback?: React__default.ReactNode;
|
|
2874
|
-
/** Shown when access is denied.
|
|
2874
|
+
/** Shown when access is denied. Defaults to a simple "Access denied" panel;
|
|
2875
|
+
* pass `null` to render nothing (e.g. when `onDeny` redirects away). */
|
|
2875
2876
|
fallback?: React__default.ReactNode;
|
|
2876
|
-
/** Fired once when access is
|
|
2877
|
+
/** Fired once when access is granted — e.g. to hydrate app state or redirect
|
|
2878
|
+
* to a landing route after a successful token check. */
|
|
2879
|
+
onGranted?: () => void;
|
|
2880
|
+
/** Fired once when access is denied — e.g. to redirect to login / logout. */
|
|
2877
2881
|
onDeny?: () => void;
|
|
2878
2882
|
className?: string;
|
|
2879
2883
|
}
|
|
@@ -2887,13 +2891,28 @@ interface SecureLayoutProps {
|
|
|
2887
2891
|
* This is a UI guard, not a security boundary — it controls what renders.
|
|
2888
2892
|
* Real authorization must be enforced by your API/server.
|
|
2889
2893
|
*
|
|
2890
|
-
* @example
|
|
2894
|
+
* @example Full RBAC + PBAC gate
|
|
2891
2895
|
* <SecureLayout token={jwt} requiredRoles={['admin']} permissions={user.perms}
|
|
2892
2896
|
* requiredPermissions={['reports:read']} onDeny={() => navigate('/login')}>
|
|
2893
2897
|
* <AdminDashboard />
|
|
2894
2898
|
* </SecureLayout>
|
|
2899
|
+
*
|
|
2900
|
+
* @example Simple JWT-only gate with a token-login bootstrap (no roles/perms)
|
|
2901
|
+
* <SecureLayout
|
|
2902
|
+
* canAccess={async () => {
|
|
2903
|
+
* const jwt = localStorage.getItem('jwt')
|
|
2904
|
+
* if (!jwt) return false
|
|
2905
|
+
* await tokenLogin(jwt) // hydrate app state from the server
|
|
2906
|
+
* return true
|
|
2907
|
+
* }}
|
|
2908
|
+
* onGranted={() => navigate('/dashboard')}
|
|
2909
|
+
* onDeny={() => navigate('/logout')}
|
|
2910
|
+
* fallback={null} // redirecting — don't flash a panel
|
|
2911
|
+
* >
|
|
2912
|
+
* <AppRoutes />
|
|
2913
|
+
* </SecureLayout>
|
|
2895
2914
|
*/
|
|
2896
|
-
declare function SecureLayout({ children, isAuthenticated, token, roles, requiredRoles, requireAllRoles, permissions, requiredPermissions, requireAllPermissions, canAccess, loadingFallback, fallback, onDeny, className, }: SecureLayoutProps): react_jsx_runtime.JSX.Element;
|
|
2915
|
+
declare function SecureLayout({ children, isAuthenticated, token, roles, requiredRoles, requireAllRoles, permissions, requiredPermissions, requireAllPermissions, canAccess, loadingFallback, fallback, onGranted, onDeny, className, }: SecureLayoutProps): react_jsx_runtime.JSX.Element;
|
|
2897
2916
|
|
|
2898
2917
|
interface ThemeColors {
|
|
2899
2918
|
background?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -2869,11 +2869,15 @@ interface SecureLayoutProps {
|
|
|
2869
2869
|
requireAllPermissions?: boolean;
|
|
2870
2870
|
/** Final say. Runs after the built-in checks; may be async. Return false to deny. */
|
|
2871
2871
|
canAccess?: () => boolean | Promise<boolean>;
|
|
2872
|
-
/** Shown while the (possibly async) check resolves. */
|
|
2872
|
+
/** Shown while the (possibly async) check resolves. Pass `null` to render nothing. */
|
|
2873
2873
|
loadingFallback?: React__default.ReactNode;
|
|
2874
|
-
/** Shown when access is denied.
|
|
2874
|
+
/** Shown when access is denied. Defaults to a simple "Access denied" panel;
|
|
2875
|
+
* pass `null` to render nothing (e.g. when `onDeny` redirects away). */
|
|
2875
2876
|
fallback?: React__default.ReactNode;
|
|
2876
|
-
/** Fired once when access is
|
|
2877
|
+
/** Fired once when access is granted — e.g. to hydrate app state or redirect
|
|
2878
|
+
* to a landing route after a successful token check. */
|
|
2879
|
+
onGranted?: () => void;
|
|
2880
|
+
/** Fired once when access is denied — e.g. to redirect to login / logout. */
|
|
2877
2881
|
onDeny?: () => void;
|
|
2878
2882
|
className?: string;
|
|
2879
2883
|
}
|
|
@@ -2887,13 +2891,28 @@ interface SecureLayoutProps {
|
|
|
2887
2891
|
* This is a UI guard, not a security boundary — it controls what renders.
|
|
2888
2892
|
* Real authorization must be enforced by your API/server.
|
|
2889
2893
|
*
|
|
2890
|
-
* @example
|
|
2894
|
+
* @example Full RBAC + PBAC gate
|
|
2891
2895
|
* <SecureLayout token={jwt} requiredRoles={['admin']} permissions={user.perms}
|
|
2892
2896
|
* requiredPermissions={['reports:read']} onDeny={() => navigate('/login')}>
|
|
2893
2897
|
* <AdminDashboard />
|
|
2894
2898
|
* </SecureLayout>
|
|
2899
|
+
*
|
|
2900
|
+
* @example Simple JWT-only gate with a token-login bootstrap (no roles/perms)
|
|
2901
|
+
* <SecureLayout
|
|
2902
|
+
* canAccess={async () => {
|
|
2903
|
+
* const jwt = localStorage.getItem('jwt')
|
|
2904
|
+
* if (!jwt) return false
|
|
2905
|
+
* await tokenLogin(jwt) // hydrate app state from the server
|
|
2906
|
+
* return true
|
|
2907
|
+
* }}
|
|
2908
|
+
* onGranted={() => navigate('/dashboard')}
|
|
2909
|
+
* onDeny={() => navigate('/logout')}
|
|
2910
|
+
* fallback={null} // redirecting — don't flash a panel
|
|
2911
|
+
* >
|
|
2912
|
+
* <AppRoutes />
|
|
2913
|
+
* </SecureLayout>
|
|
2895
2914
|
*/
|
|
2896
|
-
declare function SecureLayout({ children, isAuthenticated, token, roles, requiredRoles, requireAllRoles, permissions, requiredPermissions, requireAllPermissions, canAccess, loadingFallback, fallback, onDeny, className, }: SecureLayoutProps): react_jsx_runtime.JSX.Element;
|
|
2915
|
+
declare function SecureLayout({ children, isAuthenticated, token, roles, requiredRoles, requireAllRoles, permissions, requiredPermissions, requireAllPermissions, canAccess, loadingFallback, fallback, onGranted, onDeny, className, }: SecureLayoutProps): react_jsx_runtime.JSX.Element;
|
|
2897
2916
|
|
|
2898
2917
|
interface ThemeColors {
|
|
2899
2918
|
background?: string;
|
package/dist/index.js
CHANGED
|
@@ -1825,7 +1825,7 @@ function Stepper({
|
|
|
1825
1825
|
}
|
|
1826
1826
|
return /* @__PURE__ */ jsx("ol", { className: ["flex items-start", className].filter(Boolean).join(" "), children: steps.map((step, i) => {
|
|
1827
1827
|
const state = stateOf(i);
|
|
1828
|
-
return /* @__PURE__ */ jsxs("li", { className: "relative flex flex-1 flex-col items-center
|
|
1828
|
+
return /* @__PURE__ */ jsxs("li", { className: "relative flex flex-1 flex-col items-center", children: [
|
|
1829
1829
|
i > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1/2 h-0.5 w-full bg-border", style: { top: s.center - 1 }, children: /* @__PURE__ */ jsx(
|
|
1830
1830
|
motion.span,
|
|
1831
1831
|
{
|
|
@@ -5850,33 +5850,43 @@ function SecureLayout({
|
|
|
5850
5850
|
canAccess,
|
|
5851
5851
|
loadingFallback,
|
|
5852
5852
|
fallback,
|
|
5853
|
+
onGranted,
|
|
5853
5854
|
onDeny,
|
|
5854
5855
|
className = ""
|
|
5855
5856
|
}) {
|
|
5856
5857
|
const reduced = useReducedMotion();
|
|
5857
|
-
const [state, setState] = useState("checking");
|
|
5858
5858
|
const rolesKey = JSON.stringify(roles);
|
|
5859
5859
|
const requiredRolesKey = JSON.stringify(requiredRoles);
|
|
5860
5860
|
const permissionsKey = JSON.stringify(permissions);
|
|
5861
5861
|
const requiredPermissionsKey = JSON.stringify(requiredPermissions);
|
|
5862
|
+
const passesSync = () => {
|
|
5863
|
+
let authed = isAuthenticated;
|
|
5864
|
+
if (authed === void 0 && token !== void 0) authed = tokenValid(token);
|
|
5865
|
+
if (authed === void 0) authed = true;
|
|
5866
|
+
if (!authed) return false;
|
|
5867
|
+
if (requiredRoles?.length && !has(roles, requiredRoles, requireAllRoles)) return false;
|
|
5868
|
+
if (requiredPermissions?.length && !has(permissions, requiredPermissions, requireAllPermissions)) return false;
|
|
5869
|
+
return true;
|
|
5870
|
+
};
|
|
5871
|
+
const [state, setState] = useState(
|
|
5872
|
+
() => !passesSync() ? "denied" : canAccess ? "checking" : "granted"
|
|
5873
|
+
);
|
|
5862
5874
|
useEffect(() => {
|
|
5863
5875
|
let cancelled = false;
|
|
5864
|
-
|
|
5865
|
-
const evaluate = async () => {
|
|
5866
|
-
let authed = isAuthenticated;
|
|
5867
|
-
if (authed === void 0 && token !== void 0) authed = tokenValid(token);
|
|
5868
|
-
if (authed === void 0) authed = true;
|
|
5869
|
-
if (!authed) return false;
|
|
5870
|
-
if (requiredRoles?.length && !has(roles, requiredRoles, requireAllRoles)) return false;
|
|
5871
|
-
if (requiredPermissions?.length && !has(permissions, requiredPermissions, requireAllPermissions)) return false;
|
|
5872
|
-
if (canAccess && !await canAccess()) return false;
|
|
5873
|
-
return true;
|
|
5874
|
-
};
|
|
5875
|
-
evaluate().then((ok) => {
|
|
5876
|
+
const finish = (ok) => {
|
|
5876
5877
|
if (cancelled) return;
|
|
5877
5878
|
setState(ok ? "granted" : "denied");
|
|
5878
|
-
if (
|
|
5879
|
-
|
|
5879
|
+
if (ok) onGranted?.();
|
|
5880
|
+
else onDeny?.();
|
|
5881
|
+
};
|
|
5882
|
+
if (!passesSync()) {
|
|
5883
|
+
finish(false);
|
|
5884
|
+
} else if (!canAccess) {
|
|
5885
|
+
finish(true);
|
|
5886
|
+
} else {
|
|
5887
|
+
setState("checking");
|
|
5888
|
+
Promise.resolve(canAccess()).then((ok) => finish(Boolean(ok)));
|
|
5889
|
+
}
|
|
5880
5890
|
return () => {
|
|
5881
5891
|
cancelled = true;
|
|
5882
5892
|
};
|
|
@@ -5892,10 +5902,12 @@ function SecureLayout({
|
|
|
5892
5902
|
requiredPermissionsKey
|
|
5893
5903
|
]);
|
|
5894
5904
|
if (state === "checking") {
|
|
5895
|
-
|
|
5905
|
+
if (loadingFallback === null) return null;
|
|
5906
|
+
return /* @__PURE__ */ jsx("div", { className: ["flex min-h-[8rem] items-center justify-center", className].filter(Boolean).join(" "), children: loadingFallback !== void 0 ? loadingFallback : /* @__PURE__ */ jsx(Spinner2, {}) });
|
|
5896
5907
|
}
|
|
5897
5908
|
if (state === "denied") {
|
|
5898
|
-
|
|
5909
|
+
if (fallback === null) return null;
|
|
5910
|
+
return /* @__PURE__ */ jsx("div", { className: className || void 0, children: fallback !== void 0 ? fallback : /* @__PURE__ */ jsxs("div", { className: "flex min-h-[8rem] flex-col items-center justify-center gap-1 rounded-xl border border-border bg-surface p-8 text-center", children: [
|
|
5899
5911
|
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-foreground", children: "Access denied" }),
|
|
5900
5912
|
/* @__PURE__ */ jsx("div", { className: "text-xs text-foreground-muted", children: "You don\u2019t have permission to view this content." })
|
|
5901
5913
|
] }) });
|