Skip to content

Conversation

@jnsdls
Copy link
Member

@jnsdls jnsdls commented Sep 18, 2025

[Dashboard] Feature: Add Stripe checkout for Growth plan subscription

Notes for the reviewer

This PR adds Stripe checkout integration for the Growth plan subscription during the onboarding flow. Key changes include:

  • Added Stripe checkout integration with embedded checkout UI
  • Updated onboarding flow to include payment details step
  • Added project creation step to onboarding flow
  • Added new environment variables for Stripe configuration
  • Added analytics tracking for team member step completion

How to test

  1. Start the onboarding flow
  2. Complete team details
  3. Verify Stripe checkout appears on the payment details step
  4. Complete team members step
  5. Verify project creation form appears and works correctly

PR-Codex overview

This PR focuses on integrating Stripe payment processing into the onboarding flow of a team management application, enhancing billing features, and refining the user experience for creating projects.

Detailed summary

  • Added GROWTH_PLAN_SKU and PAYMENT_METHOD_CONFIGURATION to environment variables.
  • Integrated @stripe/react-stripe-js and @stripe/stripe-js for payment processing.
  • Implemented GrowthPlanCheckout component for Stripe embedded checkout.
  • Updated onboarding steps and analytics reporting functions.
  • Enhanced the CreateProjectFormOnboarding component for project creation.
  • Improved error handling and user feedback during payment processing.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Embedded checkout for the Growth Plan with 14‑day free trial, session handling, redirects, and error alerts.
    • Guided project creation flow in onboarding with domain controls, automatic service setup, and post‑creation API key display/confirmation.
    • Onboarding expanded to four steps, ending with “Create Project.”
  • UX/Style

    • “Get started” pages default to dark theme.
    • Updated onboarding copy and icons.
  • Chores

    • Added Stripe client libraries and new billing-related environment variables.
  • Analytics

    • Adjusted onboarding event reporting to track completion of the team‑member step.

@vercel
Copy link

vercel bot commented Sep 18, 2025

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

Project Deployment Preview Comments Updated (UTC)
docs-v2 Ready Ready Preview Comment Sep 18, 2025 4:41am
nebula Ready Ready Preview Comment Sep 18, 2025 4:41am
thirdweb_playground Ready Ready Preview Comment Sep 18, 2025 4:41am
thirdweb-www Ready Ready Preview Comment Sep 18, 2025 4:41am
wallet-ui Ready Ready Preview Comment Sep 18, 2025 4:41am

@changeset-bot
Copy link

changeset-bot bot commented Sep 18, 2025

⚠️ No Changeset found

Latest commit: e4caab4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label Sep 18, 2025
Copy link
Member Author

jnsdls commented Sep 18, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Walkthrough

Adds Stripe Embedded Checkout and server actions for creating/retrieving Stripe Checkout sessions, two new server envs (taint-guarded) and Stripe client deps, extends onboarding to include a project-creation step with UI and analytics updates, and forces dark theme for get-started routes.

Changes

Cohort / File(s) Summary
Environment & Config
apps/dashboard/.env.example, apps/dashboard/src/@/constants/server-envs.ts, apps/dashboard/package.json
Adds GROWTH_PLAN_SKU and PAYMENT_METHOD_CONFIGURATION to example env and exports (taint-guarded); adds Stripe client dependencies @stripe/react-stripe-js and @stripe/stripe-js.
Stripe Server Actions
apps/dashboard/src/@/actions/stripe-actions.ts
Adds fetchClientSecret(team) to create Embedded Checkout Sessions and getStripeSessionById(sessionId) to retrieve sessions; imports new server envs and request headers.
Select Plan Page & Checkout UI
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx, apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
Replaces prior plan selector with GrowthPlanCheckout (client) using Embedded Checkout; server page handles optional session_id searchParam, fetches session status and redirects or surfaces errors.
Create Project Page & Form
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx, apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
Adds onboarding project-creation page and CreateProjectFormOnboarding component: project creation mutation, service selection defaults, vault/token creation attempts, and API credential display with confirmation and navigation.
Onboarding Layout & Flow
apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx, apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
Extends onboarding timeline to four steps (API/text/icon updates); after member step, reports reportTeamMemberStepCompleted() and navigates to create-project.
Analytics
apps/dashboard/src/@/analytics/report.ts
Removes onboarding plan-selected and plan-selection-skipped reporters; adds reportTeamMemberStepCompleted().
Providers / Theming
apps/dashboard/src/app/(app)/providers.tsx
Adds usePathname to force dark theme on paths starting with /get-started.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SelectPage as Select Plan Page (Server)
  participant CheckoutClient as GrowthPlanCheckout (Client)
  participant StripeActions as stripe-actions.ts (Server)
  participant StripeAPI as Stripe

  User->>SelectPage: GET /get-started/.../select-plan[?session_id=...]
  SelectPage->>StripeActions: getStripeSessionById(session_id) [if present]
  StripeActions-->>SelectPage: session (status)
  alt status == "complete"
    SelectPage->>User: redirect to add-members
  else status == "open"
    SelectPage-->>User: render page with error alert
  else
    SelectPage-->>User: render page with checkout
  end

  User->>CheckoutClient: load checkout UI
  CheckoutClient->>StripeActions: fetchClientSecret(team)
  StripeActions->>StripeAPI: create Checkout Session (embedded)
  StripeAPI-->>StripeActions: session { client_secret }
  StripeActions-->>CheckoutClient: client_secret
  CheckoutClient->>StripeAPI: initialize embedded checkout with client_secret
Loading
sequenceDiagram
  autonumber
  actor User
  participant MembersUI as Invite Members (Client)
  participant Analytics as report.ts
  participant Router as App Router
  participant CreatePage as Create Project Page (Server)
  participant FormClient as CreateProjectFormOnboarding (Client)
  participant BackendAPI as Project/Vault/Token APIs

  User->>MembersUI: complete members step
  MembersUI->>Analytics: reportTeamMemberStepCompleted()
  MembersUI->>Router: push /get-started/.../create-project

  User->>CreatePage: GET create-project
  CreatePage-->>User: render form

  User->>FormClient: submit project
  FormClient->>BackendAPI: create project (services/domains)
  BackendAPI-->>FormClient: project created
  FormClient->>BackendAPI: best-effort create vault & access token
  BackendAPI-->>FormClient: vault/token responses
  FormClient-->>User: show client id & secret (require confirm)
  User->>FormClient: confirm stored secret
  FormClient->>Router: navigate to /team/{slug}/{project}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% 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 body contains only the commented repository template and was not filled out by the author, so required sections (a title in the specified "[SDK/Dashboard/Portal] ..." format, an issue tag if applicable, "Notes for the reviewer", and a concrete "How to test" block) are missing; this prevents reviewers from quickly understanding scope and test steps. The provided PR metadata and raw_summary contain details about Stripe integration, new env vars, dependency changes, and onboarding flow changes, but those details are not present in the PR description itself. Because the template was left unpopulated, the description fails the repository's required template check. Please complete the PR description by adding a proper title using the required format (e.g., "[Dashboard] Feature: Add Stripe checkout for Growth plan subscription") and include the issue/Linear tag if available, then fill the "Notes for the reviewer" with key points (new env vars GROWTH_PLAN_SKU and PAYMENT_METHOD_CONFIGURATION, added @stripe/* deps, analytics/onboarding changes, and any migration or breaking-change risks). Add a step-by-step "How to test" that lists required env values, local startup commands, the onboarding path to exercise (payment details → invite members → create project), and any manual Graphite/merge-queue instructions or CI expectations. After updating the PR body, re-run review checks.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Add Stripe checkout for Growth plan subscription" is concise and directly describes the primary change (integrating Stripe checkout for the Growth plan during onboarding), making it clear to reviewers and teammates scanning history. It avoids unnecessary noise and accurately reflects the changes summarized in the PR. Optionally adding the repository prefix (for example "[Dashboard] Feature:") would align it with the repo's title format but is stylistic rather than required.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Add_Stripe_checkout_for_Growth_plan_subscription

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

@socket-security
Copy link

socket-security bot commented Sep 18, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​stripe/​stripe-js@​7.9.010010010098100
Added@​stripe/​react-stripe-js@​4.0.29910010099100

View full report

@github-actions
Copy link
Contributor

github-actions bot commented Sep 18, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 63.96 KB (0%) 1.3 s (0%) 552 ms (+168.2% 🔺) 1.9 s
thirdweb (cjs) 361.44 KB (0%) 7.3 s (0%) 2.3 s (+11.74% 🔺) 9.5 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 215 ms (+1214.03% 🔺) 330 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 113 ms (+1943% 🔺) 124 ms
thirdweb/react (minimal + tree-shaking) 19.14 KB (0%) 383 ms (0%) 264 ms (+2587.78% 🔺) 647 ms

@codecov
Copy link

codecov bot commented Sep 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.35%. Comparing base (552f702) to head (e4caab4).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8068      +/-   ##
==========================================
- Coverage   56.53%   56.35%   -0.19%     
==========================================
  Files         904      906       +2     
  Lines       58873    59168     +295     
  Branches     4165     4172       +7     
==========================================
+ Hits        33283    33342      +59     
- Misses      25484    25720     +236     
  Partials      106      106              
Flag Coverage Δ
packages 56.35% <ø> (-0.19%) ⬇️
see 34 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

🧹 Nitpick comments (18)
apps/dashboard/src/app/(app)/providers.tsx (4)

28-30: Avoid accidental matches; precompute a segment-safe flag.

Safeguard against paths like "/get-started-now" by checking segment boundaries once and reusing.

Apply this diff:

   // get the current pathname
   const pathname = usePathname();
+  const isForceDarkPath =
+    pathname === "/get-started" || pathname.startsWith("/get-started/");

75-75: De-duplicate thirdweb client initialization.

Reuse the existing thirdwebClient to avoid a second call and keep config centralized.

Apply this diff:

-const client = getClientThirdwebClient();
@@
-          <CustomConnectWallet
-            client={client}
+          <CustomConnectWallet
+            client={thirdwebClient}
             isLoggedIn={true}
             loginRequired={false}
           />

Also applies to: 93-96


24-27: Add explicit return types per repo TS guidelines.

Small clarity win; aligns with “explicit return types” rule.

Apply this diff:

-export function AppRouterProviders(props: {
+export function AppRouterProviders(props: {
   children: React.ReactNode;
   autoConnect: boolean;
-}) {
+}): JSX.Element {
@@
-function SyncChainDefinitionsToConnectionManager() {
+function SyncChainDefinitionsToConnectionManager(): null {
@@
-const SanctionedAddressesChecker = ({
+const SanctionedAddressesChecker = ({
   children,
 }: {
   children: React.ReactNode;
-}) => {
+}): JSX.Element => {

Also applies to: 58-73, 77-102


43-45: Use a precomputed isForceDarkPath (include all onboarding prefixes)

Compute a single flag and use it for forcedTheme; onboarding-like routes found: /get-started and /login/onboarding (covers /login/onboarding/team-onboarding). No basePath/i18n found that would change pathname.

File: apps/dashboard/src/app/(app)/providers.tsx — replace forcedTheme usage:

-            forcedTheme={
-              pathname.startsWith("/get-started") ? "dark" : undefined
-            }
+            forcedTheme={isForceDarkPath ? "dark" : undefined}

Add a small precompute near where pathname is available:

const forceDarkPrefixes = ["/get-started", "/login/onboarding"];
const isForceDarkPath = forceDarkPrefixes.some(p => pathname.startsWith(p));
apps/dashboard/.env.example (1)

67-69: Alphabetize new keys before STRIPE_SECRET_KEY and annotate expected values.

Reorder per dotenv-linter and document the expected identifiers.

-STRIPE_SECRET_KEY=""
-GROWTH_PLAN_SKU=""
-PAYMENT_METHOD_CONFIGURATION=""
+GROWTH_PLAN_SKU="" # Stripe Price ID for Growth plan subscription (e.g., price_123)
+PAYMENT_METHOD_CONFIGURATION="" # Stripe Payment Method Configuration ID (e.g., pmc_123)
+STRIPE_SECRET_KEY=""

Note: The QuoteCharacter warnings are inconsistent with existing entries that use quotes; keeping quotes for consistency is fine if linter rules are relaxed.

apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx (1)

126-128: Confirm navigation intent (push vs replace) after completing members.

If you want to prevent users from navigating back to the members step, prefer router.replace. Otherwise push is fine.

apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx (1)

7-9: Add explicit return type per TS guidelines (optional).

-export default async function Page(props: {
+export default async function Page(props: {
   params: Promise<{ team_slug: string }>;
-}) {
+}): Promise<JSX.Element> {
apps/dashboard/src/@/actions/stripe-actions.ts (1)

130-135: Optional: Mark as server action if ever called from the client.

If getStripeSessionById will be invoked as a server action from client code, add "use server" at the top of the function for clarity. Otherwise, ignore.

apps/dashboard/package.json (1)

24-25: Stripe imports are in a client component — lazy‑load the checkout and add a size budget.

  • Verified: @stripe/react-stripe-js and @stripe/stripe-js are only imported in apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (starts with "use client").
  • Action: Dynamically import this checkout component in the get-started/select-plan route (next/dynamic or React.lazy) so Stripe libs aren't in the initial dashboard bundle.
  • Action: Add/adjust a size-limit budget for the get-started/select-plan flow in apps/dashboard/package.json.
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (1)

10-13: Avoid non-null assertion on publishable key; guard missing env at runtime.

Current code will crash silently or bundle with an invalid key. Guard and degrade gracefully.

-const stripePromise = loadStripe(
-  // biome-ignore lint/style/noNonNullAssertion: TODO: fix this later
-  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
-);
+const pk = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
+if (!pk && process.env.NODE_ENV !== "production") {
+  // Visible only in dev to catch misconfig early
+  // eslint-disable-next-line no-console
+  console.error("Missing NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY");
+}
+const stripePromise = pk ? loadStripe(pk) : Promise.resolve(null);
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (3)

1-1: Mark as server-only.

Prevents accidental client bundling.

+import "server-only";
 import { notFound, redirect } from "next/navigation";

56-58: Fix typo in Alert title.

-            <AlertTitle>Failed to proces subscription</AlertTitle>
+            <AlertTitle>Failed to process subscription</AlertTitle>

12-15: Add explicit return type to Page.

Matches repo TS guidelines.

-export default async function Page(props: {
+export default async function Page(props: {
   params: Promise<{ team_slug: string }>;
   searchParams: Promise<{ session_id?: string }>;
-}) {
+}): Promise<JSX.Element> {
apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx (1)

88-89: Grammar: “teammates” is one word.

-    description: "Invite your team mates",
+    description: "Invite your teammates",
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (4)

299-306: Fix button text typo.

-            Create Projecct
+            Create Project

42-47: Expose className on exported component and apply to root Card. Add return type.

Matches dashboard component convention.

 export function CreateProjectFormOnboarding(props: {
   prefill?: CreateProjectPrefillOptions;
   enableNebulaServiceByDefault: boolean;
   teamSlug: string;
   teamId: string;
-}) {
+  className?: string;
+}): JSX.Element {

52-52: Apply className to root element.

-    <Card>
+    <Card className={props.className}>

107-115: Optional: add explicit return types to local components.

For consistency, consider annotating CreateProjectForm, DomainsAlert, and CreatedProjectDetails with JSX.Element.

📜 Review details

Configuration used: CodeRabbit UI

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 00dcc34 and 52ccddb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/dashboard/.env.example (1 hunks)
  • apps/dashboard/package.json (1 hunks)
  • apps/dashboard/src/@/actions/stripe-actions.ts (2 hunks)
  • apps/dashboard/src/@/analytics/report.ts (1 hunks)
  • apps/dashboard/src/@/constants/server-envs.ts (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (2 hunks)
  • apps/dashboard/src/app/(app)/providers.tsx (3 hunks)
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx (2 hunks)
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/@/constants/server-envs.ts
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/@/actions/stripe-actions.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/@/constants/server-envs.ts
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/@/actions/stripe-actions.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/@/constants/server-envs.ts
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/@/actions/stripe-actions.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/@/constants/server-envs.ts
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/@/actions/stripe-actions.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
**/package.json

📄 CodeRabbit inference engine (AGENTS.md)

Track bundle budgets via package.json#size-limit

Files:

  • apps/dashboard/package.json
apps/{dashboard,playground}/src/@/analytics/report.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/src/@/analytics/report.ts: Check report.ts before adding a new analytics event to avoid duplicates
Analytics naming: event name as <subject> <verb>; helper function as report<Subject><Verb> (PascalCase)
Each analytics helper must include a JSDoc header (Why/Owner), accept a single typed properties object, and forward it unchanged to posthog.capture

Files:

  • apps/dashboard/src/@/analytics/report.ts
🧠 Learnings (23)
📚 Learning: 2025-05-21T05:17:31.283Z
Learnt from: jnsdls
PR: thirdweb-dev/js#6929
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx:14-19
Timestamp: 2025-05-21T05:17:31.283Z
Learning: In Next.js server components, the `params` object can sometimes be a Promise that needs to be awaited, despite type annotations suggesting otherwise. In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx, it's necessary to await the params object before accessing its properties.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-08-20T10:35:18.543Z
Learnt from: jnsdls
PR: thirdweb-dev/js#7888
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx:77-81
Timestamp: 2025-08-20T10:35:18.543Z
Learning: The webhooks/payments route exists at apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx and was added as part of the unified project layout changes.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
📚 Learning: 2025-08-20T10:35:18.543Z
Learnt from: jnsdls
PR: thirdweb-dev/js#7888
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx:77-81
Timestamp: 2025-08-20T10:35:18.543Z
Learning: The webhooks/payments route exists at apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx and was added as part of the unified project layout PR #7888.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
📚 Learning: 2025-08-07T17:24:31.965Z
Learnt from: MananTank
PR: thirdweb-dev/js#7812
File: apps/dashboard/src/app/(app)/team/~/~project/[[...paths]]/page.tsx:1-11
Timestamp: 2025-08-07T17:24:31.965Z
Learning: In Next.js App Router, page components (page.tsx files) are server components by default and do not require the "server-only" import directive. The "server-only" directive is primarily used for utility functions, API helpers, and data access modules that should never be included in the client bundle.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Server Components must start with `import "server-only"`; use `next/headers`, server‑only env, heavy data fetching, and `redirect()` where appropriate

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/@/actions/stripe-actions.ts
📚 Learning: 2025-06-10T00:50:20.795Z
Learnt from: MananTank
PR: thirdweb-dev/js#7315
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx:153-226
Timestamp: 2025-06-10T00:50:20.795Z
Learning: In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx, the updateStatus function correctly expects a complete MultiStepState["status"] object. For pending states, { type: "pending" } is the entire status object. For error states, { type: "error", message: React.ReactNode } is the entire status object. The current code incorrectly spreads the entire step object instead of passing just the status object.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{ts,tsx} : Accessing server-only environment variables or secrets.

Applied to files:

  • apps/dashboard/src/@/constants/server-envs.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use `NavLink` for internal navigation with automatic active states in dashboard and playground apps

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Use `NavLink` for internal navigation to get active state handling

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Use `NavLink` (`@/components/ui/NavLink`) for internal navigation so active states are handled automatically.

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-09-05T19:45:05.006Z
Learnt from: MananTank
PR: thirdweb-dev/js#7984
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/ProjectSidebarLayout.tsx:76-80
Timestamp: 2025-09-05T19:45:05.006Z
Learning: When verifying route existence for Next.js app router, search for the full nested path structure rather than using simple patterns. The AI summary often contains accurate information about file additions that should be referenced before running verification scripts.

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Use React Query (`tanstack/react-query`) for all client data fetching.

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Anything that consumes hooks from `tanstack/react-query` or thirdweb SDKs.

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Wrap client-side data fetching calls in React Query (`tanstack/react-query`)

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Client-side data fetching: wrap calls in React Query with descriptive, stable `queryKeys` and set sensible `staleTime/cacheTime` (≥ 60s default); keep tokens secret via internal routes or server actions

Applied to files:

  • apps/dashboard/src/app/(app)/providers.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{ts,tsx} : Redirect logic using `redirect()` from `next/navigation`.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Analytics naming: event name as `<subject> <verb>`; helper function as `report<Subject><Verb>` (PascalCase)

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Check `report.ts` before adding a new analytics event to avoid duplicates

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Analytics event name: human-readable `<subject> <verb>` (e.g., "contract deployed"); function: `report<Subject><Verb>` (PascalCase)

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/src/@/analytics/report.ts : Mandatory JSDoc: explain Why the event exists and Who owns it (`username`).

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Each analytics helper must include a JSDoc header (Why/Owner), accept a single typed `properties` object, and forward it unchanged to `posthog.capture`

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
🧬 Code graph analysis (5)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx (3)
apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx (1)
  • TeamOnboardingLayout (101-114)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (1)
  • CreateProjectFormOnboarding (42-98)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/page.tsx (1)
  • Page (9-40)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (2)
apps/dashboard/src/@/actions/stripe-actions.ts (2)
  • getStripeSessionById (130-135)
  • fetchClientSecret (81-128)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (1)
  • GrowthPlanCheckout (15-31)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (8)
packages/service-utils/src/core/services.ts (1)
  • SERVICES (92-92)
apps/dashboard/src/@/components/project/create-project-modal/index.tsx (1)
  • CreateProjectPrefillOptions (47-50)
apps/dashboard/src/@/hooks/useApi.ts (1)
  • createProjectClient (237-262)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts (1)
  • createVaultAccountAndAccessToken (78-127)
apps/dashboard/src/@/schema/validations.ts (2)
  • projectDomainsSchema (11-19)
  • projectNameSchema (6-9)
apps/dashboard/src/@/components/ui/checkbox.tsx (2)
  • CheckboxWithLabel (36-51)
  • Checkbox (34-34)
apps/dashboard/src/@/components/ui/CopyTextButton.tsx (1)
  • CopyTextButton (9-68)
apps/dashboard/src/@/analytics/report.ts (1)
  • reportOnboardingCompleted (157-159)
apps/dashboard/src/@/actions/stripe-actions.ts (1)
apps/dashboard/src/@/constants/server-envs.ts (2)
  • GROWTH_PLAN_SKU (95-95)
  • PAYMENT_METHOD_CONFIGURATION (105-106)
apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx (1)
apps/dashboard/src/@/analytics/report.ts (1)
  • reportTeamMemberStepCompleted (145-147)
🪛 dotenv-linter (3.3.0)
apps/dashboard/.env.example

[warning] 67-67: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 67-67: [UnorderedKey] The GROWTH_PLAN_SKU key should go before the STRIPE_SECRET_KEY key

(UnorderedKey)


[warning] 68-68: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 68-68: [UnorderedKey] The PAYMENT_METHOD_CONFIGURATION key should go before the STRIPE_SECRET_KEY key

(UnorderedKey)

🔇 Additional comments (5)
apps/dashboard/src/app/(app)/providers.tsx (1)

4-4: Import of usePathname is correct for a client component.

Fits Next.js app‑router patterns; no issues.

apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx (1)

9-10: Import change aligns with new event.

Looks consistent with the analytics update.

apps/dashboard/src/@/analytics/report.ts (1)

137-147: Event addition looks good; name follows " ".
Search for "onboarding members completed" in apps/dashboard/src returned a single hit at apps/dashboard/src/@/analytics/report.ts:146 — no duplicates found.

apps/dashboard/src/@/actions/stripe-actions.ts (1)

21-22: Verified — 2025-02-24.acacia is supported by stripe-node v17.7.0
2025-02-24.acacia is a valid Stripe API version, stripe-node v17.7.0 supports its additions, and Checkout Sessions accepts ui_mode: "embedded" — no code changes required.

apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (1)

441-443: Confirm analytics event choice.

Flow now has a “Create Project” step; verify reportOnboardingCompleted is the intended event here (PR notes mention a new reportTeamMemberStepCompleted). Update if needed.

Comment on lines +81 to +128
export async function fetchClientSecret(team: Team) {
"use server";
const origin = (await headers()).get("origin");
const stripe = getStripe();
const customerId = team.stripeCustomerId;

if (!customerId) {
throw new Error("No customer ID found");
}

// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
ui_mode: "embedded",
line_items: [
{
// Provide the exact Price ID (for example, price_1234) of
// the product you want to sell
price: GROWTH_PLAN_SKU,
quantity: 1,
},
],
mode: "subscription",

return_url: `${origin}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
automatic_tax: { enabled: true },
allow_promotion_codes: true,
customer: customerId,
customer_update: {
address: "auto",
},
payment_method_collection: "always",
payment_method_configuration: PAYMENT_METHOD_CONFIGURATION,
subscription_data: {
trial_period_days: 14,
trial_settings: {
end_behavior: {
missing_payment_method: "cancel",
},
},
},
});

if (!session.client_secret) {
throw new Error("No client secret found");
}

return session.client_secret;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: headers() is synchronous and origin can be null; invalid return_url breaks Embedded Checkout. Add robust base URL derivation and env guards.

  • Remove unnecessary await headers().
  • Derive a trusted base URL from x-forwarded-proto + x-forwarded-host (fallback to host), and error if unavailable.
  • Guard GROWTH_PLAN_SKU; don’t send empty payment_method_configuration.
  • Consider attaching metadata for traceability.
 export async function fetchClientSecret(team: Team) {
   "use server";
-  const origin = (await headers()).get("origin");
   const stripe = getStripe();
   const customerId = team.stripeCustomerId;
 
   if (!customerId) {
     throw new Error("No customer ID found");
   }
+  if (!GROWTH_PLAN_SKU) {
+    throw new Error("GROWTH_PLAN_SKU is not configured");
+  }
+
+  const hdrs = headers();
+  const proto = hdrs.get("x-forwarded-proto") ?? "https";
+  const host = hdrs.get("x-forwarded-host") ?? hdrs.get("host");
+  const baseUrl = host ? `${proto}://${host}` : undefined;
+  if (!baseUrl) {
+    throw new Error("Unable to determine base URL from headers");
+  }
 
   // Create Checkout Sessions from body params.
   const session = await stripe.checkout.sessions.create({
     ui_mode: "embedded",
     line_items: [
       {
         // Provide the exact Price ID (for example, price_1234) of
         // the product you want to sell
         price: GROWTH_PLAN_SKU,
         quantity: 1,
       },
     ],
     mode: "subscription",
-
-    return_url: `${origin}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
+    return_url: `${baseUrl}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
     automatic_tax: { enabled: true },
     allow_promotion_codes: true,
     customer: customerId,
     customer_update: {
       address: "auto",
     },
     payment_method_collection: "always",
-    payment_method_configuration: PAYMENT_METHOD_CONFIGURATION,
+    payment_method_configuration: PAYMENT_METHOD_CONFIGURATION || undefined,
     subscription_data: {
       trial_period_days: 14,
       trial_settings: {
         end_behavior: {
           missing_payment_method: "cancel",
         },
       },
     },
+    metadata: {
+      teamId: team.id,
+      teamSlug: team.slug,
+      source: "onboarding",
+    },
   });
 
   if (!session.client_secret) {
     throw new Error("No client secret found");
   }
 
   return session.client_secret;
 }
📝 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
export async function fetchClientSecret(team: Team) {
"use server";
const origin = (await headers()).get("origin");
const stripe = getStripe();
const customerId = team.stripeCustomerId;
if (!customerId) {
throw new Error("No customer ID found");
}
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
ui_mode: "embedded",
line_items: [
{
// Provide the exact Price ID (for example, price_1234) of
// the product you want to sell
price: GROWTH_PLAN_SKU,
quantity: 1,
},
],
mode: "subscription",
return_url: `${origin}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
automatic_tax: { enabled: true },
allow_promotion_codes: true,
customer: customerId,
customer_update: {
address: "auto",
},
payment_method_collection: "always",
payment_method_configuration: PAYMENT_METHOD_CONFIGURATION,
subscription_data: {
trial_period_days: 14,
trial_settings: {
end_behavior: {
missing_payment_method: "cancel",
},
},
},
});
if (!session.client_secret) {
throw new Error("No client secret found");
}
return session.client_secret;
}
export async function fetchClientSecret(team: Team) {
"use server";
const stripe = getStripe();
const customerId = team.stripeCustomerId;
if (!customerId) {
throw new Error("No customer ID found");
}
if (!GROWTH_PLAN_SKU) {
throw new Error("GROWTH_PLAN_SKU is not configured");
}
const hdrs = headers();
const proto = hdrs.get("x-forwarded-proto") ?? "https";
const host = hdrs.get("x-forwarded-host") ?? hdrs.get("host");
const baseUrl = host ? `${proto}://${host}` : undefined;
if (!baseUrl) {
throw new Error("Unable to determine base URL from headers");
}
// Create Checkout Sessions from body params.
const session = await stripe.checkout.sessions.create({
ui_mode: "embedded",
line_items: [
{
// Provide the exact Price ID (for example, price_1234) of
// the product you want to sell
price: GROWTH_PLAN_SKU,
quantity: 1,
},
],
mode: "subscription",
return_url: `${baseUrl}/get-started/team/${team.slug}/select-plan?session_id={CHECKOUT_SESSION_ID}`,
automatic_tax: { enabled: true },
allow_promotion_codes: true,
customer: customerId,
customer_update: {
address: "auto",
},
payment_method_collection: "always",
payment_method_configuration: PAYMENT_METHOD_CONFIGURATION || undefined,
subscription_data: {
trial_period_days: 14,
trial_settings: {
end_behavior: {
missing_payment_method: "cancel",
},
},
},
metadata: {
teamId: team.id,
teamSlug: team.slug,
source: "onboarding",
},
});
if (!session.client_secret) {
throw new Error("No client secret found");
}
return session.client_secret;
}

Comment on lines +95 to +114
export const GROWTH_PLAN_SKU = process.env.GROWTH_PLAN_SKU || "";

if (GROWTH_PLAN_SKU) {
experimental_taintUniqueValue(
"Do not pass GROWTH_PLAN_SKU to the client",
process,
GROWTH_PLAN_SKU,
);
}

export const PAYMENT_METHOD_CONFIGURATION =
process.env.PAYMENT_METHOD_CONFIGURATION || "";

if (PAYMENT_METHOD_CONFIGURATION) {
experimental_taintUniqueValue(
"Do not pass PAYMENT_METHOD_CONFIGURATION to the client",
process,
PAYMENT_METHOD_CONFIGURATION,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fail fast in prod when required billing envs are missing.

GROWTH_PLAN_SKU is required to create the Checkout Session; missing it will fail at runtime. Add a prod guard; treat PAYMENT_METHOD_CONFIGURATION as optional and avoid sending an empty string to Stripe.

 export const GROWTH_PLAN_SKU = process.env.GROWTH_PLAN_SKU || "";
 
 if (GROWTH_PLAN_SKU) {
   experimental_taintUniqueValue(
     "Do not pass GROWTH_PLAN_SKU to the client",
     process,
     GROWTH_PLAN_SKU,
   );
 }
 
 export const PAYMENT_METHOD_CONFIGURATION =
   process.env.PAYMENT_METHOD_CONFIGURATION || "";
 
 if (PAYMENT_METHOD_CONFIGURATION) {
   experimental_taintUniqueValue(
     "Do not pass PAYMENT_METHOD_CONFIGURATION to the client",
     process,
     PAYMENT_METHOD_CONFIGURATION,
   );
 }
+
+// Guard required billing envs in production to avoid runtime failures
+if (isProd && !GROWTH_PLAN_SKU) {
+  throw new Error("GROWTH_PLAN_SKU must be set in production");
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/dashboard/src/@/constants/server-envs.ts around lines 95 to 114, enforce
a production fail-fast for the required GROWTH_PLAN_SKU and make
PAYMENT_METHOD_CONFIGURATION optional: check NODE_ENV or a similar prod flag and
throw an error (or process.exit) if GROWTH_PLAN_SKU is empty in production, keep
the experimental_taintUniqueValue call when present; for
PAYMENT_METHOD_CONFIGURATION, do not default to an empty string—export it as
undefined when missing (so callers send undefined to Stripe instead of "") and
only call experimental_taintUniqueValue when a non-empty value exists.

Comment on lines +61 to +67
}).catch((error) => {
console.error(
"Failed to create vault account and access token",
error,
);
throw error;
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t rethrow on vault failure; log and proceed.

Current rethrow aborts project creation despite the UI suggesting a soft failure.

       }).catch((error) => {
         console.error(
           "Failed to create vault account and access token",
           error,
         );
-        throw error;
+        toast.warning(
+          "Project created, but we couldn't provision vault access tokens. You can set these up later.",
+        );
       });
📝 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
}).catch((error) => {
console.error(
"Failed to create vault account and access token",
error,
);
throw error;
});
}).catch((error) => {
console.error(
"Failed to create vault account and access token",
error,
);
toast.warning(
"Project created, but we couldn't provision vault access tokens. You can set these up later.",
);
});
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
around lines 61 to 67, the catch block currently rethrows the Vault error which
aborts project creation; instead remove the throw so the error is only logged
and execution continues. Replace the rethrow with a harmless swallow (log the
error with context and, if needed, set a local flag or return a resolved value)
so downstream flow proceeds as a soft failure and the UI behavior matches the
intended non-blocking error handling.

Comment on lines +100 to +106
const createProjectFormSchema = z.object({
domains: projectDomainsSchema,
name: projectNameSchema,
});

type CreateProjectFormSchema = z.infer<typeof createProjectFormSchema>;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Schema blocks “*” and empty string before your custom alerts run. Allow them explicitly.

Otherwise handleSubmit never reaches your alert branches.

-const createProjectFormSchema = z.object({
-  domains: projectDomainsSchema,
-  name: projectNameSchema,
-});
+const createProjectFormSchema = z.object({
+  // Allow special cases "" and "*" to pass form validation; confirm via custom alerts
+  domains: z.union([projectDomainsSchema, z.literal(""), z.literal("*")]),
+  name: projectNameSchema,
+});
📝 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
const createProjectFormSchema = z.object({
domains: projectDomainsSchema,
name: projectNameSchema,
});
type CreateProjectFormSchema = z.infer<typeof createProjectFormSchema>;
const createProjectFormSchema = z.object({
// Allow special cases "" and "*" to pass form validation; confirm via custom alerts
domains: z.union([projectDomainsSchema, z.literal(""), z.literal("*")]),
name: projectNameSchema,
});
type CreateProjectFormSchema = z.infer<typeof createProjectFormSchema>;
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
around lines 100 to 106, the createProjectFormSchema currently only references
projectDomainsSchema and therefore rejects the wildcard "*" and empty-string
domain values before your custom alert logic runs; update the schema to
explicitly allow "*" and "" by extending the domains schema (e.g., union it with
z.literal('*') and z.literal('') or adjust projectDomainsSchema to include those
literals) so validation permits those inputs and lets handleSubmit reach your
alert branches.

Comment on lines +16 to +18
if (!team || !authToken) {
notFound();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t 404 unauthenticated users; redirect to login like step 1.

Match step-1 behavior: redirect when authToken is missing; only notFound() when team is missing.

-import { getAuthToken } from "@/api/auth-token";
+import { getAuthToken, loginRedirect } from "@/api/auth-token";
@@
-  if (!team || !authToken) {
-    notFound();
-  }
+  if (!authToken) {
+    loginRedirect(`/get-started/team/${params.team_slug}/create-project`);
+  }
+  if (!team) {
+    notFound();
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
around lines 16–18, the current logic calls notFound() when either team or
authToken is missing; change it to mirror step-1: first check authToken and
redirect unauthenticated users to the login route (do not 404), and only call
notFound() if authToken exists but the team is missing. Implement the checks in
that order so missing authToken triggers a redirect and a missing team triggers
notFound().

Comment on lines +15 to +26
export function GrowthPlanCheckout(props: {
team: Team;
fetchClientSecret: (team: Team) => Promise<string>;
}) {
return (
<div id="checkout">
<EmbeddedCheckoutProvider
stripe={stripePromise}
options={{
fetchClientSecret: () => props.fetchClientSecret(props.team),
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Do not trust client-supplied Team; pass minimal identifiers to the server action. Also add explicit return type and className prop.

Passing the entire Team (including stripeCustomerId) from client to a server action is tamperable. Have the server action derive the customer from authenticated context using a minimal identifier (e.g., teamSlug) instead. Also add an explicit return type and expose className per dashboard guidelines.

Apply this diff (will require updating the server action signature accordingly):

-export function GrowthPlanCheckout(props: {
-  team: Team;
-  fetchClientSecret: (team: Team) => Promise<string>;
-}) {
+type GrowthPlanCheckoutProps = {
+  teamSlug: string;
+  fetchClientSecret: (teamSlug: string) => Promise<string>;
+  className?: string;
+};
+
+export function GrowthPlanCheckout(props: GrowthPlanCheckoutProps): JSX.Element {
   return (
-    <div id="checkout">
+    <div id="checkout" className={props.className}>
       <EmbeddedCheckoutProvider
         stripe={stripePromise}
         options={{
-          fetchClientSecret: () => props.fetchClientSecret(props.team),
+          fetchClientSecret: () => props.fetchClientSecret(props.teamSlug),
         }}
       >
         <EmbeddedCheckout />
       </EmbeddedCheckoutProvider>
     </div>
   );
 }
📝 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
export function GrowthPlanCheckout(props: {
team: Team;
fetchClientSecret: (team: Team) => Promise<string>;
}) {
return (
<div id="checkout">
<EmbeddedCheckoutProvider
stripe={stripePromise}
options={{
fetchClientSecret: () => props.fetchClientSecret(props.team),
}}
>
type GrowthPlanCheckoutProps = {
teamSlug: string;
fetchClientSecret: (teamSlug: string) => Promise<string>;
className?: string;
};
export function GrowthPlanCheckout(props: GrowthPlanCheckoutProps): JSX.Element {
return (
<div id="checkout" className={props.className}>
<EmbeddedCheckoutProvider
stripe={stripePromise}
options={{
fetchClientSecret: () => props.fetchClientSecret(props.teamSlug),
}}
>
<EmbeddedCheckout />
</EmbeddedCheckoutProvider>
</div>
);
}
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
around lines 15-26, the component currently sends the full Team object to a
server action and lacks an explicit return type and className prop; change the
component signature to accept props: { teamSlug: string; fetchClientSecret:
(teamSlug: string) => Promise<string>; className?: string } with an explicit
return type of JSX.Element, update the EmbeddedCheckoutProvider options to call
fetchClientSecret(() => props.fetchClientSecret(props.teamSlug)), and add
className={props.className} to the root div; then update the corresponding
server action to accept a minimal identifier (teamSlug) and resolve the stripe
customer using server-side authenticated context instead of trusting
client-supplied Team.

Comment on lines 31 to 57
if (searchParams.session_id) {
const session = await getStripeSessionById(searchParams.session_id);

switch (session.status) {
case "complete": {
console.log("session is complete", session);
redirect(`/get-started/team/${team.slug}/add-members`);
break;
}
case "open": {
error =
"Something went wrong when trying to process your payment method. Please try again.";
break;
}
default: {
error = "Please try again later.";
break;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate session ownership to the current team before acting on status.

session_id is user-controlled. Ensure the Checkout Session belongs to the team’s Stripe customer.

   if (searchParams.session_id) {
     const session = await getStripeSessionById(searchParams.session_id);
 
+    // Ensure session belongs to this team
+    const sessionCustomer =
+      typeof session.customer === "string"
+        ? session.customer
+        : session.customer?.id;
+    if (sessionCustomer && sessionCustomer !== team.stripeCustomerId) {
+      error = "Invalid session for this team.";
+    } else {
     switch (session.status) {
       case "complete": {
-        console.log("session is complete", session);
         redirect(`/get-started/team/${team.slug}/add-members`);
         break;
       }
       case "open": {
         error =
           "Something went wrong when trying to process your payment method. Please try again.";
         break;
       }
       default: {
         error = "Please try again later.";
         break;
       }
     }
+    }
   }
📝 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
if (searchParams.session_id) {
const session = await getStripeSessionById(searchParams.session_id);
switch (session.status) {
case "complete": {
console.log("session is complete", session);
redirect(`/get-started/team/${team.slug}/add-members`);
break;
}
case "open": {
error =
"Something went wrong when trying to process your payment method. Please try again.";
break;
}
default: {
error = "Please try again later.";
break;
}
}
if (searchParams.session_id) {
const session = await getStripeSessionById(searchParams.session_id);
// Ensure session belongs to this team
const sessionCustomer =
typeof session.customer === "string"
? session.customer
: session.customer?.id;
if (sessionCustomer && sessionCustomer !== team.stripeCustomerId) {
error = "Invalid session for this team.";
} else {
switch (session.status) {
case "complete": {
redirect(`/get-started/team/${team.slug}/add-members`);
break;
}
case "open": {
error =
"Something went wrong when trying to process your payment method. Please try again.";
break;
}
default: {
error = "Please try again later.";
break;
}
}
}
}
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
around lines 31 to 49, the code uses a user-controlled searchParams.session_id
without verifying the Checkout Session belongs to this team; before switching on
session.status, validate that the fetched session exists and that its customer
(or a team identifier stored in session.metadata) matches the current team's
Stripe customer ID or team ID; if the session is missing or the ownership check
fails, do not act on the status—set an error and/or redirect to an error page
and log the mismatch—only proceed to handle "complete"/"open"/default after
ownership is confirmed.

@jnsdls jnsdls force-pushed the Add_Stripe_checkout_for_Growth_plan_subscription branch from 52ccddb to e4caab4 Compare September 18, 2025 04:33
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

♻️ Duplicate comments (3)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (1)

30-57: Validate session ownership and harden session handling.

User-controlled session_id must belong to this team; also handle invalid IDs and string[] inputs.

Apply this diff:

-  if (searchParams.session_id) {
-    const session = await getStripeSessionById(searchParams.session_id);
+  const maybeSessionId = searchParams.session_id;
+  const sessionId = Array.isArray(maybeSessionId)
+    ? maybeSessionId[0]
+    : maybeSessionId;
+  if (sessionId) {
+    let session;
+    try {
+      session = await getStripeSessionById(sessionId);
+    } catch {
+      error = "Invalid or expired checkout session. Please try again.";
+      session = null;
+    }
+    if (session) {
+      const sessionCustomer =
+        typeof session.customer === "string"
+          ? session.customer
+          : session.customer?.id;
+      if (sessionCustomer && sessionCustomer !== team.stripeCustomerId) {
+        error = "Invalid session for this team.";
+      } else {
         switch (session.status) {
           case "complete": {
-            // poll the team until it shows as "growth" plan (to handle race condition with stripe webhook)
-            await pollWithTimeout({
+            // poll the team until it shows as "growth" plan (to handle race condition with stripe webhook)
+            try {
+              await pollWithTimeout({
                 shouldStop: async () => {
                   const refreshedTeam = await getTeamBySlug(team.slug);
                   return refreshedTeam?.billingPlan === "growth";
                 },
-                timeoutMs: 5000,
-            });
-            redirect(`/get-started/team/${team.slug}/add-members`);
+                timeoutMs: 15000,
+              });
+              redirect(`/get-started/team/${team.slug}/add-members`);
+            } catch {
+              error =
+                "Payment succeeded, but verification is still pending. Please refresh in a few seconds.";
+            }
             break;
           }
           case "open": {
             error =
               "Something went wrong when trying to process your payment method. Please try again.";
             break;
           }
           default: {
             error = "Please try again later.";
             break;
           }
         }
+      }
+    }
   }
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (2)

61-67: Soft‑fail Vault provisioning; don’t abort project creation.

Rethrow cancels success flow despite UI messaging this as non‑blocking.

Apply this diff:

       }).catch((error) => {
         console.error(
           "Failed to create vault account and access token",
           error,
         );
-        throw error;
+        toast.warning(
+          "Project created, but we couldn't provision vault access tokens. You can set these up later.",
+        );
       });

100-103: Allow “” and “*” domains to reach custom alerts.

Schema currently blocks them before your alert flow.

Apply this diff:

-const createProjectFormSchema = z.object({
-  domains: projectDomainsSchema,
-  name: projectNameSchema,
-});
+const createProjectFormSchema = z.object({
+  // Allow special cases "" and "*" and handle via custom alerts
+  domains: z.union([projectDomainsSchema, z.literal(""), z.literal("*")]),
+  name: projectNameSchema,
+});

Also applies to: 173-184

🧹 Nitpick comments (7)
apps/dashboard/.env.example (2)

67-68: Fix dotenv-linter warnings: remove quotes from new vars.

These server-only vars don’t need quotes; removing them silences QuoteCharacter warnings.

Apply this diff:

- GROWTH_PLAN_SKU=""
- PAYMENT_METHOD_CONFIGURATION=""
+GROWTH_PLAN_SKU=
+PAYMENT_METHOD_CONFIGURATION=

65-69: Follow key ordering to satisfy dotenv-linter.

Place GROWTH_PLAN_SKU and PAYMENT_METHOD_CONFIGURATION before STRIPE_SECRET_KEY to resolve UnorderedKey warnings. Also consider adding brief comments indicating expected IDs (price_xxx, pmc_xxx).

apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (1)

65-66: Fix typo in alert title.

“proces” → “process”.

Apply this diff:

-            <AlertTitle>Failed to proces subscription</AlertTitle>
+            <AlertTitle>Failed to process subscription</AlertTitle>
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (4)

42-52: Expose className on root and pass through to Card (repo guideline).

apps/{dashboard,playground}/**/*.tsx should expose a className on the root element.

Apply this diff:

-export function CreateProjectFormOnboarding(props: {
+export function CreateProjectFormOnboarding(props: {
   prefill?: CreateProjectPrefillOptions;
   enableNebulaServiceByDefault: boolean;
   teamSlug: string;
   teamId: string;
-}) {
+  className?: string;
+}): JSX.Element {
   const [screen, setScreen] = useState<
     { id: "create" } | { id: "api-details"; project: Project; secret: string }
   >({ id: "create" });
   return (
-    <Card>
+    <Card className={props.className}>

298-307: Fix button label typo.

“Projecct” → “Project”.

Apply this diff:

-            Create Projecct
+            Create Project

42-52: Add explicit return types to components.

Repo guidelines require explicit return types for TS/TSX.

Apply this diff:

-export function CreateProjectFormOnboarding(… ) {
+export function CreateProjectFormOnboarding(… ): JSX.Element {-function CreateProjectForm(props: { … }) {
+function CreateProjectForm(props: { … }): JSX.Element {-function DomainsAlert(props: { … }) {
+function DomainsAlert(props: { … }): JSX.Element {-function CreatedProjectDetails(props: { … }) {
+function CreatedProjectDetails(props: { … }): JSX.Element {

Also applies to: 107-115, 313-318, 361-366


38-41: Avoid hardcoding excluded service names.

Centralize “relayer” and “chainsaw” exclusions in a shared constant or derive via a property on SERVICE_DEFINITIONS to prevent drift.

📜 Review details

Configuration used: CodeRabbit UI

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 52ccddb and e4caab4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/dashboard/.env.example (1 hunks)
  • apps/dashboard/package.json (1 hunks)
  • apps/dashboard/src/@/actions/stripe-actions.ts (2 hunks)
  • apps/dashboard/src/@/analytics/report.ts (1 hunks)
  • apps/dashboard/src/@/constants/server-envs.ts (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (1 hunks)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (2 hunks)
  • apps/dashboard/src/app/(app)/providers.tsx (3 hunks)
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx (2 hunks)
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx
  • apps/dashboard/src/app/(app)/providers.tsx
  • apps/dashboard/package.json
  • apps/dashboard/src/@/constants/server-envs.ts
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx
  • apps/dashboard/src/@/analytics/report.ts
  • apps/dashboard/src/@/actions/stripe-actions.ts
  • apps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsx
  • apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial, Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/* (Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
🧠 Learnings (5)
📚 Learning: 2025-08-20T10:35:18.543Z
Learnt from: jnsdls
PR: thirdweb-dev/js#7888
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx:77-81
Timestamp: 2025-08-20T10:35:18.543Z
Learning: The webhooks/payments route exists at apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx and was added as part of the unified project layout changes.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-08-20T10:35:18.543Z
Learnt from: jnsdls
PR: thirdweb-dev/js#7888
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/page.tsx:77-81
Timestamp: 2025-08-20T10:35:18.543Z
Learning: The webhooks/payments route exists at apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/payments/page.tsx and was added as part of the unified project layout PR #7888.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx
  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-05-21T05:17:31.283Z
Learnt from: jnsdls
PR: thirdweb-dev/js#6929
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx:14-19
Timestamp: 2025-05-21T05:17:31.283Z
Learning: In Next.js server components, the `params` object can sometimes be a Promise that needs to be awaited, despite type annotations suggesting otherwise. In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx, it's necessary to await the params object before accessing its properties.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-06-10T00:50:20.795Z
Learnt from: MananTank
PR: thirdweb-dev/js#7315
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx:153-226
Timestamp: 2025-06-10T00:50:20.795Z
Learning: In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx, the updateStatus function correctly expects a complete MultiStepState["status"] object. For pending states, { type: "pending" } is the entire status object. For error states, { type: "error", message: React.ReactNode } is the entire status object. The current code incorrectly spreads the entire step object instead of passing just the status object.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{ts,tsx} : Redirect logic using `redirect()` from `next/navigation`.

Applied to files:

  • apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
🧬 Code graph analysis (2)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (8)
packages/service-utils/src/core/services.ts (1)
  • SERVICES (92-92)
apps/dashboard/src/@/components/project/create-project-modal/index.tsx (1)
  • CreateProjectPrefillOptions (47-50)
apps/dashboard/src/@/hooks/useApi.ts (1)
  • createProjectClient (237-262)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/lib/vault.client.ts (1)
  • createVaultAccountAndAccessToken (78-127)
apps/dashboard/src/@/schema/validations.ts (2)
  • projectDomainsSchema (11-19)
  • projectNameSchema (6-9)
apps/dashboard/src/@/components/ui/checkbox.tsx (2)
  • CheckboxWithLabel (36-51)
  • Checkbox (34-34)
apps/dashboard/src/@/components/ui/CopyTextButton.tsx (1)
  • CopyTextButton (9-68)
apps/dashboard/src/@/analytics/report.ts (1)
  • reportOnboardingCompleted (157-159)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx (6)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsx (1)
  • Page (7-29)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/add-members/page.tsx (1)
  • Page (8-31)
apps/dashboard/src/@/actions/stripe-actions.ts (2)
  • getStripeSessionById (130-135)
  • fetchClientSecret (81-128)
apps/dashboard/src/app/login/onboarding/onboarding-layout.tsx (1)
  • TeamOnboardingLayout (101-114)
apps/dashboard/src/@/components/ui/alert.tsx (3)
  • Alert (63-63)
  • AlertTitle (63-63)
  • AlertDescription (63-63)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/stripe-checkout.tsx (1)
  • GrowthPlanCheckout (15-31)
🪛 dotenv-linter (3.3.0)
apps/dashboard/.env.example

[warning] 67-67: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 67-67: [UnorderedKey] The GROWTH_PLAN_SKU key should go before the STRIPE_SECRET_KEY key

(UnorderedKey)


[warning] 68-68: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 68-68: [UnorderedKey] The PAYMENT_METHOD_CONFIGURATION key should go before the STRIPE_SECRET_KEY key

(UnorderedKey)

⏰ 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). (6)
  • GitHub Check: Unit Tests
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: Size
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (1)

440-443: Replace reportOnboardingCompleted() with reportTeamMemberStepCompleted() here

reportTeamMemberStepCompleted() exists and records "onboarding members completed" while reportOnboardingCompleted() records "onboarding completed". Update the call in apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsx (lines 440–443) to reportTeamMemberStepCompleted() to match the PR migration, or confirm the global onboarding-completed event is intended.

@jnsdls jnsdls merged commit 4482692 into main Sep 18, 2025
25 checks passed
@jnsdls jnsdls deleted the Add_Stripe_checkout_for_Growth_plan_subscription branch September 18, 2025 05:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants