@payez/next-mvp 3.6.0 → 3.6.1
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/auth/utils/idp-client.js +1 -0
- package/dist/components/account/MobileNavDrawer.d.ts +32 -0
- package/dist/components/account/MobileNavDrawer.js +81 -0
- package/dist/components/account/UserAvatarMenu.js +5 -1
- package/dist/components/account/index.d.ts +2 -0
- package/dist/components/account/index.js +5 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/pages/admin-page-permissions/PagePermissionsAdminPage.d.ts +18 -0
- package/dist/pages/admin-page-permissions/PagePermissionsAdminPage.js +276 -0
- package/dist/pages/admin-page-permissions/index.d.ts +6 -0
- package/dist/pages/admin-page-permissions/index.js +13 -0
- package/dist/pages/admin-roles/RolesAdminPage.d.ts +12 -11
- package/dist/pages/admin-roles/RolesAdminPage.js +249 -66
- package/dist/routes/auth/session.d.ts +1 -30
- package/dist/routes/auth/session.js +3 -4
- package/package.json +6 -1
- package/src/auth/utils/idp-client.ts +1 -0
- package/src/components/account/MobileNavDrawer.tsx +305 -0
- package/src/components/account/UserAvatarMenu.tsx +47 -17
- package/src/components/account/index.ts +5 -0
- package/src/index.ts +2 -2
- package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -0
- package/src/pages/admin-page-permissions/index.ts +7 -0
- package/src/pages/admin-roles/RolesAdminPage.tsx +494 -318
- package/src/routes/auth/session.ts +3 -4
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface NavItem {
|
|
2
|
+
href: string;
|
|
3
|
+
label: string;
|
|
4
|
+
icon?: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export interface NavSection {
|
|
7
|
+
title?: string;
|
|
8
|
+
items: Array<{
|
|
9
|
+
label: string;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
href?: string;
|
|
12
|
+
onClick?: () => void;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface MobileNavDrawerProps {
|
|
16
|
+
isOpen: boolean;
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
navItems: NavItem[];
|
|
19
|
+
/** Extra sections like Admin, rendered after nav items with optional title */
|
|
20
|
+
customSections?: NavSection[];
|
|
21
|
+
/** Base path for account link (default: '/account') */
|
|
22
|
+
basePath?: string;
|
|
23
|
+
/** Custom sign-in handler (default: next-auth signIn) */
|
|
24
|
+
onSignIn?: () => void;
|
|
25
|
+
/** Callback URL after sign in (default: '/dashboard') */
|
|
26
|
+
signInCallbackUrl?: string;
|
|
27
|
+
/** Custom unauthenticated actions (replaces default Login + Start Free buttons) */
|
|
28
|
+
unauthActions?: React.ReactNode;
|
|
29
|
+
/** Custom authenticated footer (replaces default "Account Settings" link) */
|
|
30
|
+
authFooter?: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
export declare function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath, onSignIn, signInCallbackUrl, unauthActions, authFooter, }: MobileNavDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MobileNavDrawer = MobileNavDrawer;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const react_2 = require("next-auth/react");
|
|
11
|
+
const navigation_1 = require("next/navigation");
|
|
12
|
+
const image_1 = __importDefault(require("next/image"));
|
|
13
|
+
const link_1 = __importDefault(require("next/link"));
|
|
14
|
+
const lucide_react_1 = require("lucide-react");
|
|
15
|
+
function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath = '/account', onSignIn, signInCallbackUrl = '/dashboard', unauthActions, authFooter, }) {
|
|
16
|
+
const { data: session } = (0, react_2.useSession)();
|
|
17
|
+
const pathname = (0, navigation_1.usePathname)();
|
|
18
|
+
const isAuthenticated = !!session?.user;
|
|
19
|
+
const isActiveRoute = (0, react_1.useCallback)((href) => pathname?.startsWith(href) ?? false, [pathname]);
|
|
20
|
+
// Close on Escape key
|
|
21
|
+
(0, react_1.useEffect)(() => {
|
|
22
|
+
function handleEscape(event) {
|
|
23
|
+
if (event.key === 'Escape') {
|
|
24
|
+
onClose();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (isOpen) {
|
|
28
|
+
document.addEventListener('keydown', handleEscape);
|
|
29
|
+
return () => document.removeEventListener('keydown', handleEscape);
|
|
30
|
+
}
|
|
31
|
+
}, [isOpen, onClose]);
|
|
32
|
+
// Lock body scroll when open
|
|
33
|
+
(0, react_1.useEffect)(() => {
|
|
34
|
+
if (isOpen) {
|
|
35
|
+
document.body.style.overflow = 'hidden';
|
|
36
|
+
return () => {
|
|
37
|
+
document.body.style.overflow = '';
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}, [isOpen]);
|
|
41
|
+
const handleSignIn = () => {
|
|
42
|
+
onClose();
|
|
43
|
+
if (onSignIn) {
|
|
44
|
+
onSignIn();
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
(0, react_2.signIn)(undefined, { callbackUrl: signInCallbackUrl });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handleSectionItemClick = (item) => {
|
|
51
|
+
onClose();
|
|
52
|
+
if (item.onClick) {
|
|
53
|
+
item.onClick();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// Derive display initial from name or email
|
|
57
|
+
const userName = session?.user?.name;
|
|
58
|
+
const userEmail = session?.user?.email;
|
|
59
|
+
const displaySource = userName || userEmail;
|
|
60
|
+
const userInitial = displaySource?.charAt(0).toUpperCase() || '?';
|
|
61
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `
|
|
62
|
+
fixed inset-0 bg-black/50 backdrop-blur-sm z-40 lg:hidden
|
|
63
|
+
transition-opacity duration-300
|
|
64
|
+
${isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'}
|
|
65
|
+
`, onClick: onClose, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", "aria-expanded": isOpen, className: `
|
|
66
|
+
fixed top-0 right-0 bottom-0 w-80 max-w-[85vw]
|
|
67
|
+
bg-white dark:bg-slate-900
|
|
68
|
+
shadow-[-8px_0_32px_rgba(0,0,0,0.15)]
|
|
69
|
+
dark:shadow-[-8px_0_32px_rgba(0,0,0,0.4)]
|
|
70
|
+
z-50 lg:hidden
|
|
71
|
+
overflow-y-auto
|
|
72
|
+
transition-transform duration-300 ease-out
|
|
73
|
+
${isOpen ? 'translate-x-0' : 'translate-x-full'}
|
|
74
|
+
`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-white/10", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: "Menu" }), (0, jsx_runtime_1.jsx)("button", { onClick: onClose, className: "\r\n p-2 rounded-xl\r\n text-gray-400 hover:text-gray-900\r\n dark:hover:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", "aria-label": "Close menu", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "h-5 w-5" }) })] }), isAuthenticated && session?.user && ((0, jsx_runtime_1.jsx)("div", { className: "p-4 border-b border-gray-200 dark:border-white/10", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 48, height: 48, className: "w-12 h-12 rounded-full", unoptimized: true })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold text-lg", children: userInitial })), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0", children: [userName && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold text-gray-900 dark:text-white truncate", children: userName })), userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-xs text-gray-500 dark:text-slate-400 truncate", children: userEmail }))] })] }) })), (0, jsx_runtime_1.jsx)("div", { className: "p-2", children: navItems.map((item) => ((0, jsx_runtime_1.jsxs)(link_1.default, { href: item.href, onClick: onClose, className: `
|
|
75
|
+
flex items-center gap-3 px-4 py-3.5 rounded-xl
|
|
76
|
+
transition-colors duration-200
|
|
77
|
+
${isActiveRoute(item.href)
|
|
78
|
+
? 'bg-blue-500/10 text-blue-500'
|
|
79
|
+
: 'text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-white/10'}
|
|
80
|
+
`, children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label }), isActiveRoute(item.href) && ((0, jsx_runtime_1.jsx)("span", { className: "ml-auto w-2 h-2 rounded-full bg-blue-500" }))] }, item.href))) }), customSections?.map((section, sectionIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: "p-2 border-t border-gray-200 dark:border-white/10", children: [section.title && ((0, jsx_runtime_1.jsx)("p", { className: "px-4 py-2 text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wider", children: section.title })), section.items.map((item, itemIndex) => item.href ? ((0, jsx_runtime_1.jsxs)(link_1.default, { href: item.href, onClick: onClose, className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)) : ((0, jsx_runtime_1.jsxs)("button", { onClick: () => handleSectionItemClick(item), className: "\r\n flex items-center gap-3 px-4 py-3 rounded-xl w-full text-left\r\n text-gray-900 dark:text-white\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: [item.icon && (0, jsx_runtime_1.jsx)("span", { className: "text-xl", children: item.icon }), (0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: item.label })] }, itemIndex)))] }, sectionIndex))), (0, jsx_runtime_1.jsx)("div", { className: "p-4 mt-auto border-t border-gray-200 dark:border-white/10", children: !isAuthenticated ? (unauthActions ?? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-3", children: (0, jsx_runtime_1.jsx)("button", { onClick: handleSignIn, className: "\r\n w-full px-4 py-3 rounded-xl\r\n text-blue-500 font-semibold\r\n border border-blue-500/30\r\n hover:bg-blue-500/10\r\n transition-colors\r\n ", children: "Login" }) }))) : (authFooter ?? ((0, jsx_runtime_1.jsx)(link_1.default, { href: basePath, onClick: onClose, className: "\r\n flex items-center justify-center gap-2\r\n w-full px-4 py-3 rounded-xl\r\n text-gray-500 dark:text-slate-400 font-medium\r\n hover:bg-gray-100 dark:hover:bg-white/10\r\n transition-colors\r\n ", children: "Account Settings" }))) })] })] }));
|
|
81
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
'use client';
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
exports.UserAvatarMenu = UserAvatarMenu;
|
|
5
8
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
9
|
const react_1 = require("react");
|
|
7
10
|
const react_2 = require("next-auth/react");
|
|
8
11
|
const navigation_1 = require("next/navigation");
|
|
12
|
+
const image_1 = __importDefault(require("next/image"));
|
|
9
13
|
const lucide_react_1 = require("lucide-react");
|
|
10
14
|
function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true, showSecurity = true, customItems, onSignOut, }) {
|
|
11
15
|
const { data: session, status } = (0, react_2.useSession)();
|
|
@@ -73,7 +77,7 @@ function UserAvatarMenu({ basePath = '', showProfile = true, showSettings = true
|
|
|
73
77
|
router.push(item.href);
|
|
74
78
|
}
|
|
75
79
|
};
|
|
76
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "relative", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setIsOpen(!isOpen), className: "flex items-center justify-center h-10 w-10 rounded-full bg-
|
|
80
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: menuRef, className: "relative", children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setIsOpen(!isOpen), className: "flex items-center justify-center h-10 w-10 rounded-full overflow-hidden bg-blue-500 text-white font-semibold text-lg hover:opacity-90 transition-opacity focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-slate-900", "aria-label": "User menu", "aria-expanded": isOpen, "aria-haspopup": "true", children: session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 40, height: 40, className: "w-10 h-10 rounded-full object-cover", unoptimized: true })) : (userInitial) }), isOpen && ((0, jsx_runtime_1.jsxs)("div", { className: "absolute right-0 mt-2 w-56 rounded-md shadow-lg z-50\r\n bg-white dark:bg-slate-900\r\n border border-gray-200 dark:border-slate-700", role: "menu", "aria-orientation": "vertical", children: [(0, jsx_runtime_1.jsx)("div", { className: "px-4 py-3 border-b border-gray-200 dark:border-slate-700", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [session.user.image ? ((0, jsx_runtime_1.jsx)(image_1.default, { src: session.user.image, alt: "", width: 32, height: 32, className: "w-8 h-8 rounded-full flex-shrink-0", unoptimized: true })) : ((0, jsx_runtime_1.jsx)("div", { className: "w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold text-sm flex-shrink-0", children: userInitial })), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [userName && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-700 dark:text-slate-200 truncate", children: userName })), userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-slate-400 truncate", children: userEmail })), !userName && !userEmail && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-500 dark:text-slate-400", children: "Signed in" }))] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "py-1", children: [showProfile && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.User, { className: "h-4 w-4" }), label: "Profile", onClick: () => handleNavigation(`${basePath}/profile`) })), showSettings && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { className: "h-4 w-4" }), label: "Settings", onClick: () => handleNavigation(`${basePath}/settings`) })), showSecurity && ((0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Shield, { className: "h-4 w-4" }), label: "Security", onClick: () => handleNavigation(`${basePath}/security`) })), customItems?.map((item, index) => ((0, jsx_runtime_1.jsx)(MenuItem, { icon: item.icon, label: item.label, onClick: () => handleItemClick(item) }, index)))] }), (0, jsx_runtime_1.jsx)("div", { className: "border-t border-gray-200 dark:border-slate-700 py-1", children: (0, jsx_runtime_1.jsx)(MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.LogOut, { className: "h-4 w-4" }), label: "Sign Out", onClick: handleSignOut, variant: "danger" }) })] }))] }));
|
|
77
81
|
}
|
|
78
82
|
function MenuItem({ icon, label, onClick, variant = 'default' }) {
|
|
79
83
|
const baseClasses = "flex items-center w-full px-4 py-2 text-sm cursor-pointer transition-colors";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MobileNavDrawer = exports.UserAvatarMenu = void 0;
|
|
2
5
|
/**
|
|
3
6
|
* Account Components for @payez/next-mvp
|
|
4
7
|
*
|
|
5
8
|
* User account related UI components.
|
|
6
9
|
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.UserAvatarMenu = void 0;
|
|
9
10
|
var UserAvatarMenu_1 = require("./UserAvatarMenu");
|
|
10
11
|
Object.defineProperty(exports, "UserAvatarMenu", { enumerable: true, get: function () { return UserAvatarMenu_1.UserAvatarMenu; } });
|
|
12
|
+
var MobileNavDrawer_1 = require("./MobileNavDrawer");
|
|
13
|
+
Object.defineProperty(exports, "MobileNavDrawer", { enumerable: true, get: function () { return MobileNavDrawer_1.MobileNavDrawer; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { AuthConfig } from './types/auth';
|
|
|
6
6
|
export { makeAuthDecision } from './auth/auth-decision';
|
|
7
7
|
export { isUnauthenticatedRoute, configurePublicRoutes, getRouteConfig } from './auth/route-config';
|
|
8
8
|
export { createMvpMiddleware } from './middleware/create-middleware';
|
|
9
|
-
export { UserAvatarMenu } from './components/account';
|
|
10
|
-
export type { UserAvatarMenuProps } from './components/account';
|
|
9
|
+
export { UserAvatarMenu, MobileNavDrawer } from './components/account';
|
|
10
|
+
export type { UserAvatarMenuProps, MobileNavDrawerProps, NavItem, NavSection } from './components/account';
|
|
11
11
|
export { ErrorMetricsCard, HealthMetricsCard, AuditLogViewer, AdminAnalyticsLayout, useErrorMetrics, useHealthMetrics, useAuditLog, useAdminAnalytics, getErrorMetrics, getHealthMetrics, writeAuditLog, queryAuditLog, } from './logging';
|
|
12
12
|
export type { ErrorMetrics, HealthMetrics, AuditLogEntry, AuditLogQuery, AuditLogResponse, TimeRange, RouteError, LevelCount, CategoryCount, ErrorDetail, EndpointHealth, SlowRequest, } from './logging';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Type augmentation for NextAuth - included via ambient module declaration
|
|
3
3
|
// Note: Type declarations are picked up automatically via tsconfig.json, no explicit import needed
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.queryAuditLog = exports.writeAuditLog = exports.getHealthMetrics = exports.getErrorMetrics = exports.useAdminAnalytics = exports.useAuditLog = exports.useHealthMetrics = exports.useErrorMetrics = exports.AdminAnalyticsLayout = exports.AuditLogViewer = exports.HealthMetricsCard = exports.ErrorMetricsCard = exports.UserAvatarMenu = exports.createMvpMiddleware = exports.getRouteConfig = exports.configurePublicRoutes = exports.isUnauthenticatedRoute = exports.makeAuthDecision = exports.useTraditionalAuthEnabled = exports.useFederatedAuthEnabled = exports.useFederatedProviders = exports.useAuthMode = exports.useAuthConfig = exports.AuthProvider = exports.useAnonSession = exports.fetchWithAuth = void 0;
|
|
5
|
+
exports.queryAuditLog = exports.writeAuditLog = exports.getHealthMetrics = exports.getErrorMetrics = exports.useAdminAnalytics = exports.useAuditLog = exports.useHealthMetrics = exports.useErrorMetrics = exports.AdminAnalyticsLayout = exports.AuditLogViewer = exports.HealthMetricsCard = exports.ErrorMetricsCard = exports.MobileNavDrawer = exports.UserAvatarMenu = exports.createMvpMiddleware = exports.getRouteConfig = exports.configurePublicRoutes = exports.isUnauthenticatedRoute = exports.makeAuthDecision = exports.useTraditionalAuthEnabled = exports.useFederatedAuthEnabled = exports.useFederatedProviders = exports.useAuthMode = exports.useAuthConfig = exports.AuthProvider = exports.useAnonSession = exports.fetchWithAuth = void 0;
|
|
6
6
|
// NOTE: Server-only exports are NOT exported from the root to prevent bundling Node.js modules in client code.
|
|
7
7
|
// Server-side code should import from subpath exports:
|
|
8
8
|
// - Session management: import { sessionStore } from '@payez/next-mvp/lib/session-store'
|
|
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "createMvpMiddleware", { enumerable: true, get: f
|
|
|
38
38
|
// Account Components
|
|
39
39
|
var account_1 = require("./components/account");
|
|
40
40
|
Object.defineProperty(exports, "UserAvatarMenu", { enumerable: true, get: function () { return account_1.UserAvatarMenu; } });
|
|
41
|
+
Object.defineProperty(exports, "MobileNavDrawer", { enumerable: true, get: function () { return account_1.MobileNavDrawer; } });
|
|
41
42
|
// Admin Logging & Analytics (client-side components and hooks)
|
|
42
43
|
var logging_1 = require("./logging");
|
|
43
44
|
Object.defineProperty(exports, "ErrorMetricsCard", { enumerable: true, get: function () { return logging_1.ErrorMetricsCard; } });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Permissions Admin Page (/admin/page-permissions)
|
|
3
|
+
*
|
|
4
|
+
* Design: Aurum (DESIGN_SPEC.md)
|
|
5
|
+
* Control which roles can access which pages
|
|
6
|
+
*
|
|
7
|
+
* Three sections:
|
|
8
|
+
* 1. Search & Filters — Find pages by route or category
|
|
9
|
+
* 2. Pages & Role Requirements — Table showing pages and their role assignments
|
|
10
|
+
* 3. Change History — Audit log of permission changes
|
|
11
|
+
*
|
|
12
|
+
* Design Principles:
|
|
13
|
+
* - No shadows, gradients, or animation
|
|
14
|
+
* - One accent color (blue #0066cc)
|
|
15
|
+
* - Inline interactions (no modals)
|
|
16
|
+
* - Scan-friendly tables and lists
|
|
17
|
+
*/
|
|
18
|
+
export default function PagePermissionsAdminPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Page Permissions Admin Page (/admin/page-permissions)
|
|
4
|
+
*
|
|
5
|
+
* Design: Aurum (DESIGN_SPEC.md)
|
|
6
|
+
* Control which roles can access which pages
|
|
7
|
+
*
|
|
8
|
+
* Three sections:
|
|
9
|
+
* 1. Search & Filters — Find pages by route or category
|
|
10
|
+
* 2. Pages & Role Requirements — Table showing pages and their role assignments
|
|
11
|
+
* 3. Change History — Audit log of permission changes
|
|
12
|
+
*
|
|
13
|
+
* Design Principles:
|
|
14
|
+
* - No shadows, gradients, or animation
|
|
15
|
+
* - One accent color (blue #0066cc)
|
|
16
|
+
* - Inline interactions (no modals)
|
|
17
|
+
* - Scan-friendly tables and lists
|
|
18
|
+
*/
|
|
19
|
+
'use client';
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.default = PagePermissionsAdminPage;
|
|
22
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
23
|
+
const react_1 = require("react");
|
|
24
|
+
// Mock data
|
|
25
|
+
const MOCK_PAGES = [
|
|
26
|
+
{
|
|
27
|
+
id: 1,
|
|
28
|
+
route: '/dashboard',
|
|
29
|
+
displayName: 'Dashboard',
|
|
30
|
+
requires2fa: false,
|
|
31
|
+
roles: [],
|
|
32
|
+
category: 'user',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 2,
|
|
36
|
+
route: '/admin',
|
|
37
|
+
displayName: 'Admin Dashboard',
|
|
38
|
+
requires2fa: true,
|
|
39
|
+
roles: ['SiteAdmin', 'ClientAdmin'],
|
|
40
|
+
category: 'admin',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 3,
|
|
44
|
+
route: '/admin/users',
|
|
45
|
+
displayName: 'User Management',
|
|
46
|
+
requires2fa: true,
|
|
47
|
+
roles: ['SiteAdmin'],
|
|
48
|
+
category: 'admin',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 4,
|
|
52
|
+
route: '/account/security',
|
|
53
|
+
displayName: 'Security Settings',
|
|
54
|
+
requires2fa: true,
|
|
55
|
+
roles: [],
|
|
56
|
+
category: 'account',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 5,
|
|
60
|
+
route: '/interview-practice',
|
|
61
|
+
displayName: 'Interview Practice',
|
|
62
|
+
requires2fa: false,
|
|
63
|
+
roles: ['ClientAdmin'],
|
|
64
|
+
category: 'user',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
const MOCK_CHANGES = [
|
|
68
|
+
{
|
|
69
|
+
timestamp: '3/10/2026, 10:30 AM',
|
|
70
|
+
event: '/admin/users role requirement changed: Added ClientAdmin by Admin User',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
timestamp: '3/10/2026, 10:15 AM',
|
|
74
|
+
event: '/dashboard updated: 2FA requirement removed by Admin User',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
timestamp: '3/9/2026, 3:45 PM',
|
|
78
|
+
event: '/interview-practice role requirement changed: Added SiteAdmin by Admin User',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
const CATEGORIES = ['All Pages', 'Admin Pages', 'Account Pages', 'User Pages'];
|
|
82
|
+
const categoryMap = {
|
|
83
|
+
'All Pages': '',
|
|
84
|
+
'Admin Pages': 'admin',
|
|
85
|
+
'Account Pages': 'account',
|
|
86
|
+
'User Pages': 'user',
|
|
87
|
+
};
|
|
88
|
+
function PagePermissionsAdminPage() {
|
|
89
|
+
const [pages, setPages] = (0, react_1.useState)(MOCK_PAGES);
|
|
90
|
+
const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
|
|
91
|
+
const [activeFilter, setActiveFilter] = (0, react_1.useState)('All Pages');
|
|
92
|
+
const [message, setMessage] = (0, react_1.useState)(null);
|
|
93
|
+
const [editingPageId, setEditingPageId] = (0, react_1.useState)(null);
|
|
94
|
+
const [tempRoles, setTempRoles] = (0, react_1.useState)([]);
|
|
95
|
+
const filteredPages = pages.filter((page) => {
|
|
96
|
+
const matchesSearch = page.route.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
97
|
+
page.displayName.toLowerCase().includes(searchQuery.toLowerCase());
|
|
98
|
+
const categoryFilter = categoryMap[activeFilter];
|
|
99
|
+
const matchesCategory = !categoryFilter || page.category === categoryFilter;
|
|
100
|
+
return matchesSearch && matchesCategory;
|
|
101
|
+
});
|
|
102
|
+
const handleEditRoles = (pageId, currentRoles) => {
|
|
103
|
+
setEditingPageId(pageId);
|
|
104
|
+
setTempRoles([...currentRoles]);
|
|
105
|
+
};
|
|
106
|
+
const handleToggleRole = (role) => {
|
|
107
|
+
setTempRoles((prev) => prev.includes(role) ? prev.filter((r) => r !== role) : [...prev, role]);
|
|
108
|
+
};
|
|
109
|
+
const handleSaveRoles = (pageId) => {
|
|
110
|
+
setPages((prev) => prev.map((p) => (p.id === pageId ? { ...p, roles: tempRoles } : p)));
|
|
111
|
+
setMessage('Page updated');
|
|
112
|
+
setEditingPageId(null);
|
|
113
|
+
setTimeout(() => setMessage(null), 3000);
|
|
114
|
+
};
|
|
115
|
+
const handleRemoveRole = (pageId, role) => {
|
|
116
|
+
setPages((prev) => prev.map((p) => p.id === pageId ? { ...p, roles: p.roles.filter((r) => r !== role) } : p));
|
|
117
|
+
setMessage('Role removed');
|
|
118
|
+
setTimeout(() => setMessage(null), 3000);
|
|
119
|
+
};
|
|
120
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: { background: '#f8f8f8', minHeight: '100vh', padding: '40px 20px' }, children: (0, jsx_runtime_1.jsxs)("div", { style: { maxWidth: '1200px', margin: '0 auto' }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '40px' }, children: [(0, jsx_runtime_1.jsx)("h1", { style: {
|
|
121
|
+
fontSize: '32px',
|
|
122
|
+
fontWeight: 400,
|
|
123
|
+
color: '#333',
|
|
124
|
+
marginBottom: '8px',
|
|
125
|
+
}, children: "Page Permissions" }), (0, jsx_runtime_1.jsx)("p", { style: { fontSize: '16px', color: '#666', fontWeight: 400 }, children: "Control which roles can access which pages" })] }), (0, jsx_runtime_1.jsx)("div", { style: { height: '1px', background: '#e0e0e0', margin: '24px 0' } }), (0, jsx_runtime_1.jsxs)("section", { style: { marginBottom: '40px' }, children: [(0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '16px' }, children: (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search pages...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: {
|
|
126
|
+
width: '100%',
|
|
127
|
+
padding: '10px 14px',
|
|
128
|
+
fontSize: '14px',
|
|
129
|
+
border: '1px solid #e0e0e0',
|
|
130
|
+
borderRadius: '4px',
|
|
131
|
+
background: 'white',
|
|
132
|
+
boxSizing: 'border-box',
|
|
133
|
+
} }) }), (0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' }, children: CATEGORIES.map((cat) => ((0, jsx_runtime_1.jsx)("button", { onClick: () => setActiveFilter(cat), style: {
|
|
134
|
+
padding: '8px 14px',
|
|
135
|
+
fontSize: '13px',
|
|
136
|
+
border: activeFilter === cat ? 'none' : '1px solid #e0e0e0',
|
|
137
|
+
borderRadius: '4px',
|
|
138
|
+
background: activeFilter === cat ? '#0066cc' : 'white',
|
|
139
|
+
color: activeFilter === cat ? 'white' : '#333',
|
|
140
|
+
cursor: 'pointer',
|
|
141
|
+
transition: 'all 0.2s',
|
|
142
|
+
}, onMouseEnter: (e) => {
|
|
143
|
+
if (activeFilter !== cat) {
|
|
144
|
+
e.currentTarget.style.background = '#f5f5f5';
|
|
145
|
+
}
|
|
146
|
+
}, onMouseLeave: (e) => {
|
|
147
|
+
if (activeFilter !== cat) {
|
|
148
|
+
e.currentTarget.style.background = 'white';
|
|
149
|
+
}
|
|
150
|
+
}, children: cat }, cat))) })] }), (0, jsx_runtime_1.jsx)("div", { style: { height: '1px', background: '#e0e0e0', margin: '24px 0' } }), message && ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
151
|
+
padding: '8px 12px',
|
|
152
|
+
background: '#e8f5e9',
|
|
153
|
+
color: '#2e7d32',
|
|
154
|
+
borderRadius: '4px',
|
|
155
|
+
marginBottom: '12px',
|
|
156
|
+
fontSize: '13px',
|
|
157
|
+
}, children: ["\u2713 ", message] })), (0, jsx_runtime_1.jsxs)("section", { style: { marginBottom: '60px' }, children: [(0, jsx_runtime_1.jsx)("h2", { style: {
|
|
158
|
+
fontSize: '18px',
|
|
159
|
+
fontWeight: 400,
|
|
160
|
+
color: '#666',
|
|
161
|
+
marginBottom: '24px',
|
|
162
|
+
textTransform: 'uppercase',
|
|
163
|
+
letterSpacing: '1px',
|
|
164
|
+
}, children: "Pages & Permissions" }), (0, jsx_runtime_1.jsxs)("table", { style: {
|
|
165
|
+
width: '100%',
|
|
166
|
+
borderCollapse: 'collapse',
|
|
167
|
+
background: 'white',
|
|
168
|
+
border: '1px solid #e0e0e0',
|
|
169
|
+
borderRadius: '4px',
|
|
170
|
+
overflow: 'hidden',
|
|
171
|
+
}, children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { style: { background: '#f8f8f8', borderBottom: '1px solid #e0e0e0' }, children: [(0, jsx_runtime_1.jsx)("th", { style: {
|
|
172
|
+
padding: '16px',
|
|
173
|
+
textAlign: 'left',
|
|
174
|
+
fontSize: '12px',
|
|
175
|
+
color: '#999',
|
|
176
|
+
textTransform: 'uppercase',
|
|
177
|
+
letterSpacing: '0.5px',
|
|
178
|
+
fontWeight: 'normal',
|
|
179
|
+
}, children: "Route" }), (0, jsx_runtime_1.jsx)("th", { style: {
|
|
180
|
+
padding: '16px',
|
|
181
|
+
textAlign: 'left',
|
|
182
|
+
fontSize: '12px',
|
|
183
|
+
color: '#999',
|
|
184
|
+
textTransform: 'uppercase',
|
|
185
|
+
letterSpacing: '0.5px',
|
|
186
|
+
fontWeight: 'normal',
|
|
187
|
+
}, children: "Display Name" }), (0, jsx_runtime_1.jsx)("th", { style: {
|
|
188
|
+
padding: '16px',
|
|
189
|
+
textAlign: 'center',
|
|
190
|
+
fontSize: '12px',
|
|
191
|
+
color: '#999',
|
|
192
|
+
textTransform: 'uppercase',
|
|
193
|
+
letterSpacing: '0.5px',
|
|
194
|
+
fontWeight: 'normal',
|
|
195
|
+
}, children: "2FA" }), (0, jsx_runtime_1.jsx)("th", { style: {
|
|
196
|
+
padding: '16px',
|
|
197
|
+
textAlign: 'left',
|
|
198
|
+
fontSize: '12px',
|
|
199
|
+
color: '#999',
|
|
200
|
+
textTransform: 'uppercase',
|
|
201
|
+
letterSpacing: '0.5px',
|
|
202
|
+
fontWeight: 'normal',
|
|
203
|
+
}, children: "Roles" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: filteredPages.map((page) => ((0, jsx_runtime_1.jsxs)("tr", { style: {
|
|
204
|
+
borderBottom: '1px solid #e0e0e0',
|
|
205
|
+
height: '48px',
|
|
206
|
+
}, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: [(0, jsx_runtime_1.jsx)("td", { style: {
|
|
207
|
+
padding: '16px',
|
|
208
|
+
fontSize: '12px',
|
|
209
|
+
fontFamily: 'Courier New, monospace',
|
|
210
|
+
color: '#333',
|
|
211
|
+
}, title: "Click to copy", children: page.route }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '14px', color: '#333' }, children: page.displayName }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', textAlign: 'center', fontSize: '14px' }, children: page.requires2fa ? '✓' : '✕' }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '13px' }, children: editingPageId === page.id ? ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '12px', alignItems: 'center' }, children: [(0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', gap: '12px' }, children: ['SiteAdmin', 'ClientAdmin'].map((role) => ((0, jsx_runtime_1.jsxs)("label", { style: {
|
|
212
|
+
display: 'flex',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
gap: '6px',
|
|
215
|
+
cursor: 'pointer',
|
|
216
|
+
}, children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: tempRoles.includes(role), onChange: () => handleToggleRole(role), style: { cursor: 'pointer' } }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: '12px', color: '#333' }, children: role })] }, role))) }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '6px' }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => handleSaveRoles(page.id), style: {
|
|
217
|
+
padding: '6px 10px',
|
|
218
|
+
background: '#0066cc',
|
|
219
|
+
color: 'white',
|
|
220
|
+
border: 'none',
|
|
221
|
+
borderRadius: '4px',
|
|
222
|
+
cursor: 'pointer',
|
|
223
|
+
fontSize: '11px',
|
|
224
|
+
}, onMouseEnter: (e) => (e.currentTarget.style.background = '#0052a3'), onMouseLeave: (e) => (e.currentTarget.style.background = '#0066cc'), children: "Save" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setEditingPageId(null), style: {
|
|
225
|
+
padding: '6px 10px',
|
|
226
|
+
background: 'white',
|
|
227
|
+
color: '#333',
|
|
228
|
+
border: '1px solid #e0e0e0',
|
|
229
|
+
borderRadius: '4px',
|
|
230
|
+
cursor: 'pointer',
|
|
231
|
+
fontSize: '11px',
|
|
232
|
+
}, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "Cancel" })] })] })) : ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', gap: '6px', alignItems: 'center' }, children: page.roles.length > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [page.roles.map((role) => ((0, jsx_runtime_1.jsxs)("span", { style: {
|
|
233
|
+
background: '#e3f2fd',
|
|
234
|
+
color: '#0066cc',
|
|
235
|
+
padding: '4px 8px',
|
|
236
|
+
borderRadius: '3px',
|
|
237
|
+
fontSize: '12px',
|
|
238
|
+
display: 'inline-flex',
|
|
239
|
+
alignItems: 'center',
|
|
240
|
+
gap: '4px',
|
|
241
|
+
}, children: [role, (0, jsx_runtime_1.jsx)("button", { onClick: () => handleRemoveRole(page.id, role), style: {
|
|
242
|
+
background: 'none',
|
|
243
|
+
border: 'none',
|
|
244
|
+
color: '#0066cc',
|
|
245
|
+
cursor: 'pointer',
|
|
246
|
+
fontSize: '12px',
|
|
247
|
+
padding: '0',
|
|
248
|
+
lineHeight: '1',
|
|
249
|
+
}, children: "\u2715" })] }, role))), (0, jsx_runtime_1.jsx)("button", { onClick: () => handleEditRoles(page.id, page.roles), style: {
|
|
250
|
+
padding: '4px 8px',
|
|
251
|
+
background: 'white',
|
|
252
|
+
color: '#0066cc',
|
|
253
|
+
border: '1px solid #e0e0e0',
|
|
254
|
+
borderRadius: '3px',
|
|
255
|
+
cursor: 'pointer',
|
|
256
|
+
fontSize: '11px',
|
|
257
|
+
}, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "+" })] })) : ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleEditRoles(page.id, []), style: {
|
|
258
|
+
padding: '4px 8px',
|
|
259
|
+
background: 'white',
|
|
260
|
+
color: '#0066cc',
|
|
261
|
+
border: '1px solid #e0e0e0',
|
|
262
|
+
borderRadius: '3px',
|
|
263
|
+
cursor: 'pointer',
|
|
264
|
+
fontSize: '11px',
|
|
265
|
+
}, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "+ Add Role" })) })) })] }, page.id))) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '12px', fontSize: '12px', color: '#999' }, children: [filteredPages.length, " of ", pages.length, " pages shown"] })] }), (0, jsx_runtime_1.jsx)("div", { style: { height: '1px', background: '#e0e0e0', margin: '24px 0' } }), (0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("h2", { style: {
|
|
266
|
+
fontSize: '18px',
|
|
267
|
+
fontWeight: 400,
|
|
268
|
+
color: '#666',
|
|
269
|
+
marginBottom: '24px',
|
|
270
|
+
textTransform: 'uppercase',
|
|
271
|
+
letterSpacing: '1px',
|
|
272
|
+
}, children: "Recent Changes" }), (0, jsx_runtime_1.jsx)("div", { style: { background: 'white', border: '1px solid #e0e0e0', borderRadius: '4px' }, children: MOCK_CHANGES.map((change, idx) => ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
273
|
+
padding: '16px',
|
|
274
|
+
borderBottom: idx < MOCK_CHANGES.length - 1 ? '1px solid #e0e0e0' : 'none',
|
|
275
|
+
}, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: '12px', color: '#999', marginBottom: '4px' }, children: change.timestamp }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: '14px', color: '#333' }, children: change.event })] }, idx))) })] })] }) }));
|
|
276
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Page Permissions Admin exports
|
|
4
|
+
*
|
|
5
|
+
* - PagePermissionsAdminPage: Admin interface for managing page permissions (/admin/page-permissions)
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.PagePermissionsAdminPage = void 0;
|
|
12
|
+
var PagePermissionsAdminPage_1 = require("./PagePermissionsAdminPage");
|
|
13
|
+
Object.defineProperty(exports, "PagePermissionsAdminPage", { enumerable: true, get: function () { return __importDefault(PagePermissionsAdminPage_1).default; } });
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Role Management Admin Page (/admin/roles)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Design: Aurum (DESIGN_SPEC.md)
|
|
5
|
+
* Three sections:
|
|
6
|
+
* 1. Available Roles — Cards showing SiteAdmin, ClientAdmin
|
|
7
|
+
* 2. User Assignments — Table with inline role dropdowns
|
|
8
|
+
* 3. Change History — Audit log of role changes
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* Design Principles:
|
|
11
|
+
* - No shadows, gradients, or animation
|
|
12
|
+
* - One accent color (blue #0066cc)
|
|
13
|
+
* - Inline interactions (no modals)
|
|
14
|
+
* - Scan-friendly tables and lists
|
|
9
15
|
*/
|
|
10
|
-
|
|
11
|
-
rolesEndpoint?: string;
|
|
12
|
-
matrixEndpoint?: string;
|
|
13
|
-
}
|
|
14
|
-
export default function RolesAdminPage({ rolesEndpoint, matrixEndpoint, }: RolesAdminPageProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
-
export {};
|
|
16
|
+
export default function RolesAdminPage(): import("react/jsx-runtime").JSX.Element;
|