update header dependency
All checks were successful
Build and Deploy Next.js + Nginx Docker Image / build-and-deploy (push) Successful in 1m29s
Build and Deploy Next.js + Nginx Docker Image / deploy (push) Successful in 10s

This commit is contained in:
Mohamadzadeh 2026-09-01 17:56:56 +03:30
parent 8348d3b36f
commit 07a4fd3bbc
10 changed files with 588 additions and 670 deletions

View File

@ -1,184 +0,0 @@
import { jsx as y } from "react/jsx-runtime";
import { createContext as L, useState as K, useRef as O, useCallback as h, useEffect as k, useMemo as T, useContext as R } from "react";
import { useQuery as b } from "@tanstack/react-query";
import F from "keycloak-js";
let x = null, r = null, E = null, C = !1;
const G = L(null), _ = ({ url: e, realm: s, clientId: t }) => (r || (r = new F({
url: e,
realm: s,
clientId: t
})), r), j = (e) => (x = e, x), ee = () => C, J = () => !!(r != null && r.authenticated), Q = () => (r == null ? void 0 : r.token) ?? null, te = async (e = 30) => r ? r.updateToken(e) : !1, U = (e) => r == null ? void 0 : r.logout(e), re = (e) => r == null ? void 0 : r.login(e), W = ({ config: e, initOptions: s = {} } = {}) => {
const t = e ?? x;
if (!(t != null && t.url) || !(t != null && t.realm) || !(t != null && t.clientId))
throw new Error("Keycloak config is required.");
j(t);
const n = _(t);
return E || (E = n.init({
onLoad: "login-required",
checkLoginIframe: !1,
flow: "implicit",
...s
}).then((o) => (C = !0, o)).catch((o) => {
throw C = !0, o;
})), E;
}, se = ({
children: e,
config: s,
initOptions: t,
loading: n = null
}) => {
const [o, m] = K(!1), [p, w] = K(!1), [u, c] = K(null), l = O(!1), i = h(() => {
r && (w(!!r.authenticated), c(r.token ?? null));
}, []);
k(() => (l.current = !0, W({ config: s, initOptions: t }).then((a) => {
l.current && (w(!!a), c((r == null ? void 0 : r.token) ?? null), m(!0));
}).catch(() => {
l.current && (w(!1), c(null), m(!0));
}), () => {
l.current = !1;
}), [s, t]);
const P = h(() => r == null ? void 0 : r.login(), []), d = h((a) => r == null ? void 0 : r.logout(a), []), f = h(
async (a = 30) => {
if (!r) return !1;
const z = await r.updateToken(a);
return i(), z;
},
[i]
), S = T(
() => ({
initialized: o,
authenticated: p,
token: u,
login: P,
logout: d,
updateToken: f
}),
[o, p, u, P, d, f]
);
return o ? /* @__PURE__ */ y(G.Provider, { value: S, children: e }) : n;
}, B = L(null), v = "permissions", Y = (e) => (e == null ? void 0 : e.storageKey) || v, ne = ({
user: e = null,
children: s,
config: t,
loading: n = /* @__PURE__ */ y("div", { children: "Loading permissions..." }),
errorFallback: o = /* @__PURE__ */ y("div", { children: "Error loading permissions" })
}) => {
const m = Y(t), [p, w] = K(
() => $(m)
), { data: u, isLoading: c, error: l } = V(t), i = u ?? p;
k(() => {
u && (D(m, u), w(u));
}, [u, m]);
const P = T(
() => I(i),
[i]
), d = h(
(a = "") => M(P, a),
[P]
), f = h((a = "") => !d(a), [d]), S = T(
() => ({
user: e,
can: d,
cannot: f,
permissions: i,
isLoading: c,
error: l
}),
[e, d, f, i, c, l]
);
return l && !i ? o : c && !i ? n : /* @__PURE__ */ y(B.Provider, { value: S, children: s });
}, $ = (e = v) => {
if (!(typeof window > "u"))
try {
const s = localStorage.getItem(e);
return s ? JSON.parse(s) : void 0;
} catch {
return;
}
}, D = (e = v, s) => {
if (!(typeof window > "u"))
try {
localStorage.setItem(e, JSON.stringify(s));
} catch {
}
}, oe = (e = v) => {
if (!(typeof window > "u"))
try {
localStorage.removeItem(e);
} catch {
}
}, I = (e) => e ? new Set(e.map((s) => s.name)) : /* @__PURE__ */ new Set(), M = (e, s = "") => {
if (!s) return !1;
if (e.has(s) || [...e].some((n) => n.startsWith(s))) return !0;
let t = s;
for (; t.includes(":"); )
if (t = t.slice(0, t.lastIndexOf(":")), e.has(`${t}:*`)) return !0;
return !1;
}, q = {
all: ["permissions"],
list: (e) => [...q.all, e]
}, N = async (e) => {
if (!(e != null && e.url) || !(e != null && e.clientId))
throw new Error("Permission config requires url and clientId.");
const s = Q(), t = new URL(e.url);
t.searchParams.set("client_id", e.clientId);
const n = await fetch(t.toString(), {
method: "GET",
headers: {
...s ? { Authorization: `Bearer ${s}` } : {}
}
});
if (!n.ok)
throw new Error("Failed to load permissions");
return n.json();
}, A = () => {
const e = R(B);
if (!e)
throw new Error("usePermission must be used within PermissionProvider");
return e;
}, V = (e) => b({
queryKey: q.list(e == null ? void 0 : e.clientId),
queryFn: () => N(e),
enabled: !!(e != null && e.url) && !!(e != null && e.clientId) && J(),
staleTime: 1 / 0,
refetchOnWindowFocus: !1,
refetchOnReconnect: !1
}), ie = ({
perm: e,
children: s,
fallback: t = /* @__PURE__ */ y("div", { children: "You are not allowed to do this" })
}) => {
const { can: n } = A();
return n(e) ? s : t;
}, ae = ({ children: e }) => {
const { can: s, permissions: t } = A();
return t && s("admin") ? e : null;
}, ue = ({ children: e, forceNoAccess: s = !1 }) => {
const { can: t, isLoading: n } = A();
return k(() => {
n || (s || !t("panel_access")) && U();
}, [n, t, s]), e;
};
export {
ae as A,
ie as C,
se as K,
ne as P,
te as a,
J as b,
W as c,
ue as d,
M as e,
oe as f,
Q as g,
I as h,
ee as i,
$ as j,
N as k,
U as l,
re as m,
V as n,
q as p,
D as s,
A as u
};

17
dist/core-runtime.js vendored Normal file
View File

@ -0,0 +1,17 @@
import { createContext as e, useContext as n } from "react";
const s = e(null), i = () => {
const t = n(s);
if (!t)
throw new Error("usePermission must be used within PermissionProvider");
return t;
};
let o = null;
const u = (t) => {
o = typeof t == "function" ? t : null;
}, c = (t) => o == null ? void 0 : o(t);
export {
s as PermissionContext,
c as logoutKeycloakFromRuntime,
u as setKeycloakLogoutHandler,
i as usePermission
};

669
dist/core.js vendored

File diff suppressed because one or more lines are too long

251
dist/layout.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
import Keycloak from "keycloak-js"; import Keycloak from "keycloak-js";
import { setKeycloakLogoutHandler } from "./runtime.jsx";
import { import {
createContext, createContext,
useCallback, useCallback,
@ -63,6 +64,8 @@ export const logoutKeycloak = (options) => {
return keycloakInstance?.logout(options); return keycloakInstance?.logout(options);
}; };
setKeycloakLogoutHandler(logoutKeycloak);
export const loginKeycloak = (options) => { export const loginKeycloak = (options) => {
return keycloakInstance?.login(options); return keycloakInstance?.login(options);
}; };

View File

@ -1,22 +1,17 @@
import { import {
createContext,
useCallback, useCallback,
useContext,
useEffect, useEffect,
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { PermissionContext, usePermission } from "./runtime.jsx";
import { import {
getKeycloakToken, getKeycloakToken,
isKeycloakAuthenticated, isKeycloakAuthenticated,
logoutKeycloak, logoutKeycloak,
} from "./keycloak.jsx"; } from "./keycloak.jsx";
// contexts
const PermissionContext = createContext(null);
// storage keys // storage keys
const DEFAULT_STORAGE_KEY = "permissions"; const DEFAULT_STORAGE_KEY = "permissions";
@ -178,15 +173,7 @@ export const getPermissions = async (config) => {
// hooks // hooks
export const usePermission = () => { export { usePermission } from "./runtime.jsx";
const context = useContext(PermissionContext);
if (!context) {
throw new Error("usePermission must be used within PermissionProvider");
}
return context;
};
export const usePermissionsQuery = (config) => { export const usePermissionsQuery = (config) => {
return useQuery({ return useQuery({

27
src/core/runtime.jsx Normal file
View File

@ -0,0 +1,27 @@
import { createContext, useContext } from "react";
// Lightweight shared runtime for layout/core integration.
// IMPORTANT: keep this module free of optional dependencies such as
// @tanstack/react-query and keycloak-js. `uikit/layout` imports it directly.
export const PermissionContext = createContext(null);
export const usePermission = () => {
const context = useContext(PermissionContext);
if (!context) {
throw new Error("usePermission must be used within PermissionProvider");
}
return context;
};
let keycloakLogoutHandler = null;
export const setKeycloakLogoutHandler = (handler) => {
keycloakLogoutHandler = typeof handler === "function" ? handler : null;
};
export const logoutKeycloakFromRuntime = (options) => {
return keycloakLogoutHandler?.(options);
};

View File

@ -20,42 +20,12 @@ import {
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { Children, forwardRef, isValidElement, useRef } from "react"; import { Children, forwardRef, isValidElement, useRef } from "react";
import { logoutKeycloak } from "../core/keycloak.jsx"; import { FiMoreHorizontal } from "react-icons/fi/index.js";
import { usePermission } from "../core/permission.jsx"; import { IoExit } from "react-icons/io5/index.js";
import {
const EllipsisIcon = (props) => ( logoutKeycloakFromRuntime,
<svg usePermission,
xmlns="http://www.w3.org/2000/svg" } from "../core/runtime.jsx";
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<circle cx="12" cy="12" r="1" />
<circle cx="19" cy="12" r="1" />
<circle cx="5" cy="12" r="1" />
</svg>
);
const LogOutIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="m16 17 5-5-5-5" />
<path d="M21 12H9" />
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
</svg>
);
const normalizeSidebarPath = (value = "") => { const normalizeSidebarPath = (value = "") => {
const path = String(value).split(/[?#]/)[0] || ""; const path = String(value).split(/[?#]/)[0] || "";
@ -101,34 +71,11 @@ const DesktopNavItem = ({
onNavigate, onNavigate,
}) => { }) => {
const commonProps = { const commonProps = {
variant: "ghost", variant: isActive ? "solid" : "ghost",
position: "relative", colorScheme: isActive ? "blue" : undefined,
bg: "transparent",
_before: isActive
? {
content: '""',
position: "absolute",
top: 0,
bottom: 0,
insetInlineStart: 0,
insetInlineEnd: 0,
bg: "gray.200",
borderRadius: "md",
zIndex: 0,
}
: undefined,
sx: {
"> svg": {
position: "relative",
zIndex: 1,
},
},
icon: <Icon as={item.icon} />, icon: <Icon as={item.icon} />,
fontSize: "xl", fontSize: "xl",
size: { base: "sm", lg: "md" }, size: { base: "sm", lg: "lg" },
"aria-label": item.title, "aria-label": item.title,
"aria-current": isActive ? "page" : undefined, "aria-current": isActive ? "page" : undefined,
}; };
@ -149,7 +96,11 @@ const DesktopNavItem = ({
: { as: "a", href: item.path }; : { as: "a", href: item.path };
content = ( content = (
<IconButton {...commonProps} {...linkProps} onClick={onNavigate} /> <IconButton
{...commonProps}
{...linkProps}
onClick={onNavigate}
/>
); );
} }
@ -260,7 +211,8 @@ const SidebarRoot = ({
); );
const hasExit = Boolean(exitElement); const hasExit = Boolean(exitElement);
const logoutTitle = exitElement?.props?.title || "خروج"; const logoutTitle = exitElement?.props?.title || "خروج";
const logoutIcon = exitElement?.props?.icon || LogOutIcon; const logoutIcon = exitElement?.props?.icon || IoExit;
const resolvedCurrentPath = const resolvedCurrentPath =
currentPath ?? currentPath ??
@ -272,7 +224,9 @@ const SidebarRoot = ({
const secondaryItems = items.slice(primaryMobileCount); const secondaryItems = items.slice(primaryMobileCount);
const disabled = (item) => { const disabled = (item) => {
const permissions = Array.isArray(item?.perm) ? item.perm : [item?.perm]; const permissions = Array.isArray(item?.perm)
? item.perm
: [item?.perm];
return permissions.filter(Boolean).some((permission) => cannot(permission)); return permissions.filter(Boolean).some((permission) => cannot(permission));
}; };
@ -288,7 +242,7 @@ const SidebarRoot = ({
const handleLogout = () => { const handleLogout = () => {
onClose(); onClose();
logoutKeycloak(); logoutKeycloakFromRuntime();
}; };
return ( return (
@ -401,7 +355,7 @@ const SidebarRoot = ({
color={isSecondaryItemActive ? "blue.600" : "gray.600"} color={isSecondaryItemActive ? "blue.600" : "gray.600"}
bg={isSecondaryItemActive ? "blue.50" : "transparent"} bg={isSecondaryItemActive ? "blue.50" : "transparent"}
> >
<Icon as={EllipsisIcon} boxSize={5} /> <Icon as={FiMoreHorizontal} boxSize={5} />
<Text <Text
fontSize="10px" fontSize="10px"
fontWeight={isSecondaryItemActive ? "bold" : "medium"} fontWeight={isSecondaryItemActive ? "bold" : "medium"}

BIN
uikit.zip

Binary file not shown.

View File

@ -96,6 +96,8 @@ export default defineConfig(({ mode }) => {
core: path.resolve(process.cwd(), "src/core/index.js"), core: path.resolve(process.cwd(), "src/core/index.js"),
"core-runtime": path.resolve(process.cwd(), "src/core/runtime.jsx"),
table: path.resolve(process.cwd(), "src/table/index.js"), table: path.resolve(process.cwd(), "src/table/index.js"),
form: path.resolve(process.cwd(), "src/form/index.js"), form: path.resolve(process.cwd(), "src/form/index.js"),