Skip to content

Commit 7c8532d

Browse files
Merge pull request #3561 from woocommerce/PCP-4483-add-a-button-to-copy-merchant-credentials-in-settings-tab
Add buttons to copy merchant credentials in the Settings tab (4483)
2 parents 0b6046a + 9cfd827 commit 7c8532d

File tree

5 files changed

+223
-10
lines changed

5 files changed

+223
-10
lines changed

modules/ppcp-settings/resources/css/components/screens/settings/_controls.scss

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,50 @@
11
.ppcp--static-value {
22
@include font(13, 26, 400);
3-
43
white-space: nowrap;
5-
overflow: hidden;
64
text-overflow: ellipsis;
5+
6+
&.ppcp--static-value-with-copy {
7+
display: inline-flex;
8+
align-items: flex-start;
9+
gap: 6px;
10+
overflow: visible;
11+
12+
.ppcp--static-value-text {
13+
flex: 1;
14+
min-width: 0;
15+
white-space: normal;
16+
word-break: break-all;
17+
overflow-wrap: break-word;
18+
max-width: 37ch;
19+
}
20+
}
21+
22+
&:not(.ppcp--static-value-with-copy) {
23+
overflow: hidden;
24+
}
25+
}
26+
27+
.ppcp-copy-button {
28+
display: flex;
29+
border: none;
30+
background: transparent;
31+
color: $color-gray-700;
32+
cursor: pointer;
33+
transition: color 0.2s ease;
34+
flex-shrink: 0;
35+
36+
&:hover:not(:disabled) {
37+
color: $color-blueberry;
38+
}
39+
40+
&:disabled {
41+
opacity: 0.5;
42+
cursor: not-allowed;
43+
}
44+
45+
svg {
46+
fill: currentColor;
47+
}
748
}
849

950
// Fix the checkbox layout (add gap between checkbox and label).
Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
import { Action } from '../Elements';
2+
import classNames from 'classnames';
3+
import CopyButton from '../Elements/CopyButton';
24

3-
const ControlStaticValue = ( { value } ) => (
4-
<Action>
5-
<div className="ppcp--static-value">{ value }</div>
6-
</Action>
7-
);
5+
const ControlStaticValue = ( {
6+
value,
7+
showCopy = false,
8+
copyButtonProps = {},
9+
className,
10+
...props
11+
} ) => {
12+
const wrapperClass = classNames( 'ppcp--static-value', {
13+
'ppcp--static-value-with-copy': showCopy,
14+
'ppcp--has-copy': showCopy,
15+
} );
16+
17+
return (
18+
<Action className={ className } { ...props }>
19+
{ showCopy ? (
20+
<div className={ wrapperClass }>
21+
<div className="ppcp--static-value-text">{ value }</div>
22+
<CopyButton value={ value } { ...copyButtonProps } />
23+
</div>
24+
) : (
25+
<div className={ wrapperClass }>{ value }</div>
26+
) }
27+
</Action>
28+
);
29+
};
830

931
export default ControlStaticValue;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { __ } from '@wordpress/i18n';
2+
import { speak } from '@wordpress/a11y';
3+
import { Tooltip } from '@wordpress/components';
4+
import { SVG, Path } from '@wordpress/primitives';
5+
import classNames from 'classnames';
6+
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
7+
8+
const COPY_CONFIRMATION_DURATION = 1000;
9+
10+
/**
11+
* Copy button component with tooltip and icon transition
12+
* @param {Object} props - Component props
13+
* @param {string} props.value - The text value to copy to clipboard
14+
* @param {string} [props.className] - Additional CSS class names
15+
* @param {string} [props.ariaLabel] - Custom aria-label for the button
16+
*/
17+
const CopyButton = ( { value, className, ariaLabel, ...props } ) => {
18+
const { copy, copied, error } = useCopyToClipboard( {
19+
successDuration: COPY_CONFIRMATION_DURATION,
20+
} );
21+
22+
const buttonClass = classNames( 'ppcp-copy-button', className );
23+
24+
const getTooltipText = () => {
25+
if ( copied ) {
26+
return __( 'Copied!', 'woocommerce-paypal-payments' );
27+
}
28+
if ( error ) {
29+
return __( 'Failed to copy', 'woocommerce-paypal-payments' );
30+
}
31+
return __( 'Copy to clipboard', 'woocommerce-paypal-payments' );
32+
};
33+
34+
const handleCopy = async () => {
35+
if ( ! value ) {
36+
return;
37+
}
38+
await copy( value );
39+
40+
if ( copied ) {
41+
speak(
42+
__( 'Copied to clipboard', 'woocommerce-paypal-payments' ),
43+
'assertive'
44+
);
45+
return;
46+
}
47+
48+
if ( error ) {
49+
speak(
50+
__(
51+
'Failed to copy to clipboard',
52+
'woocommerce-paypal-payments'
53+
),
54+
'assertive'
55+
);
56+
}
57+
};
58+
59+
return (
60+
<Tooltip
61+
text={ getTooltipText() }
62+
placement="top"
63+
delay={ 100 }
64+
hideOnClick={ false }
65+
>
66+
<button
67+
type="button"
68+
onClick={ handleCopy }
69+
className={ buttonClass }
70+
disabled={ ! value }
71+
aria-label={ ariaLabel || getTooltipText() }
72+
{ ...props }
73+
>
74+
{ copied ? <CheckIcon /> : <CopyIcon /> }
75+
</button>
76+
</Tooltip>
77+
);
78+
};
79+
80+
const CopyIcon = () => (
81+
<SVG
82+
width="20"
83+
height="20"
84+
viewBox="0 0 24 24"
85+
xmlns="http://www.w3.org/2000/svg"
86+
>
87+
<Path
88+
fillRule="evenodd"
89+
d="M16 16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h3V5a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-3zm2.5-10.5v9H16V9a1 1 0 0 0-1-1H9.5V5.5h9z"
90+
clipRule="evenodd"
91+
/>
92+
</SVG>
93+
);
94+
95+
const CheckIcon = () => (
96+
<SVG
97+
width="20"
98+
height="20"
99+
viewBox="0 0 24 24"
100+
xmlns="http://www.w3.org/2000/svg"
101+
>
102+
<Path d="M9 16.17L4.83 12L3.41 13.41L9 19L21 7L19.59 5.59L9 16.17Z" />
103+
</SVG>
104+
);
105+
106+
export default CopyButton;

modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Settings/ConnectionStatus.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,23 @@ const ConnectionStatus = () => {
3737
title={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
3838
className="ppcp--no-gap"
3939
>
40-
<ControlStaticValue value={ merchant.id } />
40+
<ControlStaticValue value={ merchant.id } showCopy={ true } />
4141
</SettingsBlock>
4242
<SettingsBlock
4343
title={ __( 'Email address', 'woocommerce-paypal-payments' ) }
4444
>
45-
<ControlStaticValue value={ merchant.email } />
45+
<ControlStaticValue
46+
value={ merchant.email }
47+
showCopy={ true }
48+
/>
4649
</SettingsBlock>
4750
<SettingsBlock
4851
title={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
4952
>
50-
<ControlStaticValue value={ merchant.clientId } />
53+
<ControlStaticValue
54+
value={ merchant.clientId }
55+
showCopy={ true }
56+
/>
5157
</SettingsBlock>
5258
</SettingsCard>
5359
);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useState, useRef } from '@wordpress/element';
2+
3+
/**
4+
* Custom hook for handling copy to clipboard functionality
5+
*
6+
* @param {Object} options - Configuration options
7+
* @param {number} options.successDuration - How long to show success state (ms)
8+
* @return {Object} Copy functionality and state
9+
*/
10+
export const useCopyToClipboard = ( options = {} ) => {
11+
const { successDuration = 1000 } = options;
12+
const [ copied, setCopied ] = useState( false );
13+
const [ error, setError ] = useState( false );
14+
const timerRef = useRef( null );
15+
16+
const copy = async ( text ) => {
17+
try {
18+
await navigator.clipboard.writeText( text );
19+
20+
clearTimeout( timerRef.current );
21+
setCopied( true );
22+
setError( false );
23+
24+
timerRef.current = setTimeout(
25+
() => setCopied( false ),
26+
successDuration
27+
);
28+
} catch ( err ) {
29+
console.error( 'Copy failed:', err );
30+
setError( true );
31+
setCopied( false );
32+
}
33+
};
34+
35+
return { copy, copied, error };
36+
};
37+
38+
export default useCopyToClipboard;

0 commit comments

Comments
 (0)