-
Notifications
You must be signed in to change notification settings - Fork 615
Add Stripe checkout for Growth plan subscription #8068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
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. |
WalkthroughAdds 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
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
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}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
bd3d65e to
4391513
Compare
size-limit report 📦
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
4391513 to
52ccddb
Compare
There was a problem hiding this 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
thirdwebClientto 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. Otherwisepushis 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
getStripeSessionByIdwill 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless 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@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless 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.tsxapps/dashboard/src/@/constants/server-envs.tsapps/dashboard/src/app/(app)/providers.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/@/analytics/report.tsapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/dashboard/src/@/actions/stripe-actions.tsapps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsxapps/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.tsxapps/dashboard/src/@/constants/server-envs.tsapps/dashboard/src/app/(app)/providers.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/@/analytics/report.tsapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/dashboard/src/@/actions/stripe-actions.tsapps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsxapps/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
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsxapps/dashboard/src/@/constants/server-envs.tsapps/dashboard/src/app/(app)/providers.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/@/analytics/report.tsapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/dashboard/src/@/actions/stripe-actions.tsapps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsxapps/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)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsxapps/dashboard/src/@/constants/server-envs.tsapps/dashboard/src/app/(app)/providers.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/@/analytics/report.tsapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/dashboard/src/@/actions/stripe-actions.tsapps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsxapps/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
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/page.tsxapps/dashboard/src/app/(app)/providers.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/dashboard/src/app/login/onboarding/team-onboarding/team-onboarding.tsxapps/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: Checkreport.tsbefore adding a new analytics event to avoid duplicates
Analytics naming: event name as<subject> <verb>; helper function asreport<Subject><Verb>(PascalCase)
Each analytics helper must include a JSDoc header (Why/Owner), accept a single typedpropertiesobject, and forward it unchanged toposthog.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.tsxapps/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.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/app/login/onboarding/onboarding-layout.tsxapps/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.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/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.tsxapps/dashboard/src/app/(app)/providers.tsxapps/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.tsxapps/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.tsxapps/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.tsapps/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.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 tohost), and error if unavailable. - Guard
GROWTH_PLAN_SKU; don’t send emptypayment_method_configuration. - Consider attaching
metadatafor 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.
| 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; | |
| } |
| 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, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| }).catch((error) => { | ||
| console.error( | ||
| "Failed to create vault account and access token", | ||
| error, | ||
| ); | ||
| throw error; | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| }).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.
| const createProjectFormSchema = z.object({ | ||
| domains: projectDomainsSchema, | ||
| name: projectNameSchema, | ||
| }); | ||
|
|
||
| type CreateProjectFormSchema = z.infer<typeof createProjectFormSchema>; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| if (!team || !authToken) { | ||
| notFound(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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().
| export function GrowthPlanCheckout(props: { | ||
| team: Team; | ||
| fetchClientSecret: (team: Team) => Promise<string>; | ||
| }) { | ||
| return ( | ||
| <div id="checkout"> | ||
| <EmbeddedCheckoutProvider | ||
| stripe={stripePromise} | ||
| options={{ | ||
| fetchClientSecret: () => props.fetchClientSecret(props.team), | ||
| }} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
52ccddb to
e4caab4
Compare
There was a problem hiding this 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless 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@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless 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.tsxapps/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.tsxapps/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
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/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)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx
apps/{dashboard,playground}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Expose a
classNameprop on the root element of every component
Files:
apps/dashboard/src/app/(app)/get-started/team/[team_slug]/create-project/_components/create-project-form.tsxapps/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.tsxapps/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.tsxapps/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() herereportTeamMemberStepCompleted() 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.

[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:
How to test
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
GROWTH_PLAN_SKUandPAYMENT_METHOD_CONFIGURATIONto environment variables.@stripe/react-stripe-jsand@stripe/stripe-jsfor payment processing.GrowthPlanCheckoutcomponent for Stripe embedded checkout.CreateProjectFormOnboardingcomponent for project creation.Summary by CodeRabbit
New Features
UX/Style
Chores
Analytics