react-antd-admin-panel
Version:
Modern TypeScript-first React admin panel builder with Ant Design 6
496 lines (495 loc) • 12.8 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { G as GlobalStore, U as UserState, u as useMain, a as useUser } from "./MainContext-CrnhXpyN.js";
import React from "react";
import { Space, Typography, Avatar, Dropdown, Layout, Menu } from "antd";
import { LogoutOutlined, UserOutlined, MenuUnfoldOutlined, MenuFoldOutlined } from "@ant-design/icons";
class Main {
constructor(config) {
__publicField(this, "_config");
__publicField(this, "_store");
__publicField(this, "_userState");
__publicField(this, "_navigate");
this._config = config;
this._store = new GlobalStore();
this._userState = new UserState();
}
/**
* Get the configuration
*/
getConfig() {
return this._config;
}
/**
* Get route sections
*/
getSections() {
return this._config.sections;
}
/**
* Get a specific section/route config
*/
getSection(path) {
return this._config.sections[path];
}
/**
* Get the global store
*/
Store() {
return this._store;
}
/**
* Get the user state manager
*/
User() {
return this._userState;
}
/**
* Set the navigation function (called by MainProvider)
*/
setNavigate(fn) {
this._navigate = fn;
}
/**
* Navigate to a route
*/
navigate(path) {
if (this._navigate) {
this._navigate(path);
} else {
console.warn("Main: navigate called before setNavigate");
}
}
/**
* Check if the current user can access a route
*/
canAccess(route) {
if (route.requiredRole && !this._userState.hasRole(route.requiredRole)) {
return false;
}
if (route.requiredPermissions && !this._userState.hasAllPermissions(route.requiredPermissions)) {
return false;
}
return true;
}
/**
* Get all accessible routes for the current user
*/
getAccessibleRoutes() {
const accessible = {};
for (const [path, route] of Object.entries(this._config.sections)) {
if (this.canAccess(route)) {
accessible[path] = route;
}
}
return accessible;
}
/**
* Get routes for sidebar (non-hidden, accessible)
*/
getSidebarRoutes() {
return Object.entries(this._config.sections).filter(([_, route]) => !route.hidden && this.canAccess(route)).map(([path, route]) => ({ path, route }));
}
/**
* Create a MainInstance for use in context
*/
createInstance(navigate) {
this.setNavigate(navigate);
return {
User: () => this._userState,
Store: () => this._store,
config: this._config.config,
navigate: (path) => this.navigate(path),
canAccess: (route) => this.canAccess(route)
};
}
/**
* Get the default route
*/
getDefaultRoute() {
return this._config.config.defaultRoute || "/";
}
/**
* Get the auth route (for unauthenticated users)
*/
getAuthRoute() {
return this._config.config.authRoute || "/login";
}
/**
* Check if user is authenticated
*/
isAuthenticated() {
return this._userState.isAuthenticated();
}
/**
* Flatten nested routes for routing
*/
getFlatRoutes() {
const routes = [];
const flatten = (sections, prefix = "") => {
for (const [path, route] of Object.entries(sections)) {
const fullPath = prefix + path;
routes.push({ path: fullPath, route });
if (route.children) {
flatten(route.children, fullPath);
}
}
};
flatten(this._config.sections);
return routes;
}
}
const { Text } = Typography;
function ProfileMenu({ config }) {
const main = useMain();
const user = useUser();
const profileConfig = config || main.config.profileMenu;
if (!user) {
return null;
}
const {
showAvatar = true,
avatarKey = "avatar",
nameKey = "name",
emailKey = "email",
extraItems = [],
onLogout,
showLogout = true
} = profileConfig || {};
const avatarUrl = user[avatarKey];
const userName = user[nameKey] || "User";
const userEmail = user[emailKey];
const menuItems = [];
menuItems.push({
key: "_header",
label: React.createElement(
Space,
{ direction: "vertical", size: 0, style: { padding: "8px 0" } },
React.createElement(Text, { strong: true }, userName),
userEmail && React.createElement(Text, { type: "secondary", style: { fontSize: 12 } }, userEmail)
),
disabled: true
});
menuItems.push({ type: "divider" });
for (const item of extraItems) {
if (item.divider) {
menuItems.push({ type: "divider" });
} else {
menuItems.push({
key: item.key,
label: item.label,
icon: item.icon,
danger: item.danger,
onClick: item.onClick
});
}
}
if (showLogout) {
if (extraItems.length > 0) {
menuItems.push({ type: "divider" });
}
menuItems.push({
key: "_logout",
label: "Logout",
icon: React.createElement(LogoutOutlined),
danger: true,
onClick: async () => {
if (onLogout) {
await onLogout();
}
main.User().clear();
if (main.config.authRoute) {
main.navigate(main.config.authRoute);
}
}
});
}
const avatarElement = showAvatar ? React.createElement(
Avatar,
{
src: avatarUrl,
icon: !avatarUrl ? React.createElement(UserOutlined) : void 0,
style: { cursor: "pointer" }
}
) : React.createElement(
Space,
{ style: { cursor: "pointer" } },
React.createElement(UserOutlined),
React.createElement("span", {}, userName)
);
return React.createElement(
Dropdown,
{
menu: { items: menuItems },
trigger: ["click"],
placement: "bottomRight"
},
avatarElement
);
}
const { Header, Sider, Content } = Layout;
function AppLayout({
sections,
currentPath,
sidebarConfig,
children
}) {
const main = useMain();
const user = useUser();
const config = sidebarConfig || main.config.sidebar || {};
const {
logo,
collapsedLogo,
title,
defaultCollapsed = false,
theme = "dark",
width = 256,
collapsedWidth = 80,
footer
} = config;
const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
const menuItems = React.useMemo(() => {
const items = [];
for (const [path, route] of Object.entries(sections)) {
if (route.hidden) continue;
if (!main.canAccess(route)) continue;
if (route.children) {
const childItems = [];
for (const [childPath, childRoute] of Object.entries(route.children)) {
if (childRoute.hidden) continue;
if (!main.canAccess(childRoute)) continue;
childItems.push({
key: `${path}${childPath}`,
label: childRoute.title,
icon: childRoute.icon
});
}
if (childItems.length > 0) {
items.push({
key: path,
label: route.title,
icon: route.icon,
children: childItems
});
}
} else {
items.push({
key: path,
label: route.title,
icon: route.icon
});
}
}
return items;
}, [sections, main]);
const handleMenuClick = ({ key }) => {
main.navigate(key);
};
const selectedKeys = [currentPath];
const openKeys = Object.keys(sections).filter((path) => {
const section = sections[path];
return currentPath.startsWith(path) && section && section.children;
});
const renderLogo = () => {
const logoContent = collapsed ? collapsedLogo || logo : logo;
if (!logoContent) return null;
const logoElement = typeof logoContent === "string" ? React.createElement("img", {
src: logoContent,
alt: "Logo",
style: { height: 32, objectFit: "contain" }
}) : logoContent;
return React.createElement(
"div",
{
style: {
height: 64,
display: "flex",
alignItems: "center",
justifyContent: collapsed ? "center" : "flex-start",
padding: collapsed ? 0 : "0 16px",
gap: 12
}
},
logoElement,
!collapsed && title && React.createElement(
"span",
{
style: {
color: theme === "dark" ? "#fff" : "#000",
fontSize: 18,
fontWeight: 600,
whiteSpace: "nowrap"
}
},
title
)
);
};
return React.createElement(
Layout,
{ style: { minHeight: "100vh" } },
// Sidebar
React.createElement(
Sider,
{
collapsible: true,
collapsed,
onCollapse: setCollapsed,
theme,
width,
collapsedWidth,
trigger: null,
style: {
overflow: "auto",
height: "100vh",
position: "fixed",
left: 0,
top: 0,
bottom: 0
}
},
renderLogo(),
React.createElement(
Menu,
{
theme,
mode: "inline",
selectedKeys,
defaultOpenKeys: openKeys,
items: menuItems,
onClick: handleMenuClick
}
),
footer && React.createElement(
"div",
{
style: {
position: "absolute",
bottom: 48,
left: 0,
right: 0,
padding: "0 16px"
}
},
footer
)
),
// Main content area
React.createElement(
Layout,
{
style: {
marginLeft: collapsed ? collapsedWidth : width,
transition: "margin-left 0.2s"
}
},
// Header
React.createElement(
Header,
{
style: {
padding: "0 24px",
background: "#fff",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
boxShadow: "0 1px 4px rgba(0, 21, 41, 0.08)",
position: "sticky",
top: 0,
zIndex: 1
}
},
// Collapse trigger
React.createElement(
"div",
{
onClick: () => setCollapsed(!collapsed),
style: { cursor: "pointer", fontSize: 18 }
},
React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined)
),
// Profile menu
user && React.createElement(ProfileMenu, {})
),
// Content
React.createElement(
Content,
{
style: {
margin: 24,
padding: 24,
background: "#fff",
minHeight: 280,
borderRadius: 8
}
},
children
)
)
);
}
function ProtectedRoute({
route,
children,
fallback
}) {
const main = useMain();
const user = useUser();
if (!user) {
if (main.config.authRoute) {
main.navigate(main.config.authRoute);
}
return fallback ? React.createElement(React.Fragment, {}, fallback) : null;
}
if (!main.canAccess(route)) {
return fallback ? React.createElement(React.Fragment, {}, fallback) : React.createElement(
"div",
{
style: {
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "50vh",
flexDirection: "column",
gap: 16
}
},
React.createElement("h2", {}, "Access Denied"),
React.createElement("p", {}, "You do not have permission to access this page.")
);
}
return React.createElement(React.Fragment, {}, children);
}
function Protected({
role,
permission,
permissions,
anyPermission,
fallback,
children
}) {
const main = useMain();
const userState = main.User();
if (role && !userState.hasRole(role)) {
return fallback ? React.createElement(React.Fragment, {}, fallback) : null;
}
if (permission && !userState.hasPermission(permission)) {
return fallback ? React.createElement(React.Fragment, {}, fallback) : null;
}
if (permissions && !userState.hasAllPermissions(permissions)) {
return fallback ? React.createElement(React.Fragment, {}, fallback) : null;
}
if (anyPermission && !userState.hasAnyPermission(anyPermission)) {
return fallback ? React.createElement(React.Fragment, {}, fallback) : null;
}
return React.createElement(React.Fragment, {}, children);
}
export {
AppLayout as A,
Main as M,
ProfileMenu as P,
ProtectedRoute as a,
Protected as b
};
//# sourceMappingURL=ProtectedRoute-Bh9R7Iat.js.map