diff --git a/changelog/add-9878-bank-ref-key-payout-details b/changelog/add-9878-bank-ref-key-payout-details
new file mode 100644
index 00000000000..8d88b5cef46
--- /dev/null
+++ b/changelog/add-9878-bank-ref-key-payout-details
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Show Bank reference key on top of the payout details page, whenever available.
diff --git a/client/components/copy-button/index.tsx b/client/components/copy-button/index.tsx
new file mode 100644
index 00000000000..0ec1fc3f76f
--- /dev/null
+++ b/client/components/copy-button/index.tsx
@@ -0,0 +1,64 @@
+/**
+ * External dependencies
+ */
+import React, { useState, useEffect, useRef } from 'react';
+import { __ } from '@wordpress/i18n';
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+
+interface CopyButtonProps {
+ /**
+ * The text to copy to the clipboard.
+ */
+ textToCopy: string;
+
+ /**
+ * The label for the button. Also used as the aria-label.
+ */
+ label: string;
+}
+
+export const CopyButton: React.FC< CopyButtonProps > = ( {
+ textToCopy,
+ label,
+} ) => {
+ // useRef() is used to store the timer reference for the setTimeout() function.
+ const timerRef = useRef< NodeJS.Timeout | null >( null );
+
+ // useEffect() is used to clear the timer reference when the component is unmounted.
+ useEffect( () => {
+ return () => {
+ if ( timerRef.current ) {
+ clearTimeout( timerRef.current );
+ }
+ };
+ }, [] );
+
+ const [ copied, setCopied ] = useState( false );
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText( textToCopy );
+ setCopied( true );
+ timerRef.current = setTimeout( () => {
+ setCopied( false );
+ }, 2000 );
+ };
+
+ return (
+
+ );
+};
diff --git a/client/components/copy-button/style.scss b/client/components/copy-button/style.scss
new file mode 100644
index 00000000000..bac8c2b6edd
--- /dev/null
+++ b/client/components/copy-button/style.scss
@@ -0,0 +1,37 @@
+.woopayments-copy-button {
+ line-height: 1.2em;
+ display: inline-flex;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ vertical-align: middle;
+ font-weight: normal;
+ cursor: pointer;
+ color: inherit;
+ margin-left: 2px;
+ align-items: center;
+
+ i {
+ display: block;
+ width: 1.2em;
+ height: 1.2em;
+ mask-image: url( 'assets/images/icons/copy.svg?asset' );
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ background-color: currentColor;
+
+ &:hover {
+ opacity: 0.7;
+ }
+
+ &:active {
+ transform: scale( 0.9 );
+ }
+ }
+
+ &.state--copied i {
+ mask-image: url( 'assets/images/icons/check-green.svg?asset' );
+ background-color: $studio-green-50;
+ }
+}
diff --git a/client/components/copy-button/test/__snapshots__/index.test.tsx.snap b/client/components/copy-button/test/__snapshots__/index.test.tsx.snap
new file mode 100644
index 00000000000..44cd5831dc5
--- /dev/null
+++ b/client/components/copy-button/test/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CopyButton renders the button correctly 1`] = `
+
+
+
+`;
diff --git a/client/components/copy-button/test/index.test.tsx b/client/components/copy-button/test/index.test.tsx
new file mode 100644
index 00000000000..eaaf7844e50
--- /dev/null
+++ b/client/components/copy-button/test/index.test.tsx
@@ -0,0 +1,70 @@
+/** @format **/
+
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { act, render, screen } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { CopyButton } from '..';
+
+describe( 'CopyButton', () => {
+ it( 'renders the button correctly', () => {
+ const { container: copyButtonContainer } = render(
+
+ );
+
+ expect( copyButtonContainer ).toMatchSnapshot();
+ } );
+
+ describe( 'when the button is clicked', () => {
+ beforeAll( () => {
+ jest.useFakeTimers();
+ } );
+
+ afterAll( () => {
+ jest.useRealTimers();
+ } );
+
+ it( 'copies the text to the clipboard and shows copied state', () => {
+ render(
+
+ );
+
+ const button = screen.queryByRole( 'button', {
+ name: /Copy bank reference ID to clipboard/i,
+ } );
+
+ //Mock the clipboard API
+ Object.assign( navigator, {
+ clipboard: {
+ writeText: jest.fn(),
+ },
+ } );
+
+ act( () => {
+ button?.click();
+ } );
+
+ expect( navigator.clipboard.writeText ).toHaveBeenCalledWith(
+ 'test_bank_reference_id'
+ );
+ expect( button ).toHaveClass( 'state--copied' );
+
+ act( () => {
+ jest.advanceTimersByTime( 2000 );
+ } );
+
+ expect( button ).not.toHaveClass( 'state--copied' );
+ } );
+ } );
+} );
diff --git a/client/deposits/details/index.tsx b/client/deposits/details/index.tsx
index 2176b079377..e87af290938 100644
--- a/client/deposits/details/index.tsx
+++ b/client/deposits/details/index.tsx
@@ -30,6 +30,7 @@ import classNames from 'classnames';
import type { CachedDeposit } from 'types/deposits';
import { useDeposit } from 'data';
import TransactionsList from 'transactions/list';
+import { CopyButton } from 'components/copy-button';
import Page from 'components/page';
import ErrorBoundary from 'components/error-boundary';
import { TestModeNotice } from 'components/test-mode-notice';
@@ -70,7 +71,7 @@ interface SummaryItemProps {
label: string;
value: string | JSX.Element;
valueClass?: string | false;
- detail?: string;
+ detail?: string | JSX.Element;
}
/**
@@ -102,35 +103,20 @@ const SummaryItem: React.FC< SummaryItemProps > = ( {
);
-interface DepositOverviewProps {
- deposit: CachedDeposit | undefined;
+interface DepositDateItemProps {
+ deposit: CachedDeposit;
}
-export const DepositOverview: React.FC< DepositOverviewProps > = ( {
- deposit,
-} ) => {
- if ( ! deposit ) {
- return (
-
- { __(
- `The deposit you are looking for cannot be found.`,
- 'woocommerce-payments'
- ) }
-
- );
- }
-
- const isWithdrawal = deposit.type === 'withdrawal';
-
+const DepositDateItem: React.FC< DepositDateItemProps > = ( { deposit } ) => {
let depositDateLabel = __( 'Payout date', 'woocommerce-payments' );
if ( ! deposit.automatic ) {
depositDateLabel = __( 'Instant payout date', 'woocommerce-payments' );
}
- if ( isWithdrawal ) {
+ if ( deposit.type === 'withdrawal' ) {
depositDateLabel = __( 'Withdrawal date', 'woocommerce-payments' );
}
- const depositDateItem = (
+ return (
= ( {
)
}
value={ }
- detail={ deposit.bankAccount }
+ detail={
+ <>
+ { deposit.bankAccount }
+
+ Bank reference ID:{ ' ' }
+ { deposit.bank_reference_key ? (
+ <>
+
+ { deposit.bank_reference_key }
+
+
+ >
+ ) : (
+ 'N/A'
+ ) }
+ >
+ }
/>
);
+};
+interface DepositOverviewProps {
+ deposit: CachedDeposit | undefined;
+}
+
+export const DepositOverview: React.FC< DepositOverviewProps > = ( {
+ deposit,
+} ) => {
+ if ( ! deposit ) {
+ return (
+
+ { __(
+ `The deposit you are looking for cannot be found.`,
+ 'woocommerce-payments'
+ ) }
+
+ );
+ }
+
+ const isWithdrawal = deposit.type === 'withdrawal';
return (
{ deposit.automatic ? (
- { depositDateItem }
+
-
{ formatExplicitCurrency(
deposit.amount,
@@ -161,7 +189,7 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
) : (
-
= ( {
}
>
{ () => [
- depositDateItem,
+ ,
MOCK BANK •••• 1234 (USD)
+
+ Bank reference ID:
+
+ N/A
@@ -117,6 +121,10 @@ exports[`Deposit overview renders automatic withdrawal correctly 1`] = `
class="wcpay-summary__item-detail"
>
MOCK BANK •••• 1234 (USD)
+
+ Bank reference ID:
+
+ N/A
@@ -195,6 +203,10 @@ exports[`Deposit overview renders instant deposit correctly 1`] = `
class="wcpay-summary__item-detail"
>
MOCK BANK •••• 1234 (USD)
+
+ Bank reference ID:
+
+ N/A