feat(org): admin seat override and plan tier control#2943
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
WalkthroughAdds a nullable ChangesManual seat limit override
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant ManageOrgDialog
participant manageOrganization
participant AdminAPI
participant Database
Admin->>ManageOrgDialog: edit plan and seats
ManageOrgDialog->>manageOrganization: onSave({plan, seats})
manageOrganization->>AdminAPI: PATCH /organizations/{orgId}/manage
AdminAPI->>Database: update plan and seats, write audit event
Database-->>AdminAPI: updated organization
AdminAPI-->>manageOrganization: {plan, seats}
manageOrganization-->>ManageOrgDialog: {success}
ManageOrgDialog->>Admin: router.refresh()
sequenceDiagram
participant Client
participant TeamRoute
participant resolveSeatLimit
participant Database
Client->>TeamRoute: GET /members/{organizationId}
TeamRoute->>Database: fetch organization plan and seats
Database-->>TeamRoute: {plan, seats}
TeamRoute->>resolveSeatLimit: resolveSeatLimit(plan, seats)
resolveSeatLimit-->>TeamRoute: seatLimit
TeamRoute-->>Client: {members, seatLimit}
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ebadfe1 to
f442f5d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ui/src/app/dashboard/[orgId]/org/team/team-client.tsx (1)
869-871: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winAdd Member button still uses hardcoded seat limit of 5.
The dialog copy and card description were updated to use
data?.seatLimit ?? 5, but the button'sdisabledcondition on line 869 still checks against a hardcoded5. If an admin overrides the seat limit to a higher value (e.g., 100), the button will disable at 5 members, preventing further additions despite available seats. This directly contradicts the PR's goal of dynamic seat enforcement.🐛 Proposed fix
- <Button disabled={(data?.members.length ?? 0) >= 5}> + <Button disabled={(data?.members.length ?? 0) >= (data?.seatLimit ?? 5)}>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/ui/src/app/dashboard/`[orgId]/org/team/team-client.tsx around lines 869 - 871, The Add Member button in team-client.tsx still uses a hardcoded 5-seat cap in its disabled logic, so it can block additions even when the org’s seat limit is higher. Update the Button’s disabled condition to use the same dynamic seat limit source used elsewhere in this view, namely data?.seatLimit ?? 5, so the add-member behavior stays consistent with the dialog copy and card description.
🧹 Nitpick comments (1)
ee/admin/src/app/organizations/[orgId]/manage-org-dialog.tsx (1)
39-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
PLAN_DEFAULT_SEATSduplicates backend plan defaults.If the backend plan defaults change, this UI constant will be out of sync, showing incorrect placeholder text. Consider deriving these defaults from the API or a shared constant.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ee/admin/src/app/organizations/`[orgId]/manage-org-dialog.tsx around lines 39 - 43, The PLAN_DEFAULT_SEATS constant in manage-org-dialog should not hardcode plan defaults because it can drift from backend values. Update the org management dialog to derive default seats from the API response or a shared plan-default source instead of the local PLAN_DEFAULT_SEATS map, and make sure the place that renders the seat placeholder uses that shared value in the manage-org dialog flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ee/admin/src/app/organizations/`[orgId]/manage-org-dialog.tsx:
- Around line 55-60: The form state in ManageOrgDialog is persisting across
close/reopen because the component stays mounted and only DialogContent is
conditional. Update the dialog open handling in ManageOrgDialog (especially
onOpenChange) so planValue and seatsValue are reset back to the current plan and
seats props whenever the dialog opens, ensuring unsaved edits are cleared before
the user sees the form again.
---
Outside diff comments:
In `@apps/ui/src/app/dashboard/`[orgId]/org/team/team-client.tsx:
- Around line 869-871: The Add Member button in team-client.tsx still uses a
hardcoded 5-seat cap in its disabled logic, so it can block additions even when
the org’s seat limit is higher. Update the Button’s disabled condition to use
the same dynamic seat limit source used elsewhere in this view, namely
data?.seatLimit ?? 5, so the add-member behavior stays consistent with the
dialog copy and card description.
---
Nitpick comments:
In `@ee/admin/src/app/organizations/`[orgId]/manage-org-dialog.tsx:
- Around line 39-43: The PLAN_DEFAULT_SEATS constant in manage-org-dialog should
not hardcode plan defaults because it can drift from backend values. Update the
org management dialog to derive default seats from the API response or a shared
plan-default source instead of the local PLAN_DEFAULT_SEATS map, and make sure
the place that renders the seat placeholder uses that shared value in the
manage-org dialog flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0306bd7c-ad63-4d4d-aca4-0493b3848f3d
📒 Files selected for processing (12)
apps/api/src/routes/admin.tsapps/api/src/routes/organization.tsapps/api/src/routes/team.tsapps/gateway/src/lib/rate-limit.spec.tsapps/ui/src/app/dashboard/[orgId]/org/team/team-client.tsxee/admin/src/app/organizations/[orgId]/manage-org-dialog.tsxee/admin/src/app/organizations/[orgId]/page.tsxee/admin/src/lib/admin-organizations.tspackages/db/migrations/1783469250_flowery_puppet_master.sqlpackages/db/migrations/meta/1783469250_snapshot.jsonpackages/db/migrations/meta/_journal.jsonpackages/db/src/schema.ts
| const [planValue, setPlanValue] = useState<Plan>( | ||
| plan === "pro" || plan === "enterprise" ? plan : "free", | ||
| ); | ||
| const [seatsValue, setSeatsValue] = useState( | ||
| seats === null ? "" : String(seats), | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Form state persists across dialog open/close cycles.
Since ManageOrgDialog stays mounted and only DialogContent is conditionally rendered, useState values survive close/reopen. If an admin changes the plan or seats, closes without saving, and reopens, they'll see their unsaved edits rather than the current org values. Consider resetting state in onOpenChange when the dialog opens.
💚 Proposed fix
<Dialog open={open} onOpenChange={(v) => {
setOpen(v);
+ if (v) {
+ setPlanValue(plan === "pro" || plan === "enterprise" ? plan : "free");
+ setSeatsValue(seats === null ? "" : String(seats));
+ setError(null);
+ }
}}>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ee/admin/src/app/organizations/`[orgId]/manage-org-dialog.tsx around lines 55
- 60, The form state in ManageOrgDialog is persisting across close/reopen
because the component stays mounted and only DialogContent is conditional.
Update the dialog open handling in ManageOrgDialog (especially onOpenChange) so
planValue and seatsValue are reset back to the current plan and seats props
whenever the dialog opens, ensuring unsaved edits are cleared before the user
sees the form again.
Summary
Adds a nullable
seatscolumn to the organization and a Manage org dialog in the admin dashboard where we can override an org's seat limit and change its plan tier (typically free ⇄ enterprise).The seat limit continues to fall back to the per-plan default (free/pro = 5, enterprise = 100), but an explicit
seatsoverride takes precedence everywhere it is displayed and enforced.Changes
organization.seatscolumn (null = use plan default) + migration.apps/api/src/routes/team.ts): the add-member seat cap now uses a sharedresolveSeatLimit(plan, seats)helper —seats ?? (plan === "enterprise" ? 100 : 5).GET /{orgId}/membersresponse now returns the effectiveseatLimit, and the Team page showsX/<seatLimit> seats usedinstead of a hardcoded5.PATCH /admin/organizations/{orgId}/manageroute (plan + seats), amanageOrganizationserver action, and a Manage org dialog on the org detail page. ASeats: Nbadge shows when an override is set. The change is written to the audit log asorganization.manage.Notes
60/50used, which is acceptable.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Chores