Skip to content

Commit f47358b

Browse files
yashwinclaude
andcommitted
A4A MSD: address overview cards review feedback
- Use the Heading component for the card titles. - Rename useRouterLink to shouldUseRouterLink; the prefix read as a hook. - Lock the earning cards for members without the referrals capability, which also gates the WooPayments routes today. - Stop counting attached licenses without a blog as a store. - Keep the store-count queries from refetching on focus, matching the WooPayments dashboard, and describe the de-duplication accurately. - Hide the event card once the event's end date passes. - Let SummaryButton accept target and rel, and replace the helpful-links click interception with real new-tab anchors. - Note why the earning cards don't render through OverviewCard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 629f7b4 commit f47358b

12 files changed

Lines changed: 81 additions & 57 deletions

File tree

client/dashboard/agency/overview/event-card.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
Button,
3+
__experimentalHeading as Heading,
34
__experimentalHStack as HStack,
45
__experimentalVStack as VStack,
56
} from '@wordpress/components';
@@ -15,7 +16,7 @@ interface EventCardProps {
1516
}
1617

1718
export default function EventCard( { recordTracksEvent }: EventCardProps ) {
18-
if ( ! FEATURED_EVENT ) {
19+
if ( ! FEATURED_EVENT || new Date() >= new Date( FEATURED_EVENT.endsAt ) ) {
1920
return null;
2021
}
2122

@@ -31,9 +32,9 @@ export default function EventCard( { recordTracksEvent }: EventCardProps ) {
3132
<Text variant="muted" size={ 11 } weight={ 500 } lineHeight="16px" upperCase>
3233
{ when }
3334
</Text>
34-
<Text size={ 15 } weight={ 500 } lineHeight="20px" as="h2">
35+
<Heading level={ 2 } size={ 15 } weight={ 500 } lineHeight="20px">
3536
{ title }
36-
</Text>
37+
</Heading>
3738
<Text variant="muted" size={ 12 } lineHeight="16px">
3839
{ subtitle }
3940
</Text>

client/dashboard/agency/overview/events.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ export interface FeaturedEvent {
1616
description: string[];
1717
ctaLabel: string;
1818
url: string;
19+
/** ISO date the card stops showing itself, typically the day after the event. */
20+
endsAt: string;
1921
}
2022

2123
/**
2224
* The event promoted on the agency overview, curated by hand.
2325
*
24-
* Replace the whole object when the next event comes round, and set it to `null`
25-
* in between — the card hides itself rather than advertising a past event. The
26+
* Replace the whole object when the next event comes round — the card hides
27+
* itself once `endsAt` passes rather than advertising a past event. The
2628
* legacy list lives in client/a8c-for-agencies/sections/overview/body/events.
2729
*/
2830
export const FEATURED_EVENT: FeaturedEvent | null = {
@@ -42,4 +44,5 @@ export const FEATURED_EVENT: FeaturedEvent | null = {
4244
],
4345
ctaLabel: __( 'Get your spot!' ),
4446
url: 'https://us.wordcamp.org/2026/',
47+
endsAt: '2026-08-20',
4548
};

client/dashboard/agency/overview/helpful-links-card.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface HelpfulLink {
88
id: string;
99
label: string;
1010
href?: string;
11-
/** Opens in a new tab. `SummaryButton` has no `target`, so the click is handled here. */
11+
/** Opens the link in a new tab. */
1212
isExternal?: boolean;
1313
onClick?: () => void;
1414
}
@@ -32,23 +32,13 @@ export default function HelpfulLinksCard( { links, recordTracksEvent }: HelpfulL
3232
)
3333
}
3434
href={ link.href }
35+
target={ link.isExternal ? '_blank' : undefined }
36+
rel={ link.isExternal ? 'noreferrer' : undefined }
3537
showArrow={ false }
36-
onClick={ ( event ) => {
38+
onClick={ () => {
3739
recordTracksEvent?.( 'calypso_a4a_overview_helpful_link_click', {
3840
link_id: link.id,
3941
} );
40-
// Leave modifier clicks to the browser so they keep their native
41-
// new-tab and new-window behavior.
42-
if (
43-
link.isExternal &&
44-
link.href &&
45-
! event.metaKey &&
46-
! event.ctrlKey &&
47-
! event.shiftKey
48-
) {
49-
event.preventDefault();
50-
window.open( link.href, '_blank', 'noreferrer' );
51-
}
5242
link.onClick?.();
5343
} }
5444
/>

client/dashboard/agency/overview/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export default function AgencyOverview() {
6868
tierId={ agency.tier?.id }
6969
influencedRevenue={ agency.influenced_revenue ?? 0 }
7070
approvalStatus={ approvalStatus }
71+
capabilities={ agency.user?.capabilities }
7172
links={ {
7273
tiers: '/agency/tiers',
7374
referrals: '/earn/referrals',

client/dashboard/agency/overview/overview-content.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import type { HelpfulLink } from './helpful-links-card';
1111
import type { AgencyTierType, RecordTracksEvent } from '../tiers/types';
1212
import type { AgencyApprovalStatus } from '@automattic/api-core';
1313

14+
// The WooPayments routes gate on the referrals capability too (see
15+
// a8c-for-agencies/lib/permission.ts), so one capability locks both earning cards.
16+
const REFERRALS_CAPABILITY = 'a4a_read_referrals';
17+
1418
export interface AgencyOverviewLinks {
1519
tiers: string;
1620
referrals: string;
@@ -25,8 +29,10 @@ export interface AgencyOverviewContentProps {
2529
influencedRevenue: number;
2630
/** Agencies predating the field report an empty string and are treated as approved. */
2731
approvalStatus?: AgencyApprovalStatus | '';
32+
/** The current user's agency capabilities; the earning cards lock without referrals access. */
33+
capabilities?: string[];
2834
links: AgencyOverviewLinks;
29-
useRouterLink?: boolean;
35+
shouldUseRouterLink?: boolean;
3036
onScheduleCall?: () => void;
3137
isSchedulingCall?: boolean;
3238
onRelaunchTour?: () => void;
@@ -43,8 +49,9 @@ export default function AgencyOverviewContent( {
4349
tierId,
4450
influencedRevenue,
4551
approvalStatus,
52+
capabilities,
4653
links,
47-
useRouterLink,
54+
shouldUseRouterLink,
4855
onScheduleCall,
4956
isSchedulingCall,
5057
onRelaunchTour,
@@ -54,8 +61,9 @@ export default function AgencyOverviewContent( {
5461
const spacing = isSmallViewport ? 4 : 6;
5562
const isPending = approvalStatus === 'pending';
5663
const isRejected = approvalStatus === 'rejected';
64+
const canAccessEarnings = ! capabilities || capabilities.includes( REFERRALS_CAPABILITY );
5765
// A rejection is already spelled out by the tier card, so only pending accounts get the note.
58-
const isLocked = isPending || isRejected;
66+
const isLocked = isPending || isRejected || ! canAccessEarnings;
5967
const lockedNote = isPending ? __( 'Unlocks when your account is activated' ) : undefined;
6068

6169
return (
@@ -73,7 +81,7 @@ export default function AgencyOverviewContent( {
7381
tierId={ tierId }
7482
influencedRevenue={ influencedRevenue }
7583
tiersHref={ links.tiers }
76-
useRouterLink={ useRouterLink }
84+
shouldUseRouterLink={ shouldUseRouterLink }
7785
onScheduleCall={ onScheduleCall }
7886
isSchedulingCall={ isSchedulingCall }
7987
recordTracksEvent={ recordTracksEvent }
@@ -84,15 +92,15 @@ export default function AgencyOverviewContent( {
8492
locked={ isLocked }
8593
lockedNote={ lockedNote }
8694
referralsHref={ links.referrals }
87-
useRouterLink={ useRouterLink }
95+
shouldUseRouterLink={ shouldUseRouterLink }
8896
recordTracksEvent={ recordTracksEvent }
8997
/>
9098
<WooPaymentsRevenueCard
9199
agencyId={ agencyId }
92100
locked={ isLocked }
93101
lockedNote={ lockedNote }
94102
woopaymentsHref={ links.woopayments }
95-
useRouterLink={ useRouterLink }
103+
shouldUseRouterLink={ shouldUseRouterLink }
96104
recordTracksEvent={ recordTracksEvent }
97105
/>
98106
</VStack>

client/dashboard/agency/overview/overview-link-button.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface OverviewLinkButtonProps {
1212
* Set to false in apps without the dashboard's TanStack Router, so the button
1313
* renders a plain anchor for the host app's own router to pick up.
1414
*/
15-
useRouterLink?: boolean;
15+
shouldUseRouterLink?: boolean;
1616
}
1717

1818
export default function OverviewLinkButton( {
@@ -21,9 +21,9 @@ export default function OverviewLinkButton( {
2121
variant,
2222
size,
2323
onClick,
24-
useRouterLink = true,
24+
shouldUseRouterLink = true,
2525
}: OverviewLinkButtonProps ) {
26-
if ( useRouterLink ) {
26+
if ( shouldUseRouterLink ) {
2727
return (
2828
<RouterLinkButton to={ href } variant={ variant } size={ size } onClick={ onClick }>
2929
{ children }

client/dashboard/agency/overview/referral-earnings-card.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { formatCurrency, formatNumber } from '@automattic/number-formatters';
33
import { Badge } from '@automattic/ui';
44
import { useQuery } from '@tanstack/react-query';
55
import {
6+
__experimentalHeading as Heading,
67
__experimentalHStack as HStack,
78
__experimentalVStack as VStack,
89
} from '@wordpress/components';
@@ -21,28 +22,28 @@ interface ReferralEarningsCardProps {
2122
locked?: boolean;
2223
lockedNote?: string;
2324
referralsHref: string;
24-
useRouterLink?: boolean;
25+
shouldUseRouterLink?: boolean;
2526
recordTracksEvent?: RecordTracksEvent;
2627
}
2728

2829
function ReferralEarningsEmptyState( {
2930
locked,
3031
lockedNote,
3132
referralsHref,
32-
useRouterLink,
33+
shouldUseRouterLink,
3334
recordTracksEvent,
3435
}: {
3536
locked?: boolean;
3637
lockedNote?: string;
3738
referralsHref: string;
38-
useRouterLink?: boolean;
39+
shouldUseRouterLink?: boolean;
3940
recordTracksEvent?: RecordTracksEvent;
4041
} ) {
4142
return (
4243
<VStack spacing={ 4 }>
43-
<Text size={ 15 } weight={ 500 } lineHeight="20px" as="h2">
44+
<Heading level={ 2 } size={ 15 } weight={ 500 } lineHeight="20px">
4445
{ __( 'Referral earnings' ) }
45-
</Text>
46+
</Heading>
4647
<Text variant="muted" lineHeight="20px">
4748
{ __(
4849
'Earn 20% recurring commissions on hosting and 50% on plugins when you refer clients through your dashboard.'
@@ -59,7 +60,7 @@ function ReferralEarningsEmptyState( {
5960
size="compact"
6061
variant="secondary"
6162
href={ referralsHref }
62-
useRouterLink={ useRouterLink }
63+
shouldUseRouterLink={ shouldUseRouterLink }
6364
onClick={ () =>
6465
recordTracksEvent?.( 'calypso_a4a_overview_referrals_make_referral_click' )
6566
}
@@ -72,12 +73,14 @@ function ReferralEarningsEmptyState( {
7273
);
7374
}
7475

76+
// Not OverviewCard: it renders the whole card as a single link, while this card
77+
// needs inline actions and non-interactive stat rows.
7578
export default function ReferralEarningsCard( {
7679
agencyId,
7780
locked,
7881
lockedNote,
7982
referralsHref,
80-
useRouterLink,
83+
shouldUseRouterLink,
8184
recordTracksEvent,
8285
}: ReferralEarningsCardProps ) {
8386
const { data: referrals = [], isLoading: isLoadingReferrals } = useQuery( {
@@ -102,7 +105,7 @@ export default function ReferralEarningsCard( {
102105
locked={ locked }
103106
lockedNote={ lockedNote }
104107
referralsHref={ referralsHref }
105-
useRouterLink={ useRouterLink }
108+
shouldUseRouterLink={ shouldUseRouterLink }
106109
recordTracksEvent={ recordTracksEvent }
107110
/>
108111
</CardBody>
@@ -118,9 +121,9 @@ export default function ReferralEarningsCard( {
118121
<Card>
119122
<CardBody>
120123
<VStack spacing={ 4 }>
121-
<Text size={ 15 } weight={ 500 } lineHeight="20px" as="h2">
124+
<Heading level={ 2 } size={ 15 } weight={ 500 } lineHeight="20px">
122125
{ __( 'Referral earnings' ) }
123-
</Text>
126+
</Heading>
124127
<HStack spacing={ 2 } justify="flex-start" alignment="baseline" expanded={ false }>
125128
<Text size={ 20 } weight={ 500 } lineHeight="24px">
126129
{ isLoading ? (
@@ -152,7 +155,7 @@ export default function ReferralEarningsCard( {
152155
size="compact"
153156
variant="secondary"
154157
href={ referralsHref }
155-
useRouterLink={ useRouterLink }
158+
shouldUseRouterLink={ shouldUseRouterLink }
156159
onClick={ () =>
157160
recordTracksEvent?.( 'calypso_a4a_overview_referrals_view_details_click' )
158161
}

client/dashboard/agency/overview/tier-overview-card.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface TierOverviewCardProps {
1717
tierId?: AgencyTierType;
1818
influencedRevenue: number;
1919
tiersHref: string;
20-
useRouterLink?: boolean;
20+
shouldUseRouterLink?: boolean;
2121
onScheduleCall?: () => void;
2222
isSchedulingCall?: boolean;
2323
recordTracksEvent?: RecordTracksEvent;
@@ -51,7 +51,7 @@ export default function TierOverviewCard( {
5151
tierId,
5252
influencedRevenue,
5353
tiersHref,
54-
useRouterLink,
54+
shouldUseRouterLink,
5555
onScheduleCall,
5656
isSchedulingCall,
5757
recordTracksEvent,
@@ -72,7 +72,7 @@ export default function TierOverviewCard( {
7272
heading={ tier.name }
7373
description={ content.description }
7474
link={ tiersHref }
75-
useRouterLink={ useRouterLink }
75+
shouldUseRouterLink={ shouldUseRouterLink }
7676
tracksId="agency-overview-tier"
7777
bottom={
7878
<VStack spacing={ 4 }>

client/dashboard/agency/overview/use-woopayments-store-count.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@ import {
66
import { useQuery } from '@tanstack/react-query';
77

88
/**
9-
* Counts the agency's WooPayments stores the way the WooPayments dashboard does:
10-
* attached WooPayments licenses plus sites running the plugin, de-duplicated by blog.
9+
* Counts the agency's WooPayments stores: attached WooPayments licenses plus
10+
* sites running the plugin, de-duplicated by blog — matching the site list the
11+
* WooPayments dashboard renders.
1112
*/
1213
export default function useWooPaymentsStoreCount( agencyId: number, enabled = true ) {
1314
const isEnabled = !! agencyId && enabled;
1415

1516
const { data: licenseBlogIds, isLoading: isLoadingLicenses } = useQuery( {
1617
...wooPaymentsLicensesQuery( agencyId ),
1718
enabled: isEnabled,
18-
select: ( licenses ) => licenses.map( ( license ) => license.blog_id ?? 0 ),
19+
refetchOnWindowFocus: false,
20+
// A license can be attached without a blog; folding nulls into a
21+
// placeholder id would count a phantom store.
22+
select: ( licenses ) =>
23+
licenses.flatMap( ( license ) => ( license.blog_id ? [ license.blog_id ] : [] ) ),
1924
} );
2025

2126
const { data: pluginBlogIds, isLoading: isLoadingSites } = useQuery( {
2227
...agencySitesWithPluginsQuery( agencyId, [ WOOPAYMENTS_PLUGIN ] ),
2328
enabled: isEnabled,
29+
refetchOnWindowFocus: false,
2430
select: ( sites ) => sites.map( ( site ) => site.blog_id ),
2531
} );
2632

0 commit comments

Comments
 (0)