SIGN IN SIGN UP

AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.

0 0 79 Python

feat(frontend): surface 15% yearly savings in switch-to-yearly modal (#13095)

### Why / What / How

**Why:** The current in-app cycle-switch confirmation modal (Settings →
Subscription → cycle toggle → Yearly) only mentions the prorated charge
and the next yearly renewal amount. It does not surface the **15% yearly
discount**, the monthly-equivalent yearly price, or the apples-to-apples
annual comparison. This is a high-friction billing moment — Stripe
charges the prorated difference immediately on confirm — and the user
has no in-context reason to accept it. SECRT-2345 calls this out as
Urgent.

**What:** Rewrites the body and title of the `SwitchCycleDialog` for the
monthly→yearly direction so the savings, monthly-equivalent price, and
annual comparison are the first thing the user reads, before the
existing proration line. Title now names the tier (e.g. `Switch Pro to
yearly billing?` / `Switch Max to yearly billing?`).

**How:**
- `useYourPlanCard.ts` — added `getDialogTitle()` and rewrote the yearly
branch of `getDialogBody()`. Savings percent is **computed** from
`currentMonthlyCents * 12` vs `currentYearlyCents` rather than
hard-coded `15%`, so the copy stays accurate if backend pricing shifts.
- `SwitchCycleDialog.tsx` — accepts an optional `title` prop overriding
the generic `Switch billing to Yearly?` default.
- `YourPlanCard.tsx` — wires `cycleDialogTitle` through to the dialog.
- Falls back to the original generic copy when prices are missing (e.g.
`tier_costs_yearly` not in payload) or the tier label isn't meaningful
for the title (`NO_TIER`, `BUSINESS`). No behavior change for the
monthly direction or the tier-upgrade / tier-downgrade dialogs.

Stripe price IDs, tier mapping, and the proration flow are untouched —
this is a copy-only change on the existing yearly switch path.

<img width="676" height="308" alt="Screenshot 2026-05-13 at 7 57 37 AM"
src="https://github.com/user-attachments/assets/94afe427-18fb-40fa-9b03-a98bfe5d09e7"
/>

<img width="682" height="284" alt="Screenshot 2026-05-13 at 7 58 01 AM"
src="https://github.com/user-attachments/assets/ef8de562-a957-45f0-9c6b-473a830d2635"
/>


### Changes 🏗️

- New title for monthly→yearly: `Switch Pro to yearly billing?` /
`Switch Max to yearly billing?` (was: `Switch billing to Yearly?`).
- New body for monthly→yearly (Pro example):
> Save 15% with yearly billing. Pro is $42.50/month when billed yearly,
charged as $510.00/year instead of $600.00/year monthly. You'll be
charged the prorated difference immediately. After this period, your
plan renews yearly at $510.00.
- Max body equivalent uses `$272.00/month`, `$3,264.00/year`, `instead
of $3,840.00/year`.
- Savings percent computed from live prices (currently 15% for Pro and
Max).
- Existing tests updated; new test added for the Max yearly-switch copy.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Pro monthly subscriber → Settings → Subscription → toggle Yearly →
modal title reads `Switch Pro to yearly billing?` and body shows `Save
15%`, `$42.50/month`, `$510.00/year`, `instead of $600.00/year`, plus
the proration line.
- [ x Max monthly subscriber → same flow → modal shows `Switch Max to
yearly billing?`, `$272.00/month`, `$3,264.00/year`, `instead of
$3,840.00/year`.
- [x] Confirm `Switch to Yearly` → Stripe receives the correct yearly
price ID and charges the prorated difference; subscription refetch
reflects yearly cadence.
  - [x] Cancel button closes the modal without changing the plan.
- [x] Yearly → monthly direction still shows the original end-of-period
copy (no charge today).
- [x] No copy implies the user gets new features / more usage / better
models from switching.
- [x] `pnpm test:unit` — billing-cards integration tests pass (existing
Pro test updated, new Max test added).

#### For configuration changes:

- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**)
A
Abhimanyu Yadav committed
02ff908a35b4669363a2228faab152b0e0e09727
Parent: a0c4b07
Committed by GitHub <noreply@github.com> on 5/13/2026, 2:43:34 AM