feat(roles): project roles are Admin, Director, Student - #311
Merged
Conversation
PI and Accountant both become Director, Staff becomes Student, Admin is unchanged. Edit rights are unaffected: PI and Accountant could both edit, Staff could not, so editableRoles collapses from ['PI','Accountant','Admin'] to ['Director','Admin']. The migration is expand-only. It widens the CHECK to old ∪ new so the currently deployed code can keep writing 'PI'/'Accountant'/'Staff' during the window between the migration and the lambda deploy, then backfills the rows. A follow-up migration drops the three old names once this is live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
This PR contains a database migration
It will be applied to the production database automatically when this PR merges, before the new lambda code is deployed. Please confirm before requesting review:
|
Contributor
|
Database Types Check Complete The database schema files were modified, but the regenerated TypeScript types are identical to the existing ones. No changes were needed and the type definitions are already up to date. |
nourshoreibah
marked this pull request as ready for review
August 12, 2026 01:38
This was referenced Aug 12, 2026
nourshoreibah
added a commit
that referenced
this pull request
Aug 12, 2026
…load route The presigned upload route was written against the old PI/Accountant/Admin allow-list and git merged it cleanly over #311, so it silently kept roles that no longer exist. Any non-global-admin would have been refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nourshoreibah
added a commit
that referenced
this pull request
Aug 12, 2026
) * feat(expenses): admin approve/deny review flow with receipt upload Implements the Figma "Expenses Page" section (node 3545:34605). Frontend - Status column and pill: Approved / Pending / Needs Info, using the design's exact fills. `needs_more_info` was already permitted by the DB check constraint, so no enum change was needed. - New ReviewExpenseModal. Everyone sees the expense read-only; the Admin Decision pills and Admin Notes are rendered only for admins, and Save Changes is admin-only. - Table now matches the design: Expense ID, Date, Type of Expense, Project, Amount, Receipt, Status. Description was dropped; the project detail page hides Project via `showProject`. - Filters consolidated into one "Filter By" nested menu (Month / Project / Type / Status) plus "Clear Filters (n)". - The receipt is now actually uploaded. FileUpload previously ran a fake setInterval progress bar and the File was never sent anywhere, so receipt_url was always null. It now presigns, PUTs to S3 with real XHR progress, and passes the object URL through to the POST. - resetForm did not clear the selected file, so a cancelled modal reopened holding the previous receipt. Backend - GET /expenditures/upload-url presigns a PDF PUT under receipts/. - GET /expenditures/{id}/receipt presigns a short-lived GET, so the receipt does not depend on the bucket being publicly readable. - PATCH /expenditures/{id}/status accepts and persists adminNotes. - GET /expenditures/{id} returns the submitter and project names for the modal's "Submitted By". - validateExpenditureInput read body.receipt_url while every other field was camelCase; it now accepts receiptUrl and keeps the old key working. Infra - The shared lambda role had no S3 permissions, so a presigned PUT would have failed AccessDenied. Adds PutObject/GetObject. Also fixes a pre-existing `next build` failure: page modules may not have non-page exports, and both accounts/page.tsx and expenses/page.tsx did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(expenses): size the status pill and review modal from content The pill was pinned to Figma's 81x29, which clips longer labels such as Needs Info and the legacy denied fallback. 81px is now a min-width and the label drives the real width. Review modal now shrinks below its 485px Figma width, the field labels flex instead of sitting at a fixed 120px, and a long receipt filename truncates rather than pushing the actions off the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(expenses): use the Director/Admin project roles on the receipt upload route The presigned upload route was written against the old PI/Accountant/Admin allow-list and git merged it cleanly over #311, so it silently kept roles that no longer exist. Any non-global-admin would have been refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: regenerate lambda READMEs --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
nourshoreibah
added a commit
that referenced
this pull request
Aug 12, 2026
Resolves conflicts between the admin-only dashboard and main's expense approval flow (#315), project role rename (#311) and audit fixes (#310): - routes: /dashboard is admin-gated, /expenses is not. Main opened /expenses to non-admins because they submit and read their own expenses there; only the review modal's approve/deny is admin-gated. - accounts: both sides moved the staff roster out of page.tsx to satisfy the Next.js page-export rule. Kept main's mockUsers.ts and dropped the duplicate staff.ts. - Navbar/routes tests follow the same split. Co-authored-by: Cursor <cursoragent@cursor.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.
Project membership roles become Admin, Director, Student.
Mapping
AdminAdminPIDirectorAccountantDirectorStaffStudentPermissions are unchanged
PIandAccountantcould both edit;Staffcould not. SoeditableRolescollapses from['PI', 'Accountant', 'Admin']to['Director', 'Admin']and every existing authorization outcome is preserved.Studentis read-only on expenditures, matchingStafftoday. The globalusers.is_adminflag and the frontend nav roles (admin/standard/limited) are a separate mechanism and are untouched.The migration is expand-only
20260812011405_rename_project_roles.sqlwidens the CHECK to old ∪ new before backfilling, so the currently deployed code can keep writing'PI'/'Accountant'/'Staff'during the window between the migration and the lambda deploy. A follow-up migration drops the three old names once this is live — it is the contract half and needs-- allow-destructive:.One transient effect during that window: the still-deployed code checks
['PI','Accountant','Admin'], so a backfilledDirectorrow is denied edit (read is membership-only, so reads are unaffected) until the new lambdas land. Self-healing, no data impact.Verification
expenditures— 107 tests pass (unit + e2e)projects—projects.e2e28 pass,delete-authz.unit+dashboard.unit13 passdonors— 50 pass;health test 🌞fails, but it fails identically on unmodifiedmain(it fetcheslocalhost:3000/donors/health, which needs the lambda container up)apps/frontend—tsc --noEmitcleanDirector/ 1Studentshared/types/db-types.d.tsis unchanged —roleis stillVARCHAR(30)→string, so the generated types can't drift.Left alone deliberately
The
Staffheadings inProjectCard.tsxandProjectDetailClient.tsxare generic labels for the whole member list, not the role — renaming them to "Students" would mislabel Directors.🤖 Generated with Claude Code