Skip to content

Commit f058dce

Browse files
authored
Purchase Management: Show second DataViews table for membership purchases (#103345)
* Render membership subscriptions in separate DataView table * Pull data loading checkout out of render * Render upsell for users with sites and no purchases * Create membership focused fields * Implement membership data fields in hook * Create membership DataView * Update imports for rendering no-sites view * Push local linting fixes * Prevent forcing type on string
1 parent 977b217 commit f058dce

File tree

5 files changed

+213
-32
lines changed

5 files changed

+213
-32
lines changed

client/me/purchases/membership-item/index.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { MembershipSubscription } from 'calypso/lib/purchases/types';
88

99
import 'calypso/me/purchases/style.scss';
1010

11-
const MembershipTerms = ( { subscription }: { subscription: MembershipSubscription } ) => {
11+
export const MembershipTerms = ( { subscription }: { subscription: MembershipSubscription } ) => {
1212
const translate = useTranslate();
1313
const moment = useLocalizedMoment();
1414

@@ -34,7 +34,7 @@ const MembershipTerms = ( { subscription }: { subscription: MembershipSubscripti
3434
);
3535
};
3636

37-
const SiteLink = ( { subscription }: { subscription: MembershipSubscription } ) => {
37+
export const SiteLink = ( { subscription }: { subscription: MembershipSubscription } ) => {
3838
const translate = useTranslate();
3939
const siteUrl = subscription.site_url.replace( /^https?:\/\//, '' );
4040

@@ -59,7 +59,7 @@ const SiteLink = ( { subscription }: { subscription: MembershipSubscription } )
5959
);
6060
};
6161

62-
const MembershipType = ( { subscription }: { subscription: MembershipSubscription } ) => {
62+
export const MembershipType = ( { subscription }: { subscription: MembershipSubscription } ) => {
6363
const translate = useTranslate();
6464

6565
if ( subscription.end_date === null ) {
@@ -85,7 +85,7 @@ const MembershipType = ( { subscription }: { subscription: MembershipSubscriptio
8585
);
8686
};
8787

88-
const Icon = ( { subscription }: { subscription: MembershipSubscription } ) => {
88+
export const Icon = ( { subscription }: { subscription: MembershipSubscription } ) => {
8989
const [ hasError, setErrors ] = useState( false );
9090
const [ site, setSite ] = useState< { icon?: { ico: string } } >();
9191
const siteId = subscription.site_id;

client/me/purchases/purchases-list-in-dataviews/hooks/use-field-definitions.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { useTranslate } from 'i18n-calypso';
22
import { useMemo } from 'react';
33
import { useLocalizedMoment } from 'calypso/components/localized-moment';
44
import { useStoredPaymentMethods } from 'calypso/my-sites/checkout/src/hooks/use-stored-payment-methods';
5-
import { getPurchasesFieldDefinitions } from '../purchases-data-field';
5+
import {
6+
getPurchasesFieldDefinitions,
7+
getMembershipsFieldDefinitions,
8+
} from '../purchases-data-field';
69

710
export function usePurchasesFieldDefinitions() {
811
const translate = useTranslate();
@@ -18,3 +21,14 @@ export function usePurchasesFieldDefinitions() {
1821
return fieldDefinitions;
1922
}, [ translate, moment, paymentMethods ] );
2023
}
24+
25+
export function useMembershipsFieldDefinitions() {
26+
const translate = useTranslate();
27+
28+
return useMemo( () => {
29+
const fieldDefinitions = getMembershipsFieldDefinitions( {
30+
translate,
31+
} );
32+
return fieldDefinitions;
33+
}, [ translate ] );
34+
}

client/me/purchases/purchases-list-in-dataviews/index.tsx

+68-26
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
import { recordTracksEvent } from '@automattic/calypso-analytics';
2+
import { CompactCard } from '@automattic/components';
13
import { SiteDetails } from '@automattic/data-stores';
24
import { isValueTruthy } from '@automattic/wpcom-checkout';
3-
import { LocalizeProps, localize } from 'i18n-calypso';
5+
import { LocalizeProps, localize, useTranslate } from 'i18n-calypso';
46
import { Component } from 'react';
57
import { connect } from 'react-redux';
8+
import noSitesIllustration from 'calypso/assets/images/illustrations/illustration-nosites.svg';
69
import QueryConciergeInitial from 'calypso/components/data/query-concierge-initial';
710
import QueryMembershipsSubscriptions from 'calypso/components/data/query-memberships-subscriptions';
811
import QueryUserPurchases from 'calypso/components/data/query-user-purchases';
12+
import EmptyContent from 'calypso/components/empty-content';
913
import NoSitesMessage from 'calypso/components/empty-content/no-sites-message';
1014
import InlineSupportLink from 'calypso/components/inline-support-link';
1115
import Main from 'calypso/components/main';
1216
import NavigationHeader from 'calypso/components/navigation-header';
1317
import PageViewTracker from 'calypso/lib/analytics/page-view-tracker';
14-
import { getSubscriptionsBySite } from 'calypso/lib/purchases';
18+
import TrackComponentView from 'calypso/lib/analytics/track-component-view';
1519
import { MembershipSubscription, Purchase } from 'calypso/lib/purchases/types';
1620
import { PurchaseListConciergeBanner } from 'calypso/me/purchases/purchases-list/purchase-list-concierge-banner';
1721
import PurchasesNavigation from 'calypso/me/purchases/purchases-navigation';
@@ -34,9 +38,8 @@ import getConciergeUserBlocked from 'calypso/state/selectors/get-concierge-user-
3438
import getSites from 'calypso/state/selectors/get-sites';
3539
import { getSiteId } from 'calypso/state/sites/selectors';
3640
import { AppState } from 'calypso/types';
37-
import MembershipSite from '../membership-site';
3841
import PurchasesSite from '../purchases-site';
39-
import { PurchasesDataViews } from './purchases-data-view';
42+
import { PurchasesDataViews, MembershipsDataViews } from './purchases-data-view';
4043
import './style.scss';
4144

4245
export interface PurchasesListProps {
@@ -55,17 +58,35 @@ export interface PurchasesListConnectedProps {
5558
siteId: number | null;
5659
}
5760

58-
class PurchasesListDataView extends Component<
59-
PurchasesListProps & PurchasesListConnectedProps & WithStoredPaymentMethodsProps & LocalizeProps
60-
> {
61-
isDataLoading() {
62-
if ( this.props.isFetchingUserPurchases && ! this.props.hasLoadedUserPurchasesFromServer ) {
63-
return true;
64-
}
61+
function MembershipSubscriptions( {
62+
memberships,
63+
}: {
64+
memberships: Array< MembershipSubscription >;
65+
} ) {
66+
const translate = useTranslate();
6567

66-
return ! this.props.sites.length && ! this.props.subscriptions.length;
68+
if ( ! memberships.length ) {
69+
return null;
6770
}
6871

72+
return <MembershipsDataViews memberships={ memberships } translate={ translate } />;
73+
}
74+
75+
function isDataLoading( {
76+
isFetchingUserPurchases,
77+
hasLoadedUserPurchasesFromServer,
78+
}: {
79+
hasLoadedUserPurchasesFromServer: boolean;
80+
isFetchingUserPurchases: boolean;
81+
} ) {
82+
if ( isFetchingUserPurchases && ! hasLoadedUserPurchasesFromServer ) {
83+
return true;
84+
}
85+
}
86+
87+
class PurchasesListDataView extends Component<
88+
PurchasesListProps & PurchasesListConnectedProps & WithStoredPaymentMethodsProps & LocalizeProps
89+
> {
6990
renderConciergeBanner() {
7091
const { nextAppointment, availableSessions, isUserBlocked } = this.props;
7192
return (
@@ -77,23 +98,17 @@ class PurchasesListDataView extends Component<
7798
);
7899
}
79100

80-
renderMembershipSubscriptions() {
81-
const { subscriptions } = this.props;
82-
83-
if ( ! subscriptions.length || this.isDataLoading() ) {
84-
return null;
85-
}
86-
87-
return getSubscriptionsBySite( subscriptions ).map( ( site ) => (
88-
<MembershipSite site={ site } key={ site.id } />
89-
) );
90-
}
91-
92101
render() {
93102
const { purchases, sites, translate, subscriptions } = this.props;
103+
const commonEventProps = { context: 'me' };
94104
let content;
95105

96-
if ( this.isDataLoading() ) {
106+
if (
107+
isDataLoading( {
108+
isFetchingUserPurchases: this.props.isFetchingUserPurchases,
109+
hasLoadedUserPurchasesFromServer: this.props.hasLoadedUserPurchasesFromServer,
110+
} )
111+
) {
97112
content = <PurchasesSite isPlaceholder />;
98113
}
99114

@@ -112,6 +127,33 @@ class PurchasesListDataView extends Component<
112127
</Main>
113128
);
114129
}
130+
content = (
131+
<>
132+
{ this.renderConciergeBanner() }
133+
<CompactCard className="purchases-list__no-content">
134+
<>
135+
<TrackComponentView
136+
eventName="calypso_no_purchases_upgrade_nudge_impression"
137+
eventProperties={ commonEventProps }
138+
/>
139+
{ /* this.renderPurchasesByOtherAdminsNotice() to-do: render this as functional component */ }
140+
<EmptyContent
141+
title={ translate( 'Looking to upgrade?' ) }
142+
line={ translate(
143+
'Our plans give your site the power to thrive. ' +
144+
'Find the plan that works for you.'
145+
) }
146+
action={ translate( 'Upgrade now' ) }
147+
actionURL="/plans"
148+
illustration={ noSitesIllustration }
149+
actionCallback={ () => {
150+
recordTracksEvent( 'calypso_no_purchases_upgrade_nudge_click', commonEventProps );
151+
} }
152+
/>
153+
</>
154+
</CompactCard>
155+
</>
156+
);
115157
}
116158

117159
return (
@@ -134,7 +176,7 @@ class PurchasesListDataView extends Component<
134176
/>
135177
<PurchasesNavigation section="activeUpgrades" />
136178
{ content }
137-
{ this.renderMembershipSubscriptions() }
179+
<MembershipSubscriptions memberships={ subscriptions } />
138180
<QueryConciergeInitial />
139181
</Main>
140182
);

client/me/purchases/purchases-list-in-dataviews/purchases-data-field.tsx

+79
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { LocalizeProps } from 'i18n-calypso';
44
import { useLocalizedMoment } from 'calypso/components/localized-moment';
55
import { StoredPaymentMethod } from 'calypso/lib/checkout/payment-methods';
66
import { getDisplayName, isRenewing } from 'calypso/lib/purchases';
7+
import { MembershipSubscription } from 'calypso/lib/purchases/types';
78
import { useSelector } from 'calypso/state';
89
import { getSite } from 'calypso/state/sites/selectors';
10+
import { Icon, MembershipType, MembershipTerms } from '../membership-item';
911
import {
1012
PurchaseItemSiteIcon,
1113
PurchaseItemProduct,
@@ -172,3 +174,80 @@ export function getPurchasesFieldDefinitions( {
172174
},
173175
];
174176
}
177+
178+
export function getMembershipsFieldDefinitions( {
179+
translate,
180+
}: {
181+
translate: LocalizeProps[ 'translate' ];
182+
} ): Fields< MembershipSubscription > {
183+
return [
184+
{
185+
id: 'site',
186+
label: translate( 'Site' ),
187+
type: 'text',
188+
enableGlobalSearch: true,
189+
enableSorting: true,
190+
enableHiding: false,
191+
filterBy: {
192+
operators: [ 'is' as Operator ],
193+
},
194+
// Filter by site ID
195+
getValue: ( { item }: { item: MembershipSubscription } ) => {
196+
return item.site_id;
197+
},
198+
// Render the site icon
199+
render: ( { item }: { item: MembershipSubscription } ) => {
200+
return (
201+
<div className="membership-item__site purchases-layout__site">
202+
<Icon subscription={ item } />
203+
</div>
204+
);
205+
},
206+
},
207+
{
208+
id: 'product',
209+
label: translate( 'Product' ),
210+
type: 'text',
211+
enableGlobalSearch: true,
212+
enableSorting: true,
213+
enableHiding: false,
214+
filterBy: {
215+
operators: [ 'is' as Operator ],
216+
},
217+
getValue: ( { item }: { item: MembershipSubscription } ) => {
218+
return item.product_id;
219+
},
220+
render: ( { item }: { item: MembershipSubscription } ) => {
221+
return (
222+
<div className="membership-item__information purchase-item__information purchases-layout__information">
223+
<div className="membership-item__title purchase-item__title">{ item.title }</div>
224+
<div className="membership-item__purchase-type purchase-item__purchase-type">
225+
<MembershipType subscription={ item } />
226+
</div>
227+
</div>
228+
);
229+
},
230+
},
231+
{
232+
id: 'status',
233+
label: translate( 'Status' ),
234+
type: 'text',
235+
enableGlobalSearch: true,
236+
enableSorting: true,
237+
enableHiding: false,
238+
filterBy: {
239+
operators: [ 'is' as Operator ],
240+
},
241+
getValue: ( { item }: { item: MembershipSubscription } ) => {
242+
return item.end_date;
243+
},
244+
render: ( { item }: { item: MembershipSubscription } ) => {
245+
return (
246+
<div className="membership-item__status purchase-item__status purchases-layout__status">
247+
<MembershipTerms subscription={ item } />
248+
</div>
249+
);
250+
},
251+
},
252+
];
253+
}

client/me/purchases/purchases-list-in-dataviews/purchases-data-view.tsx

+47-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Card } from '@automattic/components';
22
import { Purchases } from '@automattic/data-stores';
33
import { DataViews, View } from '@wordpress/dataviews';
44
import { LocalizeProps } from 'i18n-calypso';
5-
import { usePurchasesFieldDefinitions } from './hooks/use-field-definitions';
5+
import { MembershipSubscription } from 'calypso/lib/purchases/types';
6+
import {
7+
usePurchasesFieldDefinitions,
8+
useMembershipsFieldDefinitions,
9+
} from './hooks/use-field-definitions';
610

711
export const purchasesDataView = {
812
type: 'table',
@@ -45,3 +49,45 @@ export function PurchasesDataViews( props: {
4549
</Card>
4650
);
4751
}
52+
53+
export const membershipDataView = {
54+
type: 'table',
55+
page: 1,
56+
perPage: 5,
57+
titleField: 'site',
58+
fields: [ 'product', 'status' ],
59+
sort: {
60+
field: 'site',
61+
direction: 'desc',
62+
},
63+
layout: {},
64+
} as View;
65+
66+
export function MembershipsDataViews( props: {
67+
memberships: MembershipSubscription[];
68+
translate: LocalizeProps[ 'translate' ];
69+
} ) {
70+
const { memberships } = props;
71+
const onChangeView = () => {
72+
return;
73+
};
74+
75+
const getItemId = ( item: MembershipSubscription ) => {
76+
return item.ID;
77+
};
78+
const membershipsDataFields = useMembershipsFieldDefinitions();
79+
return (
80+
<Card id="purchases-list" className="section-content" tagName="section">
81+
<DataViews
82+
data={ memberships }
83+
fields={ membershipsDataFields }
84+
view={ membershipDataView }
85+
onChangeView={ onChangeView }
86+
defaultLayouts={ { table: {} } }
87+
actions={ undefined }
88+
getItemId={ getItemId }
89+
paginationInfo={ { totalItems: 100, totalPages: 10 } }
90+
/>
91+
</Card>
92+
);
93+
}

0 commit comments

Comments
 (0)