Skip to content

Conversation

ameer2468
Copy link
Collaborator

@ameer2468 ameer2468 commented Oct 14, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a multi-step onboarding flow (Welcome, Organization Setup, Custom Domain, Invite Team, Download) with a progress stepper and ability to skip to dashboard.
    • Organization setup supports logo upload with image type/size validation.
    • Invite Team includes interactive user count, billing cycle, and checkout; Stripe checkout now supports onboarding context.
    • Dev-only tool to restart onboarding.
  • Refactor

    • Onboarding routes and redirects streamlined to /onboarding/welcome.
  • Style

    • Updated trash icon hover color in file inputs.
    • Added a “transparent” Button variant.

Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

This PR replaces the old onboarding route/component with a multi-step onboarding flow powered by RPCs. It adds new onboarding UI pages/layout, backend Users onboarding services and RPC endpoints, updates billing/Stripe to carry onboarding context, introduces schema support for onboardingSteps, tweaks icon upload handling, and adds devtools to reset onboarding.

Changes

Cohort / File(s) Summary
Onboarding UI: pages, layout, components
apps/web/app/(org)/onboarding/components/*, apps/web/app/(org)/onboarding/layout.tsx, apps/web/app/(org)/onboarding/[...steps]/layout.tsx, apps/web/app/(org)/onboarding/[...steps]/page.tsx, apps/web/app/(org)/onboarding/page.tsx
New multi-step onboarding UI (Base, Stepper, Welcome, OrganizationSetup, CustomDomain, InviteTeam, Download, Bottom), step guard layout, redirect entry to /onboarding/welcome, and step router page.
Onboarding legacy removal
apps/web/app/(org)/onboarding/Onboarding.tsx, apps/web/app/api/settings/onboarding/route.ts
Removed legacy client onboarding component and API route that handled user/name/org creation.
Backend RPC and domain for onboarding
packages/web-domain/src/User.ts, packages/web-domain/src/Rpcs.ts, packages/web-backend/src/Users/UsersRpcs.ts, packages/web-backend/src/Rpcs.ts, packages/web-backend/src/Users/UsersOnboarding.ts, packages/web-backend/src/Users/index.ts
Introduces User onboarding RPC schemas and endpoint, merges UserRpcs into domain/backend, implements UsersOnboarding service (welcome, organizationSetup with optional S3 upload, customDomain, inviteTeam, download, skipToDashboard), and wires live layers.
Billing/Stripe onboarding context
apps/web/app/api/settings/billing/subscribe/route.ts, apps/web/app/api/webhooks/stripe/route.ts, apps/web/components/UpgradeModal.tsx
Adds isOnBoarding flag to subscribe payload/metadata, adjusts cancel_url, propagates onboarding completion in webhook (onboarding_completed_at), updates UpgradeModal to pass onboarding state and invoke optional onCheckout.
Database: schema and migrations meta
packages/database/schema.ts, packages/database/migrations/meta/_journal.json
Adds users.onboardingSteps JSON column (welcome, organizationSetup, customDomain, inviteTeam, download). Appends new migration journal entry.
Auth events cleanup
packages/database/auth/auth-options.ts
Removes NextAuth events.signIn handler (org creation/lead tracking logic).
Devtools: restart onboarding
apps/web/app/Layout/devtoolsServer.ts, apps/web/app/Layout/providers.tsx
Adds restartOnboarding server action (dev-only) to reset onboarding/user fields; wires button in CapDevtools UI.
Organization icon upload flow
apps/web/actions/organization/upload-organization-icon.ts, apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx, apps/web/components/FileInput.tsx
Changes upload field to "icon", adds image type/size validation, switches to ArrayBuffer/Uint8Array body; UI uses React useId for input id and minor icon hover style tweak.
Dashboard routing and UI tweaks
apps/web/app/(org)/dashboard/layout.tsx, apps/web/app/(org)/dashboard/caps/page.tsx, apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
Redirects missing-name users to /onboarding/welcome, removes an unused org DB query, drops hover state/indicator from admin nav items and adjusts keys.
UI Button variants
packages/ui/src/components/Button.tsx
Adds "transparent" button variant.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User (Browser)
  participant UI as Onboarding UI (Next.js)
  participant RPC as UserRpcs (Web Backend)
  participant S as UsersOnboarding Service
  participant DB as Database
  participant S3 as Object Storage

  U->>UI: Submit onboarding step (e.g., Organization Setup)
  UI->>RPC: UserCompleteOnboardingStep(step,payload)
  RPC->>S: Route to matching method
  alt step = welcome/customDomain/inviteTeam/download/skipToDashboard
    S->>DB: Update users.onboardingSteps (+user fields)
    DB-->>S: OK
  else step = organizationSetup
    S->>DB: Tx: create organization + owner member + set active org
    opt organizationIcon provided
      S->>S3: putObject(icon bytes)
      S3-->>S: OK/URL
      S->>DB: Update organization.iconUrl
    end
    DB-->>S: OK
  end
  S-->>RPC: Step result
  RPC-->>UI: Success/Failure
  UI-->>U: Navigate to next step / show error
Loading
sequenceDiagram
  autonumber
  participant U as User (Browser)
  participant UI as UpgradeModal/InviteTeam
  participant API as POST /api/settings/billing/subscribe
  participant Stripe as Stripe Checkout
  participant WH as Webhook /api/webhooks/stripe
  participant DB as Database
  participant PH as Analytics (PostHog)

  U->>UI: Start Pro checkout (onboarding)
  UI->>API: { priceId, quantity, isOnBoarding }
  API-->>UI: { url | subscription }
  UI->>Stripe: Redirect to checkout (if url)
  Stripe-->>WH: checkout.session.completed (metadata includes isOnBoarding)
  WH->>DB: Update user (onboarding_completed_at if isOnBoarding)
  WH->>PH: Capture event (is_onboarding flag)
  WH-->>Stripe: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Brendonovich

Poem

A hop, a skip, through steps anew,
I nudge the forms, then peek the queue—
RPCs hum, icons fly,
S3 clouds reflect the sky.
Stripe rings “ding!”; we’re almost done—
Dashboard fields, the burrow won.
Carrot-click: onboarding’s fun! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “web: new onboarding” succinctly captures the primary purpose of the changeset, namely introducing a new onboarding flow, without extraneous detail or noise.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch onboarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

Caution

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

⚠️ Outside diff range comments (3)
apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx (1)

225-230: Add error handling for the server action.

The async updateActiveOrganization call has no error handling. If the server action fails, users won't receive feedback and may experience unexpected behavior.

Apply this diff to add error handling:

 onSelect={async () => {
-  await updateActiveOrganization(
-    organization.organization.id,
-  );
-  setOpen(false);
-  router.push("/dashboard/caps");
+  try {
+    await updateActiveOrganization(
+      organization.organization.id,
+    );
+    setOpen(false);
+    router.push("/dashboard/caps");
+  } catch (error) {
+    console.error("Failed to switch organization:", error);
+  }
 }}

Additionally, consider showing a toast notification to inform users of success or failure.

apps/web/actions/organization/upload-organization-icon.ts (1)

86-91: Guard against undefined iconUrl before DB update

If URL construction fails, you may write undefined. Guard and fail fast.

-// Update organization with new icon URL
-await db()
+if (!iconUrl) throw new Error("Failed to construct icon URL");
+
+await db()
   .update(organizations)
   .set({ iconUrl })
   .where(eq(organizations.id, organizationId));
apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx (1)

63-63: Fix Label association for accessibility

Label htmlFor must match the input id. Currently htmlFor="icon" but id={iconInputId}.

-<Label htmlFor="icon">Organization Icon</Label>
+<Label htmlFor={iconInputId}>Organization Icon</Label>
...
- id={iconInputId}
+ id={iconInputId}

Also applies to: 71-71

🧹 Nitpick comments (20)
apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx (1)

224-224: Good addition of organization ID to the key.

Including the organization ID improves key uniqueness, as organization names may not be unique across the system. This prevents potential React reconciliation issues.

Optionally, the key can be simplified to just the ID since it's already unique:

-key={`${organization.organization.name}-organization-${organization.organization.id}`}
+key={organization.organization.id}
apps/web/app/api/webhooks/stripe/route.ts (1)

302-310: Simplify is_onboarding extraction for consistency.

The verbose type-checking pattern for is_onboarding (lines 302-310) is inconsistent with the simpler extraction at line 235. Since isOnBoarding is already computed as a boolean at line 235, reuse it here for clarity.

Apply this diff to use the existing isOnBoarding variable:

 is_onboarding:
-  typeof session.metadata === "object" &&
-  session.metadata !== null &&
-  "isOnBoarding" in session.metadata &&
-  typeof (session.metadata as Record<string, unknown>)
-    .isOnBoarding === "string"
-    ? ((session.metadata as Record<string, unknown>)
-        .isOnBoarding as string)
-    : "false",
+  isOnBoarding ? "true" : "false",
apps/web/app/api/settings/billing/subscribe/route.ts (1)

75-79: Consider consistent boolean representation in metadata.

Line 78 converts isOnBoarding to a string "true"/"false", while Stripe metadata supports boolean values. If downstream webhook handlers expect this specific string format, document this requirement; otherwise, use boolean directly for type safety.

If string format is not required, apply this diff:

 			metadata: {
 				platform: "web",
 				dubCustomerId: user.id,
-				isOnBoarding: isOnBoarding ? "true" : "false",
+				isOnBoarding: Boolean(isOnBoarding),
 			},
apps/web/app/api/settings/onboarding/complete/route.ts (1)

1-28: Adopt HttpApi + auth middleware; add error handling and revalidation

  • Route uses NextResponse instead of HttpApi/HttpApiBuilder and HttpAuthMiddleware. Please migrate to the shared HttpApi pattern to avoid duplicate session lookups and ensure exhaustive error mapping. As per coding guidelines.
  • Add try/catch and revalidatePath for consistency with other onboarding routes.

Apply this minimal improvement now:

@@
-import { NextResponse } from "next/server";
+import { NextResponse } from "next/server";
+import { revalidatePath } from "next/cache";
@@
-export async function POST() {
+export async function POST() {
   const user = await getCurrentUser();
   if (!user) {
     console.error("User not found");
     return NextResponse.json({ error: true }, { status: 401 });
   }
 
-  await db()
-    .update(users)
-    .set({
-      onboardingSteps: {
-        welcome: true,
-        organizationSetup: true,
-        customDomain: true,
-        inviteTeam: true,
-      },
-      onboarding_completed_at: new Date(),
-    })
-    .where(eq(users.id, user.id));
-
-  return NextResponse.json({ success: true }, { status: 200 });
+  try {
+    await db()
+      .update(users)
+      .set({
+        onboardingSteps: {
+          welcome: true,
+          organizationSetup: true,
+          customDomain: true,
+          inviteTeam: true,
+        },
+        onboarding_completed_at: new Date(),
+      })
+      .where(eq(users.id, user.id));
+    revalidatePath("/onboarding", "layout");
+    return NextResponse.json({ success: true }, { status: 200 });
+  } catch (error) {
+    console.error("Failed to complete onboarding", error);
+    return NextResponse.json({ error: true }, { status: 500 });
+  }
}

For the full migration, consider exposing only the handler from apiToHandler(ApiLive) with HttpAuthMiddleware and mapping domain errors via HttpApiError.*. As per coding guidelines.

apps/web/actions/organization/upload-organization-icon.ts (3)

55-57: Make file key extension robust (avoid undefined/empty extension)

Deriving the extension from file.name can yield undefined. Prefer type-derived fallback.

-const fileExtension = file.name.split(".").pop();
-const fileKey = `organizations/${organizationId}/icon-${Date.now()}.${fileExtension}`;
+const typeExt = file.type.split("/").pop() || "png";
+const fileKey = `organizations/${organizationId}/icon-${Date.now()}.${typeExt}`;

65-71: Good: sanitize + bytes; consider streaming (optional)

Array-buffering is fine under 2MB. If buckets support streams, switching to a stream can reduce memory copies for larger limits. Optional.


44-53: Remove inline comments per repository guidelines

Inline comments are disallowed in TS/TSX files. Remove comment lines at the referenced ranges.

As per coding guidelines.

Also applies to: 71-79, 86-86

apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx (3)

1-81: Rename file to kebab-case

File should be kebab-case: organization-icon.tsx. Update imports accordingly.

As per coding guidelines.


20-21: Remove inline comments

Inline comments in TSX are disallowed. Remove the noted comment lines.

As per coding guidelines.

Also applies to: 23-24, 31-31, 43-43


19-41: Optional: wrap mutation with useEffectMutation for consistency

To align client mutations with app patterns (loading/error handling and cache updates), consider using useEffectMutation from @/lib/EffectRuntime with targeted cache updates instead of manual state/toasts.

apps/web/app/(org)/onboarding/custom-domain/layout.tsx (1)

12-20: Remove inline comments

Inline comments in TSX are disallowed. Remove the comment lines.

As per coding guidelines.

apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)

16-28: Consider extracting common onboarding update logic.

This route follows the same pattern as other onboarding routes (welcome, custom-domain, org-setup): fetch user, update onboardingSteps, revalidate path. Consider extracting a shared helper function to reduce duplication and ensure consistency across all onboarding step updates.

Example helper:

async function updateOnboardingSteps(
  userId: string,
  steps: Partial<typeof users.$inferSelect.onboardingSteps>,
  completedAt?: Date
) {
  await db()
    .update(users)
    .set({
      onboardingSteps: steps,
      ...(completedAt && { onboarding_completed_at: completedAt }),
    })
    .where(eq(users.id, userId));
  
  revalidatePath("/onboarding", "layout");
}
apps/web/app/(org)/onboarding/components/Base.tsx (1)

34-36: Prefer Next.js Link for client-side navigation.

Using an anchor tag <a> triggers a full page reload. Consider using Next.js Link component for client-side navigation to maintain state and improve performance.

Apply this diff:

+import Link from "next/link";
 
-<a href="/">
+<Link href="/">
   <LogoBadge className="mx-auto w-auto h-12" />
-</a>
+</Link>
apps/web/app/(org)/onboarding/welcome/page.tsx (2)

36-39: Drop router.refresh after push.

router.push triggers a new navigation; refresh is unnecessary here.

-      startTransition(() => {
-        router.push("/onboarding/organization-setup");
-        router.refresh();
-      });
+      startTransition(() => {
+        router.push("/onboarding/organization-setup");
+      });

50-50: Minor copy fixes.

Polish text for clarity.

-      description="Lets get you started"
+      description="Let's get you started"
@@
-          placeholder="Last name: optional"
+          placeholder="Last name (optional)"

Also applies to: 69-71

apps/web/app/api/settings/onboarding/org-setup/route.ts (1)

39-55: Preserve existing onboardingSteps; don’t overwrite the JSON column.

Current update replaces onboardingSteps entirely; merge to avoid dropping other flags.

   await db().insert(organizationMembers).values({
     id: nanoId(),
     userId: user.id,
     role: "owner",
     organizationId,
   });
-  await db()
-    .update(users)
-    .set({
-      activeOrganizationId: organizationId,
-      onboardingSteps: {
-        welcome: true,
-        organizationSetup: true,
-      },
-    })
-    .where(eq(users.id, user.id));
+  const existing = await db()
+    .select({ steps: users.onboardingSteps })
+    .from(users)
+    .where(eq(users.id, user.id))
+    .limit(1);
+  const mergedSteps = { ...(existing[0]?.steps ?? {}), welcome: true, organizationSetup: true };
+  await db()
+    .update(users)
+    .set({ activeOrganizationId: organizationId, onboardingSteps: mergedSteps })
+    .where(eq(users.id, user.id));
apps/web/app/(org)/onboarding/components/Stepper.tsx (2)

1-1: Rename file to kebab-case (stepper.tsx).

Aligns with repo filename conventions for TS/JS modules.

As per coding guidelines


20-26: Drive active state from route path to avoid string drift.

Store each step’s path and compare against usePathname; avoids coupling to display labels.

-  const currentStep = useMemo(() => {
-    if (currentPath === "/onboarding/welcome") return "Welcome";
-    if (currentPath === "/onboarding/organization-setup")
-      return "Organization Setup";
-    if (currentPath === "/onboarding/custom-domain") return "Custom Domain";
-    if (currentPath === "/onboarding/invite-team") return "Invite your team";
-  }, [currentPath]);
+  const current = currentPath;
@@
-  const steps = [
+  const steps = [
     {
       id: "1",
       name: "Welcome",
+      path: "/onboarding/welcome",
       completed: completedSteps.welcome || false,
     },
     {
       id: "2",
       name: "Organization Setup",
+      path: "/onboarding/organization-setup",
       completed: completedSteps.organizationSetup || false,
     },
     {
       id: "3",
       name: "Custom Domain",
+      path: "/onboarding/custom-domain",
       completed: completedSteps.customDomain || false,
     },
     {
       id: "4",
       name: "Invite your team",
+      path: "/onboarding/invite-team",
       completed: completedSteps.inviteTeam || false,
     },
   ];
@@
-                  currentStep === step.name && !step.completed
+                  current === step.path && !step.completed
                     ? "bg-blue-500"
                     : step.completed
                       ? "bg-green-500"
                       : "bg-gray-7",
@@
-                step.completed || currentStep === step.name
+                step.completed || current === step.path
                   ? "text-gray-12"
                   : "text-gray-9",

Also applies to: 28-49, 63-69, 83-86

apps/web/app/(org)/onboarding/invite-team/page.tsx (2)

38-40: Remove unnecessary preventDefault on button click.

Not inside a form; it’s redundant.

-  const handleSubmit = async (e: React.MouseEvent<HTMLButtonElement>) => {
-    e.preventDefault();
+  const handleSubmit = async () => {

158-167: Disable quantity controls during in-flight actions.

Avoids changing quantity mid‑request.

-  <Button onClick={decrementUsers} className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit" aria-label="Decrease user count">
+  <Button disabled={loading || proLoading} onClick={decrementUsers} className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit" aria-label="Decrease user count">
@@
-  <Button onClick={incrementUsers} className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit" aria-label="Increase user count">
+  <Button disabled={loading || proLoading} onClick={incrementUsers} className="p-1 bg-gray-12 hover:bg-gray-11 min-w-fit h-fit" aria-label="Increase user count">
@@
-  <Switch checked={isAnnually} onCheckedChange={setIsAnnually} aria-label="Billing Cycle" className="scale-75" id={billingCycleId} />
+  <Switch disabled={loading || proLoading} checked={isAnnually} onCheckedChange={setIsAnnually} aria-label="Billing Cycle" className="scale-75" id={billingCycleId} />

Also applies to: 171-181, 192-199

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4d89f4 and d4c17c4.

📒 Files selected for processing (31)
  • apps/web/actions/organization/upload-organization-icon.ts (2 hunks)
  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx (1 hunks)
  • apps/web/app/(org)/dashboard/caps/page.tsx (0 hunks)
  • apps/web/app/(org)/dashboard/layout.tsx (1 hunks)
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx (3 hunks)
  • apps/web/app/(org)/onboarding/Onboarding.tsx (0 hunks)
  • apps/web/app/(org)/onboarding/components/Base.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/Stepper.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/invite-team/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/welcome/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/welcome/page.tsx (1 hunks)
  • apps/web/app/api/settings/billing/subscribe/route.ts (2 hunks)
  • apps/web/app/api/settings/onboarding/complete/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/org-setup/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/route.ts (0 hunks)
  • apps/web/app/api/settings/onboarding/welcome/route.ts (1 hunks)
  • apps/web/app/api/webhooks/stripe/route.ts (3 hunks)
  • apps/web/components/FileInput.tsx (1 hunks)
  • apps/web/components/UpgradeModal.tsx (3 hunks)
  • packages/database/auth/auth-options.ts (0 hunks)
  • packages/database/migrations/meta/_journal.json (1 hunks)
  • packages/database/schema.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • apps/web/app/(org)/dashboard/caps/page.tsx
  • apps/web/app/(org)/onboarding/Onboarding.tsx
  • packages/database/auth/auth-options.ts
  • apps/web/app/api/settings/onboarding/route.ts
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/components/FileInput.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/actions/organization/upload-organization-icon.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/components/FileInput.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • packages/database/schema.ts
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/actions/organization/upload-organization-icon.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/components/FileInput.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • packages/database/schema.ts
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/actions/organization/upload-organization-icon.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/components/FileInput.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • packages/database/schema.ts
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/actions/organization/upload-organization-icon.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/layout.tsx
  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/organization-setup/layout.tsx
  • apps/web/components/FileInput.tsx
  • apps/web/app/(org)/onboarding/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/custom-domain/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/actions/organization/upload-organization-icon.ts
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/(org)/onboarding/welcome/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx
  • apps/web/app/(org)/onboarding/components/Base.tsx
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/app/api/**/route.ts: Place API routes only under apps/web/app/api and implement each route in a route.ts file
Construct API routes with @effect/platform HttpApi/HttpApiBuilder and export only the handler from apiToHandler(ApiLive)
Map domain errors to transport errors with HttpApiError.* and keep error translation exhaustive
Use HttpAuthMiddleware for required auth and provideOptionalAuth for guest routes; avoid duplicate session lookups
Provide dependencies via Layer.provide in API routes instead of manual provideService calls

Files:

  • apps/web/app/api/settings/onboarding/complete/route.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/api/settings/billing/subscribe/route.ts
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere

Files:

  • apps/web/actions/organization/upload-organization-icon.ts
🧬 Code graph analysis (14)
apps/web/app/(org)/onboarding/custom-domain/page.tsx (2)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/components/UpgradeModal.tsx (1)
  • UpgradeModal (61-317)
apps/web/app/api/settings/onboarding/complete/route.ts (4)
apps/web/app/api/webhooks/stripe/route.ts (1)
  • POST (115-506)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-36)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/(org)/onboarding/layout.tsx (2)
apps/web/app/(org)/dashboard/layout.tsx (1)
  • dynamic (18-18)
apps/web/app/(org)/onboarding/components/Stepper.tsx (1)
  • Stepper (9-97)
apps/web/app/api/settings/onboarding/org-setup/route.ts (5)
packages/web-domain/src/Organisation.ts (1)
  • Organisation (8-11)
packages/database/helpers.ts (1)
  • nanoId (6-9)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (3)
  • organizations (169-201)
  • organizationMembers (204-226)
  • users (58-116)
apps/web/actions/organization/upload-organization-icon.ts (1)
  • uploadOrganizationIcon (15-99)
apps/web/app/api/webhooks/stripe/route.ts (2)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/api/settings/billing/subscribe/route.ts (1)
packages/env/server.ts (1)
  • serverEnv (83-87)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (6)
apps/web/app/api/settings/onboarding/complete/route.ts (1)
  • POST (7-28)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-62)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-30)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (5)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-36)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-62)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-30)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/(org)/onboarding/welcome/page.tsx (1)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/app/(org)/onboarding/invite-team/page.tsx (3)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/app/api/settings/onboarding/welcome/route.ts (5)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-36)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-62)
packages/database/index.ts (1)
  • db (30-37)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/(org)/onboarding/organization-setup/page.tsx (1)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/app/(org)/dashboard/settings/organization/components/OrganizationIcon.tsx (1)
apps/web/actions/organization/upload-organization-icon.ts (1)
  • uploadOrganizationIcon (15-99)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
packages/ui/src/components/icons/LogoBadge.tsx (1)
  • LogoBadge (1-28)
🪛 GitHub Actions: Validate Migrations
packages/database/migrations/meta/_journal.json

[error] 2-2: Migration journal version cannot be changed (was: <BASE_VERSION>, now: <CURRENT_VERSION>)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (12)
apps/web/app/api/webhooks/stripe/route.ts (3)

8-8: LGTM: Cache invalidation import.

The revalidatePath import is correctly added for Next.js App Router cache invalidation.


279-281: Cache invalidation is appropriate after user update.

Revalidating /dashboard, /onboarding (layout), and / ensures fresh data after subscription state changes. The "layout" scope on /onboarding revalidates the entire layout tree—this is broad but safe for ensuring consistency.


235-277: Clarify onboarding auto-completion in Stripe webhook (apps/web/app/api/webhooks/stripe/route.ts)
This webhook sets onboardingSteps (all true) and onboarding_completed_at when session.metadata.isOnBoarding === "true", bypassing all onboarding screens. If you intend to fast-track paid users, document this behavior; otherwise remove these fields so users complete steps via the UI.

apps/web/components/UpgradeModal.tsx (1)

27-32: LGTM!

The new onboarding and currentOnboardingStep props are properly typed with sensible defaults. The TypeScript constraint on currentOnboardingStep ensures only valid step values are passed.

Also applies to: 61-66

apps/web/components/FileInput.tsx (1)

244-244: LGTM!

The hover color adjustment improves the visual feedback for the remove icon button.

packages/database/migrations/meta/_journal.json (1)

2-2: Ignore journal version warning: The version field remains "5" as in the main branch; no change was made to _journal.json, so the pipeline failure must stem from another issue.

Likely an incorrect or invalid review comment.

apps/web/app/(org)/onboarding/custom-domain/layout.tsx (1)

9-15: Auth flow check (verify)

If user is unauthenticated, steps default to {} and redirect to /onboarding/welcome. Confirm that route enforces auth or redirects to sign-in; otherwise add an explicit sign-in redirect here.

apps/web/app/(org)/onboarding/invite-team/layout.tsx (1)

12-18: LGTM!

The guard logic correctly enforces the sequence of onboarding steps, redirecting to the first incomplete prerequisite or to the dashboard if the invite-team step is already complete.

apps/web/app/(org)/onboarding/page.tsx (1)

3-5: LGTM!

The unconditional redirect to the welcome step serves as a clean entry point for the onboarding flow. The welcome layout will then handle progression logic based on the user's onboarding state.

apps/web/app/(org)/onboarding/organization-setup/page.tsx (1)

45-59: LGTM!

The form submission logic correctly handles loading states, error cases, and navigation. Using startTransition for the router push ensures a smooth user experience during the transition.

apps/web/app/(org)/onboarding/welcome/layout.tsx (1)

13-19: Fix unreachable redirect logic.

Lines 14-16 each call redirect(), which throws and halts execution. The subsequent conditionals and Line 18 are unreachable. The logic should use if-else to redirect to the first incomplete step.

Apply this diff:

 if (steps.welcome) {
-  if (!steps.organizationSetup) redirect("/onboarding/organization-setup");
-  if (!steps.customDomain) redirect("/onboarding/custom-domain");
-  if (!steps.inviteTeam) redirect("/onboarding/invite-team");
-  // All done → go to dashboard caps
-  redirect("/dashboard/caps");
+  if (!steps.organizationSetup) {
+    redirect("/onboarding/organization-setup");
+  } else if (!steps.customDomain) {
+    redirect("/onboarding/custom-domain");
+  } else if (!steps.inviteTeam) {
+    redirect("/onboarding/invite-team");
+  } else {
+    redirect("/dashboard/caps");
+  }
 }

Likely an incorrect or invalid review comment.

apps/web/app/(org)/onboarding/components/Stepper.tsx (1)

63-64: Verify custom class “min-size-5”.

Ensure this utility exists in your Tailwind setup; otherwise replace with min-w/min-h equivalents.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
apps/web/app/api/settings/onboarding/org-setup/route.ts (2)

15-62: Consider migrating to HttpApi with HttpAuthMiddleware.

This route bypasses the standard Effect-based HttpApi architecture. Per coding guidelines, define an HttpApi with POST endpoint, use HttpAuthMiddleware for auth (no manual getCurrentUser), map domain errors via HttpApiError.*, and export only the handler from apiToHandler(ApiLive) with Layer.provide for dependencies.

As per coding guidelines


18-18: Validate and trim organizationName before inserting.

The code accepts empty or whitespace-only names, which can create invalid organizations. Trim the input and reject empty values with a 400 response.

Apply this diff:

-  const organizationName = String(formData.get("organizationName") || "");
+  const organizationName = String(formData.get("organizationName") || "").trim();

Then add validation after the user check:

   if (!user) {
     console.error("User not found");
     return NextResponse.json({ error: true }, { status: 401 });
   }
+  if (!organizationName) {
+    return NextResponse.json({ error: "organizationName is required" }, { status: 400 });
+  }
apps/web/app/(org)/dashboard/layout.tsx (1)

32-32: Fix onboarding state initialization to avoid forcing existing users through onboarding.

The || {} fallback creates an empty object when user.onboardingSteps is null/undefined. This makes all subsequent property checks (like !onboardingSteps.welcome) evaluate as truthy, forcing existing users without the field through the entire onboarding flow.

Apply this diff:

-  const onboardingSteps = user.onboardingSteps || {};
+  const onboardingSteps = user.onboardingSteps;

Then wrap the step-by-step checks in a conditional:

   if (!user.name || user.name.length === 0) {
     redirect("/onboarding/welcome");
   }
-  if ((!onboardingSteps.welcome && !user.name) || user.name.length === 0) {
-    redirect("/onboarding/welcome");
-  }
+  if (onboardingSteps) {
+    if (!onboardingSteps.welcome) {
+      redirect("/onboarding/welcome");
+    }
+    if (!onboardingSteps.organizationSetup) {
+      redirect("/onboarding/organization-setup");
+    }
+    if (!onboardingSteps.customDomain) {
+      redirect("/onboarding/custom-domain");
+    }
+    if (!onboardingSteps.inviteTeam) {
+      redirect("/onboarding/invite-team");
+    }
+  }
🧹 Nitpick comments (3)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)

8-35: Consider migrating to HttpApi with HttpAuthMiddleware.

This route manually calls getCurrentUser and constructs NextResponse. Per coding guidelines, API routes should use @effect/platform HttpApi/HttpApiBuilder with HttpAuthMiddleware for auth and Layer.provide for dependencies.

As per coding guidelines

apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)

8-34: Implementation looks correct.

The route properly validates the user, spreads existing onboarding steps (safe for null/undefined), sets inviteTeam to true, timestamps completion, and revalidates the layout path. Error handling is comprehensive.

However, consider migrating to HttpApi with HttpAuthMiddleware for consistency across API routes. As per coding guidelines

apps/web/app/(org)/onboarding/invite-team/page.tsx (1)

38-54: Use Server Actions with useEffectMutation instead of client fetch.

Per coding guidelines, web mutations should call Server Actions directly and update caches via setQueryData/setQueriesData rather than broad invalidations with router.refresh(). The current implementation using fetch and router transitions bypasses the recommended pattern.

As per coding guidelines

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4c17c4 and 7da87fe.

📒 Files selected for processing (6)
  • apps/web/app/(org)/dashboard/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/invite-team/page.tsx (1 hunks)
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/org-setup/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/welcome/route.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/app/api/**/route.ts: Place API routes only under apps/web/app/api and implement each route in a route.ts file
Construct API routes with @effect/platform HttpApi/HttpApiBuilder and export only the handler from apiToHandler(ApiLive)
Map domain errors to transport errors with HttpApiError.* and keep error translation exhaustive
Use HttpAuthMiddleware for required auth and provideOptionalAuth for guest routes; avoid duplicate session lookups
Provide dependencies via Layer.provide in API routes instead of manual provideService calls

Files:

  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/api/settings/onboarding/welcome/route.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
🧠 Learnings (1)
📚 Learning: 2025-09-22T14:17:47.407Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-22T14:17:47.407Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations

Applied to files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
🧬 Code graph analysis (4)
apps/web/app/(org)/onboarding/invite-team/page.tsx (2)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (6)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-33)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-62)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-35)
apps/web/app/api/settings/onboarding/complete/route.ts (1)
  • POST (7-28)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/api/settings/onboarding/welcome/route.ts (5)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-33)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-62)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/api/settings/onboarding/org-setup/route.ts (8)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-33)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-35)
packages/web-domain/src/Organisation.ts (1)
  • Organisation (8-11)
packages/database/helpers.ts (1)
  • nanoId (6-9)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (3)
  • organizations (169-201)
  • organizationMembers (204-226)
  • users (58-116)
apps/web/actions/organization/upload-organization-icon.ts (1)
  • uploadOrganizationIcon (15-99)
⏰ 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). (4)
  • GitHub Check: Typecheck
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
apps/web/app/(org)/onboarding/invite-team/page.tsx (1)

56-89: Error handling improvements look good.

The planCheckout function now properly wraps fetch in try/catch, checks response.ok before parsing JSON, handles the already-subscribed case with an early return, and clears loading state in finally. This addresses the past review concerns about stuck spinners and unintended redirects.

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

♻️ Duplicate comments (3)
apps/web/app/(org)/onboarding/invite-team/page.tsx (2)

38-54: Use Server Actions with useEffectMutation instead of fetch and router.refresh.

The Skip flow should call a Server Action via useEffectMutation from @/lib/EffectRuntime and perform targeted cache updates with setQueryData/setQueriesData rather than using fetch() and router.refresh().

As per coding guidelines


56-89: Use Server Actions with useEffectMutation instead of fetch.

The checkout flow should call a Server Action via useEffectMutation from @/lib/EffectRuntime rather than using fetch() directly.

As per coding guidelines

apps/web/app/api/settings/onboarding/org-setup/route.ts (1)

26-29: Validation added but whitespace handling still needed.

The empty check is a good addition. However, whitespace-only strings (e.g., " ") would still pass. The previous review comment suggested trimming the input before validation.

🧹 Nitpick comments (2)
apps/web/app/api/settings/onboarding/org-setup/route.ts (2)

19-19: Optional: Eliminate redundant organizationIcon variable.

The organizationIcon variable (line 19) is extracted but used only once in a conditional check (line 40). Consider directly checking formData.get("icon") in the conditional to reduce intermediate state.

Apply this diff:

-	const organizationIcon = (formData.get("icon") as FormDataEntryValue) || null;

 	if (!user) {
 		console.error("User not found");
 		return NextResponse.json({ error: true }, { status: 401 });
 	}

 	if (!organizationName || organizationName.length === 0) {
 		console.error("Organization name is required");
 		return NextResponse.json({ error: true }, { status: 400 });
 	}

 	const organizationId = Organisation.OrganisationId.make(nanoId());

 	try {
 		await db().insert(organizations).values({
 			id: organizationId,
 			ownerId: user.id,
 			name: organizationName,
 		});

-		if (organizationIcon) {
+		if (formData.get("icon")) {
 			await uploadOrganizationIcon(formData, organizationId);
 		}

Also applies to: 40-40


64-64: Optional: Use a more accurate error message.

The message "Failed to update user onboarding steps" is misleading because the error could originate from any operation: creating the organization, uploading the icon, creating the organization member, or updating the user.

Apply this diff:

 	} catch (error) {
-		console.error("Failed to update user onboarding steps", error);
+		console.error("Failed to complete organization setup", error);
 		return NextResponse.json({ error: true }, { status: 500 });
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4c17c4 and 73bc732.

📒 Files selected for processing (7)
  • apps/web/app/(org)/dashboard/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/invite-team/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx (1 hunks)
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/org-setup/route.ts (1 hunks)
  • apps/web/app/api/settings/onboarding/welcome/route.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/app/(org)/onboarding/organization-setup/page.tsx
  • apps/web/app/api/settings/onboarding/invite-your-team/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/api/settings/onboarding/welcome/route.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/app/api/**/route.ts: Place API routes only under apps/web/app/api and implement each route in a route.ts file
Construct API routes with @effect/platform HttpApi/HttpApiBuilder and export only the handler from apiToHandler(ApiLive)
Map domain errors to transport errors with HttpApiError.* and keep error translation exhaustive
Use HttpAuthMiddleware for required auth and provideOptionalAuth for guest routes; avoid duplicate session lookups
Provide dependencies via Layer.provide in API routes instead of manual provideService calls

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/api/settings/onboarding/custom-domain/route.ts
  • apps/web/app/(org)/onboarding/invite-team/page.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-22T14:17:47.407Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-22T14:17:47.407Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations

Applied to files:

  • apps/web/app/(org)/onboarding/invite-team/page.tsx
🧬 Code graph analysis (3)
apps/web/app/api/settings/onboarding/org-setup/route.ts (8)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-33)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-35)
packages/web-domain/src/Organisation.ts (1)
  • Organisation (8-11)
packages/database/helpers.ts (1)
  • nanoId (6-9)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (3)
  • organizations (169-201)
  • organizationMembers (204-226)
  • users (58-116)
apps/web/actions/organization/upload-organization-icon.ts (1)
  • uploadOrganizationIcon (15-99)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (5)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)
  • POST (15-67)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-35)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (1)
  • users (58-116)
apps/web/app/(org)/onboarding/invite-team/page.tsx (2)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)

20-23: Improvement: Spread operator preserves existing onboarding flags.

The spread operator ...user.onboardingSteps properly preserves existing flags like inviteTeam, addressing the past concern about overwriting the entire object.

Comment on lines 8 to 33
export async function POST() {
const user = await getCurrentUser();

if (!user) {
console.error("User not found");
return NextResponse.json({ error: true }, { status: 401 });
}

try {
await db()
.update(users)
.set({
onboardingSteps: {
...user.onboardingSteps,
customDomain: true,
},
})
.where(eq(users.id, user.id));

revalidatePath("/onboarding", "layout");
return NextResponse.json({ success: true }, { status: 200 });
} catch (error) {
console.error("Failed to update user onboarding steps", error);
return NextResponse.json({ error: true }, { status: 500 });
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to use Effect's HttpApi pattern per coding guidelines.

The coding guidelines specify: "Construct API routes with @effect/platform HttpApi/HttpApiBuilder and export only the handler from apiToHandler(ApiLive)." This route uses plain Next.js handlers instead.

Note that all other onboarding routes (welcome, org-setup, invite-your-team) also use the plain Next.js pattern, suggesting either a systemic refactor is needed or these simple routes are exempt from the guideline.

As per coding guidelines:

import { HttpApi, HttpApiBuilder, HttpApiError } from "@effect/platform";
import { Effect } from "effect";

const api = HttpApi.make("onboarding-custom-domain").pipe(
  HttpApi.post("/", {
    handler: Effect.gen(function* (_) {
      const user = yield* _(getCurrentUserEffect());
      
      if (!user.onboardingSteps?.welcome || !user.onboardingSteps?.organizationSetup) {
        return yield* _(HttpApiError.badRequest("Complete previous steps first"));
      }

      yield* _(
        db()
          .update(users)
          .set({
            onboardingSteps: {
              ...user.onboardingSteps,
              customDomain: true,
            },
          })
          .where(eq(users.id, user.id))
      );

      revalidatePath("/onboarding", "layout");
      return { success: true };
    }),
  })
);

export const POST = apiToHandler(ApiLive);
🤖 Prompt for AI Agents
In apps/web/app/api/settings/onboarding/custom-domain/route.ts around lines 8 to
33, replace the plain Next.js POST handler with an @effect/platform HttpApi
built via HttpApi/HttpApiBuilder: create an api =
HttpApi.make("onboarding-custom-domain") and add a POST route whose handler is
an Effect (use Effect.gen) that calls getCurrentUserEffect(), validates prior
onboarding steps (return HttpApiError.badRequest if they are incomplete),
performs the db update as an Effect, calls
revalidatePath("/onboarding","layout"), and returns { success: true }; then
export the route by converting the api to a handler with apiToHandler(ApiLive).
Also add the required imports from @effect/platform and ensure errors are
surfaced via HttpApiError rather than plain NextResponse or console.error.

Comment on lines 16 to 25
try {
await db()
.update(users)
.set({
onboardingSteps: {
...user.onboardingSteps,
customDomain: true,
},
})
.where(eq(users.id, user.id));
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 | 🔴 Critical

🧩 Analysis chain

Verify previous onboarding steps before updating.

The route allows users to complete the customDomain step without verifying that welcome and organizationSetup were completed first. This could lead to:

  • Data integrity issues (missing user name or organization)
  • Users bypassing required setup by directly calling this endpoint
  • Inconsistent onboarding state

Examining related routes reveals this is a systemic pattern across all onboarding endpoints—none validate previous step completion.

Apply this diff to add validation:

 	try {
+		if (!user.onboardingSteps?.welcome || !user.onboardingSteps?.organizationSetup) {
+			console.error("Previous onboarding steps not completed");
+			return NextResponse.json(
+				{ error: true, message: "Complete previous steps first" },
+				{ status: 400 }
+			);
+		}
+
 		await db()
 			.update(users)

Verify the pattern across all onboarding routes:


🏁 Script executed:

#!/bin/bash

echo "Checking for step validation in onboarding routes:"
echo "=================================================="

for route in welcome org-setup custom-domain invite-your-team; do
  echo -e "\n### Checking $route route ###"
  if [ -f "apps/web/app/api/settings/onboarding/$route/route.ts" ]; then
    echo "File exists. Checking for validation logic:"
    if rg -q "onboardingSteps\?" "apps/web/app/api/settings/onboarding/$route/route.ts"; then
      echo "✓ Contains onboardingSteps check"
      rg -C 3 "onboardingSteps\?" "apps/web/app/api/settings/onboarding/$route/route.ts"
    else
      echo "✗ No onboardingSteps validation found"
    fi
  fi
done

echo -e "\n\nSearching for any step validation patterns:"
rg -n "onboardingSteps.*welcome|onboardingSteps.*organizationSetup" apps/web/app/api/settings/onboarding/ -A 2 -B 2

Length of output: 620


Add guard for previous onboarding steps in all onboarding routes
In each route under apps/web/app/api/settings/onboarding (welcome, org-setup, custom-domain, invite-your-team), verify that user.onboardingSteps.welcome and user.onboardingSteps.organizationSetup are true before proceeding, returning a 400 error if not completed.

🤖 Prompt for AI Agents
In apps/web/app/api/settings/onboarding/custom-domain/route.ts around lines 16
to 25, add a guard that checks user.onboardingSteps.welcome and
user.onboardingSteps.organizationSetup are true before performing the DB update;
if either is falsy, short-circuit and return a 400 response indicating the
required prior steps are not completed. Apply the same guard pattern to the
other onboarding routes (welcome, org-setup, invite-your-team) so each route
validates those two fields consistently before proceeding with its logic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/app/(org)/onboarding/welcome/page.tsx (1)

15-45: Previous review comment still applies.

The architectural concern about using Server Actions with useEffectMutation instead of client-side fetch has already been comprehensively addressed in the previous review comment with detailed example code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73bc732 and cdbeced.

📒 Files selected for processing (2)
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/welcome/page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(org)/onboarding/custom-domain/page.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-22T14:17:47.407Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-22T14:17:47.407Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations

Applied to files:

  • apps/web/app/(org)/onboarding/welcome/page.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/welcome/page.tsx (1)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)

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

♻️ Duplicate comments (1)
apps/web/app/api/settings/onboarding/org-setup/route.ts (1)

1-13: Adopt HttpApi/HttpAuthMiddleware architecture per coding guidelines.

This route does not follow the mandated HttpApi/HttpApiBuilder pattern, HttpAuthMiddleware for auth, or Layer.provide for dependencies.

As per coding guidelines

Also applies to: 15-16

🧹 Nitpick comments (1)
apps/web/app/Layout/providers.tsx (1)

154-167: Consider adding user feedback for restart action.

The Onboarding section correctly wires the restart button to the restartOnboarding server action. However, users won't receive any feedback on success or failure. While this is acceptable for dev tooling, adding a toast notification would improve the developer experience.

Example implementation with toast feedback:

<form action={async () => {
  try {
    await restartOnboarding();
    toast.success("Onboarding restarted");
  } catch (error) {
    toast.error("Failed to restart onboarding");
  }
}}>
  <button
    type="submit"
    className="px-2 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700"
  >
    Restart Onboarding
  </button>
</form>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdbeced and b81659a.

📒 Files selected for processing (7)
  • apps/web/app/(org)/dashboard/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/Stepper.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/welcome/page.tsx (1 hunks)
  • apps/web/app/Layout/devtoolsServer.ts (1 hunks)
  • apps/web/app/Layout/providers.tsx (3 hunks)
  • apps/web/app/api/settings/onboarding/org-setup/route.ts (1 hunks)
  • packages/database/schema.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(org)/onboarding/welcome/page.tsx
  • packages/database/schema.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/Layout/devtoolsServer.ts
  • apps/web/app/api/settings/onboarding/org-setup/route.ts
  • apps/web/app/(org)/dashboard/layout.tsx
  • apps/web/app/(org)/onboarding/components/Stepper.tsx
  • apps/web/app/Layout/providers.tsx
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/app/api/**/route.ts: Place API routes only under apps/web/app/api and implement each route in a route.ts file
Construct API routes with @effect/platform HttpApi/HttpApiBuilder and export only the handler from apiToHandler(ApiLive)
Map domain errors to transport errors with HttpApiError.* and keep error translation exhaustive
Use HttpAuthMiddleware for required auth and provideOptionalAuth for guest routes; avoid duplicate session lookups
Provide dependencies via Layer.provide in API routes instead of manual provideService calls

Files:

  • apps/web/app/api/settings/onboarding/org-setup/route.ts
🧬 Code graph analysis (3)
apps/web/app/Layout/devtoolsServer.ts (2)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/app/api/settings/onboarding/org-setup/route.ts (8)
apps/web/app/api/settings/onboarding/custom-domain/route.ts (1)
  • POST (8-33)
apps/web/app/api/settings/onboarding/invite-your-team/route.ts (1)
  • POST (8-34)
apps/web/app/api/settings/onboarding/welcome/route.ts (1)
  • POST (8-35)
packages/web-domain/src/Organisation.ts (1)
  • Organisation (8-11)
packages/database/helpers.ts (1)
  • nanoId (6-9)
packages/database/index.ts (1)
  • db (12-19)
packages/database/schema.ts (3)
  • organizations (170-202)
  • organizationMembers (205-227)
  • users (58-117)
apps/web/actions/organization/upload-organization-icon.ts (1)
  • uploadOrganizationIcon (15-99)
apps/web/app/Layout/providers.tsx (1)
apps/web/app/Layout/devtoolsServer.ts (2)
  • demoteFromPro (44-58)
  • restartOnboarding (24-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/web/app/(org)/onboarding/components/Stepper.tsx (1)

57-102: Well-structured render implementation.

The step rendering logic appropriately handles visual states (active, completed, inactive) with clear conditional styling. The dashed connectors and icon switching (check vs. number) provide good visual feedback to users.

apps/web/app/api/settings/onboarding/org-setup/route.ts (2)

34-57: Transaction implementation looks good.

The transaction properly wraps organization creation, member insertion, and user update, ensuring atomicity. Moving icon upload outside the transaction (lines 59-61) is acceptable, as a failed upload is less critical than rolling back the entire onboarding step.


59-61: Icon upload placement is appropriate.

Uploading the icon after the transaction commits ensures that the organization is created even if the upload fails, which is a reasonable trade-off for onboarding flows.

apps/web/app/Layout/providers.tsx (1)

74-78: LGTM!

The import is correct and follows the existing pattern for other dev server actions.

Comment on lines 32 to 34
if (!user.name || user.name.length === 0) {
redirect("/onboarding");
redirect("/onboarding/welcome");
}
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 | 🔴 Critical

🧩 Analysis chain

Verify onboarding step enforcement is handled elsewhere.

The current code only checks user.name before allowing dashboard access. Given the PR adds multi-step onboarding with tracked steps (welcome, organizationSetup, customDomain, inviteTeam, download as mentioned in the AI summary), verify that:

  1. Per-step redirects are enforced by the onboarding layout/pages themselves
  2. Users cannot bypass onboarding steps by manually navigating to /dashboard after only completing the welcome step

The previous version (per past review comments) had checks for individual onboardingSteps properties here that were problematic for existing users. If those checks were intentionally moved to the onboarding layout, that's correct. If they were removed without being relocated, incomplete onboarding users could access the dashboard.

Run the following script to verify where onboarding step checks are enforced:


🏁 Script executed:

#!/bin/bash
# Search for onboarding step checks in layouts and middleware

# Check onboarding layout for step enforcement
echo "=== Onboarding layout files ==="
fd -e tsx -e ts "layout" apps/web/app | rg "onboarding"

# Search for onboardingSteps checks across the codebase
echo -e "\n=== OnboardingSteps property checks ==="
rg -nP --type=ts -C3 'onboardingSteps\.(welcome|organizationSetup|customDomain|inviteTeam)' apps/web/app

# Search for onboarding completion checks
echo -e "\n=== Onboarding completion checks ==="
rg -nP --type=ts -C3 'onboarding_completed_at|onboardingCompleted' apps/web/app

Length of output: 2510


🏁 Script executed:

#!/bin/bash
# Search for redirect calls in each onboarding layout
rg -nP --type=tsx -n 'redirect\(' apps/web/app/\(org\)/onboarding

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for redirect calls in onboarding layout files
fd -e tsx apps/web/app/\(org\)/onboarding | xargs rg -n 'redirect\('

Length of output: 4530


Enforce full onboarding in dashboard layout.
In apps/web/app/(org)/dashboard/layout.tsx, add checks for user.onboardingSteps.organizationSetup, .customDomain, and .inviteTeam (beyond just user.name) and redirect to the respective onboarding step if any are incomplete; currently only user.name is gated, allowing dashboard access after only the welcome step.

🤖 Prompt for AI Agents
In apps/web/app/(org)/dashboard/layout.tsx around lines 32 to 34, the layout
currently only checks user.name before redirecting to onboarding; add checks for
user.onboardingSteps.organizationSetup, user.onboardingSteps.customDomain, and
user.onboardingSteps.inviteTeam and redirect to their respective onboarding
routes if they are falsey or incomplete; implement safe null/undefined checks
for user.onboardingSteps before accessing properties, and ensure the redirects
follow the correct route order (organization setup -> custom domain -> invite
team) so users cannot access the dashboard until all onboarding steps are
complete.

export async function POST(request: NextRequest) {
const user = await getCurrentUser();
const formData = await request.formData();
const organizationName = String(formData.get("organizationName"));
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 | 🔴 Critical

Fix critical validation bug: organizationName can be "null" or whitespace.

Line 18 uses String(formData.get("organizationName")) without ?? "" or .trim(). If the form field is missing, formData.get() returns null, and String(null) produces the literal string "null" (length 4), which bypasses the length check at line 26 and creates an organization named "null". Similarly, whitespace-only names pass validation.

Apply this diff to fix the extraction and validation:

-	const organizationName = String(formData.get("organizationName"));
+	const organizationName = String(formData.get("organizationName") ?? "").trim();
 	const organizationIcon = (formData.get("icon") as FormDataEntryValue) || null;
 
 	if (!user) {
 		console.error("User not found");
 		return NextResponse.json({ error: true }, { status: 401 });
 	}
 
-	if (!organizationName || organizationName.length === 0) {
+	if (!organizationName) {
 		console.error("Organization name is required");
-		return NextResponse.json({ error: true }, { status: 400 });
+		return NextResponse.json({ error: "organizationName is required" }, { status: 400 });
 	}

Optionally, enforce a max length (e.g., 100 characters) to align with database constraints and UX expectations:

 	if (!organizationName) {
 		console.error("Organization name is required");
 		return NextResponse.json({ error: "organizationName is required" }, { status: 400 });
 	}
+
+	if (organizationName.length > 100) {
+		console.error("Organization name is too long");
+		return NextResponse.json({ error: "organizationName must be 100 characters or less" }, { status: 400 });
+	}

Also applies to: 26-29

🤖 Prompt for AI Agents
In apps/web/app/api/settings/onboarding/org-setup/route.ts around lines 18 and
26-29, fix extraction and validation of organizationName so missing or
whitespace-only values cannot pass: replace
String(formData.get("organizationName")) with a safe extraction that uses
formData.get("organizationName") ?? "" and then .trim() to remove whitespace,
validate length after trimming (e.g., require non-empty and enforce a max length
such as 100 chars), and update the existing length checks and error messages to
use the trimmed value.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/web-backend/src/Users/index.ts (1)

4-10: Empty service scaffold.

The Users service currently returns an empty object with no exposed methods. This appears to be scaffolding for future user-related RPC operations.

Do you want me to help scaffold common user operations (e.g., getUserById, updateOnboardingStep) based on the onboarding flow requirements mentioned in the PR objectives?

packages/web-domain/src/User.ts (1)

9-9: Empty RPC group scaffold.

The UserRpcs class is currently empty with no RPC method definitions. This appears to be scaffolding for future user-related RPC methods.

Do you want me to help scaffold user RPC method definitions (e.g., for onboarding step management) based on the PR's onboarding flow requirements?

packages/web-backend/src/Users/UsersRpcs.ts (1)

4-8: Empty RPC layer scaffold.

The UsersRpcsLive layer currently returns an empty object with no RPC method implementations. This scaffolding will need to be populated with actual user-related RPC handlers.

Do you want me to help implement RPC handlers for common user operations (e.g., fetching user details, updating onboarding progress) to complete this scaffold?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b81659a and 906d278.

📒 Files selected for processing (5)
  • packages/web-backend/src/Rpcs.ts (1 hunks)
  • packages/web-backend/src/Users/UsersRpcs.ts (1 hunks)
  • packages/web-backend/src/Users/index.ts (1 hunks)
  • packages/web-domain/src/Rpcs.ts (1 hunks)
  • packages/web-domain/src/User.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • packages/web-backend/src/Users/index.ts
  • packages/web-backend/src/Users/UsersRpcs.ts
  • packages/web-domain/src/Rpcs.ts
  • packages/web-backend/src/Rpcs.ts
  • packages/web-domain/src/User.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • packages/web-backend/src/Users/index.ts
  • packages/web-backend/src/Users/UsersRpcs.ts
  • packages/web-domain/src/Rpcs.ts
  • packages/web-backend/src/Rpcs.ts
  • packages/web-domain/src/User.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • packages/web-backend/src/Users/index.ts
  • packages/web-backend/src/Users/UsersRpcs.ts
  • packages/web-domain/src/Rpcs.ts
  • packages/web-backend/src/Rpcs.ts
  • packages/web-domain/src/User.ts
🧬 Code graph analysis (3)
packages/web-domain/src/Rpcs.ts (1)
packages/web-domain/src/User.ts (1)
  • UserRpcs (9-9)
packages/web-backend/src/Rpcs.ts (1)
packages/web-backend/src/Users/UsersRpcs.ts (1)
  • UsersRpcsLive (4-8)
packages/web-domain/src/User.ts (1)
packages/web-backend/src/Workflows.ts (1)
  • RpcGroup (10-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
packages/web-domain/src/Rpcs.ts (1)

4-7: LGTM! Clean RPC group extension.

The UserRpcs import and merge follow the established pattern for VideoRpcs and FolderRpcs, cleanly extending the RPC surface.

packages/web-backend/src/Rpcs.ts (1)

11-18: LGTM! Proper layer composition.

The UsersRpcsLive import and Layer.mergeAll integration follow the established pattern, correctly extending the backend RPC layers.

import { RpcGroup } from "@effect/rpc";
import { Schema } from "effect";

import { RpcAuthMiddleware } from "./Authentication.ts";
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify unused import.

The RpcAuthMiddleware import does not appear to be used in the visible code. Ensure this import is necessary or remove it if unused.

Run the following script to verify usage:


🏁 Script executed:

#!/bin/bash
# Search for RpcAuthMiddleware usage in User.ts beyond the visible snippet
rg -nP '\bRpcAuthMiddleware\b' packages/web-domain/src/User.ts

Length of output: 58


Remove unused RpcAuthMiddleware import.

The import from ./Authentication.ts in packages/web-domain/src/User.ts isn’t used and should be removed.

🤖 Prompt for AI Agents
In packages/web-domain/src/User.ts around line 4, the import "RpcAuthMiddleware"
from "./Authentication.ts" is unused; remove that import statement cleanly from
the file (delete the import or the specific named import) and run a quick
type-check/lint to ensure no other references exist.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/web/app/api/webhooks/stripe/route.ts (1)

255-255: Onboarding completion logic remains semantically incorrect.

This concern was previously raised: setting onboarding_completed_at when isOnBoarding is true treats subscription purchase as onboarding completion, bypassing the multi-step flow (welcome → org setup → custom domain → invite team → download). The UsersOnboarding service marks completion only after the final download step, creating an inconsistency.

Clarify whether subscription purchase should complete onboarding or if this field should only be set by the onboarding service after all steps are finished.

packages/web-domain/src/User.ts (1)

71-76: Include UnauthenticatedError in the RPC error schema

RpcAuthMiddleware can fail with UnauthenticatedError, but the RPC currently advertises only InternalError. When an unauthenticated user hits this endpoint the server will emit UnauthenticatedError, which the client cannot decode under the narrower schema, leading to a decoding failure instead of a clean auth error. Import UnauthenticatedError and union it into the error schema so callers receive the intended error type.

-import { RpcAuthMiddleware } from "./Authentication.ts";
+import { RpcAuthMiddleware, UnauthenticatedError } from "./Authentication.ts";
@@
-    error: InternalError,
+    error: Schema.Union(InternalError, UnauthenticatedError),
🧹 Nitpick comments (1)
apps/web/app/api/webhooks/stripe/route.ts (1)

245-246: Consider conditional logging for production.

While these debug logs are helpful for tracing onboarding state, session.metadata could contain sensitive information. Consider wrapping these in an environment check or removing them before production deployment.

Apply this diff to add conditional logging:

-console.log("Session metadata:", session.metadata);
-console.log("Is onboarding:", isOnBoarding);
+if (process.env.NODE_ENV === 'development') {
+  console.log("Session metadata:", session.metadata);
+  console.log("Is onboarding:", isOnBoarding);
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44aff44 and 724a038.

📒 Files selected for processing (5)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1 hunks)
  • apps/web/app/api/webhooks/stripe/route.ts (4 hunks)
  • packages/web-backend/src/Users/UsersOnboarding.ts (1 hunks)
  • packages/web-domain/src/User.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web-backend/src/Users/UsersOnboarding.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • packages/web-domain/src/User.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • packages/web-domain/src/User.ts
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/api/webhooks/stripe/route.ts
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/app/api/**/*.{ts,tsx}: Prefer Server Actions for API surface; when routes are necessary, implement under app/api and export only the handler from apiToHandler(ApiLive)
Construct API routes with @effect/platform HttpApi/HttpApiBuilder, declare contracts with Schema, and only export the handler
Use HttpAuthMiddleware for required auth and provideOptionalAuth for guests; avoid duplicating session lookups
Map domain errors to transport with HttpApiError.* and keep translation exhaustive (catchTags/tapErrorCause)
Inside HttpApiBuilder.group, acquire services with Effect.gen and provide dependencies via Layer.provide instead of manual provideService

Files:

  • apps/web/app/api/webhooks/stripe/route.ts
🧬 Code graph analysis (3)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (4)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
packages/web-domain/src/User.ts (4)
packages/web-domain/src/Organisation.ts (2)
  • OrganisationId (3-5)
  • OrganisationId (6-6)
apps/web/lib/Rpcs.ts (1)
  • Rpc (11-14)
packages/web-domain/src/Errors.ts (1)
  • InternalError (3-6)
packages/web-domain/src/Authentication.ts (1)
  • RpcAuthMiddleware (34-40)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (4)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/components/UpgradeModal.tsx (1)
  • UpgradeModal (62-325)
⏰ 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). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (6)
apps/web/app/api/webhooks/stripe/route.ts (3)

237-237: LGTM!

The isOnBoarding extraction pattern is clean and uses appropriate optional chaining with explicit string comparison.


278-278: LGTM!

The is_onboarding flag extraction for analytics is consistent with the pattern used on line 237.


459-461: LGTM!

The error response structure is now correct with proper object syntax for the status code.

apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (3)

6-6: Previous typing issue resolved.

The MouseEvent type is now correctly imported from "react", addressing the earlier concern about the missing React namespace import.


17-36: LGTM: Mutation setup follows coding guidelines.

The Effect/RPC integration is correct:

  • Uses useEffectMutation from the prescribed runtime helper.
  • Properly invokes withRpc to access RPC methods.
  • Navigation wrapped in startTransition for better UX.

38-41: Previous async suggestion implemented.

The handler now correctly uses await mutateAsync() instead of mutate(), allowing callers to wait for completion. This addresses the earlier recommendation.

.platform as string)
: "web",
is_onboarding: session.metadata?.isOnBoarding === "true",
platform: session.metadata?.platform === "web",
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 | 🟠 Major

Avoid information loss in analytics platform tracking.

Converting platform to a boolean (platform === "web") discards the actual platform value. If a user purchases from "desktop" or "mobile", PostHog will only record false, making it impossible to distinguish between different non-web platforms in analytics.

Apply this diff to preserve the platform value:

-platform: session.metadata?.platform === "web",
+platform: session.metadata?.platform || "unknown",
📝 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
platform: session.metadata?.platform === "web",
platform: session.metadata?.platform || "unknown",
🤖 Prompt for AI Agents
In apps/web/app/api/webhooks/stripe/route.ts around line 279, the code currently
converts platform to a boolean (platform: session.metadata?.platform === "web")
which loses the actual platform string; change it to preserve the string value
(e.g., platform: session.metadata?.platform ?? 'unknown' or simply platform:
session.metadata?.platform) so PostHog receives the real platform ("web",
"desktop", "mobile", etc.) and optionally provide a sensible default if metadata
is missing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (3)

97-103: Consider removing dead code.

The onError handler in planCheckoutMutation is never triggered because planCheckout catches all exceptions internally (line 91) and never rethrows. This error handler is unreachable.

If you want to keep it as a defensive measure, consider adding a comment explaining its purpose. Otherwise, remove it:

 const planCheckoutMutation = useMutation({
   mutationFn: (e: MouseEvent<HTMLButtonElement>) => planCheckout(e),
-  onError: (error) => {
-    console.error("Plan checkout error:", error);
-    toast.error("Something went wrong. Please try again.");
-  },
 });

50-51: router.refresh() is unnecessary in the checkout redirect flow.

When inviteTeamMutation succeeds via the "Get Started" button flow (line 86), router.refresh() is called here, but planCheckout immediately redirects to Stripe checkout (line 89). The refresh serves no purpose in this flow since the user navigates away.

The refresh is only needed for the "Skip" button flow where the user stays on the site.

Consider one of these approaches:

  1. Accept the minor overhead (current behavior is harmless, just slightly wasteful)
  2. Pass a flag to skip the refresh when called from checkout flow
  3. Create separate mutations for skip vs. checkout flows

80-90: Add type safety for the billing API response.

The response from /api/settings/billing/subscribe is implicitly typed as any, so data.subscription and data.url lack type checking.

Define a response interface:

interface BillingSubscribeResponse {
  subscription?: boolean;
  url?: string;
}

Then type the response:

 const data = await response.json();
+const data: BillingSubscribeResponse = await response.json();

Or better, define the type at the top of the file and reuse it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc24b11 and 0d602f7.

📒 Files selected for processing (2)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (5)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (3)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (3)

1-17: LGTM! Imports are clean and follow guidelines.

The imports are well-organized, with proper type imports and correct usage of client-side Effect utilities per the coding guidelines.


19-38: LGTM! State management and pricing logic are sound.

The state initialization, pricing calculations, and user count helpers are implemented correctly with appropriate bounds checking.


105-247: LGTM! UI is well-structured with proper accessibility.

The component renders an accessible interface with:

  • Clear pricing display using NumberFlow for smooth transitions
  • Proper aria-label attributes on increment/decrement buttons and the billing cycle switch
  • Appropriate loading and disabled states on action buttons
  • Responsive layout with the Base wrapper

Comment on lines 40 to 95
const inviteTeamMutation = useEffectMutation({
mutationFn: () =>
Effect.gen(function* () {
yield* withRpc((r) =>
r.UserCompleteOnboardingStep({
step: "inviteTeam",
data: undefined,
}),
);
}),
onSuccess: () => {
router.refresh();
},
onError: () => {
toast.error("An error occurred, please try again");
},
});

const handleSubmit = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
await inviteTeamMutation.mutateAsync();
};

const planCheckout = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
try {
const planId = getProPlanId(isAnnually ? "yearly" : "monthly");
const response = await fetch(`/api/settings/billing/subscribe`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
priceId: planId,
quantity: users,
isOnBoarding: true,
}),
});
if (!response.ok) {
toast.error("Unable to start checkout. Please try again.");
return;
}
const data = await response.json();
if (data.subscription === true) {
toast.success("You are already on the Cap Pro plan");
return;
}

await handleSubmit(e);

if (data.url) {
window.location.href = data.url;
}
} catch (error) {
console.error("Plan checkout error:", error);
toast.error("Something went wrong. Please try again.");
}
};
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 | 🟠 Major

Fix duplicate error toasts when inviteTeamMutation fails.

When handleSubmit fails at line 86 (inside planCheckout), the error is handled twice:

  1. inviteTeamMutation.onError (line 54) shows: "An error occurred, please try again"
  2. planCheckout's catch block (line 93) shows: "Something went wrong. Please try again."

The user sees both toasts for a single failure.

Apply this diff to prevent duplicate toasts by catching handleSubmit errors separately:

     if (data.subscription === true) {
       toast.success("You are already on the Cap Pro plan");
       return;
     }
 
-    await handleSubmit(e);
+    try {
+      await handleSubmit(e);
+    } catch {
+      // Error already handled by inviteTeamMutation.onError
+      return;
+    }
 
     if (data.url) {
       window.location.href = data.url;
     }
📝 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 inviteTeamMutation = useEffectMutation({
mutationFn: () =>
Effect.gen(function* () {
yield* withRpc((r) =>
r.UserCompleteOnboardingStep({
step: "inviteTeam",
data: undefined,
}),
);
}),
onSuccess: () => {
router.refresh();
},
onError: () => {
toast.error("An error occurred, please try again");
},
});
const handleSubmit = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
await inviteTeamMutation.mutateAsync();
};
const planCheckout = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
try {
const planId = getProPlanId(isAnnually ? "yearly" : "monthly");
const response = await fetch(`/api/settings/billing/subscribe`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
priceId: planId,
quantity: users,
isOnBoarding: true,
}),
});
if (!response.ok) {
toast.error("Unable to start checkout. Please try again.");
return;
}
const data = await response.json();
if (data.subscription === true) {
toast.success("You are already on the Cap Pro plan");
return;
}
await handleSubmit(e);
if (data.url) {
window.location.href = data.url;
}
} catch (error) {
console.error("Plan checkout error:", error);
toast.error("Something went wrong. Please try again.");
}
};
const planCheckout = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
try {
const planId = getProPlanId(isAnnually ? "yearly" : "monthly");
const response = await fetch(`/api/settings/billing/subscribe`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
priceId: planId,
quantity: users,
isOnBoarding: true,
}),
});
if (!response.ok) {
toast.error("Unable to start checkout. Please try again.");
return;
}
const data = await response.json();
if (data.subscription === true) {
toast.success("You are already on the Cap Pro plan");
return;
}
try {
await handleSubmit(e);
} catch {
// Error already handled by inviteTeamMutation.onError
return;
}
if (data.url) {
window.location.href = data.url;
}
} catch (error) {
console.error("Plan checkout error:", error);
toast.error("Something went wrong. Please try again.");
}
};
🤖 Prompt for AI Agents
In apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx around lines
40-95, avoid showing two toasts when inviteTeamMutation fails by handling the
mutation error locally inside planCheckout: replace the await handleSubmit(e)
call with a guarded call to the mutation (e.g. call
inviteTeamMutation.mutateAsync() inside its own try/catch), and if it throws,
swallow the error and return early so the outer catch block does not also show a
generic toast; keep the mutation's onError toast intact so only that message is
shown on mutation failures.

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

♻️ Duplicate comments (4)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1)

95-100: Avoid duplicate error toasts by guarding handleSubmit.

If inviteTeamMutation.mutateAsync throws, both its onError and this catch will toast. Guard and return early.

-      await handleSubmit(e, false);
+      try {
+        await handleSubmit(e, false);
+      } catch {
+        // Error already surfaced by inviteTeamMutation.onError
+        return;
+      }
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

10-10: Revoke object URL and avoid using it inline; prevent memory leak and blob src issues.

Create a previewUrl with cleanup; use it in Image with unoptimized to support blob URLs.

-import { startTransition, useRef, useState } from "react";
+import { startTransition, useEffect, useRef, useState } from "react";
@@
   const [selectedFile, setSelectedFile] = useState<File | null>(null);
+  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
@@
+  useEffect(() => {
+    if (!selectedFile) {
+      setPreviewUrl(null);
+      return;
+    }
+    const url = URL.createObjectURL(selectedFile);
+    setPreviewUrl(url);
+    return () => URL.revokeObjectURL(url);
+  }, [selectedFile]);
@@
-                {selectedFile ? (
-                  <Image
-                    src={URL.createObjectURL(selectedFile)}
-                    alt="Selected File"
-                    width={56}
-                    className="object-cover rounded-full size-14"
-                    height={56}
-                  />
-                ) : (
+                {previewUrl ? (
+                  <Image
+                    src={previewUrl}
+                    alt="Selected File"
+                    width={56}
+                    height={56}
+                    unoptimized
+                    className="object-cover rounded-full size-14"
+                  />
+                ) : (

Also applies to: 20-21, 104-111

apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1)

85-90: Don’t complete the “customDomain” step before Stripe checkout.

Calling handleSubmit(e, false) in onCheckout marks the step complete before payment and cancels the in-app navigation due to the full-page redirect.

  • Remove onboarding completion from onCheckout.
  • Complete the step after successful payment (Stripe return URL handler or webhook), then route to /onboarding/invite-team.

Example adjustment here:

- <UpgradeModal
-   onCheckout={(e) => handleSubmit(e, false)}
-   onboarding={true}
-   open={showUpgradeModal}
-   onOpenChange={setShowUpgradeModal}
- />
+ <UpgradeModal
+   onboarding={true}
+   open={showUpgradeModal}
+   onOpenChange={setShowUpgradeModal}
+ />

Then, in your post‑checkout success handler/server action, call UserCompleteOnboardingStep({ step: "customDomain" }) and redirect.

apps/web/app/(org)/onboarding/[...steps]/page.tsx (1)

8-16: Fix Next.js params typing; remove Promise wrapper and use catch‑all string[].

Next App Router provides a plain params object. Also include the “download” step in the type.

-export default async function OnboardingStepPage({
-  params,
-}: {
-  params: Promise<{
-    steps: "welcome" | "organization-setup" | "custom-domain" | "invite-team";
-  }>;
-}) {
-  const user = await getCurrentUser();
-  const step = (await params).steps[0];
+export default async function OnboardingStepPage({
+  params,
+}: {
+  params: { steps: string[] };
+}) {
+  const step = params.steps?.[0];
🧹 Nitpick comments (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

122-123: Minor: remove non-standard MIME type.

image/jpg isn’t a standard MIME; image/jpeg covers it.

- accept="image/jpeg, image/jpg, image/png, image/svg+xml"
+ accept="image/jpeg, image/png, image/svg+xml"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d602f7 and f9e1710.

📒 Files selected for processing (4)
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
🧬 Code graph analysis (4)
apps/web/app/(org)/onboarding/[...steps]/page.tsx (5)
apps/web/app/(org)/onboarding/components/WelcomePage.tsx (1)
  • WelcomePage (11-79)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)
  • OrganizationSetupPage (16-156)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1)
  • CustomDomainPage (13-93)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1)
  • InviteTeamPage (19-257)
apps/web/app/(org)/onboarding/components/Download.tsx (1)
  • DownloadPage (38-110)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (4)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/components/UpgradeModal.tsx (1)
  • UpgradeModal (62-325)
apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (4)
apps/web/data/homepage-copy.ts (1)
  • homepageCopy (110-318)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (4)
packages/web-api-contract-effect/src/index.ts (1)
  • User (55-62)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
🪛 GitHub Check: Typecheck
apps/web/app/(org)/onboarding/[...steps]/page.tsx

[failure] 22-22:
Type '{ name: string | null; id: string & Brand<"UserId">; lastName: string | null; email: string; emailVerified: Date | null; image: string | null; stripeCustomerId: string | null; ... 12 more ...; defaultOrgId: (string & Brand<...>) | null; } | null' is not assignable to type 'User'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

31-61: Verify RPC transport for Uint8Array payload.

Ensure organizationIcon.data: Uint8Array serializes correctly over your RPC (JSON often needs base64/number arrays). If not, encode to base64 or upload via a dedicated upload endpoint and pass an id.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/web/components/UpgradeModal.tsx (1)

165-173: Remove duplicate subscription check.

The check for data.subscription === true is duplicated. Lines 165-168 and 170-173 perform identical logic (show toast and close modal).

Apply this diff to remove the duplicate:

 			if (data.subscription === true) {
 				toast.success("You are already on the Cap Pro plan");
 				onOpenChange(false);
+				return;
 			}
 
-			if (data.subscription === true) {
-				toast.success("You are already on the Cap Pro plan");
-				onOpenChange(false);
-			}
-
 			await onCheckout?.();

Note: I've also added an early return to prevent further execution after closing the modal.

♻️ Duplicate comments (1)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1)

80-85: Defer step completion until after payment succeeds.

This is the same concern raised in the previous review: the onCheckout callback marks the onboarding step complete before the user is redirected to Stripe for payment. If the user abandons the checkout or payment fails, the onboarding state will be inconsistent (marked complete without actual Pro subscription).

The step should be marked complete in the Stripe success webhook or return-URL handler after payment confirmation, not in the pre-checkout callback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63d06c2 and 6facdf6.

📒 Files selected for processing (3)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1 hunks)
  • apps/web/components/UpgradeModal.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/components/UpgradeModal.tsx
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (4)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/components/UpgradeModal.tsx (1)
  • UpgradeModal (328-329)
🪛 GitHub Check: Typecheck
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx

[failure] 81-81:
Type '() => Promise<Exit<boolean, RpcClientError | InternalError | UnauthenticatedError>>' is not assignable to type '() => Promise'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/web/components/UpgradeModal.tsx (3)

28-33: LGTM! Props are well-typed.

The new optional props onboarding and onCheckout are appropriately typed and maintain backward compatibility with existing usage.


175-175: Verify onCheckout callback flow.

The onCheckout callback is invoked after subscription checks but before redirecting to Stripe. When a user already has an active subscription (lines 165-168), the callback is not invoked.

Confirm this is the intended behavior—should onCheckout be called to track onboarding completion even when the user is already subscribed?


141-141: LGTM! Mutation refactor is well-implemented.

The rename from planCheckout to proCheckoutMutation improves clarity. All references are updated consistently, the button handler properly prevents default behavior and triggers the mutation, and the isOnBoarding flag is correctly passed in the request payload.

Also applies to: 150-154, 276-289

apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1)

17-39: LGTM: Mutation follows Effect-TS and coding guidelines correctly.

The useEffectMutation usage correctly follows the coding guidelines for client-side Effect execution. The RPC call through withRpc and the Effect.gen pattern are appropriate.

The redirect parameter allows conditional navigation while always completing the step, which enables the upgrade flow (complete step without navigating, then redirect to Stripe) versus the skip flow (complete step and navigate to next page).

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

🧹 Nitpick comments (1)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1)

75-75: Consider awaiting handleSubmit for consistency.

The onClick handler doesn't await handleSubmit(), while the onCheckout handler (lines 80-82) does. Though the mutation's onError handles errors in both cases, making this handler async and awaiting would align with the pattern on line 80-82.

Apply this diff for consistency:

-				onClick={() => handleSubmit()}
+				onClick={async () => await handleSubmit()}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6facdf6 and a6f3843.

📒 Files selected for processing (1)
  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (4)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
apps/web/components/UpgradeModal.tsx (1)
  • UpgradeModal (328-329)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/web/app/(org)/onboarding/components/CustomDomainPage.tsx (4)

1-11: LGTM! Imports and directives follow guidelines.

The "use client" directive is appropriate for this interactive component, and all imports are necessary and correctly organized.


17-39: LGTM! Mutation properly configured per Effect guidelines.

The useEffectMutation setup correctly:

  • Uses Effect.gen with yield* for the RPC call
  • Calls withRpc per coding guidelines
  • Handles navigation in onSuccess with startTransition
  • Shows error toast in onError callback

The mutationFn returns the redirect boolean which is then consumed by onSuccess to conditionally navigate. Past type issues flagged in reviews have been resolved.


41-42: LGTM! handleSubmit properly awaits mutateAsync.

The async arrow function correctly awaits the mutation and provides a clean interface with a sensible default (redirect=true).


80-82: LGTM! Async wrapper correctly satisfies type requirements.

The async arrow function properly awaits handleSubmit() and returns Promise to match the onCheckout prop type signature, avoiding the type mismatch flagged in previous reviews.

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 (2)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (2)

89-89: Typo: apostrophe needed.

"Lets" should be "Let's" for correct grammar.

- description="Lets get your dashboard setup with your organization"
+ description="Let's get your dashboard setup with your organization"

108-115: Revoke object URL to prevent memory leak.

URL.createObjectURL() creates a blob URL that persists until explicitly revoked, causing memory leaks when the component unmounts or a new file is selected.

Add cleanup in a useEffect:

+import { useEffect } from "react";
+
 export function OrganizationSetupPage({
 	user,
 }: {
 	user: typeof users.$inferSelect | null;
 }) {
 	const [organizationName, setOrganizationName] = useState(
 		user ? `${user.name}'s organization` : "",
 	);
 	const [selectedFile, setSelectedFile] = useState<File | null>(null);
+	const [previewUrl, setPreviewUrl] = useState<string | null>(null);
 	const fileInputRef = useRef<HTMLInputElement>(null);
 	const router = useRouter();
+
+	useEffect(() => {
+		if (selectedFile) {
+			const url = URL.createObjectURL(selectedFile);
+			setPreviewUrl(url);
+			return () => URL.revokeObjectURL(url);
+		}
+		setPreviewUrl(null);
+	}, [selectedFile]);

Then use previewUrl in the Image component:

-	src={URL.createObjectURL(selectedFile)}
+	src={previewUrl!}
🧹 Nitpick comments (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

35-76: Consider adding client-side file validation for better UX.

While server-side validation is present, adding client-side checks for file size and dimensions would provide immediate feedback to users before attempting the upload.

Example validation in handleFileChange:

 const handleFileChange = () => {
 	const file = fileInputRef.current?.files?.[0];
 	if (file) {
+		// Validate file size (e.g., max 5MB)
+		if (file.size > 5 * 1024 * 1024) {
+			toast.error("File size must be less than 5MB");
+			return;
+		}
+		// Optionally validate image dimensions
+		const img = new window.Image();
+		img.onload = () => {
+			if (img.width < 120 || img.height < 120) {
+				toast.error("Image must be at least 120x120 pixels");
+				setSelectedFile(null);
+			}
+		};
+		img.src = URL.createObjectURL(file);
 		setSelectedFile(file);
 	}
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6f3843 and 8e7372a.

📒 Files selected for processing (1)
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (4)
packages/database/schema.ts (1)
  • users (58-117)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

16-23: LGTM! Prop type and null handling fixed.

The component now correctly accepts typeof users.$inferSelect | null and safely handles the null case when setting the default organization name, preventing "null's organization" from appearing. This addresses the previous review feedback.

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 (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

107-114: Revoke object URL to prevent memory leak.

URL.createObjectURL() creates a URL that persists in memory until explicitly revoked. Without cleanup, this causes a memory leak when the component unmounts or when a new file is selected.

Apply this diff to fix the memory leak:

 export function OrganizationSetupPage({
 	firstName,
 }: {
 	firstName: string | null | undefined;
 }) {
 	const [organizationName, setOrganizationName] = useState(
 		firstName ? `${firstName}'s organization` : "",
 	);
 	const [selectedFile, setSelectedFile] = useState<File | null>(null);
+	const [previewUrl, setPreviewUrl] = useState<string | null>(null);
 	const fileInputRef = useRef<HTMLInputElement>(null);
 	const router = useRouter();

Then add a useEffect to manage the object URL lifecycle:

import { startTransition, useEffect, useRef, useState } from "react";

// ... inside component ...

useEffect(() => {
	if (selectedFile) {
		const url = URL.createObjectURL(selectedFile);
		setPreviewUrl(url);
		return () => URL.revokeObjectURL(url);
	}
	setPreviewUrl(null);
}, [selectedFile]);

Finally, use previewUrl in the Image component:

 {selectedFile ? (
 	<Image
-		src={URL.createObjectURL(selectedFile)}
+		src={previewUrl!}
 		alt="Selected File"
 		width={56}
 		className="object-cover rounded-full size-14"
 		height={56}
 	/>
🧹 Nitpick comments (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)

122-128: Consider adding file size validation.

The file input restricts image types but doesn't validate file size. Large images could cause upload failures or performance issues.

Add size validation in handleFileChange:

 const handleFileChange = () => {
 	const file = fileInputRef.current?.files?.[0];
 	if (file) {
+		const maxSizeInBytes = 5 * 1024 * 1024; // 5MB
+		if (file.size > maxSizeInBytes) {
+			toast.error("File size must be less than 5MB");
+			return;
+		}
 		setSelectedFile(file);
 	}
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7372a and 16c5b6b.

📒 Files selected for processing (2)
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (3)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/components/Base.tsx (1)
  • Base (9-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (2)

15-22: LGTM! Prop type and default name fixed.

The component now correctly accepts firstName instead of User, and the default organization name safely handles null/undefined values.


34-75: LGTM! Mutation follows coding guidelines correctly.

The Effect-based mutation correctly uses useEffectMutation from EffectRuntime and withRpc for the RPC call, as required by the coding guidelines. The file-to-bytes conversion and error handling are implemented properly.

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 (9)
apps/web/app/(org)/onboarding/layout.tsx (1)

10-11: Missing authentication guard allows unauthenticated access.

If getCurrentUser() returns null, the layout still renders with completedSteps = {}. Unauthenticated users should be redirected to login.

Add after line 10:

 const user = await getCurrentUser();
+if (!user) {
+  redirect("/login");
+}
 const completedSteps = user?.onboardingSteps || {};
packages/web-backend/src/Users/UsersRpcs.ts (1)

39-43: Fix error handling: return failed effect, not a value.

catchTag currently returns an InternalError value, converting the failure into a success. This breaks the RPC error contract.

Apply:

 }).pipe(
   Effect.catchTag(
     "DatabaseError",
-    () => new InternalError({ type: "database" }),
+    () => Effect.fail(new InternalError({ type: "database" })),
   ),
 ),
apps/web/app/(org)/onboarding/[...steps]/layout.tsx (1)

9-18: Fix params typing: remove Promise wrapper.

Next.js App Router pages receive params as a plain object, not a Promise. Remove the Promise<> wrapper and the await.

Apply:

 export default async function OnboardingStepLayout({
   children,
   params,
 }: {
   children: React.ReactNode;
-  params: Promise<{ steps: string[] }>;
+  params: { steps: string[] };
 }) {
   const user = await getCurrentUser();

   if (!user) {
     redirect("/login");
   }

   const steps = user.onboardingSteps || {};
-  const currentStep = (await params).steps?.[0] ?? "welcome";
+  const currentStep = params.steps?.[0] ?? "welcome";
apps/web/app/(org)/onboarding/[...steps]/page.tsx (1)

11-15: Fix params typing for catch-all route.

For a catch-all route [...steps], params.steps is a string[], not a union of literal strings. Also, remove the Promise<> wrapper per Next.js App Router conventions.

Apply:

 export default async function OnboardingStepPage({
   params,
 }: {
-  params: Promise<{
-    steps: "welcome" | "organization-setup" | "custom-domain" | "invite-team";
-  }>;
+  params: { steps: string[] };
 }) {
-  const step = (await params).steps[0];
+  const step = params.steps[0];
apps/web/app/(org)/onboarding/components/DownloadPage.tsx (1)

4-4: Fix import path for useDetectPlatform hook.

The "hooks/*" alias is not defined in apps/web/tsconfig.json. Use the correct path alias or a relative import.

Change to:

-import { useDetectPlatform } from "hooks/useDetectPlatform";
+import { useDetectPlatform } from "@/hooks/useDetectPlatform";

And ensure "@/hooks/*": ["hooks/*"] is added to compilerOptions.paths in apps/web/tsconfig.json, or use a relative import.

packages/web-backend/src/Users/UsersOnboarding.ts (4)

35-43: Coalesce onboardingSteps before spreading.

If user.onboardingSteps is null, the spread will throw at runtime.

Apply:

 yield* db.use((db) =>
   db
     .update(Db.users)
     .set({
       onboardingSteps: {
-        ...user.onboardingSteps,
+        ...(user.onboardingSteps ?? {}),
         welcome: true,
       },

Also apply to lines 86, 150, 173, and 195.


114-121: Store S3 object key instead of expiring signed URL.

getSignedObjectUrl returns a URL that expires (typically 1 hour). Storing it in the database will break icon display after expiry. Store the object key and generate signed URLs on demand.

Consider:

 yield* bucket.putObject(fileKey, fileData, { contentType });
-const iconUrl = yield* bucket.getSignedObjectUrl(fileKey);
 
 yield* db.use((db) =>
   db
     .update(Db.organizations)
-    .set({ iconUrl })
+    .set({ iconKey: fileKey })
     .where(Dz.eq(Db.organizations.id, organizationId)),
 );

This requires a schema change to add an iconKey column (or rename iconUrliconKey). Generate signed URLs at read time when serving the icon.


85-90: Use SQL-level JSON merge to avoid lost updates.

Spreading user.onboardingSteps from line 57-62 uses a stale snapshot. Concurrent updates inside other transactions could be lost.

Apply SQL-level merge:

 await tx
   .update(Db.users)
   .set({
     activeOrganizationId: organizationId,
-    onboardingSteps: {
-      ...(user.onboardingSteps ?? {}),
-      organizationSetup: true,
-    },
+    onboardingSteps: Dz.sql`coalesce(${Db.users.onboardingSteps}, '{}'::jsonb) || '{"organizationSetup": true}'::jsonb`,
   })
   .where(Dz.eq(Db.users.id, currentUser.id));

Based on learnings.


105-106: Use Effect.fail instead of throw for consistent error handling.

Throwing an error inside an Effect generator bypasses Effect's typed error channels.

Apply:

 const fileExtension = allowedExt.get(contentType);
-if (!fileExtension)
-  throw new Error("Unsupported icon content type");
+if (!fileExtension) {
+  return yield* Effect.fail(new Error("Unsupported icon content type"));
+}
🧹 Nitpick comments (1)
packages/ui/src/components/Button.tsx (1)

32-33: Prefer transition-colors over transition-all for better performance.

The transition-all property animates all CSS properties, which can cause performance issues with layout and transform changes. Since this variant only changes text color and underline on hover, using transition-colors or a more specific transition like transition-[color,text-decoration-color] would be more performant and consistent with the base variant's approach.

Apply this diff to optimize the transition:

 				transparent:
-					"bg-transparent text-gray-10 hover:underline transition-all duration-200 hover:text-gray-12",
+					"bg-transparent text-gray-10 hover:underline transition-colors duration-200 hover:text-gray-12",

Note: The underline styling creates a link-like appearance for this button variant, which may be intentional for secondary actions in the onboarding flow.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15a0935 and a2a136c.

📒 Files selected for processing (10)
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/Bottom.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx (1 hunks)
  • apps/web/app/(org)/onboarding/layout.tsx (1 hunks)
  • packages/ui/src/components/Button.tsx (1 hunks)
  • packages/web-backend/src/Users/UsersOnboarding.ts (1 hunks)
  • packages/web-backend/src/Users/UsersRpcs.ts (1 hunks)
  • packages/web-domain/src/User.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(org)/onboarding/components/InviteTeamPage.tsx
  • packages/web-domain/src/User.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/(org)/onboarding/components/Bottom.tsx
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • packages/web-backend/src/Users/UsersOnboarding.ts
  • apps/web/app/(org)/onboarding/layout.tsx
  • packages/web-backend/src/Users/UsersRpcs.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx
  • packages/ui/src/components/Button.tsx
  • apps/web/app/(org)/onboarding/components/Bottom.tsx
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • packages/web-backend/src/Users/UsersOnboarding.ts
  • apps/web/app/(org)/onboarding/layout.tsx
  • packages/web-backend/src/Users/UsersRpcs.ts
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx
  • apps/web/app/(org)/onboarding/components/Bottom.tsx
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and fetching in the web app
Mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData
Run server-side effects via the ManagedRuntime from apps/web/lib/server.ts using EffectRuntime.runPromise/runPromiseExit; do not create runtimes ad hoc
Client code should use helpers from apps/web/lib/EffectRuntime.ts (useEffectQuery, useEffectMutation, useRpcClient); never call ManagedRuntime.make inside components

Files:

  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx
  • apps/web/app/(org)/onboarding/components/Bottom.tsx
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
apps/web/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Server components needing Effect services must call EffectRuntime.runPromise(effect.pipe(provideOptionalAuth))

Files:

  • apps/web/app/(org)/onboarding/components/DownloadPage.tsx
  • apps/web/app/(org)/onboarding/components/Bottom.tsx
  • apps/web/app/(org)/onboarding/[...steps]/layout.tsx
  • apps/web/app/(org)/onboarding/[...steps]/page.tsx
  • apps/web/app/(org)/onboarding/layout.tsx
packages/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Component files in packages/ui should use PascalCase naming if they define React/Solid components.

Files:

  • packages/ui/src/components/Button.tsx
🧬 Code graph analysis (6)
apps/web/app/(org)/onboarding/components/DownloadPage.tsx (5)
apps/web/hooks/useDetectPlatform.ts (1)
  • useDetectPlatform (8-111)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
packages/ui/src/components/icons/LogoBadge.tsx (1)
  • LogoBadge (1-28)
apps/web/utils/platform.tsx (3)
  • getDownloadUrl (3-15)
  • getPlatformIcon (33-49)
  • getDownloadButtonText (17-31)
apps/web/app/(org)/onboarding/components/Bottom.tsx (2)
apps/web/lib/EffectRuntime.ts (1)
  • useEffectMutation (23-23)
apps/web/lib/Rpcs.ts (1)
  • withRpc (16-17)
apps/web/app/(org)/onboarding/[...steps]/page.tsx (1)
apps/web/app/(org)/onboarding/components/OrganizationSetupPage.tsx (1)
  • OrganizationSetupPage (15-159)
packages/web-backend/src/Users/UsersOnboarding.ts (5)
packages/database/index.ts (1)
  • db (18-25)
packages/database/schema.ts (1)
  • s3Buckets (433-443)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (8-15)
packages/web-domain/src/Organisation.ts (1)
  • Organisation (8-11)
packages/database/helpers.ts (1)
  • nanoId (6-9)
apps/web/app/(org)/onboarding/layout.tsx (2)
apps/web/app/(org)/onboarding/components/Stepper.tsx (1)
  • Stepper (9-112)
apps/web/app/(org)/onboarding/components/Bottom.tsx (1)
  • Bottom (11-55)
packages/web-backend/src/Users/UsersRpcs.ts (2)
packages/web-backend/src/Users/UsersOnboarding.ts (1)
  • UsersOnboarding (10-225)
packages/web-domain/src/Errors.ts (1)
  • InternalError (3-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/web/app/(org)/onboarding/components/Bottom.tsx (1)

11-55: LGTM!

The component correctly uses useEffectMutation per coding guidelines, handles mutation states appropriately, and provides proper user feedback. The navigation flow with startTransition and router.refresh() is correct.

apps/web/app/(org)/onboarding/components/DownloadPage.tsx (1)

44-48: Verify mutation on mount doesn't cause issues with StrictMode.

Calling onboardingRequest.mutate() in useEffect with an empty dependency array marks the step complete on mount. In React StrictMode (dev), effects run twice, which could trigger duplicate mutations. Consider whether this is acceptable or if you need additional guards.

@ameer2468 ameer2468 merged commit 7c12ca7 into main Oct 15, 2025
16 checks passed
@ameer2468 ameer2468 deleted the onboarding branch October 15, 2025 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants