feat(platform): org management UI — create org, settings, members, invitations - #13496
feat(platform): org management UI — create org, settings, members, invitations#13496ntindle wants to merge 11 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds organization settings with profile, member, invitation, deletion, and personal-organization views. Adds organization creation controls, org/team request headers, invitee-facing invitation responses, navigation wiring, and integration tests. ChangesOrganization Settings and Invitations
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant OrgTeamSwitcher
participant CreateOrgDialog
participant usePostV2CreateOrganization
participant useOrgTeamStore
User->>OrgTeamSwitcher: Select Create organization
OrgTeamSwitcher->>CreateOrgDialog: Open dialog
User->>CreateOrgDialog: Submit organization details
CreateOrgDialog->>usePostV2CreateOrganization: Create organization
usePostV2CreateOrganization-->>CreateOrgDialog: Return organization
CreateOrgDialog->>useOrgTeamStore: Add organization and set active org
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 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 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feat/invitation-resend #13496 +/- ##
=======================================================
Coverage 80.55% 80.55%
=======================================================
Files 3335 3335
Lines 256343 256346 +3
Branches 23643 23642 -1
=======================================================
+ Hits 206489 206504 +15
+ Misses 44544 44535 -9
+ Partials 5310 5307 -3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
|
Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly. |
🔍 PR Overlap DetectionThis check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early. 🔴 Merge Conflicts DetectedThe following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.
🟡 Medium Risk — Some Line OverlapThese PRs have some overlapping changes:
🟢 Low Risk — File Overlap OnlyThese PRs touch the same files but different sections (click to expand)
Summary: 7 conflict(s), 5 medium risk, 7 low risk (out of 19 PRs with file overlap) Auto-generated on push. Ignores: |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
autogpt_platform/backend/backend/api/features/orgs/model.py (1)
187-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a type annotation for the
invparameter infrom_db.The
invparameter lacks a type hint, which is a type-safety gap. The Prisma model type (e.g.,prisma.models.OrgInvitation) should be used so static analysis can verify attribute access.♻️ Proposed type annotation
- def from_db(inv) -> "UserInvitationResponse": + def from_db(inv: prisma.models.OrgInvitation) -> "UserInvitationResponse":🤖 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 `@autogpt_platform/backend/backend/api/features/orgs/model.py` around lines 187 - 188, The `UserInvitationResponse.from_db` static method currently accepts an untyped `inv` parameter, creating a type-safety gap. Add a concrete Prisma model type annotation for `inv` in `from_db` (for example, the `OrgInvitation` model from `prisma.models`) so static analysis can validate the attribute access used inside the method. Keep the change aligned with the existing `UserInvitationResponse` implementation and related invitation model fields.Source: Coding guidelines
autogpt_platform/frontend/src/app/(platform)/settings/organization/components/MyInvitationsSection/MyInvitationsSection.tsx (1)
41-55: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winShared loading state across all invitation rows.
isAcceptingandisDecliningare global mutation flags from the hook — when a user clicks Accept on one invitation, all Accept buttons across every row show loading simultaneously (and likewise for Decline). Consider tracking the specific invitation being acted on to scope the loading indicator to the correct row.♻️ Proposed approach: track active invitation ID
export function useMyInvitationsSection() { const { setActiveOrg } = useOrgTeamStore(); + const [acceptingId, setAcceptingId] = useState<string | null>(null); + const [decliningId, setDecliningId] = useState<string | null>(null); // ... async function handleAccept(invitation: UserInvitationResponse) { + setAcceptingId(invitation.id); await acceptInvitation({ token: invitation.token }); + setAcceptingId(null); // ... } async function handleDecline(invitation: UserInvitationResponse) { + setDecliningId(invitation.id); await declineInvitation({ token: invitation.token }); + setDecliningId(null); // ... } return { invitations: invitationsQuery.data ?? [], - isAccepting, - isDeclining, + acceptingId, + decliningId, handleAccept, handleDecline, }; }Then in the component:
- <Button - size="small" - loading={isAccepting} - onClick={() => handleAccept(invitation)} - > + <Button + size="small" + loading={acceptingId === invitation.id} + onClick={() => handleAccept(invitation)} + > Accept </Button> <Button variant="secondary" size="small" - loading={isDeclining} + loading={decliningId === invitation.id} onClick={() => handleDecline(invitation)} >🤖 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 `@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/MyInvitationsSection/MyInvitationsSection.tsx around lines 41 - 55, The Accept/Decline buttons in MyInvitationsSection are using shared mutation flags, so loading state appears on every row instead of just the clicked invitation. Update the invitation action flow in MyInvitationsSection and its handlers (handleAccept, handleDecline) to track the active invitation ID locally, and derive each Button’s loading prop from whether that row matches the current active invitation. Reset the active ID when the mutation finishes or fails so only the targeted row shows loading.autogpt_platform/frontend/src/app/(platform)/settings/organization/__tests__/page.test.tsx (1)
1-268: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for the organization deletion flow.
The danger zone section performs an irreversible deletion, updates the org store, switches the active org, and resets queries — none of which are covered by the existing tests. Would you like me to generate a test that verifies the delete confirmation dialog, successful deletion, and active org switching?
🤖 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 `@autogpt_platform/frontend/src/app/`(platform)/settings/organization/__tests__/page.test.tsx around lines 1 - 268, The OrganizationSettingsPage test suite is missing coverage for the danger-zone deletion flow, including confirmation, store updates, active org switching, and query resets. Add a test alongside the existing OrganizationSettingsPage cases that renders the delete UI, opens the confirmation dialog from the org-danger-zone section, confirms deletion, and asserts the deletion handler is called while useOrgTeamStore updates the active org and resets state as expected.Source: Learnings
autogpt_platform/frontend/src/app/(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsx (2)
20-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract logic to
useDangerZoneSection.tsfor consistency with other sections.
OrgProfileSection,MembersSection, andInvitationsSectioneach have a dedicateduse<Component>.tshook, butDangerZoneSectionkeeps state, mutation setup, and the handler inline. Extracting these touseDangerZoneSection.tsfollows theComponentName.tsx + useComponentName.tspattern and keeps the render function under the ~50-line guideline.🤖 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 `@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsx around lines 20 - 110, DangerZoneSection currently keeps its local state, mutation setup, and delete handler inline instead of following the same component/hook split used by OrgProfileSection, MembersSection, and InvitationsSection. Extract the logic from DangerZoneSection into a new useDangerZoneSection hook, moving the org deletion mutation, confirmation state, and handleDeleteConfirmed behavior there, then have DangerZoneSection consume the hook so the component stays focused on rendering and matches the existing Component + useComponent pattern.Source: Coding guidelines
49-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid resetting all queries — target org-related queries only.
resetQueries()with no arguments resets every cached query in the app, causing unnecessary refetches and loading flicker in unrelated features. Invalidate or remove only organization-scoped queries instead.♻️ Proposed fix
- getQueryClient().resetQueries(); + const queryClient = getQueryClient(); + queryClient.removeQueries({ queryKey: ["orgs", org.id] }); + queryClient.invalidateQueries({ queryKey: ["orgs"] });Verify the exact query-key structure used by the generated hooks and adjust the key accordingly.
🤖 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 `@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsx at line 49, The DangerZoneSection cleanup currently calls getQueryClient().resetQueries() with no key, which resets the entire app cache. Update the organization deletion flow in DangerZoneSection to target only org-scoped queries by using the exact query-key structure from the generated hooks, and invalidate or remove only those organization-related entries instead of resetting everything.
🤖 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
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsx:
- Around line 41-52: Wrap the delete flow in
DangerZoneSection.handleDeleteConfirmed with try/catch so failures from
deleteOrg({ orgId: org.id }) do not become unhandled promise rejections. Keep
the existing success path (updating orgs, setting active org, resetting queries,
showing the success toast, closing the modal) inside the try block, and handle
the error in the catch path with appropriate failure handling.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/InvitationsSection/InvitationsSection.tsx:
- Around line 109-116: The revoke action currently uses a single shared loading
flag, so every Revoke button in InvitationsSection shows loading when one
invitation is being revoked. Update the hook in useInvitationsSection.ts to
track the specific invitation ID being revoked instead of a global boolean, and
expose that state for the UI. Then update InvitationsSection and the Button
rendering to compare each invitation’s id against the revoking ID so only the
matching row shows loading while handleRevoke(invitation) is in progress.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/OrgProfileSection/useOrgProfileSection.ts:
- Around line 81-87: The `handleSubmit` update in `useOrgProfileSection` is
mapping over a stale `orgs` snapshot captured from the store, which can
overwrite newer org changes. Update the `setOrgs` call to use the latest store
state at write time, preferably via a functional updater if `setOrgs` supports
it, or by reading the current org list from the store immediately before
applying the `orgs.map` merge. Keep the merge logic keyed on `updated.id` so
only the matching org gets `name` and `slug` refreshed.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/useOrganizationSettingsPage.ts:
- Around line 37-40: The organization settings state only treats orgQuery
failures as errors, so update useOrganizationSettingsPage to include
membersQuery.isError in the returned error state and surface the
membersQuery.error when appropriate. Then adjust the ErrorCard retry handling in
page.tsx so the retry action calls both refetchOrg and refetchMembers, using the
existing useOrganizationSettingsPage and page-level refetch helpers to locate
the change.
In
`@autogpt_platform/frontend/src/components/contextual/CreateOrgDialog/schema.ts`:
- Around line 27-33: The slugify helper in CreateOrgDialog/schema.ts can return
a value ending with a dash after truncation, which then fails the org slug
validation regex. Update slugify so the final result is re-trimmed after the
slice operation, ensuring any trailing dashes introduced by truncation are
removed before validation.
---
Nitpick comments:
In `@autogpt_platform/backend/backend/api/features/orgs/model.py`:
- Around line 187-188: The `UserInvitationResponse.from_db` static method
currently accepts an untyped `inv` parameter, creating a type-safety gap. Add a
concrete Prisma model type annotation for `inv` in `from_db` (for example, the
`OrgInvitation` model from `prisma.models`) so static analysis can validate the
attribute access used inside the method. Keep the change aligned with the
existing `UserInvitationResponse` implementation and related invitation model
fields.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/__tests__/page.test.tsx:
- Around line 1-268: The OrganizationSettingsPage test suite is missing coverage
for the danger-zone deletion flow, including confirmation, store updates, active
org switching, and query resets. Add a test alongside the existing
OrganizationSettingsPage cases that renders the delete UI, opens the
confirmation dialog from the org-danger-zone section, confirms deletion, and
asserts the deletion handler is called while useOrgTeamStore updates the active
org and resets state as expected.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsx:
- Around line 20-110: DangerZoneSection currently keeps its local state,
mutation setup, and delete handler inline instead of following the same
component/hook split used by OrgProfileSection, MembersSection, and
InvitationsSection. Extract the logic from DangerZoneSection into a new
useDangerZoneSection hook, moving the org deletion mutation, confirmation state,
and handleDeleteConfirmed behavior there, then have DangerZoneSection consume
the hook so the component stays focused on rendering and matches the existing
Component + useComponent pattern.
- Line 49: The DangerZoneSection cleanup currently calls
getQueryClient().resetQueries() with no key, which resets the entire app cache.
Update the organization deletion flow in DangerZoneSection to target only
org-scoped queries by using the exact query-key structure from the generated
hooks, and invalidate or remove only those organization-related entries instead
of resetting everything.
In
`@autogpt_platform/frontend/src/app/`(platform)/settings/organization/components/MyInvitationsSection/MyInvitationsSection.tsx:
- Around line 41-55: The Accept/Decline buttons in MyInvitationsSection are
using shared mutation flags, so loading state appears on every row instead of
just the clicked invitation. Update the invitation action flow in
MyInvitationsSection and its handlers (handleAccept, handleDecline) to track the
active invitation ID locally, and derive each Button’s loading prop from whether
that row matches the current active invitation. Reset the active ID when the
mutation finishes or fails so only the targeted row shows loading.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d32ef363-ece9-490d-9a7b-1b4807239cec
📒 Files selected for processing (26)
autogpt_platform/backend/backend/api/features/orgs/invitation_routes.pyautogpt_platform/backend/backend/api/features/orgs/model.pyautogpt_platform/frontend/src/app/(platform)/settings/components/SettingsSidebar/helpers.tsautogpt_platform/frontend/src/app/(platform)/settings/organization/__tests__/page.test.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/DangerZoneSection/DangerZoneSection.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/InvitationsSection/InvitationsSection.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/InvitationsSection/useInvitationsSection.tsautogpt_platform/frontend/src/app/(platform)/settings/organization/components/MembersSection/MembersSection.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/MembersSection/useMembersSection.tsautogpt_platform/frontend/src/app/(platform)/settings/organization/components/MyInvitationsSection/MyInvitationsSection.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/MyInvitationsSection/useMyInvitationsSection.tsautogpt_platform/frontend/src/app/(platform)/settings/organization/components/OrgProfileSection/OrgProfileSection.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/components/OrgProfileSection/useOrgProfileSection.tsautogpt_platform/frontend/src/app/(platform)/settings/organization/page.tsxautogpt_platform/frontend/src/app/(platform)/settings/organization/useOrganizationSettingsPage.tsautogpt_platform/frontend/src/app/api/mutators/custom-mutator.tsautogpt_platform/frontend/src/app/api/openapi.jsonautogpt_platform/frontend/src/app/api/proxy/[...path]/route.tsautogpt_platform/frontend/src/components/contextual/CreateOrgDialog/CreateOrgDialog.tsxautogpt_platform/frontend/src/components/contextual/CreateOrgDialog/schema.tsautogpt_platform/frontend/src/components/contextual/CreateOrgDialog/useCreateOrgDialog.tsautogpt_platform/frontend/src/components/layout/AppSidebar/components/SidebarOrgSwitcher/SidebarOrgSwitcher.tsxautogpt_platform/frontend/src/components/layout/AppSidebar/components/SidebarOrgSwitcher/__tests__/SidebarOrgSwitcher.test.tsxautogpt_platform/frontend/src/components/layout/Navbar/components/OrgTeamSwitcher/OrgTeamSwitcher.tsxautogpt_platform/frontend/src/components/layout/Navbar/components/OrgTeamSwitcher/__tests__/OrgTeamSwitcher.test.tsxautogpt_platform/frontend/src/services/org-team/headers.ts
|
!deploy |
|
🚀 Deploying PR #13496 to development environment... |
|
✅ Preview environment is live (all services healthy)
Push more commits, then comment |
|
!deploy |
|
🚀 Deploying PR #13496 to development environment... |
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
66e8a9d to
d41a246
Compare
|
Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly. |
| { | ||
| label: "Organization", | ||
| href: "/settings/organization", | ||
| Icon: BuildingIcon, | ||
| }, |
There was a problem hiding this comment.
Bug: The "Organization" settings nav item and page are unconditionally visible, bypassing the SHOW_ORG_SETTINGS feature flag because the flag is not checked in the sidebar or on the page itself.
Severity: MEDIUM
Suggested Fix
Add flag: Flag.SHOW_ORG_SETTINGS to the "Organization" item in settingsNavItems in helpers.ts. Then, update the filter logic in useSettingsSidebar.ts to correctly check for all feature flags, not just GRAPHITI_MEMORY. Finally, add a feature flag guard to the organization settings page (page.tsx) to prevent direct access.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location:
autogpt_platform/frontend/src/app/(platform)/settings/components/SettingsSidebar/helpers.ts#L36-L40
Potential issue: The "Organization" navigation item in `settingsNavItems` is defined
without the `flag: Flag.SHOW_ORG_SETTINGS` property. Furthermore, the filter in
`useSettingsSidebar` only gates items based on `Flag.GRAPHITI_MEMORY`, not other flags.
Consequently, the "Organization" item always appears in the settings sidebar. The
corresponding page at `/settings/organization` also lacks a feature flag guard, making
it unconditionally accessible. This exposes an incomplete UI to all users, contrary to
the stated goal of keeping it behind the `SHOW_ORG_SETTINGS` flag.
Also affects:
autogpt_platform/frontend/src/app/(platform)/settings/organization/page.tsx:1
d41a246 to
f8fb77d
Compare
…s, invitations
Stacked on feat/org-workspace-pr1 (backend tenancy). Builds the org
management surface so orgs can be created and administered from the app:
Plumbing (gaps the UI needs):
- X-Org-Id/X-Team-Id header injection: the switcher previously only reset
queries — every request still resolved to the personal-org fallback.
getOrgContextHeaders() feeds the orval mutator from the org/team store,
and the Next proxy forwards both headers (backend validates membership).
- GET /invitations/pending returns UserInvitationResponse with the accept/
decline token (safe: filtered to the caller's own email) and the
inviting org's name/slug — without these an invitee cannot act on an
invitation from the UI.
UI:
- CreateOrgDialog (design-system Dialog + Form + zod, slug auto-derived
from name) wired into BOTH switchers; on success the store gains the
org and switches to it.
- /settings/organization page (nav entry + already-protected route):
- MyInvitationsSection: pending invitations banner with Accept (switches
into the org) / Decline.
- OrgProfileSection: name/slug/description editing (admin-gated).
- MembersSection: member list, role select (admin/member), remove with
confirmation; owner and self protected.
- InvitationsSection: invite-by-email with admin toggle, pending list
with revoke (admin-only).
- DangerZoneSection: delete org (owner-only, confirm dialog, switches
back to personal org).
- Switcher menus gain "Manage organization" (replaces the dead /org/teams
link) and "Create organization".
Tests: 7-page integration suite (owner/member/personal-org gating, invite,
accept-and-switch, remove-member, profile update) + create-org submit flow
+ updated switcher suites. Full unit run: 3725 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm3mCG9okfdGtAXtFaDF9A
The destructured defaults inferred never[] for the invitation/member arrays (the CI typecheck runs after regenerating the client, and this file was written after the local gate had already run), and prettier wanted the file reformatted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm3mCG9okfdGtAXtFaDF9A
…Response The mock invitation never set orgId/Org.name/Org.slug, which the new invitee-facing response model reads; assertions still checked the removed email field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm3mCG9okfdGtAXtFaDF9A
…s are not context switches)
Dev added AccountMenuOrgList consuming the old useOrgTeamSwitcher shape
({orgs, teams, activeOrg, activeTeam, switchOrg, switchTeam}). This stack
slims the hook to {orgs, activeOrg, switchOrg, isLoaded} because
teams-as-context-switches is a retired product model — teams are managed
via the org-settings teams tab, not switched into from the account menu.
- Consume only {orgs, activeOrg, switchOrg, isLoaded}; render null until loaded.
- Delete the teams section (teams list, switchTeam, Manage teams link).
- Wire the Create organization button to CreateOrgDialog (open-state + dialog),
matching OrgTeamSwitcher, replacing dev's TODO stub. Keep the data-testid.
- Rewrite tests for the orgs-only shape + dialog-open + no-teams assertions.
Co-Authored-By: Claude Opus <noreply@anthropic.com>
…Better Auth useAuth Better Auth (#13330) removed @/lib/supabase/hooks/useSupabase; swap to useAuth (same { user } shape). Unblocks types on the org branches post-dev-merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…eAuth Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBwmh7CkHiF8vKLBf2GWTU
…g org store GET /api/orgs returns snake_case fields (is_personal, avatar_url, member_count) but the org-team zustand store and all its consumers expect camelCase. OrgTeamProvider fed the raw response straight into setOrgs, leaving isPersonal/avatarUrl/memberCount undefined: the "Personal" badge never rendered in the org switcher, avatars were blank, and DangerZoneSection's personal-org fallback after deleting an org silently fell through to an arbitrary org. Bug confirmed live via E2E validation against the running platform. - Extract a shared normalizeOrg() mapper (services/org-team/normalize.ts) - Use it in both OrgTeamProvider.loadOrgs and CreateOrgDialog so the two store-population paths cannot drift - Update OrgTeamProvider tests to mock the real snake_case wire shape (the camelCase mocks were masking the bug) and assert normalized store state Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…verride The LaunchDarkly key does not exist yet, so the "LD never answers" path is the only path in production today. Pin it: SHOW_ORG_SETTINGS must resolve false when LD is silent, and NEXT_PUBLIC_FORCE_FLAG_SHOW_ORG_SETTINGS must still force it on for local dev / Playwright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… key The LaunchDarkly flag was created with the literal key `SHOW_ORG_SETTINGS` and cannot be renamed. The client initialises `LDProvider` with `useCamelCaseFlagKeys: false`, so `useFlags()` is keyed by the raw LD key and `useGetFlag` / `useFlagStatus` look it up by the enum *value* — which was `show-org-settings` and therefore never resolved. The flag would have stayed pinned to its `false` default in every environment, including once LD targeting was switched on. Point the enum value at the real key and comment why it deviates from the repo's kebab-case convention, so it does not get "fixed" back. Nothing downstream of the value changes: - `defaultFlags` is keyed by the enum member, so the fail-closed `false` entry is unaffected. - `readEnvOverride` is an explicit switch returning literal `process.env.NEXT_PUBLIC_FORCE_FLAG_*` reads (required so Next inlines them into the client bundle), so the override var name is fixed by the `case` arm, not derived at runtime. The documented convention (value, `-` → `_`, upper-cased) maps the new value to itself, so `NEXT_PUBLIC_FORCE_FLAG_SHOW_ORG_SETTINGS=true` keeps working for local dev and Playwright. - `ARRAY_TYPED_FLAGS` does not contain this flag, and no other enum member collides with the new value. Pin the key in the flag-default tests alongside the existing fail-closed and force-on cases, and add an explicit force-off case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Third-party review bots flagged a batch of real defects in the org management UI. Fixes, all scoped to the flag-gated org settings surface: - MyInvitations: accepting an invitation set the active org before it existed in the store. OrgTeamProvider only reloads the org list on auth changes, so activeOrg resolved to null across the switcher until a page reload. The joined org is now pulled from GET /api/orgs (falling back to an entry built from the invitation) before switching. - Per-row loading state for revoke / accept / decline — a single shared isPending flag put every row's button into a spinner. - useOrganizationSettingsPage: surface membersQuery errors instead of silently rendering an empty member list; page retry refetches both. - slugify: re-trim after truncating so a slice landing on a separator can't emit a trailing dash that fails slug validation. - Wrap mutateAsync calls in try/catch so a failed request doesn't escape the event handler as an unhandled rejection (the onError toast already reports it). - Read the org list from the store at write time instead of a render-time snapshot when updating it after create/rename/delete. - Expose the active org via aria-pressed — the checkmark alone is invisible to screen readers. Tests: joined org lands in the store (both the happy path and the org list refresh failing), members query error surfaces, revoke spinner stays on its own row, slugify truncation, aria-pressed.
getOrgsAfterJoin only fell back to the invitation-derived entry when the org list request threw. A 200 that hasn't caught up with the membership yet would replace the store with a list missing the joined org, putting activeOrgID back out of sync with orgs. The check is now unconditional: whatever the refresh returns, the joined org is present.
f8fb77d to
af9942f
Compare
| } catch { | ||
| // onError already surfaced the failure toast; swallow the rejection so | ||
| // it doesn't escape the click handler unhandled. | ||
| } finally { |
There was a problem hiding this comment.
Bug: In handleAccept, a failure in getOrgsAfterJoin after a successful invitation acceptance is silently caught, leaving the UI in an inconsistent state without showing the new organization.
Severity: MEDIUM
Suggested Fix
Move the logic for updating the UI state (setOrgs, setActiveOrg, getQueryClient().resetQueries()) and showing the success toast into the onSuccess callback of the usePostV2AcceptInvitation mutation. This ensures that UI updates and success notifications only occur after the entire acceptance and data refresh process completes successfully.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location:
autogpt_platform/frontend/src/app/(platform)/settings/organization/components/MyInvitationsSection/useMyInvitationsSection.ts#L69-L72
Potential issue: In the `handleAccept` function, a single `try...catch` block wraps both
the `acceptInvitation` API call and subsequent UI state updates. If the
`acceptInvitation` call succeeds but a subsequent operation like `getOrgsAfterJoin`
fails due to an error during data processing (e.g., calling `.map` on an unexpected
`null` response), the error is silently caught. This leads to a success toast being
shown to the user, but the UI state is not updated to reflect the newly joined
organization, leaving the application in an inconsistent state.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit af9942f. Configure here.
| order={"createdAt": "desc"}, | ||
| ) | ||
| return [InvitationResponse.from_db(inv) for inv in invitations] | ||
| return [UserInvitationResponse.from_db(inv) for inv in invitations] |
There was a problem hiding this comment.
Pending invites use exact email
Medium Severity
GET /invitations/pending looks up invitations with an exact email match, while accept and decline already compare emails case-insensitively. The new invite form also sends the typed address unchanged, and invite email delivery is still a TODO, so a mixed-case invite never appears in You've been invited and cannot be accepted from the UI.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit af9942f. Configure here.


Note
Previously stacked on #12670 (
feat/org-workspace-pr1), which has merged —devis now merged in and the diff shows only this PR's own work.Why
PR1 shipped org/team tenancy end-to-end in the backend, but the frontend can only switch between orgs a user already has — there is no way to create an org, manage its members, or accept an invitation. Worse, two plumbing gaps made the switcher cosmetic: no
X-Org-Idheader was ever sent (every request fell back to the personal org), and the pending-invitations endpoint omitted the accept token, so an invitee could not act on an invitation at all.What
The org management UI, plus the plumbing it exposed as missing:
getOrgContextHeaders()feedsX-Org-Id/X-Team-Idfrom the org/team store into the orval mutator; the Next proxy forwards both. Backend validates membership server-side — this is scoping, not trust.GET /invitations/pendingnow returnsUserInvitationResponsewith the accept/decline token (safe — the list is filtered to the caller's own email) plus the inviting org's name/slug.Dialog+Form+ zod, slug auto-derived) wired into both switchers; creates, adds to the store, switches into the new org./settings/organization(new nav entry;/settings/*already protected):/org/teamslink) and Create organization.How
usePagehook +components/sections, each with its own hook) and the api-keys dialog/form pattern; design-system components only, Phosphor-Iconimports.orgs,invitationstags) — noBackendAPIusage.useOrgTeamStoreandresetQueries(), same as the switcher.Test plan
pnpm format && pnpm lint && pnpm typescleanpnpm test:unit: 3725 passed — 7 new page-level integration tests (owner/member/personal-org gating, invite, accept-and-switch, remove member, profile update), create-org submit flow, updated switcher suitesUserInvitationResponse)Deferred (future slices)
Teams CRUD UI, org avatars upload, transfers UI, org-level Stripe, aliases admin.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jm3mCG9okfdGtAXtFaDF9A
Note
Medium Risk
Changes org tenancy headers on all API calls and expands invitation tokens in API responses; org delete/member/role flows touch access control though backend still enforces permissions.
Overview
Adds organization management in the product and fixes tenancy/invitation plumbing the UI depends on.
Backend:
GET /invitations/pendingnow returnsUserInvitationResponse(accept/decline token, org id/name/slug) instead of admin-styleInvitationResponse, withOrgincluded in the query.Frontend plumbing: Client requests send
X-Org-Id/X-Team-Idfrom the active org/team store (mutator + API proxy).OrgTeamProvidernormalizes snake_case org list payloads into the store.New
/settings/organization(sidebar entry): invitee banner (accept/decline), profile edit, members (roles/remove), admin invitations, owner danger zone (delete). Create organization dialog on switchers;SHOW_ORG_SETTINGSfeature flag defaults off. Account menu org list drops team switching; navbar switcher links to org settings and can still switch teams when present.Accepting an invite updates the org store (including fallback when list refresh fails) before switching context. Broad integration/unit test coverage for the page and switchers.
Reviewed by Cursor Bugbot for commit af9942f. Bugbot is set up for automated code reviews on this repo. Configure here.