Introduce new BusyStateWrapper component

This commit is contained in:
Philipp Stracker 2024-12-09 18:16:13 +01:00
parent 5c50a3d970
commit 1826b95c08
No known key found for this signature in database
9 changed files with 176 additions and 103 deletions

View file

@ -0,0 +1,10 @@
.ppcp-r-busy-wrapper {
position: relative;
&.ppcp--is-loading {
pointer-events: none;
user-select: none;
--spinner-overlay-color: #fff4;
}
}

View file

@ -31,10 +31,4 @@
&__toggled-content {
margin-top: 24px;
}
&.ppcp--is-loading {
pointer-events: none;
--spinner-overlay-color: #fff4;
}
}

View file

@ -3,10 +3,11 @@
#ppcp-settings-container {
@import './global';
@import './components/reusable-components/onboarding-header';
@import './components/reusable-components/busy-state';
@import './components/reusable-components/button';
@import './components/reusable-components/settings-toggle-block';
@import './components/reusable-components/separator';
@import './components/reusable-components/onboarding-header';
@import './components/reusable-components/settings-toggle-block';
@import './components/reusable-components/payment-method-icons';
@import "./components/reusable-components/payment-method-item";
@import './components/reusable-components/settings-wrapper';

View file

@ -0,0 +1,57 @@
import {
Children,
isValidElement,
cloneElement,
useMemo,
} from '@wordpress/element';
import classNames from 'classnames';
import { CommonHooks } from '../../data';
import SpinnerOverlay from './SpinnerOverlay';
/**
* Wraps interactive child elements and modifies their behavior based on the global `isBusy` state.
* Allows custom processing of child props via the `onBusy` callback.
*
* @param {Object} props - Component properties.
* @param {Children} props.children - Child components to wrap.
* @param {boolean} props.enabled - Enables or disables the busy-state logic.
* @param {string} props.className - Additional class names for the wrapper.
* @param {Function} props.onBusy - Callback to process child props when busy.
*/
const BusyStateWrapper = ( {
children,
enabled = true,
className = '',
onBusy = () => ( { disabled: true } ),
} ) => {
const { isBusy } = CommonHooks.useBusyState();
const markAsBusy = isBusy && enabled;
const wrapperClassName = classNames( 'ppcp-r-busy-wrapper', className, {
'ppcp--is-loading': markAsBusy,
} );
const memoizedChildren = useMemo(
() =>
Children.map( children, ( child ) =>
isValidElement( child )
? cloneElement(
child,
markAsBusy ? onBusy( child.props ) : {}
)
: child
),
[ children, markAsBusy, onBusy ]
);
return (
<div className={ wrapperClassName }>
{ markAsBusy && <SpinnerOverlay /> }
{ memoizedChildren }
</div>
);
};
export default BusyStateWrapper;

View file

@ -1,23 +1,17 @@
import { ToggleControl } from '@wordpress/components';
import { useRef } from '@wordpress/element';
import SpinnerOverlay from './SpinnerOverlay';
const SettingsToggleBlock = ( {
isToggled,
setToggled,
isLoading = false,
disabled = false,
...props
} ) => {
const toggleRef = useRef( null );
const blockClasses = [ 'ppcp-r-toggle-block' ];
if ( isLoading ) {
blockClasses.push( 'ppcp--is-loading' );
}
const handleLabelClick = () => {
if ( ! toggleRef.current || isLoading ) {
if ( ! toggleRef.current || disabled ) {
return;
}
@ -52,13 +46,12 @@ const SettingsToggleBlock = ( {
ref={ toggleRef }
checked={ isToggled }
onChange={ ( newState ) => setToggled( newState ) }
disabled={ isLoading }
disabled={ disabled }
/>
</div>
</div>
{ props.children && isToggled && (
<div className="ppcp-r-toggle-block__toggled-content">
{ isLoading && <SpinnerOverlay /> }
{ props.children }
</div>
) }

View file

@ -20,6 +20,7 @@ import {
} from '../../../../hooks/useHandleConnections';
import ConnectionButton from './ConnectionButton';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
const FORM_ERRORS = {
noClientId: __(
@ -89,7 +90,7 @@ const AdvancedOptionsForm = () => {
handleConnectViaIdAndSecret( {
validation: validateManualConnectionForm,
} ),
[ validateManualConnectionForm ]
[ handleConnectViaIdAndSecret, validateManualConnectionForm ]
);
useEffect( () => {
@ -124,69 +125,79 @@ const AdvancedOptionsForm = () => {
return (
<>
<SettingsToggleBlock
label={ __(
'Enable Sandbox Mode',
'woocommerce-paypal-payments'
) }
description={ __(
'Activate Sandbox mode to safely test PayPal with sample data. Once your store is ready to go live, you can easily switch to your production account.',
'woocommerce-paypal-payments'
) }
isToggled={ !! isSandboxMode }
setToggled={ setSandboxMode }
isLoading={ isBusy }
>
<ConnectionButton
title={ __(
'Connect Account',
<BusyStateWrapper>
<SettingsToggleBlock
label={ __(
'Enable Sandbox Mode',
'woocommerce-paypal-payments'
) }
showIcon={ false }
variant="secondary"
isSandbox={
true /* This button always connects to sandbox */
}
/>
</SettingsToggleBlock>
description={ __(
'Activate Sandbox mode to safely test PayPal with sample data. Once your store is ready to go live, you can easily switch to your production account.',
'woocommerce-paypal-payments'
) }
isToggled={ !! isSandboxMode }
setToggled={ setSandboxMode }
>
<ConnectionButton
title={ __(
'Connect Account',
'woocommerce-paypal-payments'
) }
showIcon={ false }
variant="secondary"
isSandbox={
true /* This button always connects to sandbox */
}
/>
</SettingsToggleBlock>
</BusyStateWrapper>
<Separator withLine={ false } />
<SettingsToggleBlock
label={
__( 'Manually Connect', 'woocommerce-paypal-payments' ) +
( isBusy ? ' ...' : '' )
}
description={ advancedUsersDescription }
isToggled={ !! isManualConnectionMode }
setToggled={ setManualConnectionMode }
isLoading={ isBusy }
<BusyStateWrapper
onBusy={ ( props ) => ( {
disabled: true,
label: props.label + ' ...',
} ) }
>
<DataStoreControl
control={ TextControl }
ref={ refClientId }
label={ clientIdLabel }
value={ clientId }
onChange={ setClientId }
className={ classNames( {
'has-error': ! clientValid,
} ) }
/>
{ clientValid || (
<p className="client-id-error">
{ FORM_ERRORS.invalidClientId }
</p>
) }
<DataStoreControl
control={ TextControl }
ref={ refClientSecret }
label={ secretKeyLabel }
value={ clientSecret }
onChange={ setClientSecret }
type="password"
/>
<Button variant="secondary" onClick={ handleManualConnect }>
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
</Button>
</SettingsToggleBlock>
<SettingsToggleBlock
label={ __(
'Manually Connect',
'woocommerce-paypal-payments'
) }
description={ advancedUsersDescription }
isToggled={ !! isManualConnectionMode }
setToggled={ setManualConnectionMode }
>
<DataStoreControl
control={ TextControl }
ref={ refClientId }
label={ clientIdLabel }
value={ clientId }
onChange={ setClientId }
className={ classNames( {
'has-error': ! clientValid,
} ) }
/>
{ clientValid || (
<p className="client-id-error">
{ FORM_ERRORS.invalidClientId }
</p>
) }
<DataStoreControl
control={ TextControl }
ref={ refClientSecret }
label={ secretKeyLabel }
value={ clientSecret }
onChange={ setClientSecret }
type="password"
/>
<Button variant="secondary" onClick={ handleManualConnect }>
{ __(
'Connect Account',
'woocommerce-paypal-payments'
) }
</Button>
</SettingsToggleBlock>
</BusyStateWrapper>
</>
);
};

View file

@ -8,6 +8,7 @@ import {
useProductionConnection,
useSandboxConnection,
} from '../../../../hooks/useHandleConnections';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
const ConnectionButton = ( {
title,
@ -15,13 +16,11 @@ const ConnectionButton = ( {
variant = 'primary',
showIcon = true,
} ) => {
const { isBusy } = CommonHooks.useBusyState();
const { handleSandboxConnect } = useSandboxConnection();
const { handleProductionConnect } = useProductionConnection();
const className = classNames( 'ppcp-r-connection-button', {
'sandbox-mode': isSandbox,
'live-mode': ! isSandbox,
'ppcp--is-loading': isBusy,
} );
const handleConnectClick = async () => {
@ -33,15 +32,16 @@ const ConnectionButton = ( {
};
return (
<Button
className={ className }
variant={ variant }
icon={ showIcon ? openSignup : null }
onClick={ handleConnectClick }
disabled={ isBusy }
>
<span className="button-title">{ title }</span>
</Button>
<BusyStateWrapper>
<Button
className={ className }
variant={ variant }
icon={ showIcon ? openSignup : null }
onClick={ handleConnectClick }
>
<span className="button-title">{ title }</span>
</Button>
</BusyStateWrapper>
);
};

View file

@ -6,6 +6,7 @@ import classNames from 'classnames';
import { OnboardingHooks } from '../../../../data';
import useIsScrolled from '../../../../hooks/useIsScrolled';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
const { title, isFirst, percentage, showNext, canProceed } = stepDetails;
@ -20,7 +21,10 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
return (
<div className={ className }>
<div className="ppcp-r-navigation">
<div className="ppcp-r-navigation--left">
<BusyStateWrapper
className="ppcp-r-navigation--left"
enabled={ ! isFirst }
>
<Button
variant="link"
onClick={ isFirst ? onExit : onPrev }
@ -31,7 +35,7 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
{ title }
</span>
</Button>
</div>
</BusyStateWrapper>
{ ! isFirst &&
NextButton( { showNext, isDisabled, onNext, onExit } ) }
<ProgressBar percent={ percentage } />
@ -42,7 +46,7 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
const NextButton = ( { showNext, isDisabled, onNext, onExit } ) => {
return (
<div className="ppcp-r-navigation--right">
<BusyStateWrapper className="ppcp-r-navigation--right">
<Button variant="link" onClick={ onExit }>
{ __( 'Save and exit', 'woocommerce-paypal-payments' ) }
</Button>
@ -55,7 +59,7 @@ const NextButton = ( { showNext, isDisabled, onNext, onExit } ) => {
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
</Button>
) }
</div>
</BusyStateWrapper>
);
};

View file

@ -9,6 +9,7 @@ import AccordionSection from '../../ReusableComponents/AccordionSection';
import AdvancedOptionsForm from './Components/AdvancedOptionsForm';
import { CommonHooks } from '../../../data';
import BusyStateWrapper from '../../ReusableComponents/BusyStateWrapper';
const StepWelcome = ( { setStep, currentStep } ) => {
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
@ -34,16 +35,18 @@ const StepWelcome = ( { setStep, currentStep } ) => {
'woocommerce-paypal-payments'
) }
</p>
<Button
className="ppcp-r-button-activate-paypal"
variant="primary"
onClick={ () => setStep( currentStep + 1 ) }
>
{ __(
'Activate PayPal Payments',
'woocommerce-paypal-payments'
) }
</Button>
<BusyStateWrapper>
<Button
className="ppcp-r-button-activate-paypal"
variant="primary"
onClick={ () => setStep( currentStep + 1 ) }
>
{ __(
'Activate PayPal Payments',
'woocommerce-paypal-payments'
) }
</Button>
</BusyStateWrapper>
</div>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<WelcomeDocs