@payez/next-mvp 3.5.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.
Files changed (32) hide show
  1. package/dist/api-handlers/admin/index.d.ts +1 -0
  2. package/dist/api-handlers/admin/index.js +3 -1
  3. package/dist/api-handlers/admin/stats.d.ts +21 -0
  4. package/dist/api-handlers/admin/stats.js +240 -0
  5. package/dist/auth/utils/idp-client.js +1 -0
  6. package/dist/components/account/MobileNavDrawer.d.ts +32 -0
  7. package/dist/components/account/MobileNavDrawer.js +81 -0
  8. package/dist/components/account/UserAvatarMenu.js +5 -1
  9. package/dist/components/account/index.d.ts +2 -0
  10. package/dist/components/account/index.js +5 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +2 -1
  13. package/dist/pages/admin-page-permissions/PagePermissionsAdminPage.d.ts +18 -0
  14. package/dist/pages/admin-page-permissions/PagePermissionsAdminPage.js +276 -0
  15. package/dist/pages/admin-page-permissions/index.d.ts +6 -0
  16. package/dist/pages/admin-page-permissions/index.js +13 -0
  17. package/dist/pages/admin-roles/RolesAdminPage.d.ts +12 -11
  18. package/dist/pages/admin-roles/RolesAdminPage.js +249 -66
  19. package/dist/routes/auth/session.d.ts +1 -30
  20. package/dist/routes/auth/session.js +3 -4
  21. package/package.json +6 -1
  22. package/src/api-handlers/admin/index.ts +5 -0
  23. package/src/api-handlers/admin/stats.ts +240 -0
  24. package/src/auth/utils/idp-client.ts +1 -0
  25. package/src/components/account/MobileNavDrawer.tsx +305 -0
  26. package/src/components/account/UserAvatarMenu.tsx +47 -17
  27. package/src/components/account/index.ts +5 -0
  28. package/src/index.ts +2 -2
  29. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -0
  30. package/src/pages/admin-page-permissions/index.ts +7 -0
  31. package/src/pages/admin-roles/RolesAdminPage.tsx +494 -318
  32. package/src/routes/auth/session.ts +3 -4
@@ -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;