Skip to content

Commit fc90308

Browse files
committed
Stats pricing grid: address review feedback from #113366
- Eligibility fails closed when the purchases fetch errors: FETCH_FAILED marks the store loaded with an empty list, which read as 'no plan' and would have shown the grid to a site that holds one. An error now means 'plan state unknown' and falls back to the dashboard. - The price respects getCurrencyObject's symbolPosition, so locales that put the currency symbol after the amount (e.g. de-DE EUR) render correctly. - The Terms of Service line wraps instead of truncating: dropped the jetpack component's nowrap/overflow-hidden, which cut off the disclosure in locales that run longer than English. - Fixed the apostrophe in 'sync your site's data' (was U+2018, the opening quote) before the string goes out for translation. - Dropped the redundant 'Included' labels on the three boolean differentiator rows so the mobile fallback names the feature instead of showing a bare 'Included' with nothing identifying the row. Desktop rendering is unchanged (the default label is already 'Included', bolded via strong).
1 parent 74d79d4 commit fc90308

3 files changed

Lines changed: 23 additions & 11 deletions

File tree

client/my-sites/stats/pricing-grid/hooks/use-eligibility.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useSelector } from 'calypso/state';
2+
import { getPurchasesError } from 'calypso/state/purchases/selectors';
23
import { getSiteOption } from 'calypso/state/sites/selectors';
34
import useStatsPurchases from '../../hooks/use-stats-purchases';
45

@@ -16,6 +17,12 @@ const LAUNCH_DATE = Date.parse( '2026-08-07T00:00:00Z' );
1617
export default function useIsPricingGridEligible( siteId: number | null ) {
1718
const { hasAnyPlan, isLoading: isLoadingPurchases } = useStatsPurchases( siteId );
1819

20+
// A failed purchases fetch reads as "loaded, no plan" upstream (FETCH_FAILED marks
21+
// the store loaded with an empty list), which would show the grid to a site that
22+
// does hold a plan. Treat the error as "plan state unknown" and fall back to the
23+
// dashboard instead.
24+
const purchasesError = useSelector( getPurchasesError );
25+
1926
// `created_at` is the wpcom shadow blog's `wp_blogs.registered` — the closest thing
2027
// to a first-connection date the sites payload exposes. It matches the connection
2128
// moment when registration created the row, but a reused pre-existing row keeps its
@@ -32,7 +39,7 @@ export default function useIsPricingGridEligible( siteId: number | null ) {
3239
const isNewConnection = Number.isFinite( connectedAtMs ) && connectedAtMs >= LAUNCH_DATE;
3340

3441
return {
35-
isEligible: isNewConnection && ! hasAnyPlan,
42+
isEligible: isNewConnection && ! hasAnyPlan && ! purchasesError,
3643
isNewConnection,
3744
// The date check needs no fetch, so only newly connected sites ever wait.
3845
isLoading: isNewConnection && isLoadingPurchases,

client/my-sites/stats/pricing-grid/pricing-grid.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,18 @@ export default function PricingGrid( { onDismiss }: PricingGridProps ) {
6161
getProductBySlug( state, PRODUCT_JETPACK_STATS_YEARLY )
6262
) as ProductsList.RawAPIProduct | null;
6363

64-
const includedLabel = String( translate( 'Included' ) );
65-
// The four paid differentiators lead; everything below them is shared by both plans.
64+
// The four paid differentiators lead (bolded via `strong`, with the default
65+
// Included / feature-name labels so the mobile fallback still names the feature);
66+
// everything below them is shared by both plans.
6667
const features: Feature[] = [
6768
{
6869
name: String( translate( 'UTM tracking' ) ),
69-
paid: { isIncluded: true, label: includedLabel, strong: true },
70+
paid: { isIncluded: true, strong: true },
7071
free: { isIncluded: false },
7172
},
7273
{
7374
name: String( translate( 'Device stats' ) ),
74-
paid: { isIncluded: true, label: includedLabel, strong: true },
75+
paid: { isIncluded: true, strong: true },
7576
free: { isIncluded: false },
7677
},
7778
{
@@ -81,7 +82,7 @@ export default function PricingGrid( { onDismiss }: PricingGridProps ) {
8182
},
8283
{
8384
name: String( translate( 'Priority support' ) ),
84-
paid: { isIncluded: true, label: includedLabel, strong: true },
85+
paid: { isIncluded: true, strong: true },
8586
free: { isIncluded: false },
8687
},
8788
{
@@ -152,17 +153,20 @@ export default function PricingGrid( { onDismiss }: PricingGridProps ) {
152153
};
153154

154155
const renderPrice = ( value: number, currency: string, hidePriceFraction: boolean ) => {
155-
const { symbol, integer, fraction } = getCurrencyObject( value, currency );
156+
const { symbol, symbolPosition, integer, fraction } = getCurrencyObject( value, currency );
156157
const showPriceFraction = ! hidePriceFraction || ! fraction.endsWith( '00' );
158+
// Some locales put the currency symbol after the amount (e.g. de-DE EUR).
159+
const symbolElement = <sup className="stats-pricing-grid__price-symbol">{ symbol }</sup>;
157160
return (
158161
<p className="stats-pricing-grid__price">
159-
<sup className="stats-pricing-grid__price-symbol">{ symbol }</sup>
162+
{ symbolPosition === 'before' && symbolElement }
160163
{ integer }
161164
{ showPriceFraction && (
162165
<sup className="stats-pricing-grid__price-fraction">
163166
<strong>{ fraction }</strong>
164167
</sup>
165168
) }
169+
{ symbolPosition === 'after' && symbolElement }
166170
</p>
167171
);
168172
};
@@ -300,7 +304,7 @@ export default function PricingGrid( { onDismiss }: PricingGridProps ) {
300304
{ createInterpolateElement(
301305
String(
302306
translate(
303-
'By clicking <strong>%(paid)s</strong> or <strong>%(free)s</strong>, you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your sites data</shareDetailsLink> with us.',
307+
'By clicking <strong>%(paid)s</strong> or <strong>%(free)s</strong>, you agree to our <tosLink>Terms of Service</tosLink> and to <shareDetailsLink>sync your sites data</shareDetailsLink> with us.',
304308
{ args: { paid: paidLabel, free: freeLabel } }
305309
)
306310
),

client/my-sites/stats/pricing-grid/style.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,11 @@
286286
line-height: 24px;
287287

288288
// Same threshold the is-viewport-large class flips at (WP `large` breakpoint).
289+
// Diverges from the jetpack component's `nowrap; overflow: hidden` here: this is
290+
// the Terms of Service disclosure, and locales that run longer than English (DE,
291+
// PT) must wrap rather than lose the end of the sentence.
289292
@media (min-width: 960px) {
290293
padding-left: var(--padding);
291294
padding-right: var(--padding);
292-
white-space: nowrap;
293-
overflow: hidden;
294295
}
295296
}

0 commit comments

Comments
 (0)