Skip to content

feat(webapp): let customers compare and switch plans in-app - #6907

Open
macko911 wants to merge 10 commits into
masterfrom
matej/nan-6223-let-customers-compare-and-switch-plans-in-app
Open

feat(webapp): let customers compare and switch plans in-app#6907
macko911 wants to merge 10 commits into
masterfrom
matej/nan-6223-let-customers-compare-and-switch-plans-in-app

Conversation

@macko911

@macko911 macko911 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

Plan comparison and switching lived on a separate section with a bare table of rows (name, price, a button) — no limits shown, current plan marked with just a small dot, and no way to preview the app under a different plan without actually changing an account's billing.

Solution

  • Replace the plans table with side-by-side comparison cards (Free / Starter / Growth / Enterprise) matching the Figma redesign, each listing its key limits, current plan marked with the selected-state border
  • Add a compact "CURRENT PLAN" card shown instead of the grid when the account is on a plan outside the 4 self-serve tiers (e.g. a legacy plan) — the existing legacy-plan messaging in the Usage section above already covers that case, so no duplicate banner
  • Route Enterprise, and any other custom/negotiated plan, to Contact Us instead of self-serve upgrade/downgrade — this is now keyed off the active plan being non-hidden, not just its own canChange flag, so a legacy plan whose definition happens to still permit a downgrade doesn't leak a self-serve button
  • Add a client-side-only "Plan Override" entry to the dev panel (Ctrl+Shift+D) that previews any plan (including hidden/legacy ones) across the whole app — sidebar, Connect UI settings, auto-idling banner, function switches — without touching real billing
  • Add a selected state to the design-system Card and a muted background to CardFooter, and fix StyledLink's icon color defaulting to gray instead of matching the link text

Fixes NAN-6223

Testing

  • Verified live against the dev environment: card grid, current-plan border, legacy-plan banner/card, and the dev-tool override (switching through Growth, a legacy plan, and Enterprise, confirming Contact Us shows everywhere it should) all update immediately
  • npm run ts-build and npm run lint pass clean
  • Did not execute a real upgrade/downgrade end-to-end (would mutate the test account's real Orb/Stripe state) — that flow's underlying dialog/mutation is unchanged from the existing implementation

Scheduled downgrade
image

@linear-code

linear-code Bot commented Jul 24, 2026

Copy link
Copy Markdown

NAN-6223

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Preview Deploy

Status URL Deploy Logs Last Updated
✅ Ready Preview URL Deploy Logs 24 Jul 2026, 13:18 UTC

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 15 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/webapp/src/pages/Team/Billing/components/Plans.tsx Outdated
Comment thread packages/webapp/src/hooks/usePlan.tsx Outdated
Comment thread packages/design-system/src/components/ui/card.tsx
Comment thread packages/webapp/src/features/PlanOverrideOverlay.tsx Outdated
@macko911 macko911 changed the title Let customers compare and switch plans in-app feat(webapp): let customers compare and switch plans in-app Jul 24, 2026
macko911 added 4 commits July 24, 2026 12:36
The default variant hardcoded the icon to text-disabled regardless of
the link's own color, so external-link icons always rendered gray
instead of matching the surrounding link text.
Card now accepts a `selected` prop that swaps the border to
border/selected (Figma's selected-state token), and CardFooter uses
the muted panel background from Figma's Card Footer spec instead of
inheriting the panel's own background.
Adds a client-side-only plan override to the dev panel (Ctrl+Shift+D)
that lets any plan (including hidden/legacy ones) be previewed without
touching the account's real billing state.

usePlan.tsx gains useCurrentPlan(), a drop-in replacement for
useEnvironment() that overlays the override (looked up from the
already-fetched plan list) onto the environment's plan field, and
useApiGetCurrentPlan() applies the same overlay. Every read site that
gates UI on the current plan is swapped to the new hook so the override
is reflected consistently across the sidebar, Connect UI settings, the
auto-idling banner, and the function enable/disable switch.
Replaces the plans table with side-by-side comparison cards (Free /
Starter / Growth / Enterprise) matching the Figma redesign — each
card lists its key limits, the current plan is marked with the
selected-state border, and Enterprise (or any other custom/negotiated
plan) routes to Contact Us instead of self-serve upgrade/downgrade.

Limit bullets are hardcoded display copy (planCardCopy.ts) rather than
derived from PlanDefinition.flags, since paid-plan flags are null
(uncapped, metered via Orb) and can't produce these numbers.

A compact "CURRENT PLAN" card is shown in place of the grid when the
account is on a plan outside the 4 self-serve tiers (e.g. a legacy
plan) — the existing legacy-plan messaging already surfaces in the
Usage section above, so no separate banner is duplicated here.

Upgrade/downgrade CTAs reuse the existing plan-change dialog and
postPlanChange mutation unchanged; only the presentation and the
self-serve eligibility check (now keyed off the active plan being
non-hidden, not just its own canChange flag) are new.
@macko911
macko911 force-pushed the matej/nan-6223-let-customers-compare-and-switch-plans-in-app branch from 6aeab38 to 3910e0c Compare July 24, 2026 10:42
macko911 added 4 commits July 24, 2026 13:03
useApiGetCurrentPlan/useCurrentPlan's override lookup fetched the full
plans list unconditionally, so every consumer (sidebar, Connect UI
settings, auto-idling banner, function switches, ...) fired an extra
request even with dev tools never opened. Only enable it once an
override is actually set.
Accounts on a legacy/custom plan never saw a pending scheduled
downgrade or cancellation — the notice only rendered inside the 4
self-serve cards, none of which are ever the active one for a hidden
plan. Extracted the notice into ScheduledChangeNotice and render it in
CurrentPlanCard too.
The selected state (border/selected) added for the billing plan cards
had no Storybook coverage.
It only exposed a Back button, unlike Token Editor, forcing the
keyboard shortcut or an extra navigation step to dismiss the panel.
Collapsed CardFooter's className back onto one line to match
prettier's output — left multi-line by mistake during a rebase
conflict resolution.
@macko911
macko911 marked this pull request as ready for review July 24, 2026 13:03
@macko911
macko911 requested a review from gabrielle-svg July 24, 2026 13:03
StyledLink already conveys its link affordance via text-link color and
hover/active states; the underline was redundant/unwanted styling.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Confidence score: 4/5

  • In packages/webapp/src/components/ui/StyledLink.tsx, removing the base underline can leave non-icon links (like inline Signin “Sign up.” and external links in Connection/List) distinguished by color only, which risks reduced discoverability/accessibility and inconsistent UX — restore an underline for default text links (or add an explicit non-color affordance) and verify the affected call sites.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/webapp/src/components/ui/StyledLink.tsx">

<violation number="1" location="packages/webapp/src/components/ui/StyledLink.tsx:8">
P2: Removing `underline` from the base style means some StyledLink usages (those without the `icon` prop, e.g. inline `"Sign up."` links on Signin or external links in Connection/List) now rely solely on `text-text-link` color to indicate they are links. Per WCAG 1.4.1 (Use of Color), color should not be the only visual means of conveying information — users with color vision deficiency may not perceive the link affordance. Consider adding `underline` back to the default variant, or conditionally applying it when `icon` is false.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

true: '',
false: ''
}
const styledLinkVariants = cva('w-fit inline-flex items-center cursor-pointer focus-default disabled:text-text-disabled disabled:[&_svg]:text-text-disabled', {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Removing underline from the base style means some StyledLink usages (those without the icon prop, e.g. inline "Sign up." links on Signin or external links in Connection/List) now rely solely on text-text-link color to indicate they are links. Per WCAG 1.4.1 (Use of Color), color should not be the only visual means of conveying information — users with color vision deficiency may not perceive the link affordance. Consider adding underline back to the default variant, or conditionally applying it when icon is false.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/webapp/src/components/ui/StyledLink.tsx, line 8:

<comment>Removing `underline` from the base style means some StyledLink usages (those without the `icon` prop, e.g. inline `"Sign up."` links on Signin or external links in Connection/List) now rely solely on `text-text-link` color to indicate they are links. Per WCAG 1.4.1 (Use of Color), color should not be the only visual means of conveying information — users with color vision deficiency may not perceive the link affordance. Consider adding `underline` back to the default variant, or conditionally applying it when `icon` is false.</comment>

<file context>
@@ -5,37 +5,34 @@ import { Link } from 'react-router-dom';
-                true: '',
-                false: ''
-            }
+const styledLinkVariants = cva('w-fit inline-flex items-center cursor-pointer focus-default disabled:text-text-disabled disabled:[&_svg]:text-text-disabled', {
+    variants: {
+        variant: {
</file context>
Suggested change
const styledLinkVariants = cva('w-fit inline-flex items-center cursor-pointer focus-default disabled:text-text-disabled disabled:[&_svg]:text-text-disabled', {
const styledLinkVariants = cva('w-fit underline inline-flex items-center cursor-pointer focus-default disabled:text-text-disabled disabled:[&_svg]:text-text-disabled', {

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.

1 participant