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..67d14eb4725
--- /dev/null
+++ b/client/components/copy-button/index.tsx
@@ -0,0 +1,50 @@
+/**
+ * External dependencies
+ */
+import React, { useState } 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,
+} ) => {
+ const [ copied, setCopied ] = useState( false );
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText( textToCopy );
+ setCopied( true );
+ };
+
+ return (
+
+ );
+};
diff --git a/client/components/copy-button/style.scss b/client/components/copy-button/style.scss
new file mode 100644
index 00000000000..58b4555540c
--- /dev/null
+++ b/client/components/copy-button/style.scss
@@ -0,0 +1,51 @@
+.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;
+ animation: copy-indicator 2s forwards;
+ }
+
+ @keyframes copy-indicator {
+ 0% {
+ opacity: 1;
+ }
+ 95% {
+ opacity: 1;
+ }
+ // a quick fade-out from 1%→0% at the end
+ 100% {
+ opacity: 0;
+ }
+ }
+}
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..7a771a7fe39
--- /dev/null
+++ b/client/components/copy-button/test/index.test.tsx
@@ -0,0 +1,67 @@
+/** @format **/
+
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+
+/**
+ * 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', () => {
+ it( 'copies the text to the clipboard and shows copied state', async () => {
+ render(
+
+ );
+
+ const button = screen.queryByRole( 'button', {
+ name: /Copy bank reference ID to clipboard/i,
+ } );
+
+ if ( ! button ) {
+ throw new Error( 'Button not found' );
+ }
+
+ //Mock the clipboard API
+ Object.assign( navigator, {
+ clipboard: {
+ writeText: jest.fn().mockResolvedValueOnce( undefined ),
+ },
+ } );
+
+ await act( async () => {
+ fireEvent.click( button );
+ } );
+
+ expect( navigator.clipboard.writeText ).toHaveBeenCalledWith(
+ 'test_bank_reference_id'
+ );
+ expect( button ).toHaveClass( 'state--copied' );
+
+ act( () => {
+ fireEvent.animationEnd( button );
+ } );
+
+ expect( button ).not.toHaveClass( 'state--copied' );
+ } );
+ } );
+} );
diff --git a/client/deposits/details/index.tsx b/client/deposits/details/index.tsx
index 6f8ed023da1..c4c83154b0a 100644
--- a/client/deposits/details/index.tsx
+++ b/client/deposits/details/index.tsx
@@ -28,6 +28,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';
@@ -68,7 +69,7 @@ interface SummaryItemProps {
label: string;
value: string | JSX.Element;
valueClass?: string | false;
- detail?: string;
+ detail?: string | JSX.Element;
}
/**
@@ -100,6 +101,30 @@ const SummaryItem: React.FC< SummaryItemProps > = ( {
);
+interface DepositDateItemProps {
+ deposit: CachedDeposit;
+}
+
+const DepositDateItem: React.FC< DepositDateItemProps > = ( { deposit } ) => {
+ let depositDateLabel = __( 'Payout date', 'woocommerce-payments' );
+ if ( ! deposit.automatic ) {
+ depositDateLabel = __( 'Instant payout date', 'woocommerce-payments' );
+ }
+ if ( deposit.type === 'withdrawal' ) {
+ depositDateLabel = __( 'Withdrawal date', 'woocommerce-payments' );
+ }
+
+ return (
+ }
+ />
+ );
+};
interface DepositOverviewProps {
deposit: CachedDeposit | undefined;
}
@@ -120,32 +145,12 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
const isWithdrawal = deposit.type === 'withdrawal';
- let depositDateLabel = __( 'Payout date', 'woocommerce-payments' );
- if ( ! deposit.automatic ) {
- depositDateLabel = __( 'Instant payout date', 'woocommerce-payments' );
- }
- if ( isWithdrawal ) {
- depositDateLabel = __( 'Withdrawal date', 'woocommerce-payments' );
- }
-
- const depositDateItem = (
- }
- detail={ deposit.bankAccount }
- />
- );
-
return (
{ deposit.automatic ? (
- { depositDateItem }
+
-
{ formatExplicitCurrency(
deposit.amount,
@@ -155,7 +160,7 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( {
) : (
-
= ( {
}
>
{ () => [
- depositDateItem,
+ ,
= ( {
] }
) }
+
+
+
+ { isWithdrawal
+ ? __( 'Withdrawal details', 'woocommerce-payments' )
+ : __( 'Payout details', 'woocommerce-payments' ) }
+
+
+
+
+
+
+ { __( 'Bank account', 'woocommerce-payments' ) }
+
+
+ { deposit.bankAccount }
+
+
+
+
+ { __(
+ 'Bank reference ID',
+ 'woocommerce-payments'
+ ) }
+
+
+ { deposit.bank_reference_key ? (
+ <>
+
+ { deposit.bank_reference_key }
+
+
+ >
+ ) : (
+
+ { __(
+ 'Not available',
+ 'woocommerce-payments'
+ ) }
+
+ ) }
+
+
+
+
+
);
};
diff --git a/client/deposits/details/style.scss b/client/deposits/details/style.scss
index 0031184d4cf..1e084484e81 100644
--- a/client/deposits/details/style.scss
+++ b/client/deposits/details/style.scss
@@ -37,6 +37,15 @@
font-size: 20px;
line-height: 28px;
}
+
+ .wcpay-summary__item-detail {
+ color: $dark-gray-500;
+ word-wrap: break-word;
+
+ .woopayments-payout-bank-reference-id {
+ font-family: monospace;
+ }
+ }
}
.wcpay-deposit-fee {
@@ -47,11 +56,52 @@
color: $wp-green-70;
}
+ .woopayments-payout-details-header {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 0.75rem;
+
+ &__bank-reference-id {
+ font-family: monospace;
+ color: $wp-gray-50;
+ }
+
+ &__value {
+ color: $wp-gray-50;
+ align-self: center;
+
+ @include breakpoint( '<660px' ) {
+ align-self: flex-start;
+ }
+ }
+
+ &__item {
+ display: flex;
+
+ @include breakpoint( '<660px' ) {
+ flex-direction: column;
+ }
+ }
+
+ h2 {
+ font-size: 0.6875rem;
+ font-weight: 500;
+ color: $wp-gray-90;
+ min-width: 11rem;
+ text-transform: uppercase;
+ }
+ }
+
.wcpay-deposit-automatic ul {
display: flex;
margin: 0;
list-style-type: none;
+ @include breakpoint( '<660px' ) {
+ flex-direction: column;
+ }
+
.woocommerce-summary__item {
border-bottom: 0;
background-color: inherit;
@@ -59,6 +109,10 @@
.woocommerce-summary__item-label:hover {
color: inherit;
}
+
+ @include breakpoint( '<660px' ) {
+ border-right: none;
+ }
}
.wcpay-deposit-amount {
@@ -67,6 +121,12 @@
text-align: right;
font-size: 36px;
line-height: 82px;
+
+ @include breakpoint( '<660px' ) {
+ line-height: 36px;
+ text-align: left;
+ border-top: 1px solid $studio-gray-5;
+ }
}
}
diff --git a/client/deposits/details/test/__snapshots__/index.tsx.snap b/client/deposits/details/test/__snapshots__/index.tsx.snap
index fecce68ae08..6b0973874ba 100644
--- a/client/deposits/details/test/__snapshots__/index.tsx.snap
+++ b/client/deposits/details/test/__snapshots__/index.tsx.snap
@@ -41,11 +41,6 @@ exports[`Deposit overview renders automatic payout correctly 1`] = `
-
- MOCK BANK •••• 1234 (USD)
-
+
`;
@@ -113,11 +181,6 @@ exports[`Deposit overview renders automatic withdrawal correctly 1`] = `
-
- MOCK BANK •••• 1234 (USD)
-
+
`;
@@ -191,11 +327,6 @@ exports[`Deposit overview renders instant deposit correctly 1`] = `
-
- MOCK BANK •••• 1234 (USD)
-
+
`;