Skip to content

Email management: read the raw Purchase instead of the assembled one - #113959

Draft
sirbrillig wants to merge 1 commit into
trunkfrom
update/email-management-raw-purchase
Draft

Email management: read the raw Purchase instead of the assembled one#113959
sirbrillig wants to merge 1 commit into
trunkfrom
update/email-management-raw-purchase

Conversation

@sirbrillig

@sirbrillig sirbrillig commented Aug 31, 2026

Copy link
Copy Markdown
Member

Related to https://linear.app/a8c/issue/SHILL-2256

Proposed changes

Moves the email management pages off the camelCase Purchase produced by the data-stores assembler and onto the raw snake_case Purchase from @automattic/api-core, fetched with TanStack Query instead of Redux. RenewButton comes along because the email subscription card feeds it — leaving it camelCase would have relocated a bridge rather than removed one.

  • Replace the getEmailPurchaseByDomain Redux selector with a new useEmailPurchaseByDomain( domain ) hook backed by purchaseQuery. The domain object already carries the email subscription id, so this fetches the one purchase directly rather than the whole site's purchases.
  • Move the purchase fetch into EmailPlanHeader and drop its purchase / isLoadingPurchase props. Both callers only passed the value straight through, and this lets the still-class-based TitanManageMailboxes drop purchases from mapStateToProps entirely instead of gaining a container component.
  • Convert EmailPlanSubscription, EmailPlan and RenewButton to the raw shape (expiry_date, currency_code, is_auto_renew_enabled, ID), inlining the renewal price as sale_amount || amount and sourcing isExpiredOrRemoved from client/dashboard/utils/purchase.
  • Remove the now-redundant <QuerySitePurchases /> fetch triggers from both email pages — nothing else under client/my-sites/email reads the Redux purchase selectors.
  • Delete three purchase.rawPurchase bridges (the email renew handler, the AutoRenewToggle in the subscription card, and RenewButton's own). RenewButton's two remaining camelCase callers — registered-domain-details and the domain security page — bridge with purchase?.rawPurchase ?? null until the domain settings cluster migrates.

Why are these changes being made?

The data-stores assembler (createPurchaseObject) exists only to rename the API's snake_case purchase fields into camelCase, which means every purchase-rendering surface in Calypso carries a duplicate Purchase type and an extra transformation the backend never asked for. SHILL-2256 removes it; the assembler stays alive until the last consumer migrates, so the work is landing page by page.

Email management is a self-contained cluster: a single selector fed three consumers, all of which passed the purchase into the same header component. That made it possible to delete the selector outright and let the component that actually renders the subscription fetch its own data, which removes a layer of prop drilling as a side effect. RenewButton was pulled in deliberately rather than bridged — it is the direct child of the email subscription card, so converting only the card would have moved a createPurchaseObject call into client/my-sites/email instead of deleting one.

The <QuerySitePurchases /> removals are safe because these pages fetched the entire site's purchase list purely to look up one subscription by id; purchaseQuery asks the API for exactly that purchase.

Testing instructions

No test files exist for any of the touched components, so no new coverage was added. The surrounding suites confirm nothing regressed:

TC:

yarn test-client client/my-sites/email client/my-sites/domains/domain-management client/me/purchases

Manual testing:

  • Use a Simple site with an active Professional Email (Titan) or Google Workspace subscription.
  • Go to Upgrades → Emails and open the domain. The subscription card should show the correct copy and price: "Renews on {date} for {price}" when auto-renew is on, "Expires on {date}." when it is off, "Expired on {date}." once past expiry.
  • Toggle auto-renew off and back on. The card copy should switch between the "Renews on" and "Expires on" variants.
  • Click Renew now and confirm checkout opens with the correct email subscription in the cart at the right price.
  • Click View billing and payment settings and confirm it lands on the correct Manage Purchase page for that subscription.
  • For a Titan subscription, open Manage all mailboxes and confirm the same subscription card renders there.
  • Regression check for the shared RenewButton: on Domains → {domain} → Settings, confirm the renew button shows the correct price and starts a renewal; and on an expired domain's Security page (HTTPS disabled notice), confirm the reactivate/renew button still shows the correct price.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • For UI changes, have you tested the affected components in dark mode?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

Replaces the getEmailPurchaseByDomain Redux selector with a
useEmailPurchaseByDomain hook backed by purchaseQuery, and converts
EmailPlanHeader, EmailPlanSubscription, EmailPlan, TitanManageMailboxes
and the shared RenewButton to the raw api-core Purchase.

RenewButton's two remaining camelCase callers (registered-domain-details
and the domain security page) bridge with purchase.rawPurchase until the
domain settings cluster migrates.

Part of SHILL-2256.
@sirbrillig sirbrillig self-assigned this Aug 31, 2026
@github-actions

Copy link
Copy Markdown

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

Sections (~92 bytes removed 📉 [gzipped])

Details
Name Parsed Gzip
email -406 B -0.0% -107 B -0.0%
domains -162 B -0.0% -43 B -0.0%
overview -162 B -0.0% -43 B -0.0%
checkout -111 B -0.0% -42 B -0.0%

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

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