Skip to content

319 payment redirect from landing#320

Merged
InfinityBowman merged 6 commits into
mainfrom
319-payment-redirect-from-landing
Jan 20, 2026
Merged

319 payment redirect from landing#320
InfinityBowman merged 6 commits into
mainfrom
319-payment-redirect-from-landing

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Jan 20, 2026

Summary by CodeRabbit

  • New Features

    • Select billing interval via signup/checkout URLs; centralized pending-plan capture and redirect utilities; exported eligible-plan list; centralized OAuth error hook with friendly toasts and URL cleanup; billing API calls accept optional options for consistent toast behavior.
  • Bug Fixes

    • Prevent duplicate plan signups by validating current plan before checkout; more consistent redirect/toast behavior and error handling for trial/checkout flows; additional user-facing OAuth/account-linking error messages.
  • Chores

    • Removed multiple mock preview components/routes; adjusted landing build script order.

✏️ Tip: You can customize this high-level summary in your review settings.

@InfinityBowman InfinityBowman linked an issue Jan 20, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Capture and persist signup plan/interval params, centralize pending-plan redirect handling (trial/checkout/single-project), extend billing API signatures with options, add worker-side pre-check preventing re-subscribe to same tier, integrate OAuth error handling, and remove several large mock UI components.

Changes

Cohort / File(s) Summary
Landing & Pricing Helpers
packages/landing/src/lib/config.js, packages/landing/src/routes/pricing.jsx
urls.signUp signature changed to signUp(plan, interval) and now adds an optional interval query param; pricing page passes billingInterval() for subscribe CTAs.
Plans Catalog Export
packages/shared/src/plans/catalog.ts, packages/shared/src/plans/index.ts
Added exported CHECKOUT_ELIGIBLE_TIERS derived from billing catalog to enumerate plans eligible for checkout.
Billing API wrapper
packages/web/src/api/billing.js
Billing functions accept an optional options object (e.g., createCheckoutSession, redirectToCheckout, startTrial) and redirect flows default showToast: false.
Plan Redirect Utilities
packages/web/src/lib/plan-redirect-utils.js
New module exposing VALID_INTERVALS, DEFAULT_INTERVAL, STORAGE_KEYS, BILLING_MESSAGES, validateInterval, capturePlanParams, getPendingPlan, hasPendingPlan, clearPendingPlan, handlePendingPlanRedirect to orchestrate pending-plan processing and error handling.
Auth integration
packages/web/src/components/auth/AuthLayout.jsx, packages/web/src/components/auth/SignUp.jsx, packages/web/src/components/auth/CompleteProfile.jsx
Capture plan params on signup mount; after auth/profile, check hasPendingPlan() and redirect to /settings/plans when present; CompleteProfile invokes handlePendingPlanRedirect and surfaces billing errors via toast/navigation.
Plans Settings redirect flow
packages/web/src/components/settings/pages/PlansSettings.jsx
Adds page states ('checking','redirecting','error','ready') and processPendingPlan calling handlePendingPlanRedirect; renders loading/error/ready states with retry/dismiss handlers.
Worker-side checkout pre-check
packages/workers/src/routes/billing/checkout.ts
Added pre-validation via resolveOrgAccess to return INVALID_INPUT/already_on_plan if requesting the same subscription tier as the org's current subscription.
OAuth / account-linking errors
packages/web/src/api/better-auth-store.js, packages/web/src/components/auth/SignIn.jsx, packages/web/src/lib/account-linking-errors.js, packages/web/src/primitives/useOAuthError.js
Added errorCallbackURL support for OAuth flows; new useOAuthError hook to parse/surface OAuth errors from URL params; extended account-linking error codes/messages.
Mock components removed/trimmed
packages/web/src/components/mocks/* (MockIndex.jsx, MockRoutes.jsx, ProjectViewDashboard.jsx, ProjectViewEditorial.jsx, ProjectViewKanban.jsx, ProjectWizardMock.jsx)
Removed multiple large mock components and routes; verify references/tests relying on these mocks.
Build script ordering
packages/landing/package.json
Reordered build:prod script to build @corates/shared before the landing package, then @corates/web.
sequenceDiagram
    participant User
    participant SignUp as SignUp Component
    participant Auth as Auth System
    participant Settings as PlansSettings
    participant Utils as plan-redirect-utils
    participant API as Billing API
    participant Worker as Checkout Worker

    User->>SignUp: GET /signup?plan=PLAN&interval=INT
    SignUp->>Utils: capturePlanParams(urlParams)
    Utils->>Utils: validate & store pending plan

    User->>Auth: complete auth/profile
    Auth->>Utils: hasPendingPlan()
    Utils-->>Auth: true
    Auth->>Settings: redirect /settings/plans

    Settings->>Utils: handlePendingPlanRedirect({ navigate, refetch })
    Utils->>Utils: getPendingPlan()
    alt trial
        Utils->>API: startTrial(options)
        API-->>Utils: success/failure
    else single_project
        Utils->>API: redirectToSingleProjectCheckout(options)
        API-->>Utils: redirect
    else subscription
        Utils->>API: redirectToCheckout(tier, interval, options)
        API->>Worker: POST /checkout (tier, interval)
        Worker->>Worker: resolveOrgAccess()
        alt already on same tier
            Worker-->>API: error INVALID_INPUT (already_on_plan)
            API-->>Utils: error
        else proceed
            Worker-->>API: session/redirect
            API-->>Utils: session/redirect
        end
    end
    Utils->>Utils: clearPendingPlan()
    Utils-->>Settings: { handled, error }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 directly relates to the main objective of the PR: implementing a payment/billing redirect flow initiated from the landing page. The changes span multiple packages to capture plan parameters from the landing URL, store them, and redirect users through checkout flows after signup/authentication.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d471dc and 952d4a0.

📒 Files selected for processing (1)
  • packages/landing/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never use emojis or unicode symbols anywhere in code, comments, documentation, plan files, commit messages, or examples

Files:

  • packages/landing/package.json
⏰ 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: Workers Builds: corates
🔇 Additional comments (1)
packages/landing/package.json (1)

11-11: LGTM!

The corrected build order ensures @corates/shared is built first before landing and web, which properly respects the dependency chain and guarantees shared exports are available during subsequent builds.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jan 20, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
corates 952d4a0 Commit Preview URL Jan 20 2026, 05:31 AM

Copy link
Copy Markdown

@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

🤖 Fix all issues with AI agents
In `@packages/web/src/components/settings/pages/PlansSettings.jsx`:
- Around line 115-123: handleDismissError currently sets the page state to
'ready' but leaves the pending plan in storage so the failed redirect can
re-trigger later; update handleDismissError to also clear the pending plan
(e.g., call an existing clearPendingPlan/removePendingPlan helper or implement
one that removes the pending plan key from storage) so that when the user
dismisses the error the pending plan is removed and processPendingPlan won't run
on subsequent visits; reference handleDismissError and processPendingPlan and
add the clear call alongside setPageState('ready').

In `@packages/workers/src/routes/billing/checkout.ts`:
- Around line 279-291: The current guard uses resolveOrgAccess and blocks
checkout whenever currentBilling.effectivePlanId === tier, which wrongly
prevents interval-only changes and upgrades from grant/trial to paid; update the
check so it only short-circuits when the org has an active paid subscription
with the same plan AND the billing interval also matches. Specifically, in the
checkout flow around resolveOrgAccess/currentBilling, refine the condition to
verify the subscription is active (e.g., status or isActive flag on
currentBilling) and compare both currentBilling.effectivePlanId === tier AND
currentBilling.interval === requestedInterval (or similar request param) before
returning the "already_on_plan" error, but allow proceed when subscription is a
grant/trial or when the interval differs. Ensure you reference currentBilling,
effectivePlanId, tier, and the interval/requestedInterval variable in your
condition.
🧹 Nitpick comments (2)
packages/web/src/components/mocks/MockIndex.jsx (1)

115-119: Consider removing the empty "Other Mocks" section.

The section header and empty grid div remain after the mock cards were removed. If there are no "Other Mocks" to display, consider removing this section entirely to avoid dead UI code.

Suggested cleanup
           </A>
          </div>
        </div>
-
-        {/* Other Mocks */}
-        <div>
-          <h2 class='mb-3 text-lg font-semibold text-gray-700'>Other Mocks</h2>
-          <div class='grid gap-4'></div>
-        </div>
       </div>
     </div>
   );
packages/web/src/lib/plan-redirect-utils.js (1)

189-198: Potential race condition: clearPendingPlan() may not execute before page unloads.

For checkout redirects, clearPendingPlan() is called after redirectToSingleProjectCheckout() / redirectToCheckout(), which set window.location.href. While localStorage writes are synchronous and typically fast, there's a race with the page unload. If the clear doesn't complete, the user could be redirected again on return.

Consider clearing before the redirect:

Suggested fix
     if (pendingPlan === 'single_project') {
+      clearPendingPlan();
       await redirectToSingleProjectCheckout();
-      clearPendingPlan();
       return { handled: true, error: null };
     }

     // Subscription plans (starter_team, team, unlimited_team)
+    clearPendingPlan();
     await redirectToCheckout(pendingPlan, pendingInterval);
-    clearPendingPlan();
     return { handled: true, error: null };
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between caca775 and dd11fa4.

📒 Files selected for processing (17)
  • packages/landing/src/lib/config.js
  • packages/landing/src/routes/pricing.jsx
  • packages/shared/src/plans/catalog.ts
  • packages/shared/src/plans/index.ts
  • packages/web/src/api/billing.js
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/components/mocks/MockRoutes.jsx
  • packages/web/src/components/mocks/ProjectViewDashboard.jsx
  • packages/web/src/components/mocks/ProjectViewEditorial.jsx
  • packages/web/src/components/mocks/ProjectViewKanban.jsx
  • packages/web/src/components/mocks/ProjectWizardMock.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/workers/src/routes/billing/checkout.ts
💤 Files with no reviewable changes (5)
  • packages/web/src/components/mocks/ProjectViewEditorial.jsx
  • packages/web/src/components/mocks/ProjectWizardMock.jsx
  • packages/web/src/components/mocks/ProjectViewKanban.jsx
  • packages/web/src/components/mocks/ProjectViewDashboard.jsx
  • packages/web/src/components/mocks/MockRoutes.jsx
🧰 Additional context used
📓 Path-based instructions (20)
packages/web/src/**/*.{js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/form-state.mdc)

packages/web/src/**/*.{js,jsx}: Save form state to IndexedDB before initiating OAuth redirects (Google Drive, ORCID) using saveFormState() with form type ('createProject' or 'addStudies') and serializable state only
Only save serializable data to IndexedDB—exclude File objects, ArrayBuffers, functions, and other non-serializable objects from form state persistence
Restore form state after OAuth redirects by checking URL restore params with getRestoreParamsFromUrl(), retrieving saved state with getFormState(), restoring to form, clearing saved state with clearFormState(), and clearing URL params with clearRestoreParamsFromUrl()
For temporary File object storage during OAuth flows (e.g., pending PDFs), use projectStore.setPendingProjectData() instead of IndexedDB, as File objects cannot be serialized
Add restore parameters to the URL after OAuth redirects in the format '?restore=&projectId=' to signal form state restoration on mount
Clear URL restore parameters after form state restoration by calling clearRestoreParamsFromUrl() to prevent stale restoration attempts on subsequent navigation
Form state persistence library functions (saveFormState, getFormState, clearFormState, getRestoreParamsFromUrl, clearRestoreParamsFromUrl) are implemented in packages/web/src/lib/formStatePersistence.js and should be imported from @/lib/formStatePersistence.js

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/pdf-handling.mdc)

packages/web/src/**/*.{js,jsx,ts,tsx}: Always use uploadPdf from @api/pdf-api.js for uploading PDF files. Pass object with projectId, studyId, tag ('primary' or 'supplementary'), and fileName
Validate PDF files on frontend by checking file.type includes 'pdf' and file.size is within MAX_PDF_SIZE limit (full validation is done on backend)
Use cachePdf and getCachedPdf from @primitives/pdfCache.js for caching PDF data in IndexedDB
Implement PDF caching strategy: check cache first, download from server if not cached, then cache the downloaded PDF
Store only PDF metadata in Yjs (id, name, size, tag, uploadedAt, uploadedBy), never store PDF binary data in Yjs as it is too large
Use importFromGoogleDrive from @api/google-drive.js for importing PDFs from Google Drive. Pass fileId, projectId, studyId, and tag
Save form state using saveFormState from @/lib/formStatePersistence.js before triggering OAuth redirects for Google Drive
Use addPdfToStudy operation from useProject hook to add PDFs to a study, passing id, name, size, tag, uploadedAt, and uploadedBy
Use removePdfFromStudy operation from useProject hook to remove PDFs from a study
Use pdfPreviewStore from @/stores/pdfPreviewStore.js for managing PDF preview state (openPreview, closePreview, getPreview methods)
Use downloadPdf from @api/pdf-api.js to download PDFs from server, then cache the result using cachePdf
Always cache PDFs after download and check cache before downloading to avoid redundant downloads
Use PDF operations from useProject hook instead of bypassing through direct API calls

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
packages/web/src/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/pdf-handling.mdc)

Use PdfViewer component from @/components/checklist-ui/pdf/PdfViewer.jsx for displaying PDFs, passing pdfData as ArrayBuffer, fileName, readOnly, and onPageChange

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
{packages/web/**,packages/landing/**}/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/solidjs.mdc)

{packages/web/**,packages/landing/**}/**/*.{jsx,tsx}: NEVER destructure props in SolidJS components - access props directly or wrap in functions to maintain reactivity
Use external stores in packages/web/src/stores/ for shared/cross-feature state instead of prop-drilling
Keep SolidJS components lean and focused on rendering - move business logic to stores, primitives, or utilities
Create reusable logic in primitives (hooks) in packages/web/src/primitives/ and import them into components
Use Solid's Show component for conditional rendering instead of ternary operators
Use Solid's For component for rendering lists instead of Array.map()
Use the children helper when manipulating props.children in SolidJS components

Use Show and For components for conditional and list rendering in SolidJS

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
{packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/solidjs.mdc)

{packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx}: Use createSignal for simple reactive values in SolidJS components
Use createStore for complex objects and arrays that need granular reactivity in SolidJS
Use createMemo for computed/derived values that depend on reactive state in SolidJS
Always clean up SolidJS effects that create subscriptions or timers using onCleanup
Import stores directly in components and use store read/write action pattern - read from store, write via actions store
Prefer derived state with createMemo or signals over effects whenever possible
Use local createSignal or createStore for local component state; use external stores for shared/cross-feature state

{packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx}: NEVER destructure props in SolidJS components - it breaks reactivity. Access props directly (e.g., props.name) or wrap in a function (e.g., () => props.name)
Store imports should access store data directly from external stores (packages/web/src/stores/) rather than receiving store data via props
Use createSignal for simple, reactive values in SolidJS
Use createStore for complex objects and nested state in SolidJS, with nested updates using setState pattern (e.g., setState('items', items => [...items, newItem]))
Use createMemo for derived/computed values in SolidJS to maintain reactivity
Use local createSignal or createStore for local component state
Components should receive at most 1-5 props for local configuration only
Move business logic to stores, utilities, or SolidJS primitives rather than keeping it in component code

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/landing/src/lib/config.js
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)

**/*.{js,jsx,ts,tsx}: Use the useProject hook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/landing/src/lib/config.js
  • packages/workers/src/routes/billing/checkout.ts
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/catalog.ts
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
packages/web/src/**

📄 CodeRabbit inference engine (.cursor/rules/organizations.mdc)

packages/web/src/**: Use human-readable slug in frontend URLs: /orgs/:orgSlug/...
Use orgId (UUID) for API calls, not orgSlug, when making fetch requests to backend endpoints

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
packages/web/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

packages/web/**/*.{js,ts,jsx,tsx}: Use apiFetch for all frontend API calls instead of raw fetch to automatically handle JSON parsing, errors, and toast notifications
Use handleFetchError for wrapping legacy raw fetch calls in frontend code (legacy pattern, prefer apiFetch)
Use error utility functions like isErrorCode from error-utils to check specific error types instead of direct string comparisons

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
packages/web/**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

Use createFormErrorSignals for form error handling in frontend forms to manage field-level and global error states

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
packages/{web,workers}/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

Never throw string literals; always throw Error objects or return domain errors from API routes

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/workers/src/routes/billing/checkout.ts
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
{packages/web/src/stores/**/*.{js,jsx,ts,tsx},packages/web/src/**/*.{js,jsx,ts,tsx}}

📄 CodeRabbit inference engine (.github/instructions/solidjs.instructions.md)

Shared or cross-feature state should use external stores located in packages/web/src/stores/

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)

**/*.{js,ts,jsx,tsx}: Prefer modern ES6+ syntax and features
Comments should explain why something is being done, not what the code is doing
Reserve comments for explaining intent, context, workarounds, assumptions, edge cases, or limitations
Use TODO(agent): prefix for incomplete work or flagged items with brief description and relevant doc references

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/landing/src/lib/config.js
  • packages/workers/src/routes/billing/checkout.ts
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/catalog.ts
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never use emojis or unicode symbols anywhere in code, comments, documentation, plan files, commit messages, or examples

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/landing/src/lib/config.js
  • packages/workers/src/routes/billing/checkout.ts
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/catalog.ts
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
packages/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/*.{ts,tsx,js,jsx}: Prefer modern ES6+ syntax and features
Prefer config files over hardcoding values
Keep files small, focused, and modular - extract large files into sub-modules or separate utilities
Each file should handle one coherent responsibility
Comments should explain WHY something is being done or provide context, not repeat what the code is saying
Do NOT narrate what code is doing, don't duplicate function/variable names in comments, and don't leave stale comments that contradict the code
Use the Agent TODO convention // TODO(agent): Brief description for incomplete work, flagging items for future attention, known limitations, and documentation section references

Use TODO(agent) convention with brief description and relevant doc section references for incomplete work or flagged issues

Files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/landing/src/lib/config.js
  • packages/workers/src/routes/billing/checkout.ts
  • packages/web/src/components/auth/CompleteProfile.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/api/billing.js
  • packages/web/src/components/mocks/MockIndex.jsx
  • packages/shared/src/plans/index.ts
  • packages/shared/src/plans/catalog.ts
  • packages/web/src/lib/plan-redirect-utils.js
  • packages/web/src/components/settings/pages/PlansSettings.jsx
  • packages/landing/src/routes/pricing.jsx
packages/workers/src/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)

ALWAYS use db.batch() for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together

Files:

  • packages/workers/src/routes/billing/checkout.ts
packages/workers/src/routes/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)

packages/workers/src/routes/**/*.{js,ts,jsx,tsx}: ALWAYS validate request bodies using validateRequest middleware from the validation config rather than manual validation
Use validateQueryParams middleware for validating query parameters in routes

Files:

  • packages/workers/src/routes/billing/checkout.ts
packages/workers/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)

packages/workers/**/*.{js,ts}: Always use createDomainError from @corates/shared for backend error handling instead of manually creating error objects
Use predefined error constants from @corates/shared (PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes

Files:

  • packages/workers/src/routes/billing/checkout.ts
packages/workers/**/routes/**/*.{js,ts}

📄 CodeRabbit inference engine (.github/instructions/api-routes.instructions.md)

packages/workers/**/routes/**/*.{js,ts}: Always use validateRequest middleware for request body validation in API routes, imported from '../config/validation.js'
Create DB client from environment using createDb(c.env.DB) instead of creating DB connections directly
Use db.batch() for atomic database operations when performing multiple related inserts or updates
Use error helpers from @corates/shared (notFound, forbidden, badRequest, etc.) for error handling in API routes

Files:

  • packages/workers/src/routes/billing/checkout.ts
packages/workers/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)

packages/workers/src/**/*.{ts,tsx}: Use Zod for schema and input validation on backend services
Use Drizzle ORM for all database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped and use /api/orgs/:orgId/projects/... pattern in backend
Use requireOrgMembership and requireProjectAccess middleware for authentication in org-scoped routes
Never bypass Drizzle for database access

Never bypass Drizzle for database access - use Drizzle ORM for all database operations

Files:

  • packages/workers/src/routes/billing/checkout.ts
packages/workers/src/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/workers/src/**/*.ts: Use Zod for schema and input validation in backend code
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
Never bypass Drizzle for database access

Files:

  • packages/workers/src/routes/billing/checkout.ts
🧠 Learnings (34)
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : Add restore parameters to the URL after OAuth redirects in the format '?restore=<formType>&projectId=<projectId>' to signal form state restoration on mount

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Save form state using `saveFormState` from `@/lib/formStatePersistence.js` before triggering OAuth redirects for Google Drive

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : Restore form state after OAuth redirects by checking URL restore params with getRestoreParamsFromUrl(), retrieving saved state with getFormState(), restoring to form, clearing saved state with clearFormState(), and clearing URL params with clearRestoreParamsFromUrl()

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2026-01-17T00:25:12.507Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/solidjs.instructions.md:0-0
Timestamp: 2026-01-17T00:25:12.507Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx} : Use local createSignal or createStore for local component state

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : Save form state to IndexedDB before initiating OAuth redirects (Google Drive, ORCID) using saveFormState() with form type ('createProject' or 'addStudies') and serializable state only

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx} : Use local createSignal or createStore for local component state; use external stores for shared/cross-feature state

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{jsx,tsx} : Create reusable logic in primitives (hooks) in packages/web/src/primitives/ and import them into components

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : Form state persistence library functions (saveFormState, getFormState, clearFormState, getRestoreParamsFromUrl, clearRestoreParamsFromUrl) are implemented in packages/web/src/lib/formStatePersistence.js and should be imported from `@/lib/formStatePersistence.js`

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/web/**/*.{jsx,tsx} : Use `createFormErrorSignals` for form error handling in frontend forms to manage field-level and global error states

Applied to files:

  • packages/web/src/components/auth/SignUp.jsx
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `getOrgContext(c)` to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T00:25:35.702Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.702Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware for authentication in org-scoped routes

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.js : Use organization-scoped route patterns with path structure `/api/orgs/:orgId/...` and leverage `requireOrgMembership`, `requireProjectAccess`, `getOrgContext`, and `getProjectContext` middleware

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T00:25:03.925Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.925Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Always use `validateRequest` middleware for request body validation in API routes, imported from '../config/validation.js'

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T00:25:35.702Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-17T00:25:35.702Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Use Drizzle ORM for all database interactions and migrations

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T16:09:36.904Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.904Z
Learning: Applies to packages/workers/src/**/*.ts : Use Drizzle ORM for ALL database interactions and migrations

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T00:25:03.925Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/api-routes.instructions.md:0-0
Timestamp: 2026-01-17T00:25:03.925Z
Learning: Applies to packages/workers/**/routes/**/*.{js,ts} : Create DB client from environment using `createDb(c.env.DB)` instead of creating DB connections directly

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Always create DB client from environment using `createDb` function in route handlers

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Always use Drizzle ORM with query builders - never use raw SQL in database operations

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/**/*.{js,ts,jsx,tsx} : ALWAYS use `db.batch()` for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-17T16:10:07.519Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-17T16:10:07.519Z
Learning: Applies to packages/workers/src/**/*.{ts,tsx} : Never bypass Drizzle for database access - use Drizzle ORM for all database operations

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes

Applied to files:

  • packages/workers/src/routes/billing/checkout.ts
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx} : Use createSignal for simple reactive values in SolidJS components

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx} : Prefer derived state with createMemo or signals over effects whenever possible

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2026-01-17T00:25:12.507Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/solidjs.instructions.md:0-0
Timestamp: 2026-01-17T00:25:12.507Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx} : Use createSignal for simple, reactive values in SolidJS

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{jsx,tsx} : Keep SolidJS components lean and focused on rendering - move business logic to stores, primitives, or utilities

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/mocks/MockIndex.jsx
📚 Learning: 2026-01-17T00:25:12.507Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/instructions/solidjs.instructions.md:0-0
Timestamp: 2026-01-17T00:25:12.507Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{jsx,tsx} : Use Show and For components for conditional and list rendering in SolidJS

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{jsx,tsx} : Use Solid's Show component for conditional rendering instead of ternary operators

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
  • packages/web/src/components/settings/pages/PlansSettings.jsx
📚 Learning: 2026-01-17T16:09:36.904Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-17T16:09:36.904Z
Learning: Applies to packages/{web,landing}/src/**/*.{ts,tsx} : Use `createMemo` for derived values in SolidJS

Applied to files:

  • packages/web/src/components/auth/AuthLayout.jsx
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Only re-mock Postmark and Stripe modules inside a test file if you need behavior different from the global mock setup in packages/workers/src/__tests__/setup.js

Applied to files:

  • packages/web/src/components/mocks/MockIndex.jsx
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use `removePdfFromStudy` operation from `useProject` hook to remove PDFs from a study

Applied to files:

  • packages/web/src/components/mocks/MockIndex.jsx
📚 Learning: 2025-12-27T03:01:06.933Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/checklist-operations.mdc:0-0
Timestamp: 2025-12-27T03:01:06.933Z
Learning: Applies to packages/web/src/components/checklist-ui/**,packages/web/src/lib/checklist-domain.js : Register and retrieve checklists using checklist-registry: getChecklistType and getChecklistComponent functions

Applied to files:

  • packages/shared/src/plans/index.ts
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/components/project-ui/**/*.{js,jsx} : Form state should include serializable metadata when handling files (name, size, type) and use the store's pendingPdfs pattern for actual File objects that persist across redirects

Applied to files:

  • packages/web/src/components/settings/pages/PlansSettings.jsx
📚 Learning: 2025-12-27T03:02:26.947Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/solidjs.mdc:0-0
Timestamp: 2025-12-27T03:02:26.947Z
Learning: Applies to {packages/web/**,packages/landing/**}/**/*.{jsx,tsx} : Use Solid's For component for rendering lists instead of Array.map()

Applied to files:

  • packages/web/src/components/settings/pages/PlansSettings.jsx
🧬 Code graph analysis (9)
packages/web/src/components/auth/SignUp.jsx (1)
packages/web/src/lib/plan-redirect-utils.js (1)
  • capturePlanParams (66-81)
packages/landing/src/lib/config.js (2)
packages/web/src/lib/plan-redirect-utils.js (1)
  • plan (89-89)
packages/workers/scripts/setup-stripe-test.mjs (1)
  • interval (330-330)
packages/workers/src/routes/billing/checkout.ts (1)
packages/workers/src/lib/billingResolver.ts (1)
  • resolveOrgAccess (72-211)
packages/web/src/components/auth/CompleteProfile.jsx (1)
packages/web/src/lib/plan-redirect-utils.js (5)
  • hasPendingPlan (105-111)
  • handlePendingPlanRedirect (169-246)
  • BILLING_MESSAGES (28-49)
  • BILLING_MESSAGES (28-49)
  • clearPendingPlan (116-123)
packages/web/src/components/auth/AuthLayout.jsx (1)
packages/web/src/lib/plan-redirect-utils.js (2)
  • capturePlanParams (66-81)
  • hasPendingPlan (105-111)
packages/web/src/api/billing.js (1)
packages/web/src/lib/apiFetch.js (1)
  • apiFetch (134-253)
packages/shared/src/plans/catalog.ts (1)
packages/shared/src/plans/index.ts (3)
  • CHECKOUT_ELIGIBLE_TIERS (32-32)
  • BillingCatalogTier (18-18)
  • getBillingPlanCatalog (32-32)
packages/web/src/lib/plan-redirect-utils.js (2)
packages/shared/src/plans/index.ts (1)
  • CHECKOUT_ELIGIBLE_TIERS (32-32)
packages/web/src/api/billing.js (3)
  • startTrial (92-95)
  • redirectToSingleProjectCheckout (72-76)
  • redirectToCheckout (43-47)
packages/landing/src/routes/pricing.jsx (1)
packages/landing/src/lib/config.js (2)
  • urls (8-19)
  • urls (8-19)
⏰ 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: Workers Builds: corates
🔇 Additional comments (25)
packages/landing/src/routes/pricing.jsx (1)

51-58: LGTM!

The updated getButtonUrl correctly passes the billing interval for subscription plans while omitting it for trial and single-project flows. The reactive signal access (billingInterval()) is properly placed within the function body.

packages/shared/src/plans/catalog.ts (1)

149-156: LGTM!

The derived constant correctly filters eligible tiers and provides a useful validation surface for the plan-redirect flow.

packages/shared/src/plans/index.ts (1)

32-32: LGTM!

The re-export correctly extends the public API surface for the plans module.

packages/landing/src/lib/config.js (1)

10-16: LGTM!

Clean implementation using URLSearchParams. The optional interval parameter maintains backwards compatibility while enabling the new billing interval flow.

packages/web/src/api/billing.js (3)

24-26: LGTM!

Good addition of the options parameter with JSDoc documentation.


43-47: LGTM!

Defaulting showToast: false for redirect flows is sensible since the page navigates away before users would see the toast. The spread pattern allows callers to override if needed.


63-65: LGTM!

Consistent options pattern across all checkout and trial functions.

Also applies to: 72-75, 92-95

packages/web/src/lib/plan-redirect-utils.js (6)

1-9: LGTM!

Clean module header with well-organized imports separating shared config, billing API, and UI utilities.


11-49: LGTM!

Well-structured constants with centralized storage keys and user-friendly toast messages. Good separation of concerns.


51-81: LGTM!

validateInterval provides safe normalization with a sensible default. capturePlanParams properly validates against CHECKOUT_ELIGIBLE_TIERS and handles localStorage errors gracefully.


83-123: LGTM!

Defensive localStorage access with try-catch wrappers. Good use of validateInterval for consistent interval normalization in getPendingPlan.


125-156: LGTM!

Error type detection functions are well-structured. Using optional chaining on startsWith (err?.code?.startsWith?.()) provides defensive handling for unexpected error shapes.


199-245: LGTM!

Error handling is well thought out:

  • Specific error types get appropriate messaging and navigation
  • Trial errors are cleared (non-retryable)
  • Checkout errors preserve pending plan for retry (transient failures)
packages/web/src/components/auth/SignUp.jsx (2)

15-15: Plan redirect helper import is aligned with usage.


53-54: Plan params captured on mount as intended.

packages/web/src/components/auth/AuthLayout.jsx (3)

4-4: Plan redirect helpers imported for new flow.


34-38: Plan params are captured before redirecting logged-in users from signup.


41-50: Pending plan routing preserves checkout intent post-login.

packages/web/src/components/auth/CompleteProfile.jsx (3)

35-40: Pending-plan helpers integrated for profile completion flow.


92-98: Completed profiles now respect pending plan redirects.


253-275: Pending plan redirect handling after profile submission is well structured.

packages/web/src/components/settings/pages/PlansSettings.jsx (4)

7-17: Imports cover the new redirect state flow.


83-107: Pending plan processing flow is clear and deterministic.


108-113: On-mount processing of pending plans is consistent with the redirect flow.


125-219: Conditional UI states are clean and readable.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread packages/web/src/components/settings/pages/PlansSettings.jsx
Comment on lines +279 to +291
// Check if user is already on this plan
const currentBilling = await resolveOrgAccess(db, orgId!);
if (currentBilling.effectivePlanId === tier) {
const error = createDomainError(
VALIDATION_ERRORS.INVALID_INPUT,
{
reason: 'already_on_plan',
currentPlan: tier,
},
`You are already subscribed to the ${tier} plan.`,
);
return c.json(error, error.statusCode as ContentfulStatusCode);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid blocking interval changes or grant-to-paid upgrades

The new guard blocks any checkout when effectivePlanId === tier, even if the org is on a grant/trial or is switching interval on the same plan. That can prevent legitimate upgrades (e.g., monthly → yearly) and conversions from grants to paid subscriptions. Please scope this check to active subscriptions and include interval matching.

Proposed fix
-    const currentBilling = await resolveOrgAccess(db, orgId!);
-    if (currentBilling.effectivePlanId === tier) {
+    const currentBilling = await resolveOrgAccess(db, orgId!);
+    const isSameActiveSubscription =
+      currentBilling.source === 'subscription' &&
+      currentBilling.effectivePlanId === tier &&
+      currentBilling.subscription?.interval === interval;
+    if (isSameActiveSubscription) {
       const error = createDomainError(
         VALIDATION_ERRORS.INVALID_INPUT,
         {
           reason: 'already_on_plan',
           currentPlan: tier,
         },
         `You are already subscribed to the ${tier} plan.`,
       );
       return c.json(error, error.statusCode as ContentfulStatusCode);
     }
🤖 Prompt for AI Agents
In `@packages/workers/src/routes/billing/checkout.ts` around lines 279 - 291, The
current guard uses resolveOrgAccess and blocks checkout whenever
currentBilling.effectivePlanId === tier, which wrongly prevents interval-only
changes and upgrades from grant/trial to paid; update the check so it only
short-circuits when the org has an active paid subscription with the same plan
AND the billing interval also matches. Specifically, in the checkout flow around
resolveOrgAccess/currentBilling, refine the condition to verify the subscription
is active (e.g., status or isActive flag on currentBilling) and compare both
currentBilling.effectivePlanId === tier AND currentBilling.interval ===
requestedInterval (or similar request param) before returning the
"already_on_plan" error, but allow proceed when subscription is a grant/trial or
when the interval differs. Ensure you reference currentBilling, effectivePlanId,
tier, and the interval/requestedInterval variable in your condition.

@InfinityBowman InfinityBowman merged commit cc6f4c6 into main Jan 20, 2026
3 checks passed
@InfinityBowman InfinityBowman deleted the 319-payment-redirect-from-landing branch January 20, 2026 05:31
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.

Payment redirect from landing

2 participants