Skip to content

Commit 8b2b869

Browse files
nagpaiNagesh Paihaszarishendy-a8c
authored
Reporting: Show bank reference ID on Payout details page (#9945)
Co-authored-by: Nagesh Pai <[email protected]> Co-authored-by: Rua Haszard <[email protected]> Co-authored-by: Shendy <[email protected]>
1 parent 6bb24e1 commit 8b2b869

File tree

8 files changed

+548
-39
lines changed

8 files changed

+548
-39
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: add
3+
4+
Show Bank reference key on top of the payout details page, whenever available.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import React, { useState } from 'react';
5+
import { __ } from '@wordpress/i18n';
6+
import classNames from 'classnames';
7+
8+
/**
9+
* Internal dependencies
10+
*/
11+
import './style.scss';
12+
13+
interface CopyButtonProps {
14+
/**
15+
* The text to copy to the clipboard.
16+
*/
17+
textToCopy: string;
18+
19+
/**
20+
* The label for the button. Also used as the aria-label.
21+
*/
22+
label: string;
23+
}
24+
25+
export const CopyButton: React.FC< CopyButtonProps > = ( {
26+
textToCopy,
27+
label,
28+
} ) => {
29+
const [ copied, setCopied ] = useState( false );
30+
31+
const copyToClipboard = () => {
32+
navigator.clipboard.writeText( textToCopy );
33+
setCopied( true );
34+
};
35+
36+
return (
37+
<button
38+
type="button"
39+
className={ classNames( 'woopayments-copy-button', {
40+
'state--copied': copied,
41+
} ) }
42+
aria-label={ label }
43+
title={ __( 'Copy to clipboard', 'woocommerce-payments' ) }
44+
onClick={ copyToClipboard }
45+
onAnimationEnd={ () => setCopied( false ) }
46+
>
47+
<i></i>
48+
</button>
49+
);
50+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.woopayments-copy-button {
2+
line-height: 1.2em;
3+
display: inline-flex;
4+
background: transparent;
5+
border: none;
6+
border-radius: 0;
7+
vertical-align: middle;
8+
font-weight: normal;
9+
cursor: pointer;
10+
color: inherit;
11+
margin-left: 2px;
12+
align-items: center;
13+
14+
i {
15+
display: block;
16+
width: 1.2em;
17+
height: 1.2em;
18+
mask-image: url( 'assets/images/icons/copy.svg?asset' );
19+
mask-size: contain;
20+
mask-repeat: no-repeat;
21+
mask-position: center;
22+
background-color: currentColor;
23+
24+
&:hover {
25+
opacity: 0.7;
26+
}
27+
28+
&:active {
29+
transform: scale( 0.9 );
30+
}
31+
}
32+
33+
&.state--copied i {
34+
mask-image: url( 'assets/images/icons/check-green.svg?asset' );
35+
background-color: $studio-green-50;
36+
animation: copy-indicator 2s forwards;
37+
}
38+
39+
@keyframes copy-indicator {
40+
0% {
41+
opacity: 1;
42+
}
43+
95% {
44+
opacity: 1;
45+
}
46+
// a quick fade-out from 1%→0% at the end
47+
100% {
48+
opacity: 0;
49+
}
50+
}
51+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`CopyButton renders the button correctly 1`] = `
4+
<div>
5+
<button
6+
aria-label="Copy bank reference ID to clipboard"
7+
class="woopayments-copy-button"
8+
title="Copy to clipboard"
9+
type="button"
10+
>
11+
<i />
12+
</button>
13+
</div>
14+
`;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/** @format **/
2+
3+
/**
4+
* External dependencies
5+
*/
6+
import React from 'react';
7+
import { act, fireEvent, render, screen } from '@testing-library/react';
8+
import '@testing-library/jest-dom/extend-expect';
9+
10+
/**
11+
* Internal dependencies
12+
*/
13+
import { CopyButton } from '..';
14+
15+
describe( 'CopyButton', () => {
16+
it( 'renders the button correctly', () => {
17+
const { container: copyButtonContainer } = render(
18+
<CopyButton
19+
textToCopy="test_bank_reference_id"
20+
label="Copy bank reference ID to clipboard"
21+
/>
22+
);
23+
24+
expect( copyButtonContainer ).toMatchSnapshot();
25+
} );
26+
27+
describe( 'when the button is clicked', () => {
28+
it( 'copies the text to the clipboard and shows copied state', async () => {
29+
render(
30+
<CopyButton
31+
textToCopy="test_bank_reference_id"
32+
label="Copy bank reference ID to clipboard"
33+
/>
34+
);
35+
36+
const button = screen.queryByRole( 'button', {
37+
name: /Copy bank reference ID to clipboard/i,
38+
} );
39+
40+
if ( ! button ) {
41+
throw new Error( 'Button not found' );
42+
}
43+
44+
//Mock the clipboard API
45+
Object.assign( navigator, {
46+
clipboard: {
47+
writeText: jest.fn().mockResolvedValueOnce( undefined ),
48+
},
49+
} );
50+
51+
await act( async () => {
52+
fireEvent.click( button );
53+
} );
54+
55+
expect( navigator.clipboard.writeText ).toHaveBeenCalledWith(
56+
'test_bank_reference_id'
57+
);
58+
expect( button ).toHaveClass( 'state--copied' );
59+
60+
act( () => {
61+
fireEvent.animationEnd( button );
62+
} );
63+
64+
expect( button ).not.toHaveClass( 'state--copied' );
65+
} );
66+
} );
67+
} );

client/deposits/details/index.tsx

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import classNames from 'classnames';
2828
import type { CachedDeposit } from 'types/deposits';
2929
import { useDeposit } from 'data';
3030
import TransactionsList from 'transactions/list';
31+
import { CopyButton } from 'components/copy-button';
3132
import Page from 'components/page';
3233
import ErrorBoundary from 'components/error-boundary';
3334
import { TestModeNotice } from 'components/test-mode-notice';
@@ -68,7 +69,7 @@ interface SummaryItemProps {
6869
label: string;
6970
value: string | JSX.Element;
7071
valueClass?: string | false;
71-
detail?: string;
72+
detail?: string | JSX.Element;
7273
}
7374

7475
/**
@@ -100,6 +101,30 @@ const SummaryItem: React.FC< SummaryItemProps > = ( {
100101
</li>
101102
);
102103

104+
interface DepositDateItemProps {
105+
deposit: CachedDeposit;
106+
}
107+
108+
const DepositDateItem: React.FC< DepositDateItemProps > = ( { deposit } ) => {
109+
let depositDateLabel = __( 'Payout date', 'woocommerce-payments' );
110+
if ( ! deposit.automatic ) {
111+
depositDateLabel = __( 'Instant payout date', 'woocommerce-payments' );
112+
}
113+
if ( deposit.type === 'withdrawal' ) {
114+
depositDateLabel = __( 'Withdrawal date', 'woocommerce-payments' );
115+
}
116+
117+
return (
118+
<SummaryItem
119+
key="depositDate"
120+
label={
121+
`${ depositDateLabel }: ` +
122+
formatDateTimeFromString( deposit.date )
123+
}
124+
value={ <DepositStatusIndicator deposit={ deposit } /> }
125+
/>
126+
);
127+
};
103128
interface DepositOverviewProps {
104129
deposit: CachedDeposit | undefined;
105130
}
@@ -120,32 +145,12 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
120145

121146
const isWithdrawal = deposit.type === 'withdrawal';
122147

123-
let depositDateLabel = __( 'Payout date', 'woocommerce-payments' );
124-
if ( ! deposit.automatic ) {
125-
depositDateLabel = __( 'Instant payout date', 'woocommerce-payments' );
126-
}
127-
if ( isWithdrawal ) {
128-
depositDateLabel = __( 'Withdrawal date', 'woocommerce-payments' );
129-
}
130-
131-
const depositDateItem = (
132-
<SummaryItem
133-
key="depositDate"
134-
label={
135-
`${ depositDateLabel }: ` +
136-
formatDateTimeFromString( deposit.date )
137-
}
138-
value={ <DepositStatusIndicator deposit={ deposit } /> }
139-
detail={ deposit.bankAccount }
140-
/>
141-
);
142-
143148
return (
144149
<div className="wcpay-deposit-overview">
145150
{ deposit.automatic ? (
146151
<Card className="wcpay-deposit-automatic">
147152
<ul>
148-
{ depositDateItem }
153+
<DepositDateItem deposit={ deposit } />
149154
<li className="wcpay-deposit-amount">
150155
{ formatExplicitCurrency(
151156
deposit.amount,
@@ -155,7 +160,7 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
155160
</ul>
156161
</Card>
157162
) : (
158-
<SummaryList
163+
<SummaryList // For instant deposits only
159164
label={
160165
isWithdrawal
161166
? __(
@@ -166,7 +171,7 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
166171
}
167172
>
168173
{ () => [
169-
depositDateItem,
174+
<DepositDateItem key="dateItem" deposit={ deposit } />,
170175
<SummaryItem
171176
key="depositAmount"
172177
label={
@@ -222,6 +227,60 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
222227
] }
223228
</SummaryList>
224229
) }
230+
<Card>
231+
<CardHeader>
232+
<Text size={ 16 } weight={ 600 }>
233+
{ isWithdrawal
234+
? __( 'Withdrawal details', 'woocommerce-payments' )
235+
: __( 'Payout details', 'woocommerce-payments' ) }
236+
</Text>
237+
</CardHeader>
238+
<CardBody>
239+
<div className="woopayments-payout-details-header">
240+
<div className="woopayments-payout-details-header__item">
241+
<h2>
242+
{ __( 'Bank account', 'woocommerce-payments' ) }
243+
</h2>
244+
<div className="woopayments-payout-details-header__value">
245+
{ deposit.bankAccount }
246+
</div>
247+
</div>
248+
<div className="woopayments-payout-details-header__item">
249+
<h2>
250+
{ __(
251+
'Bank reference ID',
252+
'woocommerce-payments'
253+
) }
254+
</h2>
255+
<div className="woopayments-payout-details-header__value">
256+
{ deposit.bank_reference_key ? (
257+
<>
258+
<span className="woopayments-payout-details-header__bank-reference-id">
259+
{ deposit.bank_reference_key }
260+
</span>
261+
<CopyButton
262+
textToCopy={
263+
deposit.bank_reference_key
264+
}
265+
label={ __(
266+
'Copy bank reference ID to clipboard',
267+
'woocommerce-payments'
268+
) }
269+
/>
270+
</>
271+
) : (
272+
<div className="woopayments-payout-details-header__value">
273+
{ __(
274+
'Not available',
275+
'woocommerce-payments'
276+
) }
277+
</div>
278+
) }
279+
</div>
280+
</div>
281+
</div>
282+
</CardBody>
283+
</Card>
225284
</div>
226285
);
227286
};

0 commit comments

Comments
 (0)