Skip to content

fix team invitations with server actions#983

Merged
BilalG1 merged 5 commits intodevfrom
fix-team-invitations
Oct 30, 2025
Merged

fix team invitations with server actions#983
BilalG1 merged 5 commits intodevfrom
fix-team-invitations

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Oct 27, 2025

Summary by CodeRabbit

  • New Features
    • Invite users to teams by email with customizable callback URLs.
    • View and revoke pending invitations from the team management UI.
    • Track and enforce team seat capacity, disabling invites when full.
  • Improvements
    • Upgrade flow now redirects to the checkout URL from the team UI.

@vercel
Copy link

vercel bot commented Oct 27, 2025

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

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Oct 30, 2025 4:50pm
stack-dashboard Ready Ready Preview Comment Oct 30, 2025 4:50pm
stack-demo Ready Ready Preview Comment Oct 30, 2025 4:50pm
stack-docs Ready Ready Preview Comment Oct 30, 2025 4:50pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a server-side module exposing revokeInvitation, listInvitations, and inviteUser, and refactors the client TeamAddUserDialog to accept a Team prop, manage invitations state (list/invite/revoke), and enforce seat-capacity UI behavior.

Changes

Cohort / File(s) Summary
Server-side invitation actions
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts
New server module exporting revokeInvitation(teamId, invitationId), listInvitations(teamId), and inviteUser(teamId, email, callbackUrl). Each obtains the current user via stackServerApp.getUser(), validates the team and/or invitation, and delegates operations to team methods.
Client-side invitation UI & props
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Refactors TeamAddUserDialog and TeamAddUserDialogContent to accept a full Team prop. Adds invitation state and lifecycle (fetchInvitations using listInvitations), invite flow (inviteUser), revoke flow (revokeInvitation), activeSeats calculation (users + invitations), capacity gating (disable inputs, show upgrade), and loading skeletons/spinner UI.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Client (Browser)
    participant UI as TeamAddUserDialog
    participant Actions as Server Actions
    participant Server as Stack Server

    Browser->>UI: Render dialog with Team prop
    UI->>Actions: listInvitations(teamId)
    Actions->>Server: getUser() -> team.listInvitations()
    Server-->>Actions: invitations[]
    Actions-->>UI: [{id, recipientEmail, expiresAt}]
    UI->>UI: update invitations state

    alt Invite allowed (under capacity)
        UI->>Actions: inviteUser(teamId, email, callbackUrl)
        Actions->>Server: getUser() -> team.inviteUser()
        Server-->>Actions: invitation created
        Actions-->>UI: success
        UI->>Actions: listInvitations(teamId) (refresh)
    else At capacity
        UI-->>Browser: disable input, show upgrade path
    end

    Browser->>UI: Revoke invitation
    UI->>Actions: revokeInvitation(teamId, invitationId)
    Actions->>Server: getUser() -> team.revoke(invitationId)
    Server-->>Actions: revoked
    Actions-->>UI: success
    UI->>Actions: listInvitations(teamId) (refresh)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Attention points:
    • Verify all callers pass the full Team object to TeamAddUserDialog and TeamAddUserDialogContent.
    • Confirm capacity calculation uses users.length + invitations.length and UI gating behaves correctly.
    • Check server action error handling aligns with team methods and that auth checks (getUser) are adequate.

Possibly related PRs

Poem

🐰 I hopped into code today,
New actions led the way,
Props now carry each Team's tune,
Invitations bloom like June,
Seats counted, invites sent—hop hooray! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The pull request description provided by the author consists only of an HTML comment reminding contributors to read CONTRIBUTING.md, with no actual substantive content describing the changes, objectives, or context of the pull request. While the repository's description template is minimal and appears to only require this comment, the description lacks any meaningful explanation of what the PR accomplishes. Given that this PR introduces significant changes including new server-side actions for team invitation management and UI modifications to support invitation workflows, readers would benefit from at least a brief explanation of the problem being solved and how these changes address it. The PR title ("fix team invitations with server actions") provides minimal context, but the description itself is essentially empty. The author should add a description beyond the template comment that explains what team invitation issues are being fixed, how the new server actions resolve these issues, and any relevant implementation details or considerations. At minimum, the description should provide context about the purpose of the changes to help reviewers understand the intent of the pull request.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix team invitations with server actions" directly aligns with the main changes in this pull request. The raw summary confirms that the changeset introduces a new server-side actions module containing three async functions (revokeInvitation, listInvitations, inviteUser) for team invitation management, and refactors page-client.tsx to use these new server actions. The title is concise, clear, and accurately conveys the primary change a teammate would need to understand from scanning the commit history.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-team-invitations

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.

@BilalG1 BilalG1 marked this pull request as ready for review October 28, 2025 00:09
@BilalG1 BilalG1 requested a review from N2D4 October 28, 2025 00:09
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Oct 28, 2025
@claude
Copy link
Contributor

claude bot commented Oct 28, 2025

Claude finished @BilalG1's task —— View job


PR Review: Fix Team Invitations with Server Actions

Review Checklist:

  • Read and analyze the new actions.ts file
  • Review changes to page-client.tsx
  • Assess code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Review security concerns
  • Assess test coverage needs
  • Provide comprehensive feedback

🔍 Code Review Summary

This PR introduces server actions to handle team invitations, moving from client-side logic to a more secure server-side approach. Overall, this is a solid improvement that enhances security and follows Next.js best practices.

✅ Strengths

Server Actions Implementation (actions.ts)

  • Proper server actions pattern: Uses "use server" directive correctly
  • Good error handling: Throws meaningful errors for missing teams/invitations
  • Clean API design: Functions are focused and well-named
  • Proper authentication flow: Correctly gets user and validates team access

React Component Updates (page-client.tsx)

  • Proper state management: Uses useState and useCallback effectively
  • Good UX patterns: Shows loading states, proper error handling with toasts
  • Accessibility: Good aria-labels and titles
  • Responsive design: Proper responsive grid and flexbox usage

⚠️ Issues & Recommendations

Security Concerns

  1. Missing Authorization Checks in listInvitations (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts:18)

    export async function listInvitations(teamId: string) {
      const user = await stackServerApp.getUser();
      const team = await user?.getTeam(teamId); // What if user is null?

    Issue: If user is null, user?.getTeam() returns undefined, but the error handling assumes the team exists.
    Fix: Add explicit null check for user before proceeding.

  2. Inconsistent "use server" Directive (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts:4-5)

    export async function revokeInvitation(teamId: string, invitationId: string) {
      "use server"; // Redundant - already declared at file level

    Issue: The directive is already at the file level (line 1), making the function-level directive redundant.

Potential Bugs

  1. Race Condition in Invitation Management (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:270-273)

    onClick={async () => {
      await revokeInvitation(props.team.id, invitation.id);
      await fetchInvitations(); // Could miss updates if another user modifies invitations
    }}

    Issue: Multiple users could create race conditions when managing invitations simultaneously.
    Recommendation: Consider optimistic updates or real-time synchronization.

  2. Inefficient Invitation Lookup (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts:11)

    const invite = await team.listInvitations().then(invites => invites.find(invite => invite.id === invitationId));

    Issue: Fetches all invitations just to find one by ID.
    Recommendation: If the Stack API supports it, use a direct invitation lookup method.

Performance Considerations

  1. Unnecessary Re-renders (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:162)
    const [invitations, setInvitations] = useState<Awaited<ReturnType<typeof listInvitations>>>();
    Issue: The fetchInvitations callback recreates on every team.id change, potentially causing unnecessary effect runs.
    Recommendation: Already handled well with useCallback dependency array.

Error Handling

  1. Generic Error Messages (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts:9, 13)
    throw new Error("Team not found");
    throw new Error("Invitation not found");
    Issue: Generic errors don't provide context about why the operation failed.
    Recommendation: Include more specific error codes or context for better debugging.

Code Quality

  1. Missing Input Validation

    • No explicit validation of teamId and invitationId parameters in server actions
    • Consider adding runtime validation for these critical parameters
  2. Type Safety (apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:265)

    <Typography>{invitation.recipientEmail ?? "Pending invitation"}</Typography>

    Good: Proper null handling with fallback text.

🧪 Test Coverage Recommendations

  1. Server Actions Unit Tests:

    • Test authorization failures (user not in team)
    • Test edge cases (invalid team IDs, non-existent invitations)
    • Test error handling paths
  2. Component Integration Tests:

    • Test invitation flow end-to-end
    • Test capacity limits and upgrade flow
    • Test error states and loading states
  3. Security Tests:

    • Verify users can't access other teams' invitations
    • Test rate limiting on invitation creation

🔒 Security Assessment

Overall: Good security posture with server-side validation, but needs improvement in user null checking.

Recommendations:

  • Add explicit user authentication checks
  • Consider rate limiting for invitation operations
  • Add audit logging for invitation actions

📋 Suggested Fixes

Priority 1 (Security)

// In actions.ts, add null checks:
export async function listInvitations(teamId: string) {
  const user = await stackServerApp.getUser();
  if (!user) {
    throw new Error("User not authenticated");
  }
  const team = await user.getTeam(teamId);
  // ... rest of function
}

Priority 2 (Code Quality)

// Remove redundant "use server" directive in revokeInvitation function
export async function revokeInvitation(teamId: string, invitationId: string) {
  // Remove this line: "use server";
  const user = await stackServerApp.getUser();
  // ... rest of function
}

📊 Overall Assessment

Score: 7.5/10 - This is a solid implementation that improves security by moving to server actions. The main issues are around edge case handling and some minor code quality improvements.

Recommendation: ✅ Approve with minor changes - Address the security null checks and remove redundant directives before merging.


Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

refactored team invitation functionality to use Next.js server actions instead of client-side operations, improving security and reliability.

  • Created new server actions (revokeInvitation, listInvitations, inviteUser) to handle team invitation operations
  • Restored previously commented-out seat limit display and capacity checks
  • Fixed invitation listing to fetch via server action rather than client-side hook
  • All callback URLs are validated against trusted domains by the backend verification code handler

Confidence Score: 4/5

  • This PR is safe to merge with minor style improvements recommended
  • The refactoring properly moves team invitation logic to server actions with appropriate authentication and authorization checks. The callback URL is validated by the backend against trusted domains. One minor style improvement was suggested to use runAsynchronouslyWithAlert for consistent error handling, but this doesn't affect functionality.
  • No files require special attention - the changes are straightforward and well-structured

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts 5/5 new server actions for team invitation operations: revoke, list, and invite. URL validation handled by backend.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx 4/5 refactored team invitations to use server actions, restored seat limit display, minor style improvement suggested for error handling

Sequence Diagram

sequenceDiagram
    participant User
    participant Dialog as TeamAddUserDialog
    participant Client as page-client.tsx
    participant Actions as actions.ts
    participant Server as stackServerApp
    participant Backend as Backend API

    User->>Dialog: Click Settings Icon
    Dialog->>Client: Open Dialog & Mount
    Client->>Actions: listInvitations(teamId)
    Actions->>Server: getUser()
    Server->>Actions: user
    Actions->>Server: user.getTeam(teamId)
    Server->>Actions: team
    Actions->>Server: team.listInvitations()
    Server->>Actions: invitations
    Actions->>Client: invitations data
    Client->>Dialog: Display invitations

    User->>Dialog: Enter email & click Invite
    Dialog->>Client: handleInvite()
    Client->>Actions: inviteUser(teamId, email, callbackUrl)
    Actions->>Server: getUser()
    Server->>Actions: user
    Actions->>Server: user.getTeam(teamId)
    Server->>Actions: team
    Actions->>Server: team.inviteUser({email, callbackUrl})
    Server->>Backend: POST /team-invitations/send-code
    Backend->>Backend: Validate callbackUrl against trusted domains
    Backend->>Backend: Send invitation email
    Backend->>Server: success
    Server->>Actions: success
    Actions->>Client: success
    Client->>Actions: fetchInvitations()
    Actions->>Client: updated invitations
    Client->>Dialog: Show success toast & refresh list

    User->>Dialog: Click Revoke
    Dialog->>Client: onClick handler
    Client->>Actions: revokeInvitation(teamId, invitationId)
    Actions->>Server: getUser()
    Server->>Actions: user
    Actions->>Server: user.getTeam(teamId)
    Server->>Actions: team
    Actions->>Server: team.listInvitations()
    Server->>Actions: invitations
    Actions->>Actions: find invitation by id
    Actions->>Server: invitation.revoke()
    Server->>Backend: DELETE invitation
    Backend->>Server: success
    Server->>Actions: success
    Actions->>Client: success
    Client->>Actions: fetchInvitations()
    Actions->>Client: updated invitations
    Client->>Dialog: Refresh invitation list
Loading

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

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: 2

🧹 Nitpick comments (7)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (3)

24-29: Ensure action return is safely serializable across boundaries.

expiresAt may be a Date; return a string to avoid ambiguity and timezone issues.

   const invitations = await team.listInvitations();
   return invitations.map(invite => ({
     id: invite.id,
     recipientEmail: invite.recipientEmail,
-    expiresAt: invite.expiresAt,
+    expiresAt: invite.expiresAt ? new Date(invite.expiresAt).toISOString() : null,
   }));

5-5: Redundant "use server" inside a server-marked module.

Module already has "use server" at Line 1; the inner directive is unnecessary noise.

 export async function revokeInvitation(teamId: string, invitationId: string) {
-  "use server";

11-15: Avoid O(n) scan if a direct revoke API exists.

If team exposes getInvitation(id) or revokeInvitation(id), prefer it over list+find for clarity and efficiency.

If not available, current approach is acceptable.

apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (4)

199-201: Use inline alerts for blocking errors; avoid error toasts in dashboard.

Per app guidelines, replace destructive toasts with inline error messaging in the dialog.

Apply minimal changes using existing formError:

-      } else {
-        const message = error instanceof Error ? error.message : "Unknown error";
-        toast({ variant: "destructive", title: "Failed to send invitation", description: message });
-      }
+      } else {
+        const message = error instanceof Error ? error.message : "Unknown error";
+        setFormError(message);
+      }
@@
-    } catch (error) {
-      const message = error instanceof Error ? error.message : "Unknown error";
-      toast({ variant: "destructive", title: "Failed to start upgrade", description: message });
-    };
+    } catch (error) {
+      const message = error instanceof Error ? error.message : "Unknown error";
+      setFormError(message);
+    };

If stack-ui provides an Alert component, we can switch to that instead of the label. Based on coding guidelines.

Also applies to: 213-215


87-93: Remove artificial 2s delay after Create Project.

Delay hurts UX and provides no benefit here.

-          <Button
-            onClick={async () => {
-              router.push('/new-project');
-              return await wait(2000);
-            }}
-          >Create Project
-          </Button>
+          <Button onClick={() => router.push('/new-project')}>
+            Create Project
+          </Button>

146-152: Suspense fallback won’t show (no suspension).

TeamAddUserDialogContent doesn’t suspend; the Suspense wrapper is redundant.

-          <Suspense fallback={<TeamAddUserDialogContentSkeleton />}>
-            <TeamAddUserDialogContent
-              team={props.team}
-              onClose={() => setOpen(false)}
-            />
-          </Suspense>
+          <TeamAddUserDialogContent
+            team={props.team}
+            onClose={() => setOpen(false)}
+          />

179-183: Guard against missing or transient seatLimit.

Avoid crashes if admins or admins.quantity are undefined during load.

-  const activeSeats = users.length + (invitations?.length ?? 0);
-  const seatLimit = admins.quantity;
-  const atCapacity = activeSeats >= seatLimit;
+  const activeSeats = users.length + (invitations?.length ?? 0);
+  const seatLimit = admins?.quantity ?? Infinity;
+  const atCapacity = Number.isFinite(seatLimit) ? activeSeats >= seatLimit : false;

Confirm that users refers to “dashboard admins” and that invitations are only for admin seats; otherwise counting may be inflated.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7a48f0 and 0c8a531.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (10 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ES6 Maps instead of Records wherever possible in TypeScript code

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
apps/{dashboard,dev-launchpad}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in the UI, never use toast; use alerts instead

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
apps/{dashboard,dev-launchpad}/**/*.{css,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Animations: keep hover/click transitions snappy; do not delay actions with pre-hover transitions; apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (3)
packages/template/src/lib/stack-app/teams/index.ts (1)
  • Team (36-50)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (3)
  • listInvitations (18-30)
  • inviteUser (32-39)
  • revokeInvitation (4-16)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: claude-review
  • GitHub Check: Security Check
🔇 Additional comments (2)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (1)

6-10: Auth vs. not-found semantics — confirm behavior.

If stackServerApp.getUser() can be null/undefined, you’re converting “unauthorized” into “Team not found.” Confirm desired semantics; otherwise throw an “Unauthorized” error when user is missing.

Would you like a patch that distinguishes Unauthorized (401/403) from Not Found (404)?

Also applies to: 18-23

apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (1)

164-172: LGTM: async invite fetching pattern.

useCallback + runAsynchronously avoids blocking UI and centralizes error logging.

@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Oct 30, 2025
@BilalG1 BilalG1 enabled auto-merge (squash) October 30, 2025 16:43
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: 1

Caution

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

⚠️ Outside diff range comments (2)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (2)

209-209: Use alert instead of toast for blocking errors.

According to coding guidelines, blocking errors in the UI should use alerts instead of toast. Toast is appropriate for success messages but not for errors that require user attention.

As per coding guidelines

Apply this diff:

       } else {
         const message = error instanceof Error ? error.message : "Unknown error";
-        toast({ variant: "destructive", title: "Failed to send invitation", description: message });
+        alert(`Failed to send invitation: ${message}`);
       }

223-223: Use alert instead of toast for blocking errors.

According to coding guidelines, blocking errors in the UI should use alerts instead of toast.

As per coding guidelines

Apply this diff:

     } catch (error) {
       const message = error instanceof Error ? error.message : "Unknown error";
-      toast({ variant: "destructive", title: "Failed to start upgrade", description: message });
+      alert(`Failed to start upgrade: ${message}`);
     };
♻️ Duplicate comments (3)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (3)

164-167: Add error handling to fetchInvitations.

The fetchInvitations callback lacks error handling. If listInvitations fails, the error propagates to callers (lines 203, 281) without handling, potentially leaving the UI in an inconsistent state or causing unhandled promise rejections.

Apply this diff to add error handling:

 const fetchInvitations = useCallback(async () => {
-  const invitations = await listInvitations(props.team.id);
-  setInvitations(invitations);
+  try {
+    const invitations = await listInvitations(props.team.id);
+    setInvitations(invitations);
+  } catch (error) {
+    const message = error instanceof Error ? error.message : "Unknown error";
+    alert("Failed to load invitations: " + message);
+    setInvitations([]);
+  }
 }, [props.team.id]);

200-200: Remove client-side callbackUrl parameter for security.

Passing window.location.origin from the client is a security risk as it can be spoofed. The server should compute a safe callback URL based on the request origin or a configured whitelist.

However, the server action signature in actions.ts still expects three parameters. First update the server action to compute the callback URL safely, then apply this diff:

-      await inviteUser(props.team.id, values.email, window.location.origin);
+      await inviteUser(props.team.id, values.email);

279-282: Use runAsynchronouslyWithAlert for async button handler.

The revoke button's onClick handler is an async arrow function without error handling, which can cause unhandled promise rejections if revokeInvitation fails. According to codebase learnings, async button click handlers should use runAsynchronouslyWithAlert to automatically handle errors and show alerts to users.

Based on learnings

Apply this diff:

                   <Button
                     variant="ghost"
                     size="sm"
-                    onClick={async () => {
+                    onClick={() => runAsynchronouslyWithAlert(async () => {
                       await revokeInvitation(props.team.id, invitation.id);
                       await fetchInvitations();
-                    }}
+                    })}
                   >
🧹 Nitpick comments (2)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (2)

301-307: Consider using runAsynchronouslyWithAlert directly in button handlers.

The button onClick handlers currently call async functions with manual try/catch blocks. According to codebase learnings, async button click handlers should use runAsynchronouslyWithAlert to automatically handle errors and show alerts. This would simplify the code and ensure consistent error handling.

Based on learnings

Example for the Invite button:

-        <Button onClick={handleInvite}>
+        <Button onClick={() => runAsynchronouslyWithAlert(async () => {
+          setFormError(null);
+          const values = await inviteFormSchema.validate({ email: email.trim() });
+          await inviteUser(props.team.id, values.email);
+          setEmail("");
+          await fetchInvitations();
+        })}>
           Invite
         </Button>

Note: You would need to handle validation errors separately with a try/catch around the validate call, or use runAsynchronouslyWithAlert's error handler option.


88-93: Clarify or remove the 2-second wait after navigation.

The await wait(2000) after router.push('/new-project') seems unusual. If this is intentional (e.g., to show a loading state), please add a comment explaining why. Otherwise, consider removing it.

           <Button
             onClick={async () => {
               router.push('/new-project');
-              return await wait(2000);
             }}
           >Create Project
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8a531 and 227e921.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (10 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ES6 Maps instead of Records wherever possible in TypeScript code

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
apps/{dashboard,dev-launchpad}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in the UI, never use toast; use alerts instead

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
apps/{dashboard,dev-launchpad}/**/*.{css,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Animations: keep hover/click transitions snappy; do not delay actions with pre-hover transitions; apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
PR: stack-auth/stack-auth#943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
📚 Learning: 2025-10-20T22:25:40.427Z
Learnt from: CR
PR: stack-auth/stack-auth#0
File: AGENTS.md:0-0
Timestamp: 2025-10-20T22:25:40.427Z
Learning: Applies to packages/{stack-ui,react}/**/*.{tsx,jsx} : For blocking alerts and errors in shared/UI packages, never use toast; use alerts instead

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
📚 Learning: 2025-10-20T22:25:40.427Z
Learnt from: CR
PR: stack-auth/stack-auth#0
File: AGENTS.md:0-0
Timestamp: 2025-10-20T22:25:40.427Z
Learning: Applies to apps/{dashboard,dev-launchpad}/**/*.{tsx,jsx} : For blocking alerts and errors in the UI, never use toast; use alerts instead

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (3)
packages/template/src/lib/stack-app/teams/index.ts (1)
  • Team (36-50)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts (3)
  • listInvitations (18-30)
  • inviteUser (32-39)
  • revokeInvitation (4-16)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check

@BilalG1 BilalG1 disabled auto-merge October 30, 2025 16:59
@BilalG1 BilalG1 merged commit 40d878d into dev Oct 30, 2025
24 of 26 checks passed
@BilalG1 BilalG1 deleted the fix-team-invitations branch October 30, 2025 16:59
@coderabbitai coderabbitai bot mentioned this pull request Oct 31, 2025
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.

2 participants