UNPKG

react-antd-admin-panel

Version:

Modern TypeScript-first React admin panel builder with Ant Design 6

496 lines (495 loc) 12.8 kB
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