Skip to content

UpgradePromptCard: primary destination → Pro Annual ('2 months free' framing) across 5 surfaces#50

Merged
mastermanas805 merged 1 commit into
mainfrom
feat/upgrade-prompt-pro-annual-fresh
May 13, 2026
Merged

UpgradePromptCard: primary destination → Pro Annual ('2 months free' framing) across 5 surfaces#50
mastermanas805 merged 1 commit into
mainfrom
feat/upgrade-prompt-pro-annual-fresh

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

Flips the primary upgrade CTA on every UpgradePromptCard surface from
generic monthly Pro → Pro Annual ("Get Pro — $7.50/mo billed yearly
· 2 months free"), per the 2026-05-13 pricing playbook (highest-LTV
product, anchored on effective per-month price). A small "Or pay
monthly — $9/mo" text link sits directly under the primary CTA so
mid-cycle users still have a frictionless monthly path.

  • Primary/app/billing?frequency=yearly&plan=pro
  • Secondary/app/billing?frequency=monthly&plan=pro
  • Both destinations built via a new centralized buildCtaHref() helper
    so the query-param logic lives in one place.

Surfaces touched (5 + private_deploy comes along for free)

vault_prod · provision_twin · family_bindings · quota_wall ·
custom_domain · private_deploy. Each priceLine now reads "$7.50/mo
billed yearly · 2 months free".

P1 experiment composition preserved

The experiments.upgrade_cta.label payload from /auth/me continues
to override the primary CTA label only — the secondary "Or pay
monthly" link is intentionally static so the A/B test doesn't run on
two surfaces at once. Crucially, when P1 overrides the label, the
destination still points at yearly so the experiment measures
copy, not destination. Asserted in UpgradePromptCard.test.tsx with:

expect(parsed.frequency).toBe('yearly')  // even with P1 variant active

Test plan

  • npm test green — 395 pass / 3 skip / 0 fail (was 395 baseline; +13 new asserts)
  • npm run build clean — TypeScript + Vite production build OK
  • UpgradePromptCard.test.tsx: 23 tests (was 15) — primary, secondary, P1 composition, link target
  • UpgradePromptSurfaces.test.tsx: 14 tests (was 9) — 5-surface regression block + updated P1 composition tests
  • BillingPage tests still pass (parallel PR coordinates at merge time)
  • DeploymentsPage upsell tests still pass (uses private_deploy surface)

Coordination note

BillingPage redesign is in a parallel PR — this PR only touches the
5 in-context upgrade prompt surfaces. BillingPage doesn't yet read
the ?frequency= URL param (it reads from localStorage), so the
landing experience for users who click these CTAs is forward-compat:
the URL carries the right intent, BillingPage will start honouring
it when the parallel PR lands.

🤖 Generated with Claude Code

Flips UpgradePromptCard's primary CTA from generic monthly Pro to a
Pro-Annual-anchored CTA ("Get Pro — $7.50/mo billed yearly · 2 months
free") per the 2026-05-13 pricing playbook. A secondary "Or pay
monthly — $9/mo" link sits directly under the primary so mid-cycle
users still have a frictionless monthly path.

Primary navigates to /app/billing?frequency=yearly&plan=pro; secondary
to ?frequency=monthly&plan=pro. Frequencies + plan are appended via a
new buildCtaHref() helper so the destination logic lives in one place.

P1's /auth/me experiment variant continues to override the PRIMARY CTA
label only — the secondary link is intentionally static so the A/B
test doesn't run on two surfaces at once. When P1 overrides the label,
the destination still points at the yearly frequency so the experiment
measures copy, not destination.

5 surfaces covered: vault_prod, provision_twin, family_bindings,
quota_wall, custom_domain. Each priceLine now carries the "2 months
free" framing. private_deploy gets the same treatment for free since
it reads from the same UPGRADE_COPY map.

Tests: 23 unit + 14 surface (was 15 + 9). Full dashboard suite 395
pass / 3 skip / 0 fail. Build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit 340cec9 into main May 13, 2026
2 checks passed
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