@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.
@@ -1,78 +1,261 @@
1
1
  "use strict";
2
2
  /**
3
- * Roles Admin Page for @payez/next-mvp
3
+ * Role Management Admin Page (/admin/roles)
4
4
  *
5
- * Read-only admin interface for viewing roles and permissions (/admin/roles).
6
- * MVP scope: View IDP roles and their page permissions only.
7
- * Role creation/editing deferred to post-MVP.
5
+ * Design: Aurum (DESIGN_SPEC.md)
6
+ * Three sections:
7
+ * 1. Available Roles Cards showing SiteAdmin, ClientAdmin
8
+ * 2. User Assignments — Table with inline role dropdowns
9
+ * 3. Change History — Audit log of role changes
8
10
  *
9
- * @see docs/specs/ROLES_MANAGEMENT_SPEC.md
11
+ * Design Principles:
12
+ * - No shadows, gradients, or animation
13
+ * - One accent color (blue #0066cc)
14
+ * - Inline interactions (no modals)
15
+ * - Scan-friendly tables and lists
10
16
  */
11
17
  'use client';
12
18
  Object.defineProperty(exports, "__esModule", { value: true });
13
19
  exports.default = RolesAdminPage;
14
20
  const jsx_runtime_1 = require("react/jsx-runtime");
15
21
  const react_1 = require("react");
16
- const useTheme_1 = require("../../theme/useTheme");
17
- const components_1 = require("../roles/components");
18
- function RolesAdminPage({ rolesEndpoint = '/api/v1/admin/roles', matrixEndpoint = '/api/v1/admin/permissions-matrix', }) {
19
- const layout = (0, useTheme_1.useLayout)();
20
- const colors = (0, useTheme_1.useColors)();
21
- const isDark = colors?.background?.includes('slate-9') ||
22
- colors?.background?.includes('gray-9') ||
23
- colors?.card?.includes('slate-8');
24
- // State
25
- const [viewMode, setViewMode] = (0, react_1.useState)('list');
26
- const [roles, setRoles] = (0, react_1.useState)([]);
27
- const [matrixData, setMatrixData] = (0, react_1.useState)(null);
28
- const [loading, setLoading] = (0, react_1.useState)(true);
29
- const [expandedRole, setExpandedRole] = (0, react_1.useState)(null);
30
- // Theme colors
31
- const bgColor = isDark ? 'bg-slate-900' : 'bg-gray-50';
32
- const cardBg = isDark ? 'bg-slate-800' : 'bg-white';
33
- const borderColor = isDark ? 'border-slate-700' : 'border-gray-200';
34
- const textPrimary = isDark ? 'text-white' : 'text-gray-900';
35
- const textMuted = isDark ? 'text-slate-400' : 'text-gray-500';
36
- const hoverBg = isDark ? 'hover:bg-slate-700' : 'hover:bg-gray-50';
37
- // Fetch data
38
- const fetchRoles = (0, react_1.useCallback)(async () => {
39
- try {
40
- const res = await fetch(rolesEndpoint, { credentials: 'include' });
41
- if (res.ok) {
42
- const data = await res.json();
43
- setRoles(data.roles || data.idp_roles || []);
44
- }
45
- }
46
- catch (err) {
47
- console.error('Failed to fetch roles:', err);
48
- }
49
- }, [rolesEndpoint]);
50
- const fetchMatrix = (0, react_1.useCallback)(async () => {
51
- try {
52
- const res = await fetch(matrixEndpoint, { credentials: 'include' });
53
- if (res.ok) {
54
- const data = await res.json();
55
- setMatrixData(data);
56
- }
57
- }
58
- catch (err) {
59
- console.error('Failed to fetch matrix:', err);
60
- }
61
- }, [matrixEndpoint]);
62
- (0, react_1.useEffect)(() => {
63
- setLoading(true);
64
- Promise.all([fetchRoles(), fetchMatrix()])
65
- .finally(() => setLoading(false));
66
- }, [fetchRoles, fetchMatrix]);
67
- const toggleRoleExpanded = (roleName) => {
68
- setExpandedRole(expandedRole === roleName ? null : roleName);
22
+ // Mock data
23
+ const MOCK_ROLES = [
24
+ {
25
+ id: 1,
26
+ name: 'SiteAdmin',
27
+ description: 'System-wide administrator. Manages all vibe_app features.',
28
+ userCount: 1,
29
+ lastChanged: '3/10/2026 by Admin',
30
+ },
31
+ {
32
+ id: 2,
33
+ name: 'ClientAdmin',
34
+ description: 'Resume admin for Ideal Resume. Manages users, resumes, audit logs.',
35
+ userCount: 2,
36
+ lastChanged: '3/9/2026 by Admin',
37
+ },
38
+ ];
39
+ const MOCK_USERS = [
40
+ { id: 1, email: 'alice@example.com', role: 'ClientAdmin', assigned: '3/10/2026' },
41
+ { id: 2, email: 'bob@example.com', role: 'SiteAdmin', assigned: '2/28/2026' },
42
+ { id: 3, email: 'carol@example.com', role: null, assigned: null },
43
+ { id: 4, email: 'dave@example.com', role: 'ClientAdmin', assigned: '3/5/2026' },
44
+ { id: 5, email: 'eve@example.com', role: 'ClientAdmin', assigned: '3/8/2026' },
45
+ ];
46
+ const MOCK_CHANGES = [
47
+ { timestamp: '3/10/2026, 10:15 AM', event: 'Alice assigned to ClientAdmin by Admin User' },
48
+ { timestamp: '3/9/2026, 2:30 PM', event: 'Bob assigned to SiteAdmin by Admin User' },
49
+ { timestamp: '3/8/2026, 4:45 PM', event: 'Carol removed from ClientAdmin by Admin User' },
50
+ { timestamp: '3/8/2026, 3:00 PM', event: 'SiteAdmin edited: Description changed by Admin User' },
51
+ ];
52
+ function RolesAdminPage() {
53
+ const [users, setUsers] = (0, react_1.useState)(MOCK_USERS);
54
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
55
+ const [editingUserId, setEditingUserId] = (0, react_1.useState)(null);
56
+ const [tempRole, setTempRole] = (0, react_1.useState)(null);
57
+ const [message, setMessage] = (0, react_1.useState)(null);
58
+ const filteredUsers = users.filter((u) => u.email.toLowerCase().includes(searchQuery.toLowerCase()));
59
+ const handleEditRole = (userId, currentRole) => {
60
+ setEditingUserId(userId);
61
+ setTempRole(currentRole);
69
62
  };
70
- if (loading) {
71
- return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen ${bgColor} flex items-center justify-center`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center space-y-4", children: [(0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin h-8 w-8 text-blue-500", viewBox: "0 0 24 24", fill: "none", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })] }), (0, jsx_runtime_1.jsx)("p", { className: textMuted, children: "Loading roles..." })] }) }));
72
- }
73
- return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen ${bgColor}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `max-w-6xl mx-auto ${layout?.padding || 'p-6'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: `text-3xl font-bold ${textPrimary}`, children: "Role Permissions" }), (0, jsx_runtime_1.jsx)("p", { className: `mt-1 ${textMuted}`, children: "View IDP roles and their page access permissions" })] }), (0, jsx_runtime_1.jsx)("a", { href: "/admin", className: `text-sm hover:underline ${textMuted}`, children: "Back to Admin" })] }), (0, jsx_runtime_1.jsxs)("div", { className: `flex border-b ${borderColor} mb-6`, children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => setViewMode('list'), className: `px-4 py-3 font-medium text-sm border-b-2 transition-colors ${viewMode === 'list'
74
- ? 'border-blue-500 text-blue-500'
75
- : `border-transparent ${textMuted} hover:text-blue-400`}`, children: "Role List" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setViewMode('matrix'), className: `px-4 py-3 font-medium text-sm border-b-2 transition-colors ${viewMode === 'matrix'
76
- ? 'border-blue-500 text-blue-500'
77
- : `border-transparent ${textMuted} hover:text-blue-400`}`, children: "Permissions Matrix" })] }), viewMode === 'list' && ((0, jsx_runtime_1.jsxs)("div", { className: `${cardBg} border ${borderColor} rounded-lg overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-4 py-3 border-b ${borderColor}`, children: (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${textMuted}`, children: "Click a role to see what pages it grants access to" }) }), (0, jsx_runtime_1.jsx)("div", { className: `divide-y ${borderColor}`, children: roles.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: `p-8 text-center ${textMuted}`, children: "No roles found" })) : (roles.map((role) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("button", { onClick: () => toggleRoleExpanded(role.role_name), className: `w-full px-4 py-4 flex items-center justify-between ${hoverBg} transition-colors`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("svg", { className: `w-5 h-5 ${textMuted} transition-transform ${expandedRole === role.role_name ? 'rotate-90' : ''}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "text-left", children: [(0, jsx_runtime_1.jsx)("div", { className: `font-medium ${textPrimary}`, children: role.display_name || role.role_name }), (0, jsx_runtime_1.jsx)("div", { className: `text-sm ${textMuted}`, children: role.role_name })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)(components_1.RoleBadge, { roleName: "IDP", source: "idp", size: "sm", isDark: isDark }), (0, jsx_runtime_1.jsxs)("span", { className: `text-sm ${textMuted}`, children: [role.permission_count ?? role.permissions?.length ?? 0, " pages"] })] })] }), expandedRole === role.role_name && ((0, jsx_runtime_1.jsx)("div", { className: `px-4 pb-4 ${isDark ? 'bg-slate-800/50' : 'bg-gray-50'}`, children: (0, jsx_runtime_1.jsx)("div", { className: "pl-8", children: role.permissions && role.permissions.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-2 pt-2", children: role.permissions.map((perm) => ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center justify-between py-2 px-3 rounded ${isDark ? 'bg-slate-700/50' : 'bg-white'} border ${borderColor}`, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: `font-mono text-sm ${textPrimary}`, children: perm.route_pattern }), perm.display_name && ((0, jsx_runtime_1.jsxs)("span", { className: `ml-2 text-sm ${textMuted}`, children: ["- ", perm.display_name] }))] }), perm.requires_2fa && ((0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800", children: "2FA Required" }))] }, perm.page_permission_id))) })) : ((0, jsx_runtime_1.jsx)("p", { className: `py-4 text-sm ${textMuted}`, children: "No page permissions assigned to this role" })) }) }))] }, role.role_name)))) }), (0, jsx_runtime_1.jsxs)("div", { className: `px-4 py-3 border-t ${borderColor} ${textMuted} text-sm`, children: [roles.length, " roles total"] })] })), viewMode === 'matrix' && matrixData && ((0, jsx_runtime_1.jsxs)("div", { className: `${cardBg} border ${borderColor} rounded-lg overflow-hidden`, children: [(0, jsx_runtime_1.jsx)("div", { className: `px-4 py-3 border-b ${borderColor}`, children: (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${textMuted}`, children: "Read-only view of which roles have access to which pages" }) }), (0, jsx_runtime_1.jsx)("div", { className: "overflow-x-auto", children: (0, jsx_runtime_1.jsxs)("table", { className: "w-full", children: [(0, jsx_runtime_1.jsx)("thead", { className: isDark ? 'bg-slate-700/50' : 'bg-gray-50', children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-left text-sm font-medium ${textMuted} sticky left-0 ${isDark ? 'bg-slate-700' : 'bg-gray-50'}`, children: "Page" }), matrixData.roles.map((role) => ((0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-center text-sm font-medium ${textMuted} whitespace-nowrap`, children: role }, role))), (0, jsx_runtime_1.jsx)("th", { className: `px-4 py-3 text-center text-sm font-medium ${textMuted}`, children: "2FA" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { className: `divide-y ${borderColor}`, children: matrixData.pages.map((page) => ((0, jsx_runtime_1.jsxs)("tr", { className: hoverBg, children: [(0, jsx_runtime_1.jsx)("td", { className: `px-4 py-3 ${textPrimary} sticky left-0 ${cardBg}`, children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "font-mono text-sm", children: page.route_pattern }), page.display_name && ((0, jsx_runtime_1.jsx)("span", { className: `block text-xs ${textMuted}`, children: page.display_name }))] }) }), matrixData.roles.map((role) => ((0, jsx_runtime_1.jsx)("td", { className: "px-4 py-3 text-center", children: page.role_access[role] ? ((0, jsx_runtime_1.jsx)("svg", { className: "w-5 h-5 mx-auto text-green-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) })) : ((0, jsx_runtime_1.jsx)("svg", { className: `w-5 h-5 mx-auto ${textMuted}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })) }, role))), (0, jsx_runtime_1.jsx)("td", { className: "px-4 py-3 text-center", children: page.requires_2fa && ((0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800", children: "2FA" })) })] }, page.page_permission_id))) })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: `px-4 py-3 border-t ${borderColor} ${textMuted} text-sm flex items-center gap-4`, children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("svg", { className: "w-4 h-4 text-green-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }), "Access granted"] }), (0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("svg", { className: `w-4 h-4 ${textMuted}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }), "No access"] })] })] })), viewMode === 'matrix' && !matrixData && ((0, jsx_runtime_1.jsx)("div", { className: `${cardBg} border ${borderColor} rounded-lg p-8 text-center ${textMuted}`, children: "Unable to load permissions matrix" })), (0, jsx_runtime_1.jsx)("div", { className: `mt-6 p-4 rounded-lg ${isDark ? 'bg-blue-900/20 border border-blue-700' : 'bg-blue-50 border border-blue-200'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [(0, jsx_runtime_1.jsx)("svg", { className: `w-5 h-5 mt-0.5 ${isDark ? 'text-blue-400' : 'text-blue-600'}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: (0, jsx_runtime_1.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: `font-medium ${isDark ? 'text-blue-300' : 'text-blue-800'}`, children: "Roles are managed by your Identity Provider" }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm mt-1 ${isDark ? 'text-blue-400' : 'text-blue-600'}`, children: "Role assignments are controlled through your organization's IDP. Contact your system administrator to request role changes." })] })] }) })] }) }));
63
+ const handleSaveRole = (userId) => {
64
+ setUsers((prev) => prev.map((u) => u.id === userId ? { ...u, role: tempRole, assigned: '3/10/2026' } : u));
65
+ setMessage(`Role updated`);
66
+ setEditingUserId(null);
67
+ setTimeout(() => setMessage(null), 3000);
68
+ };
69
+ const handleRemoveRole = (userId) => {
70
+ setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, role: null, assigned: null } : u)));
71
+ setMessage(`Role removed`);
72
+ setTimeout(() => setMessage(null), 3000);
73
+ };
74
+ 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: {
75
+ fontSize: '32px',
76
+ fontWeight: 400,
77
+ color: '#333',
78
+ marginBottom: '8px',
79
+ }, children: "Role Management" }), (0, jsx_runtime_1.jsx)("p", { style: { fontSize: '16px', color: '#666', fontWeight: 400 }, children: "Manage who has access to what role" })] }), (0, jsx_runtime_1.jsx)("div", { style: { height: '1px', background: '#e0e0e0', margin: '24px 0' } }), (0, jsx_runtime_1.jsxs)("section", { style: { marginBottom: '60px' }, children: [(0, jsx_runtime_1.jsx)("h2", { style: {
80
+ fontSize: '18px',
81
+ fontWeight: 400,
82
+ color: '#666',
83
+ marginBottom: '24px',
84
+ textTransform: 'uppercase',
85
+ letterSpacing: '1px',
86
+ }, children: "Available Roles" }), (0, jsx_runtime_1.jsx)("div", { style: {
87
+ display: 'grid',
88
+ gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
89
+ gap: '24px',
90
+ }, children: MOCK_ROLES.map((role) => ((0, jsx_runtime_1.jsxs)("div", { style: {
91
+ background: 'white',
92
+ border: '1px solid #e0e0e0',
93
+ borderRadius: '6px',
94
+ padding: '20px',
95
+ display: 'flex',
96
+ flexDirection: 'column',
97
+ transition: 'all 0.2s ease',
98
+ cursor: 'default',
99
+ }, onMouseEnter: (e) => {
100
+ e.currentTarget.style.background = '#f9f9f9';
101
+ e.currentTarget.style.borderColor = '#d0d0d0';
102
+ }, onMouseLeave: (e) => {
103
+ e.currentTarget.style.background = 'white';
104
+ e.currentTarget.style.borderColor = '#e0e0e0';
105
+ }, children: [(0, jsx_runtime_1.jsx)("h3", { style: {
106
+ fontSize: '18px',
107
+ fontWeight: 600,
108
+ color: '#333',
109
+ marginBottom: '8px',
110
+ }, children: role.name }), (0, jsx_runtime_1.jsx)("p", { style: {
111
+ fontSize: '14px',
112
+ color: '#666',
113
+ marginBottom: '16px',
114
+ flex: 1,
115
+ lineHeight: 1.6,
116
+ }, children: role.description }), (0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '16px' }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { fontSize: '12px', color: '#999' }, children: ["Users: ", role.userCount] }), (0, jsx_runtime_1.jsxs)("div", { style: { fontSize: '12px', color: '#999' }, children: ["Last changed: ", role.lastChanged] })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '8px' }, children: [(0, jsx_runtime_1.jsx)("button", { style: {
117
+ padding: '8px 16px',
118
+ fontSize: '13px',
119
+ background: '#0066cc',
120
+ color: 'white',
121
+ border: 'none',
122
+ borderRadius: '4px',
123
+ cursor: 'pointer',
124
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#0052a3'), onMouseLeave: (e) => (e.currentTarget.style.background = '#0066cc'), children: "Edit" }), (0, jsx_runtime_1.jsx)("button", { style: {
125
+ padding: '8px 16px',
126
+ fontSize: '13px',
127
+ background: 'white',
128
+ color: '#333',
129
+ border: '1px solid #e0e0e0',
130
+ borderRadius: '4px',
131
+ cursor: 'pointer',
132
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "Remove" })] })] }, role.id))) })] }), (0, jsx_runtime_1.jsx)("div", { style: { height: '1px', background: '#e0e0e0', margin: '24px 0' } }), (0, jsx_runtime_1.jsxs)("section", { style: { marginBottom: '60px' }, children: [(0, jsx_runtime_1.jsx)("h2", { style: {
133
+ fontSize: '18px',
134
+ fontWeight: 400,
135
+ color: '#666',
136
+ marginBottom: '24px',
137
+ textTransform: 'uppercase',
138
+ letterSpacing: '1px',
139
+ }, children: "User Assignments" }), (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '24px' }, children: (0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search users...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), style: {
140
+ width: '100%',
141
+ padding: '10px 14px',
142
+ fontSize: '14px',
143
+ border: '1px solid #e0e0e0',
144
+ borderRadius: '4px',
145
+ background: 'white',
146
+ boxSizing: 'border-box',
147
+ } }) }), message && ((0, jsx_runtime_1.jsxs)("div", { style: {
148
+ padding: '8px 12px',
149
+ background: '#e8f5e9',
150
+ color: '#2e7d32',
151
+ borderRadius: '4px',
152
+ marginBottom: '12px',
153
+ fontSize: '13px',
154
+ }, children: ["\u2713 ", message] })), (0, jsx_runtime_1.jsxs)("table", { style: {
155
+ width: '100%',
156
+ borderCollapse: 'collapse',
157
+ background: 'white',
158
+ border: '1px solid #e0e0e0',
159
+ borderRadius: '4px',
160
+ overflow: 'hidden',
161
+ }, 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: {
162
+ padding: '16px',
163
+ textAlign: 'left',
164
+ fontSize: '12px',
165
+ color: '#999',
166
+ textTransform: 'uppercase',
167
+ letterSpacing: '0.5px',
168
+ fontWeight: 'normal',
169
+ }, children: "User" }), (0, jsx_runtime_1.jsx)("th", { style: {
170
+ padding: '16px',
171
+ textAlign: 'left',
172
+ fontSize: '12px',
173
+ color: '#999',
174
+ textTransform: 'uppercase',
175
+ letterSpacing: '0.5px',
176
+ fontWeight: 'normal',
177
+ }, children: "Role" }), (0, jsx_runtime_1.jsx)("th", { style: {
178
+ padding: '16px',
179
+ textAlign: 'left',
180
+ fontSize: '12px',
181
+ color: '#999',
182
+ textTransform: 'uppercase',
183
+ letterSpacing: '0.5px',
184
+ fontWeight: 'normal',
185
+ }, children: "Assigned" }), (0, jsx_runtime_1.jsx)("th", { style: {
186
+ padding: '16px',
187
+ textAlign: 'left',
188
+ fontSize: '12px',
189
+ color: '#999',
190
+ textTransform: 'uppercase',
191
+ letterSpacing: '0.5px',
192
+ fontWeight: 'normal',
193
+ }, children: "Actions" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: filteredUsers.map((user) => ((0, jsx_runtime_1.jsxs)("tr", { style: {
194
+ borderBottom: '1px solid #e0e0e0',
195
+ height: '48px',
196
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: [(0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '14px', color: '#333' }, children: user.email }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '14px' }, children: editingUserId === user.id ? ((0, jsx_runtime_1.jsxs)("select", { value: tempRole || '', onChange: (e) => setTempRole(e.target.value || null), style: {
197
+ padding: '6px 10px',
198
+ fontSize: '13px',
199
+ border: '1px solid #0066cc',
200
+ borderRadius: '4px',
201
+ background: 'white',
202
+ color: '#333',
203
+ }, children: [(0, jsx_runtime_1.jsx)("option", { value: "", children: "\u2014 Remove role \u2014" }), (0, jsx_runtime_1.jsx)("option", { value: "SiteAdmin", children: "SiteAdmin" }), (0, jsx_runtime_1.jsx)("option", { value: "ClientAdmin", children: "ClientAdmin" })] })) : user.role ? ((0, jsx_runtime_1.jsx)("span", { style: {
204
+ background: '#e3f2fd',
205
+ color: '#0066cc',
206
+ padding: '6px 10px',
207
+ borderRadius: '4px',
208
+ fontSize: '13px',
209
+ display: 'inline-block',
210
+ }, children: user.role })) : ((0, jsx_runtime_1.jsx)("span", { style: { color: '#999', fontStyle: 'italic' }, children: "(none)" })) }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '12px', color: '#999' }, children: user.assigned || '—' }), (0, jsx_runtime_1.jsx)("td", { style: { padding: '16px', fontSize: '13px' }, children: editingUserId === user.id ? ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '8px' }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: () => handleSaveRole(user.id), style: {
211
+ padding: '6px 12px',
212
+ background: '#0066cc',
213
+ color: 'white',
214
+ border: 'none',
215
+ borderRadius: '4px',
216
+ cursor: 'pointer',
217
+ fontSize: '12px',
218
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#0052a3'), onMouseLeave: (e) => (e.currentTarget.style.background = '#0066cc'), children: "Save" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setEditingUserId(null), style: {
219
+ padding: '6px 12px',
220
+ background: 'white',
221
+ color: '#333',
222
+ border: '1px solid #e0e0e0',
223
+ borderRadius: '4px',
224
+ cursor: 'pointer',
225
+ fontSize: '12px',
226
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "Cancel" })] })) : ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '8px' }, children: [user.role && ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleEditRole(user.id, user.role), style: {
227
+ padding: '6px 10px',
228
+ background: 'white',
229
+ color: '#0066cc',
230
+ border: '1px solid #e0e0e0',
231
+ borderRadius: '4px',
232
+ cursor: 'pointer',
233
+ fontSize: '12px',
234
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "\u2193" })), !user.role ? ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleEditRole(user.id, null), style: {
235
+ padding: '6px 10px',
236
+ background: 'white',
237
+ color: '#0066cc',
238
+ border: '1px solid #e0e0e0',
239
+ borderRadius: '4px',
240
+ cursor: 'pointer',
241
+ fontSize: '12px',
242
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#f5f5f5'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "+" })) : ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleRemoveRole(user.id), style: {
243
+ padding: '6px 10px',
244
+ background: 'white',
245
+ color: '#cc0000',
246
+ border: '1px solid #e0e0e0',
247
+ borderRadius: '4px',
248
+ cursor: 'pointer',
249
+ fontSize: '12px',
250
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = '#fff0f0'), onMouseLeave: (e) => (e.currentTarget.style.background = 'white'), children: "\u2715" }))] })) })] }, user.id))) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '12px', fontSize: '12px', color: '#999' }, children: [filteredUsers.length, " of ", users.length, " users 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: {
251
+ fontSize: '18px',
252
+ fontWeight: 400,
253
+ color: '#666',
254
+ marginBottom: '24px',
255
+ textTransform: 'uppercase',
256
+ letterSpacing: '1px',
257
+ }, 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: {
258
+ padding: '16px',
259
+ borderBottom: idx < MOCK_CHANGES.length - 1 ? '1px solid #e0e0e0' : 'none',
260
+ }, 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))) })] })] }) }));
78
261
  }
@@ -21,36 +21,7 @@ import { NextRequest, NextResponse } from 'next/server';
21
21
  * - Token expiry status
22
22
  * - Session validity
23
23
  */
24
- export declare function GET(req: NextRequest): Promise<NextResponse<{
25
- authenticated: boolean;
26
- message: string;
27
- }> | NextResponse<{
28
- user: {
29
- id: string | undefined;
30
- email: string | null | undefined;
31
- name: string | null | undefined;
32
- image: any;
33
- roles: string[];
34
- twoFactorSessionVerified: boolean;
35
- requiresTwoFactor: boolean;
36
- authenticationMethods: string[] | undefined;
37
- authenticationLevel: string | undefined;
38
- mfaCompletedAt: number | undefined;
39
- mfaExpiresAt: number | undefined;
40
- mfaValidityHours: number | undefined;
41
- oauthProvider: string | undefined;
42
- idpClientId: string | undefined;
43
- merchantId: string | undefined;
44
- };
45
- sessionToken: any;
46
- accessToken: string | undefined;
47
- refreshToken: string | undefined;
48
- accessTokenExpires: number | undefined;
49
- expires: string;
50
- }> | NextResponse<{
51
- error: string;
52
- details: string;
53
- }>>;
24
+ export declare function GET(req: NextRequest): Promise<NextResponse<{}>>;
54
25
  /**
55
26
  * POST /api/auth/session - Update session data
56
27
  *
@@ -52,10 +52,9 @@ async function GET(req) {
52
52
  const token = await (0, jwt_1.getToken)({ req, secret, cookieName });
53
53
  if (!token) {
54
54
  console.warn('[SESSION_ROUTE] getToken returned null');
55
- return server_1.NextResponse.json({
56
- authenticated: false,
57
- message: 'No session found'
58
- }, { status: 200 });
55
+ // MUST return empty {} — NextAuth's useSession() treats any non-empty
56
+ // response object as "authenticated", causing redirect loops on login page.
57
+ return server_1.NextResponse.json({});
59
58
  }
60
59
  // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
61
60
  const redisSessionId = token.sessionToken || token.redisSessionId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "3.6.0",
3
+ "version": "3.6.1",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -687,6 +687,11 @@
687
687
  "require": "./dist/components/account/UserAvatarMenu.js",
688
688
  "default": "./dist/components/account/UserAvatarMenu.js"
689
689
  },
690
+ "./components/account/MobileNavDrawer": {
691
+ "types": "./dist/components/account/MobileNavDrawer.d.ts",
692
+ "require": "./dist/components/account/MobileNavDrawer.js",
693
+ "default": "./dist/components/account/MobileNavDrawer.js"
694
+ },
690
695
  "./pages/security": {
691
696
  "import": "./dist/pages/security/index.js",
692
697
  "require": "./dist/pages/security/index.js",
@@ -187,6 +187,7 @@ export async function idpOAuthCallback(oauthData: {
187
187
  access_token: oauthData.accessToken || '',
188
188
  refresh_token: oauthData.refreshToken || '',
189
189
  expires_at: oauthData.expiresAt || 0,
190
+ client_id: clientId,
190
191
  }),
191
192
  });
192
193