Split Administrator into Bookings and a gated Management page (#64) - #67
Merged
Conversation
) Administrator had outgrown one tab strip: five tabs, one of which opened a second strip of five underneath it. Those five inner tabs also have a different audience -- they configure the system rather than run the week's bookings. Advanced Settings' sub-tabs (Users, Bodies, Audit, Archive, Other Settings) are now the main tabs of a new root page, Management, reachable only by the four high-access roles: EVP, VP of Operational Affairs, Digital Innovation Manager and Information Manager. That is the set already allowed to grant and revoke admin roles, so everything else on the page is downstream of a power they hold anyway; the other admin roles keep everything else. Administrator is renamed Bookings, in the sidebar and on the page itself. Its URL stays /administrator so existing links keep working. Supporting changes: - lib/admin-roles.ts holds the role lists in one place. It has no imports, so the client components can share it with the API route; ROLE_EDITORS in app/api/administrator/users/route.ts and users-tab.tsx were the same four names written twice, and are now that constant. - ShellIdentity carries adminRole, already present in the row it reads, so ManagementGuard resolves synchronously like AdminGuard does. - The booking forms' "create a semester in Advanced Settings" hint now points at Management -> Other Settings. ManagementGuard governs what the dashboard renders. The endpoints behind these tabs still authorize as they did before -- role editing is restricted to these same four roles, the rest accept any admin -- so narrowing them is a separate change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…64) Two follow-ups to the split. The route matches the name now: /administrator is /bookings, and the sidebar, ManagementGuard's fallback and the four cross-directory imports of its time-picker and booking-modal all point at the new path. next.config.ts redirects the old URL so year-old bookmarks keep resolving -- 307 rather than 308, because a permanent redirect is cached by the browser indefinitely and there is nothing here worth making that hard to walk back. Membership requests no longer appear in Pending Actions for admins who cannot resolve them. They are worked on Management > Users, so an admin outside MANAGEMENT_ROLES was being shown a task with nowhere to go. fetchPendingActions takes the caller's admin_role and skips the query outright rather than fetching rows to discard; omitting the role reads as "not a management role", so a caller that forgets to pass it hides the actions rather than leaking them. Its `now` parameter moved into that same options object. Nothing passed it, and two positional arguments where the second is a clock would have read badly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… admin ManagementGuard decides what the dashboard renders. The endpoints behind those tabs were still authorizing on `app_metadata.is_admin` alone, so a Comptroller or Digital Innovation Project Member could read the user list, the audit log and the archive, edit app settings and bodies, resolve membership requests, and invite users -- by calling the routes directly. The gate was a rendering decision. Each of those handlers now runs isManagementRole() against the live admin_role that getAuthedUserWithLiveRoles() resolves from the users row, and answers 403. Users (GET/POST/PATCH), user memberships, resend-invite, membership requests, audit logs, archive and settings are gated whole, because every caller of each is under /management. Bodies is the exception: only POST and PATCH are gated. GET stays open to any admin because the Bookings page reads it for its body picker, and that page is open to every admin by design. POST /api/administrator/users is a role-grant path, not just a read: it accepts an admin_role and writes it to both the users row and the auth metadata, so an ungated Comptroller could have invited themselves a second account as Executive Vice President. The membership-request routes are the pair 54f5b7a stopped surfacing in Pending Actions for admins outside MANAGEMENT_ROLES. That hid the task; this closes the endpoints it pointed at. With the route-wide check in place, the ROLE_EDITORS branch inside PATCH is unreachable, so it and the const come out. Role editing is unchanged; the is_active and full_name branches are now narrowed too, which matches the one place they are called from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #64.
Administrator had outgrown one tab strip: five tabs, one of which opened a second strip of five underneath it. Those inner five also have a different audience — they configure the system rather than run the week's bookings.
What changed
Management is a new root page. Advanced Settings' sub-tabs (Users, Bodies, Audit, Archive, Other Settings) are now its main tabs; the five components moved as-is, so they show up as renames.
advanced-settings-tab.tsxis gone.It is gated to the four high-access roles — EVP, VP of Operational Affairs, Digital Innovation Manager, Information Manager — through a new
ManagementGuard, built the same way asAdminGuard: the identity is already resolved on the server and shipped with the document, so the check is synchronous. That set is exactly the existingROLE_EDITORS, which is the point — those four already decide who is an admin at all, so everything else on the page is downstream of a power they hold anyway. Comptroller and Digital Innovation Project Member keep everything else.Administrator is renamed Bookings, and moved to
/bookings. The sidebar, the page heading,ManagementGuard's fallback, and the four cross-directory imports oftime-picker/booking-modalall follow.next.config.tsredirects/administrator→/bookingsso year-old bookmarks keep resolving — 307 rather than 308, since a permanent redirect is cached by the browser indefinitely and there is nothing here worth making that hard to walk back.Membership requests leave Pending Actions for admins who cannot resolve them. They are worked on Management > Users, so an admin outside the four roles was being shown a task with nowhere to go.
fetchPendingActions()takes the caller'sadmin_roleand skips the query outright rather than fetching rows to discard; omitting the role reads as "not a management role", so a caller that forgets to pass it hides the actions rather than leaking them. Itsnowparameter moved into that same options object — nothing was passing it.Supporting changes
lib/admin-roles.tsholds the role lists in one place. It has no imports, so client components can share it with the API route;ROLE_EDITORSwas the same four names written twice.ShellIdentitycarriesadminRole, already present in the rowresolveShellIdentity()reads.Scope
ManagementGuardgoverns what the dashboard renders. The endpoints behind these tabs still authorize as they did before — role editing is restricted to these same four roles, the rest accept any admin — so this is not a regression, but it is not enforcement either. Narrowing them is a separate change, with the caveat thatGET /api/administrator/bodiesmust stay open: the Bookings page uses it for its body picker.Verification
npx tsc --noEmitclean andnpm run buildsucceeds. Against a dev server,/administrator307s to/bookings, and/bookingsand/managementboth resolve (307 to login when signed out).npm run lintreports only the errors the moved files already had ondev. The authenticated admin views were not exercised in a browser.Note
This branch and the #63/#65 branch both touch
lib/pending-actions.ts— different hunks, but whichever merges second is worth a rebase check.🤖 Generated with Claude Code