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.
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