From b11a9542d27246b2a2b1f9dd0c46240b953e27f8 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 01:19:23 +0800 Subject: [PATCH 1/9] Premium Analytics: badge the Earnings History widget statuses Per Eder's design pass: the widget's payment status is a badge as on the report, and the two pending statuses read "Pending" with their reason in an info icon beside the badge, on both surfaces. The report's Status filter offers Pending once for both codes, and the CSV keeps the reason. Co-Authored-By: Claude Fable 5.1 --- ...d-premium-analytics-earnings-status-badges | 4 ++ .../__tests__/fields.test.tsx | 40 +++++++++-- .../earnings-history-list.module.scss | 2 +- .../earnings-history-list.tsx | 6 +- .../earnings-status-badge.module.scss | 11 +++ .../wordads-earnings-history/fields.tsx | 69 +++++++++++-------- .../routes/reports/earnings/page.tsx | 8 ++- ...d-premium-analytics-earnings-status-badges | 4 ++ ...d-premium-analytics-earnings-status-badges | 4 ++ ...d-premium-analytics-earnings-status-badges | 4 ++ 10 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges create mode 100644 projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss create mode 100644 projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges create mode 100644 projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges create mode 100644 projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges diff --git a/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges b/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges new file mode 100644 index 000000000000..0be6f6f0f96b --- /dev/null +++ b/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx index c02f07bb999a..ed76f3da46dc 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx @@ -12,8 +12,14 @@ describe( 'getEarningsStatus', () => { expect( getEarningsStatus( 0 ).label ).toBe( 'Unpaid' ); expect( getEarningsStatus( 1 ).label ).toBe( 'Paid' ); expect( getEarningsStatus( 2 ).label ).toBe( 'a8c-only' ); - expect( getEarningsStatus( 3 ).label ).toBe( 'Pending (Missing Tax Info)' ); - expect( getEarningsStatus( 4 ).label ).toBe( 'Pending (Invalid PayPal)' ); + expect( getEarningsStatus( 3 ).label ).toBe( 'Pending' ); + expect( getEarningsStatus( 4 ).label ).toBe( 'Pending' ); + } ); + + it( 'keeps the pending reason beside the label, not in it', () => { + expect( getEarningsStatus( 3 ).detail ).toBe( 'Missing tax info' ); + expect( getEarningsStatus( 4 ).detail ).toBe( 'Invalid PayPal' ); + expect( getEarningsStatus( 0 ).detail ).toBeUndefined(); } ); it( 'falls back to "?" for unknown or absent statuses', () => { @@ -47,6 +53,16 @@ describe( 'EarningsStatusBadge', () => { render( ); expect( screen.getByText( 'a8c-only' ) ).not.toHaveAttribute( 'tabindex' ); } ); + + it( 'puts a pending reason in an info icon beside a one-word badge', () => { + render( ); + + expect( screen.getByText( 'Pending' ) ).not.toHaveAttribute( 'tabindex' ); + expect( screen.getByRole( 'img', { name: 'Missing tax info' } ) ).toHaveAttribute( + 'tabindex', + '0' + ); + } ); } ); describe( 'flattenEarningsBreakdown', () => { @@ -120,17 +136,31 @@ describe( 'getWordAdsHistoryFields', () => { expect( data.map( row => row.period ) ).toEqual( [ period ] ); } ); - it( 'offers every status but a8c-only in the Status filter', () => { + it( 'offers every status but a8c-only in the Status filter, pending once', () => { const status = fields.find( field => field.id === 'status' ); expect( status?.elements?.map( element => element.value ) ).toEqual( [ 'Unpaid', 'Paid', - 'Pending (Missing Tax Info)', - 'Pending (Invalid PayPal)', + 'Pending', ] ); } ); + it( 'filters "Pending" to both pending codes', () => { + const pending = [ + ...rows, + { id: '2026-03', period: '2026-03', amount: 1, pageviews: 1, status: 3 }, + { id: '2026-04', period: '2026-04', amount: 1, pageviews: 1, status: 4 }, + ]; + const { data } = filterSortAndPaginate( + pending, + { ...view, filters: [ { field: 'status', operator: 'is', value: 'Pending' } ] } as View, + fields + ); + + expect( data.map( row => row.period ) ).toEqual( [ '2026-03', '2026-04' ] ); + } ); + it.each( [ [ 'asc', [ '2025-12', '2026-09', '2012-03' ] ], [ 'desc', [ '2026-09', '2025-12', '2012-03' ] ], diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss index 3ac25172b4da..2170d6673c05 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss @@ -54,7 +54,7 @@ text-align: end; } -/* Negative amounts and unpaid periods. */ +/* Negative amounts. */ .attention { color: var(--wpds-color-foreground-content-error-weak); } diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx index 27447c4a2b91..b1e4769dad6d 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx @@ -9,7 +9,7 @@ import { useMemo } from 'react'; */ import { useElementSize } from '../../hooks/use-element-size'; import styles from './earnings-history-list.module.scss'; -import { EarningsStatusLabel, formatEarningsPeriod, type EarningsHistoryRow } from './fields'; +import { EarningsStatusBadge, formatEarningsPeriod, type EarningsHistoryRow } from './fields'; export type EarningsHistoryListProps = { rows?: EarningsHistoryRow[]; @@ -59,8 +59,8 @@ export function EarningsHistoryList( { rows = [], className }: EarningsHistoryLi { formatMetricValue( row.amount, 'currency' ) } - - + + ) ) } diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss new file mode 100644 index 000000000000..cca028d4f74c --- /dev/null +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss @@ -0,0 +1,11 @@ +/* Inline so a cell's text-align still places the pair. */ +.root { + display: inline-flex; + align-items: center; + gap: var(--wpds-dimension-gap-xs); +} + +.info { + display: inline-flex; + color: var(--wpds-color-foreground-content-neutral-weak); +} diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index b7dad0d742a1..b8a4482cf230 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -2,13 +2,15 @@ * External dependencies */ import { parseSiteDateTime } from '@jetpack-premium-analytics/datetime'; -import { Badge } from '@jetpack-premium-analytics/externals'; +import { Badge, Icon } from '@jetpack-premium-analytics/externals'; import { formatDate, formatMetricValue } from '@jetpack-premium-analytics/formatters'; import { Tooltip } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { info } from '@wordpress/icons'; /** * Internal dependencies */ +import styles from './earnings-status-badge.module.scss'; import type { StatsWordAdsEarningsBreakdown } from '@jetpack-premium-analytics/data'; import type { Field } from '@jetpack-premium-analytics/externals'; import type { ComponentProps } from 'react'; @@ -24,7 +26,13 @@ export type EarningsHistoryRow = { type EarningsStatusIntent = NonNullable< ComponentProps< typeof Badge >[ 'intent' ] >; -type EarningsStatus = { label: string; tooltip?: string; intent: EarningsStatusIntent }; +type EarningsStatus = { + label: string; + tooltip?: string; + intent: EarningsStatusIntent; + /** Why a payment is pending; shown beside the badge, not in it, so the label stays short. */ + detail?: string; +}; /** Automattic-internal status: never shown to site owners, so kept out of the Status filter. */ const A8C_ONLY_STATUS = 2; @@ -33,12 +41,12 @@ const A8C_ONLY_STATUS = 2; * WordAds payment statuses by code, ported verbatim from the Jetpack Stats WordAds * `getStatus` map (wp-calypso client/my-sites/stats/wordads/earnings.jsx). * - * @return The label, optional tooltip and badge intent for each known code. + * @return The label, optional tooltip, badge intent and pending detail for each known code. */ function getEarningsStatuses(): Record< number, EarningsStatus > { return { - // Unpaid is red as in the design and the widget; the pending codes wait on - // the site owner, so they get the warning tint instead. + // Unpaid is red as in the design; the pending codes wait on the site owner, + // so they get the warning tint, one word, and the reason beside the badge. 0: { label: __( 'Unpaid', 'jetpack-premium-analytics-pkg' ), tooltip: __( @@ -57,7 +65,8 @@ function getEarningsStatuses(): Record< number, EarningsStatus > { intent: 'draft', }, 3: { - label: __( 'Pending (Missing Tax Info)', 'jetpack-premium-analytics-pkg' ), + label: __( 'Pending', 'jetpack-premium-analytics-pkg' ), + detail: __( 'Missing tax info', 'jetpack-premium-analytics-pkg' ), tooltip: __( 'Payment is pending due to missing information. You can provide tax information in the settings screen.', 'jetpack-premium-analytics-pkg' @@ -65,7 +74,8 @@ function getEarningsStatuses(): Record< number, EarningsStatus > { intent: 'medium', }, 4: { - label: __( 'Pending (Invalid PayPal)', 'jetpack-premium-analytics-pkg' ), + label: __( 'Pending', 'jetpack-premium-analytics-pkg' ), + detail: __( 'Invalid PayPal', 'jetpack-premium-analytics-pkg' ), tooltip: __( 'Payment processing has failed due to invalid PayPal address. You can correct the PayPal address in the settings screen.', 'jetpack-premium-analytics-pkg' @@ -129,25 +139,6 @@ export function formatEarningsPeriod( period: string ): string { return parsed ? formatDate( parsed, 'monthYear' ) : period; } -/** - * A payment status label, with its explanation in a tooltip when there is one. - * - * @param props - The component props. - * @param props.status - The numeric status from the earnings payload, if any. - * @return The rendered label. - */ -export function EarningsStatusLabel( { status }: { status: number | undefined } ) { - const { label, tooltip } = getEarningsStatus( status ); - - return tooltip ? ( - - { label } - - ) : ( - { label } - ); -} - /** * Numeric sort that keeps rows without a count last in either direction. * @@ -168,15 +159,29 @@ function compareOptionalCounts( a: unknown, b: unknown, direction: 'asc' | 'desc } /** - * A payment status as a badge, with its explanation in a tooltip when there is - * one. The report table's rendering; the widget list keeps the plain label. + * A payment status as a badge. A pending status puts its reason in an info icon + * beside the badge; any other status keeps its explanation on the badge itself. * * @param props - The component props. * @param props.status - The numeric status from the earnings payload, if any. * @return The rendered badge. */ export function EarningsStatusBadge( { status }: { status: number | undefined } ) { - const { label, tooltip, intent } = getEarningsStatus( status ); + const { label, tooltip, intent, detail } = getEarningsStatus( status ); + + if ( detail ) { + return ( + + + + + + + { label } + + ); + } + const badge = ( { label } @@ -227,9 +232,13 @@ export function getWordAdsHistoryFields(): Field< EarningsHistoryRow >[] { // A filter rather than search: a substring match for "Paid" also finds "Unpaid". // Sorts and filters by the visible label rather than the numeric code. getValue: ( { item } ) => getEarningsStatus( item.status ).label, + // Both pending codes share a label, so one "Pending" option covers them. elements: Object.entries( getEarningsStatuses() ) .filter( ( [ code ] ) => Number( code ) !== A8C_ONLY_STATUS ) - .map( ( [ , { label } ] ) => ( { value: label, label } ) ), + .map( ( [ , { label } ] ) => ( { value: label, label } ) ) + .filter( + ( option, index, all ) => all.findIndex( o => o.value === option.value ) === index + ), filterBy: { operators: [ 'is' ] }, render: ( { item } ) => , }, diff --git a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx index 8857c2367a63..f963f602e667 100644 --- a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx +++ b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx @@ -95,8 +95,12 @@ function EarningsReport(): JSX.Element { : [] ), { label: __( 'Status', 'jetpack-premium-analytics-pkg' ), - // The numeric code says nothing to a reader of the export. - getValue: row => getEarningsStatus( row.status ).label, + // The numeric code says nothing to a reader of the export; a pending + // row keeps its reason, which the table shows in an icon. + getValue: row => { + const { label, detail } = getEarningsStatus( row.status ); + return detail ? `${ label } (${ detail })` : label; + }, }, ], [ showAdsServed ] diff --git a/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges new file mode 100644 index 000000000000..479b0747984c --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. diff --git a/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges new file mode 100644 index 000000000000..0be6f6f0f96b --- /dev/null +++ b/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. diff --git a/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges new file mode 100644 index 000000000000..10239b9209c3 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. From a28ab3a1b6d7c579a608cf65f4c6cced211951d7 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 01:27:18 +0800 Subject: [PATCH 2/9] Premium Analytics: keep widget amounts neutral The status badge carries the colour now; a negative amount reads with its minus sign in the normal text colour, as on the report. Co-Authored-By: Claude Fable 5.1 --- .../earnings-history-list.module.scss | 5 ----- .../wordads-earnings-history/earnings-history-list.tsx | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss index 2170d6673c05..f67430345443 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.module.scss @@ -53,8 +53,3 @@ color: var(--wpds-color-foreground-content-neutral-weak); text-align: end; } - -/* Negative amounts. */ -.attention { - color: var(--wpds-color-foreground-content-error-weak); -} diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx index b1e4769dad6d..9701143ca53d 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-history-list.tsx @@ -56,9 +56,7 @@ export function EarningsHistoryList( { rows = [], className }: EarningsHistoryLi hidden={ index >= visibleCount } > { formatEarningsPeriod( row.period ) } - - { formatMetricValue( row.amount, 'currency' ) } - + { formatMetricValue( row.amount, 'currency' ) } From 28a34da64ea6b3c14d6c87d604b306f618266674 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 01:45:36 +0800 Subject: [PATCH 3/9] Premium Analytics: keep the pending badge the height of its neighbours An inline-flex wrapper blockified the badge, so a pending row's badge came out taller than the bare badges around it. The wrapper stays inline and only the info icon is an inline-level box. Co-Authored-By: Claude Fable 5.1 --- .../earnings-status-badge.module.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss index cca028d4f74c..e640152e09e8 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss @@ -1,11 +1,12 @@ -/* Inline so a cell's text-align still places the pair. */ +/* Stays inline: a flex wrapper would blockify the badge and make the pending + * row's badge taller than its neighbours. */ .root { - display: inline-flex; - align-items: center; - gap: var(--wpds-dimension-gap-xs); + white-space: nowrap; } .info { display: inline-flex; + vertical-align: middle; + margin-inline-end: var(--wpds-dimension-gap-xs); color: var(--wpds-color-foreground-content-neutral-weak); } From bdd6425e63577a5dfa59b9ad6c8637144ab297ca Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 01:48:43 +0800 Subject: [PATCH 4/9] Premium Analytics: stand every status badge at its full height Every status now sits in the same inline-flex wrapper, which blockifies the badge so it takes its line height plus padding, as the design draws it, rather than painting over a shorter line box. The pending row's icon shares that wrapper. Co-Authored-By: Claude Fable 5.1 --- .../earnings-status-badge.module.scss | 9 +++++---- .../src/components/wordads-earnings-history/fields.tsx | 6 +++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss index e640152e09e8..5698a0759453 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss @@ -1,12 +1,13 @@ -/* Stays inline: a flex wrapper would blockify the badge and make the pending - * row's badge taller than its neighbours. */ +/* Inline-flex blockifies the badge, so it stands its full line height plus + * padding, as in the design, and every status comes out the same height. */ .root { + display: inline-flex; + align-items: center; + gap: var(--wpds-dimension-gap-xs); white-space: nowrap; } .info { display: inline-flex; - vertical-align: middle; - margin-inline-end: var(--wpds-dimension-gap-xs); color: var(--wpds-color-foreground-content-neutral-weak); } diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index b8a4482cf230..44c540e47470 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -188,7 +188,11 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } ); - return tooltip ? { badge } : badge; + return ( + + { tooltip ? { badge } : badge } + + ); } /** From e767c729732d29d5bbc1713cc1f2d812169269f5 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 01:58:50 +0800 Subject: [PATCH 5/9] Premium Analytics: open the pending reason on hover or click The info icon is the design system Popover the report chart help uses, which opens on a short hover, a click, or the keyboard. The components Tooltip waited on a long hover and ignored clicks. Co-Authored-By: Claude Fable 5.1 --- .../__tests__/fields.test.tsx | 7 ++---- .../earnings-status-badge.module.scss | 9 ++++++++ .../wordads-earnings-history/fields.tsx | 23 +++++++++++++++---- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx index ed76f3da46dc..11271f04b7b4 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx @@ -54,14 +54,11 @@ describe( 'EarningsStatusBadge', () => { expect( screen.getByText( 'a8c-only' ) ).not.toHaveAttribute( 'tabindex' ); } ); - it( 'puts a pending reason in an info icon beside a one-word badge', () => { + it( 'puts a pending reason in an info button beside a one-word badge', () => { render( ); expect( screen.getByText( 'Pending' ) ).not.toHaveAttribute( 'tabindex' ); - expect( screen.getByRole( 'img', { name: 'Missing tax info' } ) ).toHaveAttribute( - 'tabindex', - '0' - ); + expect( screen.getByRole( 'button', { name: 'Missing tax info' } ) ).toBeInTheDocument(); } ); } ); diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss index 5698a0759453..82153ee301f2 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss @@ -9,5 +9,14 @@ .info { display: inline-flex; + align-items: center; + border: none; + padding: 0; + background: none; color: var(--wpds-color-foreground-content-neutral-weak); + cursor: pointer; +} + +.popup { + max-inline-size: 20rem; } diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index 44c540e47470..d08233ab863e 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { parseSiteDateTime } from '@jetpack-premium-analytics/datetime'; -import { Badge, Icon } from '@jetpack-premium-analytics/externals'; +import { Badge, Icon, Popover, VisuallyHidden } from '@jetpack-premium-analytics/externals'; import { formatDate, formatMetricValue } from '@jetpack-premium-analytics/formatters'; import { Tooltip } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -170,13 +170,26 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } const { label, tooltip, intent, detail } = getEarningsStatus( status ); if ( detail ) { + // The same hover-or-click tip as a report chart's help icon; the + // components Tooltip waits on a long hover and ignores clicks. return ( - - + + - - + + + + }>{ detail } + { tooltip ?? detail } + + { label } ); From 15cff6ada6e4cbb872d48cbb794692489ab19ae5 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 02:00:07 +0800 Subject: [PATCH 6/9] Premium Analytics: show the pending reason as the tip's title Co-Authored-By: Claude Fable 5.1 --- .../src/components/wordads-earnings-history/fields.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index d08233ab863e..28a70a87803e 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { parseSiteDateTime } from '@jetpack-premium-analytics/datetime'; -import { Badge, Icon, Popover, VisuallyHidden } from '@jetpack-premium-analytics/externals'; +import { Badge, Icon, Popover } from '@jetpack-premium-analytics/externals'; import { formatDate, formatMetricValue } from '@jetpack-premium-analytics/formatters'; import { Tooltip } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -186,8 +186,8 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } - }>{ detail } - { tooltip ?? detail } + { detail } + { tooltip && { tooltip } } { label } From c3b8c9f3bc25c743b87c672390279ca18725fb29 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 02:31:04 +0800 Subject: [PATCH 7/9] Premium Analytics: open the pending reason on click, like the widget header tip Co-Authored-By: Claude Fable 5.1 --- .../components/wordads-earnings-history/fields.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index 28a70a87803e..c8c3885b259e 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -170,18 +170,11 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } const { label, tooltip, intent, detail } = getEarningsStatus( status ); if ( detail ) { - // The same hover-or-click tip as a report chart's help icon; the - // components Tooltip waits on a long hover and ignores clicks. + // The same click-open tip as the widget header's info icon. return ( - - + + From 4d9c22144c2f0ccf49d484e7026a3668f569237f Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 04:21:46 +0800 Subject: [PATCH 8/9] Premium Analytics: lead the pending tip with its reason in bold The popover title is heading-sized and stays hidden, as in the widget header's tip; the reason opens the description instead. Co-Authored-By: Claude Fable 5.1 --- .../earnings-status-badge.module.scss | 8 ++++++++ .../src/components/wordads-earnings-history/fields.tsx | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss index 82153ee301f2..f727443da129 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/earnings-status-badge.module.scss @@ -20,3 +20,11 @@ .popup { max-inline-size: 20rem; } + +/* The reason leads the explanation; the popover title is heading-sized, + * so it stays hidden. */ +.reason { + display: block; + margin-block-end: var(--wpds-dimension-gap-xs); + font-weight: var(--wpds-typography-font-weight-emphasis); +} diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index c8c3885b259e..b64ad12f895e 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { parseSiteDateTime } from '@jetpack-premium-analytics/datetime'; -import { Badge, Icon, Popover } from '@jetpack-premium-analytics/externals'; +import { Badge, Icon, Popover, VisuallyHidden } from '@jetpack-premium-analytics/externals'; import { formatDate, formatMetricValue } from '@jetpack-premium-analytics/formatters'; import { Tooltip } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -179,8 +179,11 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } - { detail } - { tooltip && { tooltip } } + }>{ detail } + + { detail } + { tooltip } + { label } From 34b431ba1485f0c6609b50cf588f0567f10dca0b Mon Sep 17 00:00:00 2001 From: dognose24 Date: Tue, 22 Sep 2026 23:49:05 +0800 Subject: [PATCH 9/9] Premium Analytics: address the status badge review Drop `modal` from the pending tip: without a `Popover.Close` there is nothing for a focus trap to cycle to, and non-modal matches the report chart help. Put the CSV "Pending (reason)" string through sprintf so the order and parentheses can be translated. Tests now click the tip and read its explanation, assert the status cell in the widget list, and export a pending row. Changelog entries say the widget's negative amounts are no longer red. Co-Authored-By: Claude Fable 5.1 --- ...add-premium-analytics-earnings-status-badges | 2 +- .../__tests__/earnings-history-list.test.tsx | 9 +++++++++ .../__tests__/fields.test.tsx | 16 ++++++++-------- .../wordads-earnings-history/fields.tsx | 8 ++++---- .../routes/reports/earnings/page.tsx | 11 +++++++++-- .../routes/reports/report-csv-exports.test.tsx | 17 +++++++++++++++++ ...add-premium-analytics-earnings-status-badges | 2 +- ...add-premium-analytics-earnings-status-badges | 2 +- ...add-premium-analytics-earnings-status-badges | 2 +- 9 files changed, 51 insertions(+), 18 deletions(-) diff --git a/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges b/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges index 0be6f6f0f96b..0f223c62a91b 100644 --- a/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges +++ b/projects/packages/premium-analytics/changelog/add-premium-analytics-earnings-status-badges @@ -1,4 +1,4 @@ Significance: patch Type: changed -Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. +Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. Negative amounts in the widget are no longer red; only the badge carries colour. diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/earnings-history-list.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/earnings-history-list.test.tsx index f6329411d536..24704f05c5b5 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/earnings-history-list.test.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/earnings-history-list.test.tsx @@ -49,4 +49,13 @@ describe( 'EarningsHistoryList', () => { expect( hiddenFlags() ).toEqual( [ false, true, true ] ); } ); + + it( 'renders each status as a badge, with a pending reason in an info button', () => { + mockSizes( 200, 36 ); + render( ); + + expect( screen.getAllByText( 'Paid' ) ).toHaveLength( 3 ); + expect( screen.getByText( 'Pending' ) ).toBeInTheDocument(); + expect( screen.getByRole( 'button', { name: 'Missing tax info' } ) ).toBeInTheDocument(); + } ); } ); diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx index 11271f04b7b4..220e93a6d8d8 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/__tests__/fields.test.tsx @@ -1,5 +1,6 @@ import { filterSortAndPaginate, type View } from '@jetpack-premium-analytics/externals'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { EarningsStatusBadge, flattenEarningsBreakdown, @@ -16,12 +17,6 @@ describe( 'getEarningsStatus', () => { expect( getEarningsStatus( 4 ).label ).toBe( 'Pending' ); } ); - it( 'keeps the pending reason beside the label, not in it', () => { - expect( getEarningsStatus( 3 ).detail ).toBe( 'Missing tax info' ); - expect( getEarningsStatus( 4 ).detail ).toBe( 'Invalid PayPal' ); - expect( getEarningsStatus( 0 ).detail ).toBeUndefined(); - } ); - it( 'falls back to "?" for unknown or absent statuses', () => { expect( getEarningsStatus( 99 ).label ).toBe( '?' ); expect( getEarningsStatus( undefined ).label ).toBe( '?' ); @@ -54,11 +49,16 @@ describe( 'EarningsStatusBadge', () => { expect( screen.getByText( 'a8c-only' ) ).not.toHaveAttribute( 'tabindex' ); } ); - it( 'puts a pending reason in an info button beside a one-word badge', () => { + it( 'puts a pending reason in an info button beside a one-word badge', async () => { render( ); expect( screen.getByText( 'Pending' ) ).not.toHaveAttribute( 'tabindex' ); - expect( screen.getByRole( 'button', { name: 'Missing tax info' } ) ).toBeInTheDocument(); + + await userEvent.click( screen.getByRole( 'button', { name: 'Missing tax info' } ) ); + + await expect( + screen.findByText( /You can provide tax information in the settings screen/ ) + ).resolves.toBeInTheDocument(); } ); } ); diff --git a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx index b64ad12f895e..6cfe7aa1ce6d 100644 --- a/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx +++ b/projects/packages/premium-analytics/packages/widgets-toolkit/src/components/wordads-earnings-history/fields.tsx @@ -38,8 +38,8 @@ type EarningsStatus = { const A8C_ONLY_STATUS = 2; /** - * WordAds payment statuses by code, ported verbatim from the Jetpack Stats WordAds - * `getStatus` map (wp-calypso client/my-sites/stats/wordads/earnings.jsx). + * WordAds payment statuses by code, adapted from the Jetpack Stats WordAds + * `getStatus` map (wp-calypso client/my-sites/stats/wordads/earnings.jsx), * * @return The label, optional tooltip, badge intent and pending detail for each known code. */ @@ -170,10 +170,10 @@ export function EarningsStatusBadge( { status }: { status: number | undefined } const { label, tooltip, intent, detail } = getEarningsStatus( status ); if ( detail ) { - // The same click-open tip as the widget header's info icon. + // Click-open like the widget header's info icon; non-modal, so Tab leaves and closes it. return ( - + diff --git a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx index f963f602e667..3a606a794804 100644 --- a/projects/packages/premium-analytics/routes/reports/earnings/page.tsx +++ b/projects/packages/premium-analytics/routes/reports/earnings/page.tsx @@ -19,7 +19,7 @@ import { type EarningsHistoryRow, } from '@jetpack-premium-analytics/widgets-toolkit'; import { useMemo } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ @@ -99,7 +99,14 @@ function EarningsReport(): JSX.Element { // row keeps its reason, which the table shows in an icon. getValue: row => { const { label, detail } = getEarningsStatus( row.status ); - return detail ? `${ label } (${ detail })` : label; + return detail + ? sprintf( + /* translators: 1: payment status, e.g. "Pending"; 2: the reason, e.g. "Missing tax info". */ + __( '%1$s (%2$s)', 'jetpack-premium-analytics-pkg' ), + label, + detail + ) + : label; }, }, ], diff --git a/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx b/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx index 6c677c4f3d22..435cc755fe67 100644 --- a/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx +++ b/projects/packages/premium-analytics/routes/reports/report-csv-exports.test.tsx @@ -372,6 +372,23 @@ describe( 'report CSV exports', () => { ); } ); + it( 'exports a pending status with its reason', () => { + const rows = [ { id: '2026-09', period: '2026-09', amount: 30.25, pageviews: 300, status: 3 } ]; + useEarningsReportRecordsMock.mockReturnValue( { + ...reportStatus, + tab: 'wordads', + availableTabs: [ 'wordads' ], + rows, + } as ReturnType< typeof useEarningsReportRecords > ); + + expectCsvExport( EarningsReportPage, 'earnings-wordads', rows, [ + '2026-09', + 30.25, + 300, + 'Pending (Missing tax info)', + ] ); + } ); + it( 'configures the Clicks export with parent rows in hierarchy order', () => { const group = { id: 'social', clickedUrl: 'Social', isGroup: true, clicks: 10 }; const lowerRow = { diff --git a/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges index 479b0747984c..cbd15bb5056c 100644 --- a/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges +++ b/projects/plugins/jetpack/changelog/add-premium-analytics-earnings-status-badges @@ -1,4 +1,4 @@ Significance: patch Type: enhancement -Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. +Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. Negative amounts in the widget are no longer red; only the badge carries colour. diff --git a/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges index 0be6f6f0f96b..0f223c62a91b 100644 --- a/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges +++ b/projects/plugins/premium-analytics/changelog/add-premium-analytics-earnings-status-badges @@ -1,4 +1,4 @@ Significance: patch Type: changed -Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. +Ads: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. Negative amounts in the widget are no longer red; only the badge carries colour. diff --git a/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges b/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges index 10239b9209c3..4dc3b26eb0f7 100644 --- a/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges +++ b/projects/plugins/wpcomsh/changelog/add-premium-analytics-earnings-status-badges @@ -1,4 +1,4 @@ Significance: patch Type: changed -Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. +Premium Analytics: Show payment status as a badge in the Earnings History widget, and shorten the pending statuses to one word with the reason beside them. Negative amounts in the widget are no longer red; only the badge carries colour.