Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds a server-side module exposing Changes
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @BilalG1's task —— View job PR Review: Fix Team Invitations with Server ActionsReview Checklist:
🔍 Code Review SummaryThis 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. ✅ StrengthsServer Actions Implementation (actions.ts)
React Component Updates (page-client.tsx)
|
There was a problem hiding this comment.
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
runAsynchronouslyWithAlertfor 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
2 files reviewed, 1 comment
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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
📒 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.tsapps/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.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/actions.ts
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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 tofetchInvitations.The
fetchInvitationscallback lacks error handling. IflistInvitationsfails, 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-sidecallbackUrlparameter for security.Passing
window.location.originfrom 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.tsstill 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: UserunAsynchronouslyWithAlertfor async button handler.The revoke button's
onClickhandler is an async arrow function without error handling, which can cause unhandled promise rejections ifrevokeInvitationfails. According to codebase learnings, async button click handlers should userunAsynchronouslyWithAlertto 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 usingrunAsynchronouslyWithAlertdirectly in button handlers.The button
onClickhandlers currently call async functions with manual try/catch blocks. According to codebase learnings, async button click handlers should userunAsynchronouslyWithAlertto 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)afterrouter.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
📒 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
Summary by CodeRabbit