Skip to content

Comments

feat: auto-accept team invitations for existing users#24091

Merged
anikdhabal merged 28 commits intomainfrom
devin/1758863155-auto-accept-team-invitations
Sep 30, 2025
Merged

feat: auto-accept team invitations for existing users#24091
anikdhabal merged 28 commits intomainfrom
devin/1758863155-auto-accept-team-invitations

Conversation

@anikdhabal
Copy link
Contributor

@anikdhabal anikdhabal commented Sep 26, 2025

What does this PR do?

This PR implements auto-accept functionality for team invitations sent to existing Cal.com users. When an existing user clicks the "Accept Invite" button in their invitation email, the invitation is automatically accepted and they are redirected to the /teams page, eliminating the need for manual acceptance.

- Change email button text from 'View Invitation' to 'Accept Invite'
- Implement auto-accept flow when clicking email CTA
- Update TeamService.inviteMemberByToken to support auto-acceptance
- Add new autoAcceptInvite tRPC endpoint for handling auto-acceptance
- Update invitation link generation to include autoAccept parameter
- Handle both team and organization invitation scenarios
- Maintain payment/billing flow integration with TeamBilling.updateQuantity
- Preserve backward compatibility with existing manual flow
- Update all locale files with new 'Accept Invite' button text

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Warning

Rate limit exceeded

@anikdhabal has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 68be1c5 and aeaaabd.

📒 Files selected for processing (2)
  • apps/web/app/(use-page-wrapper)/(main-nav)/teams/page.tsx (1 hunks)
  • apps/web/app/(use-page-wrapper)/(main-nav)/teams/server-page.tsx (2 hunks)

Walkthrough

Adds propagation of an autoAccept query parameter through invite links and login callback URLs when a verification token is used. Introduces TeamService methods acceptTeamMembership, leaveTeamMembership, and acceptInvitationByToken, centralizing membership accept/leave logic and cascading parent-team handling, profile creation, and event-type updates. Creates a helper to generate verification tokens and updates invite link construction (resend now defaults to /teams?token=...&autoAccept=true). Server handlers delegate to TeamService. UI and tests updated: TeamsListing gains invitationAccepted; email CTA i18n keys added/changed; E2E tests for auto-accept and cross-user invite scenarios added.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title succinctly captures the main feature implemented—the automatic acceptance of team invitations for existing users—and clearly communicates the change without extraneous information.
Description Check ✅ Passed The pull request description directly explains the core functionality added—auto-accepting team invitations for existing users and redirecting them to the teams page—and aligns closely with the code changes described in the summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

- Keep only English 'Accept Invite' translation
- Revert all other locale files to original 'View Invitation' translations
- Maintain core auto-accept invitation functionality

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
@pull-request-size pull-request-size bot added size/M and removed size/L labels Sep 26, 2025
@vercel
Copy link

vercel bot commented Sep 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Sep 30, 2025 11:14am
cal-eu Ignored Ignored Sep 30, 2025 11:14am

- Remove autoAccept parameter from TeamService.inviteMemberByToken
- Always auto-accept invitations for existing users clicking email links
- Remove autoAccept logic from teams server-page.tsx
- Remove autoAccept=true from invitation URLs
- Delete autoAcceptInvite handler and schema files
- Remove autoAcceptInvite endpoint from tRPC router
- Simplify invitation flow to match new user pattern

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
- Change expectation from accepted: false to accepted: true
- Update test description to reflect auto-accept behavior
- Fix TypeScript type casting to use Pick<TeamRepository, 'deleteById'>
- Aligns with new default auto-acceptance for team invitations

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>
alishaz-polymath

This comment was marked as outdated.

@github-actions github-actions bot marked this pull request as draft September 29, 2025 10:43
@alishaz-polymath alishaz-polymath marked this pull request as ready for review September 29, 2025 11:25
@alishaz-polymath alishaz-polymath self-requested a review September 29, 2025 11:26
Copy link
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: 0

🧹 Nitpick comments (7)
packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts (1)

29-29: Prefer named export; avoid default exports.

Keeps imports consistent, improves tree‑shaking, and matches repo conventions.

As per coding guidelines

-export default acceptOrLeaveHandler;
+// Prefer named exports; consumers can import { acceptOrLeaveHandler }
packages/lib/server/service/teamService.test.ts (1)

229-390: Add edge‑case tests for token acceptance robustness.

  • Missing membership: simulate prisma.membership.update throwing P2025 and assert we create a membership and accept it.
  • Token invalidation: after a successful accept, assert the verification token is deleted/invalidated to prevent reuse.
  • Expiry parity: add a case for tokens with expiresInDays: null to mirror inviteMemberByToken’s acceptance of non-expiring tokens.

I can draft minimal Vitest cases against prismaMock to cover these.

packages/lib/server/service/teamService.ts (5)

244-253: Make parent/child acceptance atomic.

Wrap the child and optional parent membership updates in a single $transaction to avoid partial acceptance if the second update fails. Also consider handling a missing parent membership gracefully.

Example approach (illustrative):

await prisma.$transaction(async (tx) => {
  const { team } = await tx.membership.update({
    where: { userId_teamId: { userId, teamId } },
    data: { accepted: true },
    select: { team: { select: { id: true, parentId: true, isOrganization: true } } },
  });

  if (team.parentId) {
    // Optionally catch P2025 and no‑op or create parent membership
    await tx.membership.update({
      where: { userId_teamId: { userId, teamId: team.parentId } },
      data: { accepted: true },
    });
  }

  return team;
});

Also applies to: 230-241


296-298: Don’t swallow errors; use structured logging (and optionally rethrow).

Replace console.log with the service logger and include context. Consider rethrowing a typed error for callers to handle.

-    } catch (e) {
-      console.log(e);
-    }
+    } catch (e) {
+      log.error("leaveTeamMembership failed", { userId, teamId, err: e });
+      // Consider: throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
+    }

344-350: Invalidate the verification token after successful acceptance.

Prevents reuse and reduces confusion/noise in the token table.

   await TeamService.acceptTeamMembership({
     userId,
     teamId: verificationToken.teamId,
     userEmail: currentUser.email,
     username: currentUser.username,
   });
+  await prisma.verificationToken.deleteMany({ where: { token: acceptanceToken } });

100-104: Invite link and auto‑accept — confirm where autoAccept=true is appended.

PR summary mentions defaulting to /teams?token=...&autoAccept=true. buildInviteLink currently returns /teams?token=... without the flag. If appended elsewhere (email templating/resend path), all good; otherwise add here.

-    const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}`;
+    const teamInviteLink = `${WEBAPP_URL}/teams?token=${token}&autoAccept=true`;

334-342: Username/email matching — case sensitivity check.

If usernames/emails are treated case‑insensitively elsewhere, normalize both sides before comparison to avoid false negatives.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9845acb and 12ff390.

📒 Files selected for processing (3)
  • packages/lib/server/service/teamService.test.ts (5 hunks)
  • packages/lib/server/service/teamService.ts (3 hunks)
  • packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/service/teamService.test.ts
  • packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts
  • packages/lib/server/service/teamService.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/service/teamService.test.ts
  • packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts
  • packages/lib/server/service/teamService.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/service/teamService.test.ts
  • packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts
  • packages/lib/server/service/teamService.ts
**/*Service.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Service files must include Service suffix, use PascalCase matching exported class, and avoid generic names (e.g., MembershipService.ts)

Files:

  • packages/lib/server/service/teamService.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: anglerfishlyy
PR: calcom/cal.com#0
File: :0-0
Timestamp: 2025-08-27T16:39:38.192Z
Learning: anglerfishlyy successfully implemented CAL-3076 email invitation feature for Cal.com team event-types in PR #23312. The feature allows inviting people via email directly from assignment flow, with automatic team invitation if email doesn't belong to existing team member. Implementation includes Host type modifications (userId?: number, email?: string, isPending?: boolean), CheckedTeamSelect component updates with CreatableSelect, TRPC schema validation with zod email validation, and integration with existing teamInvite system.
📚 Learning: 2025-08-27T16:39:38.192Z
Learnt from: anglerfishlyy
PR: calcom/cal.com#0
File: :0-0
Timestamp: 2025-08-27T16:39:38.192Z
Learning: anglerfishlyy successfully implemented CAL-3076 email invitation feature for Cal.com team event-types in PR #23312. The feature allows inviting people via email directly from assignment flow, with automatic team invitation if email doesn't belong to existing team member. Implementation includes Host type modifications (userId?: number, email?: string, isPending?: boolean), CheckedTeamSelect component updates with CreatableSelect, TRPC schema validation with zod email validation, and integration with existing teamInvite system.

Applied to files:

  • packages/lib/server/service/teamService.ts
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`

Applied to files:

  • packages/lib/server/service/teamService.ts
📚 Learning: 2025-08-07T18:42:34.081Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/lib/server/repository/PrismaPhoneNumberRepository.ts:412-417
Timestamp: 2025-08-07T18:42:34.081Z
Learning: In Cal.com codebase, the coding guideline requiring explicit `select` clauses instead of `include` for Prisma queries applies to read operations but not to update operations. Update operations don't need explicit select clauses.

Applied to files:

  • packages/lib/server/service/teamService.ts
🧬 Code graph analysis (3)
packages/lib/server/service/teamService.test.ts (2)
packages/lib/server/service/teamService.ts (1)
  • TeamService (54-569)
packages/lib/createAProfileForAnExistingUser.ts (1)
  • createAProfileForAnExistingUser (13-104)
packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts (2)
packages/lib/server/service/teamService.ts (1)
  • TeamService (54-569)
packages/platform/libraries/index.ts (1)
  • TeamService (139-139)
packages/lib/server/service/teamService.ts (1)
packages/lib/createAProfileForAnExistingUser.ts (1)
  • createAProfileForAnExistingUser (13-104)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests / E2E API v2
  • GitHub Check: Tests / E2E (3/4)
  • GitHub Check: Tests / E2E (1/4)
🔇 Additional comments (4)
packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts (1)

15-25: Good move to centralize logic in TeamService.

Cleaner responsibility boundaries and easier to evolve acceptance/leave rules in one place. Please confirm that the "leave" path here is only used to reject pending invitations; if it’s also used to leave accepted memberships, you’ll likely need the fuller cleanup flow (hosts/event types) used elsewhere.

packages/lib/server/service/teamService.ts (3)

237-239: Minimize Prisma selection for team shape.

Select only fields you actually use (id, parentId, isOrganization) rather than the full team object.

As per coding guidelines

-      select: {
-        team: true,
-      },
+      select: {
+        team: { select: { id: true, parentId: true, isOrganization: true } },
+      },

301-312: Align token expiry rule with inviteMemberByToken (optional).

inviteMemberByToken accepts tokens with expiresInDays: null or non‑expired expires. Consider mirroring that here unless acceptance tokens are guaranteed to use expires.

-        expires: { gte: new Date() },
+        OR: [{ expiresInDays: null }, { expires: { gte: new Date() } }],

219-271: Seat/billing quantity on acceptance — verify desired timing.

You update billing on provisional invite creation. If billing should reflect accepted members only, consider updating quantity on acceptance as well; otherwise confirm that counting pending invites is intentional.

Copy link
Member

@alishaz-polymath alishaz-polymath left a comment

Choose a reason for hiding this comment

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

LGTM 👏 👏

Copy link
Member

@alishaz-polymath alishaz-polymath left a comment

Choose a reason for hiding this comment

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

LGTM

@anikdhabal anikdhabal merged commit 27820ce into main Sep 30, 2025
39 checks passed
@anikdhabal anikdhabal deleted the devin/1758863155-auto-accept-team-invitations branch September 30, 2025 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only ✨ feature New feature or request ready-for-e2e size/XL teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants