Skip to content

Commit b602ce8

Browse files
authored
Purchase Management: Add manage button to dataviews purchase (#103789)
* Add "Manage this purchase" link to PurchasesDataViews * Fix dataview width style so edges are visible * Switch to automattic/dataviews package * Hide status and payment-method fields when at mobile width * Fix display of site field and hide at small width * Prevent wide product cell from pushing actions off-screen * Hide manage purchase link for subscriptions without site or id * Collapse membershipDataView fields at mobile width also * Add actions for MembershipsDataViews * Fix filtering to only include payment method existence * Do not show "Add payment method" for expired purchases * Allow sorting payment method (removes filtering) * Allow sorting by date * Remove unused translate prop
1 parent 34256bf commit b602ce8

File tree

5 files changed

+123
-57
lines changed

5 files changed

+123
-57
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,7 @@ export function PurchaseItemPaymentMethod( {
638638

639639
if (
640640
purchase.isAutoRenewEnabled &&
641+
! isExpired( purchase ) &&
641642
( ! hasPaymentMethod( purchase ) || isPaidWithCredits( purchase ) ) &&
642643
! isPartnerPurchase( purchase ) &&
643644
! isAkismetFreeProduct( purchase )

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { recordTracksEvent } from '@automattic/calypso-analytics';
22
import { CompactCard } from '@automattic/components';
33
import { SiteDetails } from '@automattic/data-stores';
44
import { isValueTruthy } from '@automattic/wpcom-checkout';
5-
import { LocalizeProps, localize, useTranslate } from 'i18n-calypso';
5+
import { LocalizeProps, localize } from 'i18n-calypso';
66
import { Component } from 'react';
77
import { connect } from 'react-redux';
88
import noSitesIllustration from 'calypso/assets/images/illustrations/illustration-nosites.svg';
@@ -63,13 +63,11 @@ function MembershipSubscriptions( {
6363
}: {
6464
memberships: Array< MembershipSubscription >;
6565
} ) {
66-
const translate = useTranslate();
67-
6866
if ( ! memberships.length ) {
6967
return null;
7068
}
7169

72-
return <MembershipsDataViews memberships={ memberships } translate={ translate } />;
70+
return <MembershipsDataViews memberships={ memberships } />;
7371
}
7472

7573
function isDataLoading( {
@@ -113,7 +111,7 @@ class PurchasesListDataView extends Component<
113111
}
114112

115113
if ( purchases && purchases.length ) {
116-
content = <PurchasesDataViews purchases={ purchases } translate={ translate } />;
114+
content = <PurchasesDataViews purchases={ purchases } />;
117115
}
118116

119117
if ( purchases && ! purchases.length && ! subscriptions.length ) {

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

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Purchases, SiteDetails } from '@automattic/data-stores';
2-
import { Fields, Operator } from '@wordpress/dataviews';
2+
import { Fields } from '@wordpress/dataviews';
33
import { LocalizeProps } from 'i18n-calypso';
44
import { useLocalizedMoment } from 'calypso/components/localized-moment';
55
import { StoredPaymentMethod } from 'calypso/lib/checkout/payment-methods';
6-
import { getDisplayName, isRenewing, purchaseType } from 'calypso/lib/purchases';
6+
import { getDisplayName, isExpired, isRenewing, purchaseType } from 'calypso/lib/purchases';
77
import { MembershipSubscription } from 'calypso/lib/purchases/types';
88
import { useSelector } from 'calypso/state';
99
import { getSite } from 'calypso/state/sites/selectors';
@@ -94,9 +94,6 @@ export function getPurchasesFieldDefinitions( {
9494
enableGlobalSearch: true,
9595
enableSorting: true,
9696
enableHiding: false,
97-
filterBy: {
98-
operators: [ 'is' as Operator ],
99-
},
10097
getValue: ( { item }: { item: Purchases.Purchase } ) => {
10198
return item.siteName;
10299
},
@@ -113,9 +110,6 @@ export function getPurchasesFieldDefinitions( {
113110
enableGlobalSearch: true,
114111
enableSorting: true,
115112
enableHiding: false,
116-
filterBy: {
117-
operators: [ 'is' as Operator ],
118-
},
119113
getValue: ( { item }: { item: Purchases.Purchase } ) => {
120114
// Render a bunch of things to make this easily searchable.
121115
const site = sites?.[ item.siteId ];
@@ -153,11 +147,13 @@ export function getPurchasesFieldDefinitions( {
153147
enableGlobalSearch: true,
154148
enableSorting: true,
155149
enableHiding: false,
156-
filterBy: {
157-
operators: [ 'is' as Operator ],
158-
},
159150
getValue: ( { item }: { item: Purchases.Purchase } ) => {
160-
return item.expiryStatus;
151+
if ( isExpired( item ) ) {
152+
// Prefix expired items with a z so they sort to the end of the list.
153+
return 'zzz ' + item.expiryStatus + ' ' + item.expiryDate;
154+
}
155+
// Include date in value to sort similar expiries together.
156+
return item.expiryDate + ' ' + item.expiryStatus;
161157
},
162158
render: ( { item }: { item: Purchases.Purchase } ) => {
163159
return (
@@ -172,11 +168,14 @@ export function getPurchasesFieldDefinitions( {
172168
enableGlobalSearch: true,
173169
enableSorting: true,
174170
enableHiding: false,
175-
filterBy: {
176-
operators: [ 'is' as Operator ],
177-
},
178171
getValue: ( { item }: { item: Purchases.Purchase } ) => {
179-
return item.payment.storedDetailsId ?? '';
172+
// Allows sorting by card number or payment partner (eg: `type === 'paypal'`).
173+
return isExpired( item )
174+
? // Do not return card number for expired purchases because it
175+
// will not be displayed so it will look wierd if we sort
176+
// expired purchases with active ones that have the same card.
177+
'expired'
178+
: item.payment.creditCard?.number ?? item.payment.type ?? 'no-payment-method';
180179
},
181180
render: ( { item }: { item: Purchases.Purchase } ) => {
182181
let isBackupMethodAvailable = false;
@@ -213,11 +212,8 @@ export function getMembershipsFieldDefinitions( {
213212
label: translate( 'Site' ),
214213
type: 'text',
215214
enableGlobalSearch: true,
216-
enableSorting: true,
215+
enableSorting: false,
217216
enableHiding: false,
218-
filterBy: {
219-
operators: [ 'is' as Operator ],
220-
},
221217
getValue: ( { item }: { item: MembershipSubscription } ) => {
222218
return item.site_id + ' ' + item.site_title + ' ' + item.site_url;
223219
},
@@ -237,11 +233,8 @@ export function getMembershipsFieldDefinitions( {
237233
enableGlobalSearch: true,
238234
enableSorting: true,
239235
enableHiding: false,
240-
filterBy: {
241-
operators: [ 'is' as Operator ],
242-
},
243236
getValue: ( { item }: { item: MembershipSubscription } ) => {
244-
return item.title;
237+
return item.title + ' ' + item.site_title + ' ' + item.site_url;
245238
},
246239
render: ( { item }: { item: MembershipSubscription } ) => {
247240
return (
@@ -259,11 +252,8 @@ export function getMembershipsFieldDefinitions( {
259252
label: translate( 'Status' ),
260253
type: 'text',
261254
enableGlobalSearch: true,
262-
enableSorting: true,
255+
enableSorting: false,
263256
enableHiding: false,
264-
filterBy: {
265-
operators: [ 'is' as Operator ],
266-
},
267257
getValue: ( { item }: { item: MembershipSubscription } ) => {
268258
return item.end_date ?? '';
269259
},

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

Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,82 @@
1-
import { Card } from '@automattic/components';
1+
import page from '@automattic/calypso-router';
2+
import { Gridicon, Card } from '@automattic/components';
23
import { Purchases } from '@automattic/data-stores';
4+
import { DESKTOP_BREAKPOINT } from '@automattic/viewport';
5+
import { useBreakpoint } from '@automattic/viewport-react';
36
import { DataViews, View, filterSortAndPaginate } from '@wordpress/dataviews';
4-
import { LocalizeProps } from 'i18n-calypso';
5-
import { useMemo, useState } from 'react';
7+
import { useTranslate } from 'i18n-calypso';
8+
import { useEffect, useMemo, useState } from 'react';
69
import { MembershipSubscription } from 'calypso/lib/purchases/types';
710
import {
811
usePurchasesFieldDefinitions,
912
useMembershipsFieldDefinitions,
1013
} from './hooks/use-field-definitions';
1114

15+
const purchasesDesktopFields = [ 'site', 'product', 'status', 'payment-method' ];
16+
const purchasesMobileFields = [ 'product' ];
1217
export const purchasesDataView: View = {
1318
type: 'table',
1419
page: 1,
1520
perPage: 5,
1621
titleField: 'purchase-id',
1722
showTitle: false,
18-
fields: [ 'site', 'product', 'status', 'payment-method' ],
23+
fields: purchasesDesktopFields,
1924
sort: {
20-
field: 'site',
25+
field: 'product',
2126
direction: 'desc',
2227
},
2328
layout: {},
2429
};
2530

26-
export function PurchasesDataViews( props: {
27-
purchases: Purchases.Purchase[];
28-
translate: LocalizeProps[ 'translate' ];
29-
} ) {
30-
const { purchases } = props;
31+
export function PurchasesDataViews( { purchases }: { purchases: Purchases.Purchase[] } ) {
32+
const isDesktop = useBreakpoint( DESKTOP_BREAKPOINT );
33+
const translate = useTranslate();
3134
const [ currentView, setView ] = useState( purchasesDataView );
35+
// Hide fields at mobile width
36+
useEffect( () => {
37+
if ( isDesktop && currentView.fields === purchasesMobileFields ) {
38+
setView( { ...currentView, fields: purchasesDesktopFields } );
39+
return;
40+
}
41+
if ( ! isDesktop && currentView.fields === purchasesDesktopFields ) {
42+
setView( { ...currentView, fields: purchasesMobileFields } );
43+
return;
44+
}
45+
}, [ isDesktop, currentView, setView ] );
3246
const purchasesDataFields = usePurchasesFieldDefinitions( purchasesDataView.fields );
3347

3448
const { data: adjustedPurchases, paginationInfo } = useMemo( () => {
3549
return filterSortAndPaginate( purchases, currentView, purchasesDataFields );
3650
}, [ purchases, currentView, purchasesDataFields ] );
3751

52+
const actions = useMemo(
53+
() => [
54+
{
55+
id: 'manage-purchase',
56+
label: translate( 'Manage this purchase', { textOnly: true } ),
57+
isPrimary: true,
58+
icon: <Gridicon icon="chevron-right" />,
59+
isEligible: ( item: Purchases.Purchase ) => Boolean( item.domain && item.id ),
60+
callback: ( items: Purchases.Purchase[] ) => {
61+
const siteUrl = items[ 0 ].domain;
62+
const subscriptionId = items[ 0 ].id;
63+
if ( ! siteUrl ) {
64+
// eslint-disable-next-line no-console
65+
console.error( 'Cannot display manage purchase page for subscription without site' );
66+
return;
67+
}
68+
if ( ! subscriptionId ) {
69+
// eslint-disable-next-line no-console
70+
console.error( 'Cannot display manage purchase page for subscription without ID' );
71+
return;
72+
}
73+
page( `/me/purchases/${ siteUrl }/${ subscriptionId }` );
74+
},
75+
},
76+
],
77+
[ translate ]
78+
);
79+
3880
const getItemId = ( item: Purchases.Purchase ) => {
3981
return item.id.toString();
4082
};
@@ -46,34 +88,69 @@ export function PurchasesDataViews( props: {
4688
view={ currentView }
4789
onChangeView={ setView }
4890
defaultLayouts={ { table: {} } }
49-
actions={ undefined }
91+
actions={ actions }
5092
getItemId={ getItemId }
5193
paginationInfo={ paginationInfo }
5294
/>
5395
</Card>
5496
);
5597
}
5698

99+
const membershipsDesktopFields = [ 'site', 'product', 'status' ];
100+
const membershipsMobileFields = [ 'product' ];
57101
export const membershipDataView: View = {
58102
type: 'table',
59103
page: 1,
60104
perPage: 5,
61-
titleField: 'site',
62-
fields: [ 'product', 'status' ],
105+
titleField: 'purchase-id',
106+
showTitle: false,
107+
fields: membershipsDesktopFields,
63108
sort: {
64-
field: 'site',
109+
field: 'product',
65110
direction: 'desc',
66111
},
67112
layout: {},
68113
};
69114

70-
export function MembershipsDataViews( props: {
71-
memberships: MembershipSubscription[];
72-
translate: LocalizeProps[ 'translate' ];
73-
} ) {
74-
const { memberships } = props;
115+
export function MembershipsDataViews( { memberships }: { memberships: MembershipSubscription[] } ) {
75116
const membershipsDataFields = useMembershipsFieldDefinitions();
76-
const [ currentView, setView ] = useState( purchasesDataView );
117+
const [ currentView, setView ] = useState( membershipDataView );
118+
const isDesktop = useBreakpoint( DESKTOP_BREAKPOINT );
119+
const translate = useTranslate();
120+
121+
// Hide fields at mobile width
122+
useEffect( () => {
123+
if ( isDesktop && currentView.fields === membershipsMobileFields ) {
124+
setView( { ...currentView, fields: membershipsDesktopFields } );
125+
return;
126+
}
127+
if ( ! isDesktop && currentView.fields === membershipsDesktopFields ) {
128+
setView( { ...currentView, fields: membershipsMobileFields } );
129+
return;
130+
}
131+
}, [ isDesktop, currentView, setView ] );
132+
133+
const actions = useMemo(
134+
() => [
135+
{
136+
id: 'manage-purchase',
137+
label: translate( 'Manage this purchase', { textOnly: true } ),
138+
isPrimary: true,
139+
icon: <Gridicon icon="chevron-right" />,
140+
isEligible: ( item: MembershipSubscription ) => Boolean( item.ID ),
141+
callback: ( items: MembershipSubscription[] ) => {
142+
const subscriptionId = items[ 0 ].ID;
143+
if ( ! subscriptionId ) {
144+
// eslint-disable-next-line no-console
145+
console.error( 'Cannot display manage purchase page for subscription without ID' );
146+
return;
147+
}
148+
page( `/me/purchases/other/${ subscriptionId }` );
149+
},
150+
},
151+
],
152+
[ translate ]
153+
);
77154

78155
const { data: adjustedMemberships, paginationInfo } = useMemo( () => {
79156
return filterSortAndPaginate( memberships, currentView, membershipsDataFields );
@@ -90,7 +167,7 @@ export function MembershipsDataViews( props: {
90167
view={ currentView }
91168
onChangeView={ setView }
92169
defaultLayouts={ { table: {} } }
93-
actions={ undefined }
170+
actions={ actions }
94171
getItemId={ getItemId }
95172
paginationInfo={ paginationInfo }
96173
/>

client/me/purchases/purchases-list-in-dataviews/style.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
/* Purchase listing
22
================================================== */
33
#purchases-list {
4-
padding: 8px 0 0;
5-
64
.dataviews-view-table,
75
.dataviews-view-table tr {
86
max-width: 1040px;
@@ -18,6 +16,7 @@
1816
.dataviews-view-table tr td:first-child,
1917
.dataviews-view-table tr th:first-child {
2018
padding-left: 24px;
19+
max-width: 250px;
2120
}
2221

2322
.dataviews-view-table__cell-content-wrapper .purchase-item__site {
@@ -40,7 +39,8 @@
4039
max-width: 462px;
4140
}
4241

43-
.purchase-item__purchase-type, .purchase-item__purchase-type a.purchase-item__link {
42+
.purchase-item__purchase-type,
43+
.purchase-item__purchase-type a.purchase-item__link {
4444
overflow: hidden;
4545
}
4646
}

0 commit comments

Comments
 (0)