Skip to content

fix: add server-side redirect for users with pending invites on onboarding page#29

Open
tomerqodo wants to merge 3 commits intocoderabbit_full_base_fix_add_server-side_redirect_for_users_with_pending_invites_on_onboarding_page_pr2from
coderabbit_full_head_fix_add_server-side_redirect_for_users_with_pending_invites_on_onboarding_page_pr2
Open

fix: add server-side redirect for users with pending invites on onboarding page#29
tomerqodo wants to merge 3 commits intocoderabbit_full_base_fix_add_server-side_redirect_for_users_with_pending_invites_on_onboarding_page_pr2from
coderabbit_full_head_fix_add_server-side_redirect_for_users_with_pending_invites_on_onboarding_page_pr2

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 29, 2026

Benchmark PR from agentic-review-benchmarks#2

Summary by CodeRabbit

  • New Features

    • Enhanced onboarding flow to automatically redirect users with pending membership invites to their personal settings page, ensuring proper handling of outstanding invitations.
  • Tests

    • Added comprehensive integration tests validating pending invite detection across multiple scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

devin-ai-integration bot and others added 3 commits January 25, 2026 12:01
…rding page

When users sign up with an invite token, they were being redirected to
/onboarding/getting-started (plan selection page) instead of going
directly to /onboarding/personal/settings. This caused users to see
the payment prompt for team/org plans before being redirected.

This fix adds a server-side check in the /onboarding/getting-started
page to redirect users with pending invites directly to the personal
onboarding flow, preventing them from seeing the plan selection page.

Also refactored onboardingUtils.ts to use MembershipRepository instead
of direct prisma access.

Co-Authored-By: sean@cal.com <Sean@brydon.io>
Co-Authored-By: sean@cal.com <Sean@brydon.io>
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Walkthrough

The changes introduce a new method hasPendingInviteByUserId to MembershipRepository that queries whether a user has pending membership invitations. This method is then integrated into the onboarding flow to redirect users with pending invites to the settings page. Comprehensive integration tests verify the functionality across multiple scenarios.

Changes

Cohort / File(s) Summary
Onboarding Page Integration
apps/web/app/(use-page-wrapper)/onboarding/getting-started/page.tsx
Adds check for pending invites using the new repository method; redirects to settings page if a pending invite exists. Minor variable initialization adjustment for userEmail.
Onboarding Utilities
packages/features/auth/lib/onboardingUtils.ts
Replaces direct Prisma query with MembershipRepository.hasPendingInviteByUserId call; updates eligibility condition to check for pending invites or v3 enablement flag. Normalizes feature flag check formatting.
MembershipRepository Implementation
packages/features/membership/repositories/MembershipRepository.ts
Adds new static method hasPendingInviteByUserId that queries memberships for pending invitations; includes minor formatting adjustments to existing method signatures.
MembershipRepository Tests
packages/features/membership/repositories/MembershipRepository.integration-test.ts
Comprehensive integration test suite covering pending invite scenarios: existing pending invites, no pending invites, no memberships for user, and mixed invite states. Includes proper setup/teardown hooks.

Sequence Diagram

sequenceDiagram
    participant User as User/Session
    participant Page as Onboarding Page
    participant Repo as MembershipRepository
    participant DB as Database
    participant Redirect as Redirect Handler

    User->>Page: Request /onboarding/getting-started
    Page->>Page: Get session user ID
    Page->>Repo: hasPendingInviteByUserId(userId)
    Repo->>DB: Query membership where accepted=false
    DB-->>Repo: Membership result
    Repo-->>Page: boolean (true if pending)
    
    alt Pending Invite Exists
        Page->>Redirect: Redirect to /onboarding/personal/settings
        Redirect-->>User: Settings page
    else No Pending Invite
        Page-->>User: Continue onboarding flow
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through invite chains,
Pending members caught in the reins,
New paths redirect with careful care,
Settings page awaits them there!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a server-side redirect for users with pending invites on the onboarding page, which aligns with the primary objective of the PR.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/features/auth/lib/onboardingUtils.ts (1)

69-73: Unreachable code path: The ternary at line 73 always evaluates to "/getting-started".

Since line 69 checks hasPendingInvite || onboardingV3Enabled, if we reach line 73, onboardingV3Enabled must be false. The ternary condition will therefore always return "/getting-started", making the "/onboarding/getting-started" branch dead code.

If the intent is to never use /onboarding/getting-started anymore, simplify the return:

🧹 Proposed simplification
   if (hasPendingInvite || onboardingV3Enabled) {
     return "/onboarding/personal/settings";
   }

-  return onboardingV3Enabled ? "/onboarding/getting-started" : "/getting-started";
+  return "/getting-started";
🤖 Fix all issues with AI agents
In `@packages/features/membership/repositories/MembershipRepository.ts`:
- Around line 584-595: The hasPendingInviteByUserId method currently queries
prisma.membership with accepted: true which is inverted; change the filter in
MembershipRepository.hasPendingInviteByUserId to accepted: false so it searches
for unaccepted (pending) invites, keeping the same select { id: true } and
return boolean logic (i.e., !!pendingInvite); update any related tests or
callers if they assume the old behavior.
🧹 Nitpick comments (2)
packages/features/membership/repositories/MembershipRepository.integration-test.ts (2)

119-162: Fragile test: Conditional logic makes behavior non-deterministic.

The test relies on finding an existing second team (lines 127-132), and if none exists, it degenerates into testing only the accepted-membership case (already covered by another test). The conditional expectation at line 158 makes the test outcome environment-dependent.

Consider creating the second team within the test to ensure consistent behavior:

♻️ Proposed refactor for deterministic test
     it("should return true when user has both accepted and pending invites", async () => {
       const newUser = await prisma.user.create({
         data: {
           email: `test-mixed-invites-${Date.now()}@example.com`,
           username: `test-mixed-${Date.now()}`,
         },
       });

-      const team2 = await prisma.team.findFirst({
-        where: {
-          slug: { not: null },
-          id: { not: testTeamId },
-        },
+      // Create a second team for this test to ensure deterministic behavior
+      const team2 = await prisma.team.create({
+        data: {
+          name: "Test Team 2 for Mixed Invites",
+          slug: `test-team-mixed-${Date.now()}`,
+        },
       });

       const acceptedMembership = await prisma.membership.create({
         data: {
           userId: newUser.id,
           teamId: testTeamId,
           role: MembershipRole.MEMBER,
           accepted: true,
         },
       });
       createdMembershipIds.push(acceptedMembership.id);

-      if (team2) {
-        const pendingMembership = await prisma.membership.create({
-          data: {
-            userId: newUser.id,
-            teamId: team2.id,
-            role: MembershipRole.MEMBER,
-            accepted: false,
-          },
-        });
-        createdMembershipIds.push(pendingMembership.id);
-      }
+      const pendingMembership = await prisma.membership.create({
+        data: {
+          userId: newUser.id,
+          teamId: team2.id,
+          role: MembershipRole.MEMBER,
+          accepted: false,
+        },
+      });
+      createdMembershipIds.push(pendingMembership.id);

       const result = await MembershipRepository.hasPendingInviteByUserId({ userId: newUser.id });

-      expect(result).toBe(team2 ? true : false);
+      expect(result).toBe(true);

       await clearTestMemberships();
+      await prisma.team.delete({ where: { id: team2.id } });
       await prisma.user.delete({ where: { id: newUser.id } });
     });

8-19: Consider tracking test users for cleanup in hooks.

The tests create users that are manually deleted at the end of each test. If a test fails mid-execution, these users remain orphaned. Consider tracking created user IDs similarly to createdMembershipIds and cleaning them up in afterEach for more robust test isolation.

Also applies to: 45-47

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e8b98d and 9710b22.

📒 Files selected for processing (4)
  • apps/web/app/(use-page-wrapper)/onboarding/getting-started/page.tsx
  • packages/features/auth/lib/onboardingUtils.ts
  • packages/features/membership/repositories/MembershipRepository.integration-test.ts
  • packages/features/membership/repositories/MembershipRepository.ts
🔇 Additional comments (5)
packages/features/membership/repositories/MembershipRepository.ts (1)

6-7: LGTM!

The import adjustments are appropriate for the module's needs.

packages/features/membership/repositories/MembershipRepository.integration-test.ts (1)

50-75: Tests are correct but will fail due to implementation bug.

This test correctly expects that a membership with accepted: false (a pending invite) should make hasPendingInviteByUserId return true. However, the current implementation in MembershipRepository.ts queries for accepted: true, so this test will fail until the implementation is fixed.

Additionally, the manual cleanup at lines 72-74 is redundant since afterEach already calls clearTestMemberships(). Consider removing the duplicate cleanup or relying solely on the hooks.

packages/features/auth/lib/onboardingUtils.ts (1)

55-56: LGTM on the refactoring approach.

Using MembershipRepository.hasPendingInviteByUserId instead of direct Prisma access aligns with the repository pattern. Note that this depends on fixing the bug in the repository method where accepted: true should be accepted: false.

Also applies to: 67-67

apps/web/app/(use-page-wrapper)/onboarding/getting-started/page.tsx (2)

28-33: Server-side redirect logic is well-structured.

The placement after session validation ensures the user ID is available before checking for pending invites. This correctly handles the case where users sign up with an invite token and need to be redirected to the appropriate onboarding flow.

Note: This depends on fixing the inverted logic in MembershipRepository.hasPendingInviteByUserId (should query accepted: false instead of accepted: true).


5-7: LGTM!

Import organization and the empty string fallback for userEmail are appropriate.

Also applies to: 35-35

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +584 to +595
static async hasPendingInviteByUserId({ userId }: { userId: number }): Promise<boolean> {
const pendingInvite = await prisma.membership.findFirst({
where: {
userId,
accepted: true,
},
select: {
id: true,
},
});
return !!pendingInvite;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: Logic is inverted – queries accepted: true instead of accepted: false.

The method name hasPendingInviteByUserId implies checking for pending (unaccepted) invites, but the query filters on accepted: true, which matches accepted memberships. This will:

  1. Return true for users with accepted memberships (no pending invite)
  2. Return false for users with actual pending invites (accepted: false)

This will cause incorrect redirects in the onboarding flow.

🐛 Proposed fix
 static async hasPendingInviteByUserId({ userId }: { userId: number }): Promise<boolean> {
   const pendingInvite = await prisma.membership.findFirst({
     where: {
       userId,
-      accepted: true,
+      accepted: false,
     },
     select: {
       id: true,
     },
   });
   return !!pendingInvite;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static async hasPendingInviteByUserId({ userId }: { userId: number }): Promise<boolean> {
const pendingInvite = await prisma.membership.findFirst({
where: {
userId,
accepted: true,
},
select: {
id: true,
},
});
return !!pendingInvite;
}
static async hasPendingInviteByUserId({ userId }: { userId: number }): Promise<boolean> {
const pendingInvite = await prisma.membership.findFirst({
where: {
userId,
accepted: false,
},
select: {
id: true,
},
});
return !!pendingInvite;
}
🤖 Prompt for AI Agents
In `@packages/features/membership/repositories/MembershipRepository.ts` around
lines 584 - 595, The hasPendingInviteByUserId method currently queries
prisma.membership with accepted: true which is inverted; change the filter in
MembershipRepository.hasPendingInviteByUserId to accepted: false so it searches
for unaccepted (pending) invites, keeping the same select { id: true } and
return boolean logic (i.e., !!pendingInvite); update any related tests or
callers if they assume the old behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant