Skip to content

New onboarding plan#17776

Merged
charlesBochet merged 6 commits intotwentyhq:mainfrom
BOHEUS:new-onboarding-plan
Feb 17, 2026
Merged

New onboarding plan#17776
charlesBochet merged 6 commits intotwentyhq:mainfrom
BOHEUS:new-onboarding-plan

Conversation

@BOHEUS
Copy link
Contributor

@BOHEUS BOHEUS commented Feb 8, 2026

Copilot AI review requested due to automatic review settings February 8, 2026 13:23
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 8, 2026

Greptile Overview

Greptile Summary

This PR adds a plan query parameter that hydrates billingCheckoutSessionState so the onboarding “Choose your plan” page can deep-link to a specific plan. The onboarding page’s footer links are also updated to switch between Pro and Enterprise by navigating to AppPath.PlanRequired with ?plan=....

Key integration point: useInitializeQueryParamState reads window.location.search and writes to Recoil, which ChooseYourPlanContent then uses to determine the current plan and which plan-switch link to render.

Confidence Score: 2/5

  • This PR has a couple of user-facing runtime issues that should be fixed before merge.
  • Score is reduced due to (1) a Recoil state update happening during render in ChooseYourPlanContent, and (2) an inconsistent BillingPlanKey enum import between query-param hydration and UI comparisons, which can break plan toggling/conditional rendering when initialized from the URL.
  • packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx; packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts

Important Files Changed

Filename Overview
packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts Adds support for a plan query param to update billingCheckoutSessionState; main concern is BillingPlanKey enum source mismatch with onboarding UI comparisons.
packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx Replaces pricing link with plan-toggle links (adds ?plan=) but currently updates Recoil state during render, which can cause render/update loops.

Sequence Diagram

sequenceDiagram
  participant User as User
  participant Page as ChooseYourPlanContent
  participant Recoil as Recoil
  participant Init as useInitializeQueryParamState
  participant URL as URLSearchParams

  User->>Page: Navigate to /onboarding (PlanRequired)
  Page->>Init: initializeQueryParamState()
  Init->>URL: Read window.location.search
  URL-->>Init: plan=pro|enterprise (optional)
  Init->>Recoil: set(billingCheckoutSessionState.plan)
  Recoil-->>Page: billingCheckoutSessionState updated
  Page->>Page: Render links based on currentPlanKey
  Page-->>User: Shows plan toggle link
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 5 to 8
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
import deepEqual from 'deep-equal';
import { BillingPlanKey } from '~/generated/graphql';

Copy link
Contributor

Choose a reason for hiding this comment

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

Mismatched BillingPlanKey enum

This hook sets BillingCheckoutSession.plan using BillingPlanKey from ~/generated/graphql (e.g. BillingPlanKey.PRO), but ChooseYourPlanContent compares currentPlanKey against BillingPlanKey from ~/generated-metadata/graphql. If these are different generated enum objects, strict equality checks like currentPlanKey === BillingPlanKey.PRO will fail, leading to wrong plan-specific UI when the plan is initialized from query params. Align the enum source used across both places (one canonical import).

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 8, 2026

Additional Comments (1)

packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx
State update during render

setVerifyEmailRedirectPath(undefined) is called during render when verifyEmailRedirectPath is defined (lines 108-110). This triggers a state update while rendering, which React/Recoil can warn about and can cause repeated renders for users who land here with that state set. This should be moved into a useEffect or otherwise handled outside the render path.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the onboarding “Choose your plan” step to let users switch between Pro and Organization (Enterprise) plans via a ?plan= query parameter, removing the previous external “Change Plan” link.

Changes:

  • Remove the “Change Plan” link to the external pricing page from the onboarding plan step.
  • Add “Pro plan” / “Organization plan” links that navigate to /plan-required?plan=....
  • Extend query-param hydration to support a plan parameter that updates billingCheckoutSessionState.plan.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx Replaces the external plan-change link with internal links that toggle the plan via ?plan=.
packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts Adds query-param handling for `plan=pro

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +268 to +273
<ClickToActionLink href={AppPath.PlanRequired.concat('?plan=enterprise')}>
<Trans>Organization plan</Trans>
</ClickToActionLink>
) : (
<ClickToActionLink href={AppPath.PlanRequired.concat('?plan=pro')}>
<Trans>Pro plan</Trans>
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The plan switch links are manually built with AppPath.PlanRequired.concat('?plan=...'). There’s already a buildAppPathWithQueryParams(AppPath.PlanRequired, {...}) helper used elsewhere for this route; using it here would improve readability/consistency and avoid subtle mistakes when more params are added later.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +67
const updatedBillingCheckoutSession: BillingCheckoutSession = {
plan: changedPlan,
interval: billingCheckoutSession.interval,
requirePaymentMethod:
billingCheckoutSession.requirePaymentMethod,
};
set(billingCheckoutSessionState, updatedBillingCheckoutSession);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The new plan query-param handler always calls set(billingCheckoutSessionState, ...) when the param is valid, even if the current session already has the requested plan. This can trigger unnecessary state updates/rerenders on every navigation where ?plan= is present; consider checking billingCheckoutSession.plan !== changedPlan (similar to the deepEqual guard used in the billingCheckoutSession handler) before calling set.

Suggested change
const updatedBillingCheckoutSession: BillingCheckoutSession = {
plan: changedPlan,
interval: billingCheckoutSession.interval,
requirePaymentMethod:
billingCheckoutSession.requirePaymentMethod,
};
set(billingCheckoutSessionState, updatedBillingCheckoutSession);
if (billingCheckoutSession.plan !== changedPlan) {
const updatedBillingCheckoutSession: BillingCheckoutSession = {
plan: changedPlan,
interval: billingCheckoutSession.interval,
requirePaymentMethod:
billingCheckoutSession.requirePaymentMethod,
};
set(
billingCheckoutSessionState,
updatedBillingCheckoutSession,
);
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts">

<violation number="1" location="packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts:50">
P2: `plan` builds the new `billingCheckoutSessionState` from a snapshot captured before earlier handlers run, so when both `billingCheckoutSession` and `plan` query params are present, the `plan` handler can overwrite the state set by `billingCheckoutSession` with stale values. Use the functional updater form of `set` to merge with the latest pending state.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 8, 2026

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:38043

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts">

<violation number="1" location="packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts:61">
P2: The `plan` handler compares against a stale Recoil snapshot. If both `billingCheckoutSession` and `plan` query params are present, the earlier handler can queue a plan update but this check uses the old snapshot and may skip the override when the initial state matches the URL plan, preventing the URL plan from taking precedence.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

);
}
},
plan: (value: string) => {
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit lost here, why do we need a dedicated entry for plan? I would expect it to be part of billingCheckoutSession handled above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

According to the issue, new plan parameter should be introduced, I assumed it should be only within planRequired page when user wants to change the plan and billingCheckoutSession should be left as is in pricing page

<span />
{currentPlanKey === BillingPlanKey.PRO ? (
<ClickToActionLink
href={AppPath.PlanRequired.concat('?plan=enterprise')}
Copy link
Member

Choose a reason for hiding this comment

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

this concatenation does not seem to be enough, we should create a link with a full billingSessionState

Image

Copy link
Contributor Author

@BOHEUS BOHEUS Feb 10, 2026

Choose a reason for hiding this comment

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

@charlesBochet just to be clear, said plan parameter shouldn't be used and instead should we use billingCheckoutSession everywhere? If so, I'll quickly add changes

Copy link
Member

Choose a reason for hiding this comment

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

yes, I think so, that's the purpose of this existing query param, let's use it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On it

Copy link
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

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

Thanks @BOHEUS, I've left comments

@charlesBochet
Copy link
Member

@BOHEUS could you fix the conflits and we are good to go :)

@BOHEUS
Copy link
Contributor Author

BOHEUS commented Feb 12, 2026

Done @charlesBochet

@charlesBochet charlesBochet merged commit 171efe2 into twentyhq:main Feb 17, 2026
62 checks passed
@github-actions
Copy link
Contributor

Thanks @BOHEUS for your contribution!
This marks your 53rd PR on the repo. You're top 1% of all our contributors 🎉
See contributor page - Share on LinkedIn - Share on Twitter

Contributions

@twenty-eng-sync
Copy link

Hey @charlesBochet! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

@greptile-apps greptile-apps bot mentioned this pull request Feb 17, 2026
@BOHEUS BOHEUS deleted the new-onboarding-plan branch February 17, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants