@payez/next-mvp 3.6.0 → 3.6.2

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.
@@ -160,6 +160,7 @@ async function idpOAuthCallback(oauthData) {
160
160
  access_token: oauthData.accessToken || '',
161
161
  refresh_token: oauthData.refreshToken || '',
162
162
  expires_at: oauthData.expiresAt || 0,
163
+ client_id: clientId,
163
164
  }),
164
165
  });
165
166
  if (!response.ok) {
@@ -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-[#349AD5] text-white font-semibold text-lg hover:bg-[#2980b9] transition-colors focus:outline-none focus:ring-2 focus:ring-[#349AD5] focus:ring-offset-2 dark:focus:ring-offset-slate-900", "aria-label": "User menu", "aria-expanded": isOpen, "aria-haspopup": "true", children: 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.jsxs)("div", { className: "px-4 py-3 border-b border-gray-200 dark:border-slate-700", 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" }) })] }))] }));
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";
@@ -5,3 +5,5 @@
5
5
  */
6
6
  export { UserAvatarMenu } from './UserAvatarMenu';
7
7
  export type { UserAvatarMenuProps } from './UserAvatarMenu';
8
+ export { MobileNavDrawer } from './MobileNavDrawer';
9
+ export type { MobileNavDrawerProps, NavItem, NavSection } from './MobileNavDrawer';
@@ -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,6 @@
1
+ /**
2
+ * Page Permissions Admin exports
3
+ *
4
+ * - PagePermissionsAdminPage: Admin interface for managing page permissions (/admin/page-permissions)
5
+ */
6
+ export { default as PagePermissionsAdminPage } from './PagePermissionsAdminPage';
@@ -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
- * Roles Admin Page for @payez/next-mvp
2
+ * Role Management Admin Page (/admin/roles)
3
3
  *
4
- * Read-only admin interface for viewing roles and permissions (/admin/roles).
5
- * MVP scope: View IDP roles and their page permissions only.
6
- * Role creation/editing deferred to post-MVP.
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
- * @see docs/specs/ROLES_MANAGEMENT_SPEC.md
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
- interface RolesAdminPageProps {
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;