Skip to content

feat: organizations CRUD#29396

Closed
regisstedile wants to merge 11 commits into
calcom:mainfrom
regisstedile:feat/organizations-crud
Closed

feat: organizations CRUD#29396
regisstedile wants to merge 11 commits into
calcom:mainfrom
regisstedile:feat/organizations-crud

Conversation

@regisstedile
Copy link
Copy Markdown

Summary

  • Add tRPC router with 10 procedures: getCurrent, create, update, listMembers, inviteMember, removeMember, updateMemberRole, listPendingInvites, acceptInvite, declineInvite
  • Add Next.js API route /api/trpc/organizations/[trpc].ts and register organizations in the ENDPOINTS array
  • Add settings pages: /settings/organizations/general, /members, /invites
  • Remove conflicting redirect that sent /settings/organizations/members to /members
  • Add 6 E2E tests (all passing)

Test plan

  • PLAYWRIGHT_HEADLESS=1 yarn e2e apps/web/playwright/settings/organizations.e2e.ts
  • All 6 tests pass: create org, list members, invite flow, accept invite, decline invite, remove member

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @regisstedile! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Regis seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

regisstedile and others added 7 commits May 18, 2026 13:38
Adds viewer.organizations tRPC router (getCurrent, create, update)
and settings UI at /settings/organizations/{profile,general}.

- Create org: Team(isOrganization) + OrganizationSettings + Membership
  OWNER + Profile + User.organizationId in single transaction
- Update: restricted to OWNER/ADMIN, validates slug uniqueness
- Sidebar shows org nav only when user belongs to an org
- /settings/organizations redirects to /profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e update)

Adds viewer.organizations.listMembers/inviteMember/removeMember/updateMemberRole
tRPC procedures and /settings/organizations/members UI page.

- listMembers: paginated search via existing MembershipRepository.searchMembers
- inviteMember: creates pending Membership + sends team invite email (existing users only)
- removeMember: guards against removing OWNER or self
- updateMemberRole: Zod-validated to MEMBER|ADMIN only, blocks changing OWNER
- Sidebar members link now routes to internal /settings/organizations/members

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds listPendingInvites, acceptInvite, declineInvite tRPC procedures
and /settings/organizations/invites UI page.

- acceptInvite: sets membership.accepted=true + updates user.organizationId
  in a single transaction; redirects to org settings after accept
- declineInvite: deletes pending membership
- Sidebar shows "invites" link under organization section for all users;
  badge appears when pendingInviteCount > 0
- availableOrganizationSettingsPages expanded to include invites + members

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /api/trpc/organizations/[trpc].ts endpoint (was missing from ENDPOINTS)
- Add "organizations" to tRPC ENDPOINTS so client routes correctly
- Remove conflicting next.config.ts redirect /settings/organizations/members → /members
- Fix DialogTitle import (doesn't exist in this fork's UI lib, use DialogHeader title prop)
- Add E2E tests for all 6 organization flows (create, list members, invite,
  accept invite, decline invite, remove member)
- Fix E2E test: use apiLoginOnNewBrowser for multi-user invitee scenarios
- Fix E2E test: use unique slugs to avoid DB state conflicts across runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- acceptInvite: create Profile + guard against user already in another org
- removeMember: delete Profile + clear user.organizationId in transaction
- inviteMember: reject invite if invitee already belongs to an org
- profile page: fix members link pointing to /teams (now /settings/organizations/members)
- E2E acceptInvite: assert user.organizationId and Profile after accept
- E2E members list: scope name selector to ul li to avoid strict mode violation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons merge

The organizations feature commits overwrote shared.ts, losing the teams
endpoint registration. Re-adds /api/trpc/teams/[trpc].ts and "teams" to
ENDPOINTS so viewer.teams.* calls resolve correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile regisstedile force-pushed the feat/organizations-crud branch from e61ca4f to 5c4c176 Compare May 18, 2026 13:41
@regisstedile regisstedile marked this pull request as ready for review May 18, 2026 13:41
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d124c01d-51a4-4444-899e-0ebeb8dda100

📥 Commits

Reviewing files that changed from the base of the PR and between d818e20 and fbcac7c.

📒 Files selected for processing (3)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx

📝 Walkthrough

Walkthrough

This PR adds a complete organization settings feature: Zod schemas and utilities, a viewer organizations TRPC router with handlers for create/update, member listing, invites (list/accept/decline/invite), role updates, and removals; endpoint and API route wiring; settings navigation updates (members, invites, badges); new client pages for profile, general settings, members, and invites; removal of an obsolete redirect; and Playwright E2E tests covering core organization flows.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: organizations CRUD' clearly and concisely summarizes the main change: adding complete CRUD functionality for organizations, including the router, API routes, UI pages, and tests.
Description check ✅ Passed The description is well-related to the changeset, providing a clear summary of additions (tRPC router with 10 procedures, API routes, settings pages), removals (conflicting redirect), and test coverage (6 E2E tests), with an actionable test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 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
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx:
- Around line 142-144: The hardcoded permission hint paragraph using the
canUpdate flag should be localized: replace the string in the JSX (the <p> that
renders when !canUpdate) with a call to
t('organizations.general.permissionHint') and add an entry
"organizations.general.permissionHint": "Only organization owners and admins can
update these settings." to packages/i18n/locales/en/common.json; ensure the
component imports/uses the i18n hook/function (t) already used elsewhere in this
file and that the key matches the new JSON entry.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx:
- Around line 22-23: The page has hardcoded user-facing strings (header, status,
button labels and toast messages) — replace them with t(...) keys using the
translations hook and add corresponding keys in
packages/i18n/locales/en/common.json. Locate the invites page component
(page.tsx) and replace each literal string (e.g., the header text, status text,
the "You have joined the organization." success toast, and any other messages at
the referenced spots) with t('settings.organizations.invites.<key>') or similar
unique keys, then add those keys and English values into common.json; ensure you
import/use the same translation helper used in the repo (e.g.,
useTranslations/t) and update showToast calls to pass translated text.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx:
- Around line 20-23: Replace all hardcoded user-facing strings on this members
page with i18n lookups and add matching keys to
packages/i18n/locales/en/common.json: change the roleOptions labels
("Member","Admin") to use t('...') keys (e.g., t('members.role.member'),
t('members.role.admin') ) and update all other hardcoded texts referenced in the
review (button labels, empty/loading states, confirmation/toast messages at the
indicated spots) to t('...') keys; then add those keys and English values into
common.json. Make sure to import and use the page's translation hook/function
(t) where needed and keep enum values (MembershipRole.MEMBER/ADMIN) unchanged.
Ensure toast calls (e.g., success/error toasts) pass translated strings and that
every replaced literal has a corresponding entry in common.json.

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx:
- Around line 22-23: Replace hardcoded UI strings in the Organization profile
page with localization keys: change the <h1> text "Organization profile" in
page.tsx to use t("profile_org_title") and update the empty-state copy (the
strings referenced around lines 55-56) to use appropriate t(...) keys (e.g.,
t("profile_org_empty_title") / t("profile_org_empty_description")); then add
these new keys and their English values into
packages/i18n/locales/en/common.json so the translations are available (ensure
keys match exactly the t(...) calls used in the component).

In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx:
- Around line 395-401: The filter that checks tabs uses tab.name (in the block
referencing organizationRequiredKeys, adminRequiredKeys, and the "other_teams"
check) which is brittle because tab.name can be replaced by org display name;
update the comparisons to use a stable identifier such as tab.href (or an
internal key field if present) instead of tab.name—i.e., change the checks in
the function where organizationRequiredKeys.includes(tab.name), tab.name ===
"other_teams", and adminRequiredKeys.includes(tab.name) to use tab.href (or the
dedicated key) so the org/admin tab is matched reliably.

In `@packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts`:
- Around line 19-22: The membership lookup in acceptInvite.handler.ts (the
prisma.membership.findUnique call using ctx.user.id and input.teamId) does not
verify the target team is an organization, so update the logic to first load the
team record (e.g., prisma.team.findUnique or include the team in the membership
query) and assert team.type === 'ORGANIZATION' before proceeding to mutate
user.organizationId or marking the invite accepted; if the team is not an
organization, throw a suitable error and abort. Apply the same organization-type
check to the other membership handling block referenced in the comment (the
similar logic around lines 41–57).
- Around line 37-39: Add an explicit guard that throws a deterministic TRPCError
when the request has no authenticated user before any transaction/write logic:
in acceptInvite.handler.ts check if (!user) and throw new TRPCError({ code:
"UNAUTHORIZED", message: "User not authenticated." }) before the existing
organizationId check and again before the transaction block referenced around
lines 51-57; reference the local variable user and the acceptInvite handler's
transaction usage so the write-path never runs with a null user.

In `@packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts`:
- Around line 15-30: The current two-step flow (prisma.membership.findUnique
then prisma.membership.delete) can race with concurrent acceptance; replace it
with an atomic conditional delete or a transaction that deletes only if accepted
is false and then check the result. Specifically, remove the separate
prisma.membership.findUnique + check and instead perform a conditional delete
(e.g., deleteMany / delete with a where that includes { userId: ctx.user.id,
teamId: input.teamId, accepted: false }) or run both operations in a transaction
and assert the delete affected one row; if the delete affected zero rows, throw
the same TRPCError NOT_FOUND or BAD_REQUEST as appropriate. Ensure you reference
and update the prisma.membership.delete usage and any error throw sites to use
the delete result to decide whether to error.

In `@packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts`:
- Around line 41-60: The pre-check using prisma.membership.findUnique plus the
subsequent prisma.membership.create is non-atomic and can race; wrap the create
call in a try/catch that handles unique-constraint DB errors
(Prisma.PrismaClientKnownRequestError with code "P2002") and convert them into
the same TRPCError({ code: "CONFLICT", message: ... }) response; to produce the
correct message, on catching P2002 re-query prisma.membership.findUnique for
userId_teamId (or reuse the earlier check if still valid) and throw "User is
already a member." when accepted is true or "Invite already sent." when accepted
is false; keep the MembershipRole.MEMBER default and only treat other errors as
rethrows.

In `@packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts`:
- Around line 23-47: Replace the pre-check + plain membership.delete with an
atomic transaction that prevents removing an OWNER: inside the
prisma.$transaction call, use prisma.membership.deleteMany({ where: { userId:
input.userId, teamId: membership.team.id, role: { not: MembershipRole.OWNER } }
}) as the first operation, then run the profile.deleteMany and user.updateMany;
after the transaction check the deleted count (result[0].count) and if it is 0
throw TRPCError FORBIDDEN/NOT_FOUND as appropriate. This keeps the
owner-protection enforced inside prisma.$transaction and references
prisma.$transaction, prisma.membership.deleteMany (replacing
prisma.membership.delete), and MembershipRole.

In
`@packages/trpc/server/routers/viewer/organizations/updateMemberRole.handler.ts`:
- Around line 19-35: The current code uses prisma.membership.findUnique to check
for MembershipRole.OWNER then separately calls prisma.membership.update, which
is vulnerable to race conditions; replace the two-step check+update with an
atomic conditional update (e.g., use prisma.membership.updateMany or
prisma.membership.update with a where that includes role: { not:
MembershipRole.OWNER }) so the DB only updates if the existing role is not
OWNER, then inspect the returned count/affected rows and throw TRPCError ({
code: "FORBIDDEN", message: "Cannot change the owner's role." }) when no rows
were updated; this ensures the protection around MembershipRole.OWNER (refer to
prisma.membership.findUnique, prisma.membership.update,
prisma.membership.updateMany, and MembershipRole.OWNER) is enforced atomically.
🪄 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: 57acebb7-f6cb-4368-b4d9-f21deed507c9

📥 Commits

Reviewing files that changed from the base of the PR and between 180ede2 and 5c4c176.

📒 Files selected for processing (24)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx
  • apps/web/next.config.ts
  • apps/web/pages/api/trpc/organizations/[trpc].ts
  • apps/web/playwright/settings/organizations.e2e.ts
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/create.handler.ts
  • packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/getCurrent.handler.ts
  • packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listPendingInvites.handler.ts
  • packages/trpc/server/routers/viewer/organizations/organizationUtils.ts
  • packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/schema.ts
  • packages/trpc/server/routers/viewer/organizations/update.handler.ts
  • packages/trpc/server/routers/viewer/organizations/updateMemberRole.handler.ts
💤 Files with no reviewable changes (1)
  • apps/web/next.config.ts

Comment thread packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts Outdated
Comment thread packages/trpc/server/routers/viewer/organizations/updateMemberRole.handler.ts Outdated
- Fix race conditions: declineInvite uses atomic deleteMany with accepted=false
  condition; inviteMember catches P2002 unique violation instead of pre-check;
  removeMember and updateMemberRole push owner guard inside the DB operation
- Add null user guard in acceptInvite before transaction writes
- Add i18n keys for all hardcoded UI strings in organization pages
- Fix SettingsLayoutAppDirClient to match org tab by href instead of name
  (tab.name is replaced with org display name, making name-based check brittle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts (1)

23-30: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make member deletion and cleanup a single atomic transaction.

Line 23 deletes membership before Line 42 starts the transaction. If the cleanup transaction fails, the membership is already gone, leaving partial state (e.g., stale user.organizationId).

Suggested fix
-  const { count } = await prisma.membership.deleteMany({
-    where: {
-      userId: input.userId,
-      teamId: membership.team.id,
-      role: { not: MembershipRole.OWNER },
-    },
-  });
-
-  if (count === 0) {
-    const exists = await prisma.membership.findUnique({
-      where: { userId_teamId: { userId: input.userId, teamId: membership.team.id } },
-      select: { role: true },
-    });
-    if (!exists) {
-      throw new TRPCError({ code: "NOT_FOUND", message: "Member not found." });
-    }
-    throw new TRPCError({ code: "FORBIDDEN", message: "Cannot remove the organization owner." });
-  }
-
-  await prisma.$transaction([
-    prisma.profile.deleteMany({
-      where: { userId: input.userId, organizationId: membership.team.id },
-    }),
-    prisma.user.updateMany({
-      where: { id: input.userId, organizationId: membership.team.id },
-      data: { organizationId: null },
-    }),
-  ]);
+  await prisma.$transaction(async (tx) => {
+    const { count } = await tx.membership.deleteMany({
+      where: {
+        userId: input.userId,
+        teamId: membership.team.id,
+        role: { not: MembershipRole.OWNER },
+      },
+    });
+
+    if (count === 0) {
+      const exists = await tx.membership.findUnique({
+        where: { userId_teamId: { userId: input.userId, teamId: membership.team.id } },
+        select: { role: true },
+      });
+      if (!exists) throw new TRPCError({ code: "NOT_FOUND", message: "Member not found." });
+      throw new TRPCError({ code: "FORBIDDEN", message: "Cannot remove the organization owner." });
+    }
+
+    await tx.profile.deleteMany({
+      where: { userId: input.userId, organizationId: membership.team.id },
+    });
+    await tx.user.updateMany({
+      where: { id: input.userId, organizationId: membership.team.id },
+      data: { organizationId: null },
+    });
+  });

Also applies to: 42-50

🤖 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 `@packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts`
around lines 23 - 30, The deletion of the membership
(prisma.membership.deleteMany) is executed outside the transaction, causing
partial state if subsequent cleanup (e.g., updating user.organizationId) fails;
modify the removeMember handler to perform the membership deletion and all
related cleanup updates inside a single Prisma transaction (use
prisma.$transaction) so the deleteMany call, the user update (clearing
organizationId) and any other cleanup steps execute atomically; locate the
removeMember handler and replace the standalone prisma.membership.deleteMany and
the later cleanup logic with a single prisma.$transaction that runs the delete
and the follow-up updates together and returns/validates the affected count
within the transaction.
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx (1)

134-134: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the invite-email placeholder text.

"member@example.com" is still hardcoded; switch to a translation key and add it to packages/i18n/locales/en/common.json.

As per coding guidelines: **/*.{ts,tsx,jsx}: Add translations to packages/i18n/locales/en/common.json for all UI strings.

🤖 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/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx
at line 134, Replace the hardcoded placeholder "member@example.com" in the
invite input (the placeholder prop in page.tsx) with a translation key (e.g. use
t('inviteEmailPlaceholder') or useTranslations()/i18n hook your app uses) and
import/use the app's translation hook in that component; then add
"inviteEmailPlaceholder": "member@example.com" to
packages/i18n/locales/en/common.json so the string is localized. Ensure the
placeholder prop references the translation function result (not a raw string).
🤖 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 `@packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts`:
- Line 66: The joinLink currently built in inviteMember.handler.ts points
invitees to `/settings/organizations/members` which doesn't surface
accept/decline actions; update the joinLink value used when creating the invite
(the joinLink property in the invite creation payload in
inviteMember.handler.ts) to `${WEBAPP_URL}/settings/organizations/invites` so
recipients land on the invites page that shows pending invites and
accept/decline controls.

---

Duplicate comments:
In
`@apps/web/app/`(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx:
- Line 134: Replace the hardcoded placeholder "member@example.com" in the invite
input (the placeholder prop in page.tsx) with a translation key (e.g. use
t('inviteEmailPlaceholder') or useTranslations()/i18n hook your app uses) and
import/use the app's translation hook in that component; then add
"inviteEmailPlaceholder": "member@example.com" to
packages/i18n/locales/en/common.json so the string is localized. Ensure the
placeholder prop references the translation function result (not a raw string).

In `@packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts`:
- Around line 23-30: The deletion of the membership
(prisma.membership.deleteMany) is executed outside the transaction, causing
partial state if subsequent cleanup (e.g., updating user.organizationId) fails;
modify the removeMember handler to perform the membership deletion and all
related cleanup updates inside a single Prisma transaction (use
prisma.$transaction) so the deleteMany call, the user update (clearing
organizationId) and any other cleanup steps execute atomically; locate the
removeMember handler and replace the standalone prisma.membership.deleteMany and
the later cleanup logic with a single prisma.$transaction that runs the delete
and the follow-up updates together and returns/validates the affected count
within the transaction.
🪄 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: 783be998-8ad4-4e4b-b5e7-e11fa562df88

📥 Commits

Reviewing files that changed from the base of the PR and between 5c4c176 and b05375a.

📒 Files selected for processing (13)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/invites/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/members/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx
  • apps/web/pages/api/trpc/teams/[trpc].ts
  • packages/i18n/locales/en/common.json
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/declineInvite.handler.ts
  • packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/removeMember.handler.ts
  • packages/trpc/server/routers/viewer/organizations/updateMemberRole.handler.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/trpc/react/shared.ts
  • packages/trpc/server/routers/viewer/organizations/acceptInvite.handler.ts
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/profile/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/general/page.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx

Comment thread packages/trpc/server/routers/viewer/organizations/inviteMember.handler.ts Outdated
regisstedile and others added 2 commits May 18, 2026 14:39
Invitees should land on /settings/organizations/invites where they can
accept or decline. The /members page has no accept/decline action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Button.variant only accepts "button"|"icon"|"fab". Destructive and minimal
are colors, not variants. This caused the Docker build to fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@regisstedile
Copy link
Copy Markdown
Author

@coderabbitai resolve

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

✅ Actions performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants