Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a Slack support invite feature: admin UI and API, workspace/user-facing API with rate limits and plan gating, Slack integration library for channel creation and invites, onboarding/settings UI controls, new icons, and related config/type updates. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Admin
participant Web as Web UI
participant API as API Route
participant SlackLib as Slack Support Lib
participant SlackAPI as Slack Web API
User->>Web: Click "Request Invite" or submit admin form
Web->>API: POST { email, workspaceSlug, channelId? }
API->>API: Validate, auth, plan/permission checks, rate limit
API->>SlackLib: request/invite call
SlackLib->>SlackLib: Ensure channel (create or use channelId)
SlackLib->>SlackAPI: conversations.create or conversations.inviteShared
SlackAPI-->>SlackLib: Success or error (e.g., name_taken)
alt name_taken
SlackLib-->>API: { nameTaken: true }
API-->>Web: 409 conflict + error
Web->>User: show error toast / prompt channelId
else success
SlackLib-->>API: { inviteId }
API-->>Web: 200 { inviteId }
Web->>User: show success toast
end
sequenceDiagram
participant Admin as Admin User
participant AdminUI as Admin Dashboard
participant AdminAPI as Admin API
participant SlackLib as Slack Support Lib
participant SlackAPI as Slack Web API
Admin->>AdminUI: Fill and submit invite form
AdminUI->>AdminAPI: POST payload
AdminAPI->>AdminAPI: withAdmin auth
AdminAPI->>SlackLib: inviteToSlackSupportChannel(...)
SlackLib->>SlackAPI: create/invite calls, pre-invite internal users
SlackAPI-->>SlackLib: responses (success or errors)
alt name_taken
SlackLib-->>AdminAPI: { nameTaken: true }
AdminAPI-->>AdminUI: 409 with message
AdminUI->>AdminUI: set needsChannelId = true, show error
else success
SlackLib-->>AdminAPI: { inviteId }
AdminAPI-->>AdminUI: 200 { inviteId }
AdminUI->>AdminUI: clear form, show success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/success/page-client.tsx (1)
50-55: Minor:slackInviteInFlightref is redundant withslackInvitingstate.The button at line 301 is already
disabled={slackInviteDone || slackInviting}, andsetSlackInviting(true)runs synchronously before thefetch. The additionaluseRefguard is unnecessary unless you anticipate programmatic callers; consider removing it to reduce complexity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/success/page-client.tsx around lines 50 - 55, Remove the redundant slackInviteInFlight useRef and all checks/assignments that reference it; rely on the existing slackInviting useState (which is set synchronously before the fetch) and the button disabled prop (disabled={slackInviteDone || slackInviting}) to prevent re-entrancy. Update the invite handler (the function that calls setSlackInviting and performs the fetch) to no longer read or write slackInviteInFlight, and delete the slackInviteInFlight declaration and imports so only slackInviting controls in-flight guarding.apps/web/ui/workspaces/slack-support-settings-card.tsx (1)
65-75:AnimatePresencehas no effect here.The
motion.divis unconditionally rendered (the earlyreturn nullhappens above), soAnimatePresencenever sees a child being removed and theexitanimation will not run on dismiss. Either wrap a conditional ({!dismissed && <motion.div ... />}) insideAnimatePresence(and move the dismissal checks into the JSX), or dropAnimatePresenceentirely.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/ui/workspaces/slack-support-settings-card.tsx` around lines 65 - 75, The AnimatePresence wrapper currently has no effect because motion.div is always rendered after the early return; update the JSX so AnimatePresence controls the conditional rendering of the motion.div (e.g., move the dismissal logic into the render and render {!dismissed && <motion.div ... />} inside AnimatePresence) so exit animations run when dismissed, or if you prefer, remove the AnimatePresence import/wrapper and keep the unconditional motion.div; reference AnimatePresence, motion.div and the dismissed check to locate where to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/web/app/`(ee)/admin.dub.co/(dashboard)/components/slack-support-invite.tsx:
- Around line 14-42: The form action's fetch block doesn't handle non-OK HTTP
statuses or network/JSON parse errors, which can lead to false success toasts
and unhandled rejections; wrap the fetch/res.json sequence in a try/catch, check
res.ok before treating the response as success, attempt to read an error message
from the parsed body when res.ok is false (fall back to a generic message), and
only call toast.success with json.inviteId when res.ok is true and inviteId is
present; update the inline action function that calls
fetch("/api/admin/slack-support-invite") and keep using setNeedsChannelId,
toast.error/toast.success, and Form as-is.
In `@apps/web/app/`(ee)/api/admin/slack-support-invite/route.ts:
- Around line 10-24: Wrap the call to inviteToSlackSupportChannel in a try/catch
that catches DubApiError and uses handleAndReturnErrorResponse to return a
structured JSON error (or rethrow other errors); ensure this is done inside the
POST handler defined via withAdmin. Replace the manual presence checks for email
and workspaceSlug with a zod schema (validate email format and non-empty
strings) at the top of the POST handler, parse req.json() with that schema, and
return the schema error response shape when validation fails so the route
matches other admin endpoints.
In `@apps/web/app/api/workspaces/`[idOrSlug]/support/slack-invite/route.ts:
- Around line 28-38: The current per-user key in ratelimit
(ratelimit(...).limit(`slack-support-invite:${workspace.id}:${session.user.id}`))
doesn't prevent many invites from being generated by different users in the same
workspace, so add a second, coarser workspace-level rate check before
proceeding: call ratelimit(5, "1
day").limit(`slack-support-invite:workspace:${workspace.id}`) (or your chosen
cap/window), and if that check fails throw the same DubApiError; keep the
existing per-user check as well (order can be workspace-first then user) so both
checks must pass before creating the Slack channel/invite.
In
`@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/success/page-client.tsx:
- Around line 57-83: The bug is that setSlackInviteDone(true) is called in the
finally block of requestSlackSupportInvite, which marks the invite as completed
even on failure; change requestSlackSupportInvite so setSlackInviteDone(true) is
only called inside the success branch (where res.ok is true) and remove it from
the finally block, while keeping slackInviteInFlight.current = false and
setSlackInviting(false) in finally to always clear in-flight state.
In `@apps/web/lib/slack/support-invite.ts`:
- Around line 241-251: The current flow treats a "nameTaken"/nameTaken result
from createSharedCustomerChannel as a hard conflict and throws a DubApiError,
which makes repeated self-serve invites fail; instead, make the flow idempotent
by resolving the existing channel and sending the invite: when
createSharedCustomerChannel returns the nameTaken variant, lookup or fetch the
existing channel ID for the channel name (e.g., "shared-{workspaceSlug}") or
return the channelId if provided, and then call
sendSlackConnectInvite(channelId, email) just like the success branch;
alternatively persist the created channelId from createSharedCustomerChannel (or
call a resolver function) and use that persisted/found channelId to call
sendSlackConnectInvite so retries and subsequent invites succeed rather than
throwing from the nameTaken branch.
- Around line 90-99: The slackWebApi wrapper must be hardened: add an
AbortController with a configurable timeout (e.g., 5–10s) around the fetch to
prevent hangs, then check response.ok and handle non-2xx statuses (treat 429
specially) before attempting to parse JSON; wrap the response.json() call in
try/catch to convert any parsing or network errors into a DubApiError and
include structured fields (status, statusText, method, API path, raw body/text
when parse fails) to preserve debugging context. Update slackWebApi to throw a
DubApiError on timeout, non-ok HTTP responses, or JSON parse failures so all
callers receive a bounded, typed error.
In `@apps/web/ui/workspaces/slack-support-settings-card.tsx`:
- Around line 42-62: Remove the unreachable "conflict" branch inside the error
handling after the fetch to `/api/workspaces/${slug}/support/slack-invite`:
delete the `if (json?.error?.code === "conflict") { setDismissed(true); }` check
so only the existing toast.error(json?.error?.message ?? "...") runs on non-ok
responses; keep the existing success path that calls toast.success(...) and
setDismissed(true). This touches the error handling around the fetch/await json,
toast.error usage and the setDismissed calls in the Slack invite handler.
In `@packages/email/src/templates/upgrade-email.tsx`:
- Around line 27-38: The default workspaceSlug value should not be the sample
"acme" because missing callers (like the Stripe webhook) will incorrectly
produce /acme links; update the function signature to default workspaceSlug to
null instead of "acme" and change the fallback settingsUrl to use APP_DOMAIN
(i.e., when workspaceSlug is falsy use `${APP_DOMAIN}/settings`); locate the
workspaceSlug parameter and the settingsUrl computation in upgrade-email.tsx
(see getPlanDetails, workspaceSlug, settingsUrl, and APP_DOMAIN) and make those
two changes.
---
Nitpick comments:
In
`@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/success/page-client.tsx:
- Around line 50-55: Remove the redundant slackInviteInFlight useRef and all
checks/assignments that reference it; rely on the existing slackInviting
useState (which is set synchronously before the fetch) and the button disabled
prop (disabled={slackInviteDone || slackInviting}) to prevent re-entrancy.
Update the invite handler (the function that calls setSlackInviting and performs
the fetch) to no longer read or write slackInviteInFlight, and delete the
slackInviteInFlight declaration and imports so only slackInviting controls
in-flight guarding.
In `@apps/web/ui/workspaces/slack-support-settings-card.tsx`:
- Around line 65-75: The AnimatePresence wrapper currently has no effect because
motion.div is always rendered after the early return; update the JSX so
AnimatePresence controls the conditional rendering of the motion.div (e.g., move
the dismissal logic into the render and render {!dismissed && <motion.div ...
/>} inside AnimatePresence) so exit animations run when dismissed, or if you
prefer, remove the AnimatePresence import/wrapper and keep the unconditional
motion.div; reference AnimatePresence, motion.div and the dismissed check to
locate where to change.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2196fed5-7c2d-4969-92de-1fe1382c4ba7
📒 Files selected for processing (17)
apps/web/.env.exampleapps/web/app/(ee)/admin.dub.co/(dashboard)/components/slack-support-invite.tsxapps/web/app/(ee)/admin.dub.co/(dashboard)/page.tsxapps/web/app/(ee)/api/admin/slack-support-invite/route.tsapps/web/app/(ee)/api/stripe/webhook/checkout-session-completed.tsapps/web/app/api/workspaces/[idOrSlug]/support/slack-invite/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/page-client.tsxapps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/success/page-client.tsxapps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/success/page.tsxapps/web/lib/plan-capabilities.tsapps/web/lib/slack/support-invite.tsapps/web/ui/workspaces/slack-support-settings-card.tsxpackages/email/src/templates/upgrade-email.tsxpackages/ui/src/icons/index.tsxpackages/ui/src/icons/slack-2.tsxpackages/ui/src/icons/slack-colorful.tsxpackages/utils/src/constants/pricing/pricing-plans.tsx
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/success/page-client.tsx (1)
69-89:⚠️ Potential issue | 🟠 MajorBug still present: invite is marked "done" on failure, blocking retries.
setSlackInviteDone(true)is still inside thefinallyblock (line 87), so any failure path (network error, rate-limit hit, transient 5xx, plan/trial race, etc.) permanently flips the persistedslack-support-dismissed:${workspace.slug}flag totrue. The user then sees a generic error toast and the CTA becomes "Invite sent" forever, with the early-return at line 64 preventing any retry.Move the
setSlackInviteDone(true)call into the success branch only:🐛 Proposed fix
try { const res = await fetch( `/api/workspaces/${workspace.slug}/support/slack-invite`, { method: "POST" }, ); const json = await res.json().catch(() => ({})); if (res.ok) { toast.success( "Check your email for your Slack Connect invitation to our team.", ); + setSlackInviteDone(true); } else { toast.error( json?.error?.message ?? "Contact support for help connecting Slack.", ); } } finally { slackInviteInFlight.current = false; setSlackInviting(false); - setSlackInviteDone(true); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/success/page-client.tsx around lines 69 - 89, The invite flow marks the invite as "done" even on failure because setSlackInviteDone(true) is in the finally block; move setSlackInviteDone(true) into the success branch (inside the res.ok block) and remove it from the finally block so failures don't persist the persisted slack-support-dismissed:${workspace.slug} flag; keep slackInviteInFlight.current = false and setSlackInviting(false) in the finally block so in-flight state is always cleared but only mark done when the POST to /api/workspaces/.../support/slack-invite succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@apps/web/app/app.dub.co/`(onboarding)/onboarding/(steps)/success/page-client.tsx:
- Around line 69-89: The invite flow marks the invite as "done" even on failure
because setSlackInviteDone(true) is in the finally block; move
setSlackInviteDone(true) into the success branch (inside the res.ok block) and
remove it from the finally block so failures don't persist the persisted
slack-support-dismissed:${workspace.slug} flag; keep slackInviteInFlight.current
= false and setSlackInviting(false) in the finally block so in-flight state is
always cleared but only mark done when the POST to
/api/workspaces/.../support/slack-invite succeeds.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: df749fa6-c1dd-451e-9afe-f1128eb77505
📒 Files selected for processing (3)
apps/web/app/api/workspaces/[idOrSlug]/support/slack-invite/route.tsapps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/success/page-client.tsxapps/web/ui/workspaces/slack-support-settings-card.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/ui/workspaces/slack-support-settings-card.tsx
Summary by CodeRabbit
New Features
UI Enhancements
Pricing