mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 16:24:33 +08:00
✨ Introduce new BusyStateWrapper component
This commit is contained in:
parent
5c50a3d970
commit
1826b95c08
9 changed files with 176 additions and 103 deletions
|
@ -0,0 +1,10 @@
|
||||||
|
.ppcp-r-busy-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.ppcp--is-loading {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
--spinner-overlay-color: #fff4;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,10 +31,4 @@
|
||||||
&__toggled-content {
|
&__toggled-content {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ppcp--is-loading {
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
--spinner-overlay-color: #fff4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
#ppcp-settings-container {
|
#ppcp-settings-container {
|
||||||
@import './global';
|
@import './global';
|
||||||
@import './components/reusable-components/onboarding-header';
|
@import './components/reusable-components/busy-state';
|
||||||
@import './components/reusable-components/button';
|
@import './components/reusable-components/button';
|
||||||
@import './components/reusable-components/settings-toggle-block';
|
|
||||||
@import './components/reusable-components/separator';
|
@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-icons';
|
||||||
@import "./components/reusable-components/payment-method-item";
|
@import "./components/reusable-components/payment-method-item";
|
||||||
@import './components/reusable-components/settings-wrapper';
|
@import './components/reusable-components/settings-wrapper';
|
||||||
|
|
|
@ -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;
|
|
@ -1,23 +1,17 @@
|
||||||
import { ToggleControl } from '@wordpress/components';
|
import { ToggleControl } from '@wordpress/components';
|
||||||
import { useRef } from '@wordpress/element';
|
import { useRef } from '@wordpress/element';
|
||||||
|
|
||||||
import SpinnerOverlay from './SpinnerOverlay';
|
|
||||||
|
|
||||||
const SettingsToggleBlock = ( {
|
const SettingsToggleBlock = ( {
|
||||||
isToggled,
|
isToggled,
|
||||||
setToggled,
|
setToggled,
|
||||||
isLoading = false,
|
disabled = false,
|
||||||
...props
|
...props
|
||||||
} ) => {
|
} ) => {
|
||||||
const toggleRef = useRef( null );
|
const toggleRef = useRef( null );
|
||||||
const blockClasses = [ 'ppcp-r-toggle-block' ];
|
const blockClasses = [ 'ppcp-r-toggle-block' ];
|
||||||
|
|
||||||
if ( isLoading ) {
|
|
||||||
blockClasses.push( 'ppcp--is-loading' );
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleLabelClick = () => {
|
const handleLabelClick = () => {
|
||||||
if ( ! toggleRef.current || isLoading ) {
|
if ( ! toggleRef.current || disabled ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,13 +46,12 @@ const SettingsToggleBlock = ( {
|
||||||
ref={ toggleRef }
|
ref={ toggleRef }
|
||||||
checked={ isToggled }
|
checked={ isToggled }
|
||||||
onChange={ ( newState ) => setToggled( newState ) }
|
onChange={ ( newState ) => setToggled( newState ) }
|
||||||
disabled={ isLoading }
|
disabled={ disabled }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ props.children && isToggled && (
|
{ props.children && isToggled && (
|
||||||
<div className="ppcp-r-toggle-block__toggled-content">
|
<div className="ppcp-r-toggle-block__toggled-content">
|
||||||
{ isLoading && <SpinnerOverlay /> }
|
|
||||||
{ props.children }
|
{ props.children }
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
} from '../../../../hooks/useHandleConnections';
|
} from '../../../../hooks/useHandleConnections';
|
||||||
|
|
||||||
import ConnectionButton from './ConnectionButton';
|
import ConnectionButton from './ConnectionButton';
|
||||||
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
|
||||||
const FORM_ERRORS = {
|
const FORM_ERRORS = {
|
||||||
noClientId: __(
|
noClientId: __(
|
||||||
|
@ -89,7 +90,7 @@ const AdvancedOptionsForm = () => {
|
||||||
handleConnectViaIdAndSecret( {
|
handleConnectViaIdAndSecret( {
|
||||||
validation: validateManualConnectionForm,
|
validation: validateManualConnectionForm,
|
||||||
} ),
|
} ),
|
||||||
[ validateManualConnectionForm ]
|
[ handleConnectViaIdAndSecret, validateManualConnectionForm ]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
@ -124,69 +125,79 @@ const AdvancedOptionsForm = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsToggleBlock
|
<BusyStateWrapper>
|
||||||
label={ __(
|
<SettingsToggleBlock
|
||||||
'Enable Sandbox Mode',
|
label={ __(
|
||||||
'woocommerce-paypal-payments'
|
'Enable Sandbox Mode',
|
||||||
) }
|
|
||||||
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',
|
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
showIcon={ false }
|
description={ __(
|
||||||
variant="secondary"
|
'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.',
|
||||||
isSandbox={
|
'woocommerce-paypal-payments'
|
||||||
true /* This button always connects to sandbox */
|
) }
|
||||||
}
|
isToggled={ !! isSandboxMode }
|
||||||
/>
|
setToggled={ setSandboxMode }
|
||||||
</SettingsToggleBlock>
|
>
|
||||||
|
<ConnectionButton
|
||||||
|
title={ __(
|
||||||
|
'Connect Account',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
showIcon={ false }
|
||||||
|
variant="secondary"
|
||||||
|
isSandbox={
|
||||||
|
true /* This button always connects to sandbox */
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingsToggleBlock>
|
||||||
|
</BusyStateWrapper>
|
||||||
<Separator withLine={ false } />
|
<Separator withLine={ false } />
|
||||||
<SettingsToggleBlock
|
<BusyStateWrapper
|
||||||
label={
|
onBusy={ ( props ) => ( {
|
||||||
__( 'Manually Connect', 'woocommerce-paypal-payments' ) +
|
disabled: true,
|
||||||
( isBusy ? ' ...' : '' )
|
label: props.label + ' ...',
|
||||||
}
|
} ) }
|
||||||
description={ advancedUsersDescription }
|
|
||||||
isToggled={ !! isManualConnectionMode }
|
|
||||||
setToggled={ setManualConnectionMode }
|
|
||||||
isLoading={ isBusy }
|
|
||||||
>
|
>
|
||||||
<DataStoreControl
|
<SettingsToggleBlock
|
||||||
control={ TextControl }
|
label={ __(
|
||||||
ref={ refClientId }
|
'Manually Connect',
|
||||||
label={ clientIdLabel }
|
'woocommerce-paypal-payments'
|
||||||
value={ clientId }
|
) }
|
||||||
onChange={ setClientId }
|
description={ advancedUsersDescription }
|
||||||
className={ classNames( {
|
isToggled={ !! isManualConnectionMode }
|
||||||
'has-error': ! clientValid,
|
setToggled={ setManualConnectionMode }
|
||||||
} ) }
|
>
|
||||||
/>
|
<DataStoreControl
|
||||||
{ clientValid || (
|
control={ TextControl }
|
||||||
<p className="client-id-error">
|
ref={ refClientId }
|
||||||
{ FORM_ERRORS.invalidClientId }
|
label={ clientIdLabel }
|
||||||
</p>
|
value={ clientId }
|
||||||
) }
|
onChange={ setClientId }
|
||||||
<DataStoreControl
|
className={ classNames( {
|
||||||
control={ TextControl }
|
'has-error': ! clientValid,
|
||||||
ref={ refClientSecret }
|
} ) }
|
||||||
label={ secretKeyLabel }
|
/>
|
||||||
value={ clientSecret }
|
{ clientValid || (
|
||||||
onChange={ setClientSecret }
|
<p className="client-id-error">
|
||||||
type="password"
|
{ FORM_ERRORS.invalidClientId }
|
||||||
/>
|
</p>
|
||||||
<Button variant="secondary" onClick={ handleManualConnect }>
|
) }
|
||||||
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
|
<DataStoreControl
|
||||||
</Button>
|
control={ TextControl }
|
||||||
</SettingsToggleBlock>
|
ref={ refClientSecret }
|
||||||
|
label={ secretKeyLabel }
|
||||||
|
value={ clientSecret }
|
||||||
|
onChange={ setClientSecret }
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<Button variant="secondary" onClick={ handleManualConnect }>
|
||||||
|
{ __(
|
||||||
|
'Connect Account',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
</Button>
|
||||||
|
</SettingsToggleBlock>
|
||||||
|
</BusyStateWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
useProductionConnection,
|
useProductionConnection,
|
||||||
useSandboxConnection,
|
useSandboxConnection,
|
||||||
} from '../../../../hooks/useHandleConnections';
|
} from '../../../../hooks/useHandleConnections';
|
||||||
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
|
||||||
const ConnectionButton = ( {
|
const ConnectionButton = ( {
|
||||||
title,
|
title,
|
||||||
|
@ -15,13 +16,11 @@ const ConnectionButton = ( {
|
||||||
variant = 'primary',
|
variant = 'primary',
|
||||||
showIcon = true,
|
showIcon = true,
|
||||||
} ) => {
|
} ) => {
|
||||||
const { isBusy } = CommonHooks.useBusyState();
|
|
||||||
const { handleSandboxConnect } = useSandboxConnection();
|
const { handleSandboxConnect } = useSandboxConnection();
|
||||||
const { handleProductionConnect } = useProductionConnection();
|
const { handleProductionConnect } = useProductionConnection();
|
||||||
const className = classNames( 'ppcp-r-connection-button', {
|
const className = classNames( 'ppcp-r-connection-button', {
|
||||||
'sandbox-mode': isSandbox,
|
'sandbox-mode': isSandbox,
|
||||||
'live-mode': ! isSandbox,
|
'live-mode': ! isSandbox,
|
||||||
'ppcp--is-loading': isBusy,
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const handleConnectClick = async () => {
|
const handleConnectClick = async () => {
|
||||||
|
@ -33,15 +32,16 @@ const ConnectionButton = ( {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<BusyStateWrapper>
|
||||||
className={ className }
|
<Button
|
||||||
variant={ variant }
|
className={ className }
|
||||||
icon={ showIcon ? openSignup : null }
|
variant={ variant }
|
||||||
onClick={ handleConnectClick }
|
icon={ showIcon ? openSignup : null }
|
||||||
disabled={ isBusy }
|
onClick={ handleConnectClick }
|
||||||
>
|
>
|
||||||
<span className="button-title">{ title }</span>
|
<span className="button-title">{ title }</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
</BusyStateWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { OnboardingHooks } from '../../../../data';
|
import { OnboardingHooks } from '../../../../data';
|
||||||
import useIsScrolled from '../../../../hooks/useIsScrolled';
|
import useIsScrolled from '../../../../hooks/useIsScrolled';
|
||||||
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
|
||||||
const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
|
const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
|
||||||
const { title, isFirst, percentage, showNext, canProceed } = stepDetails;
|
const { title, isFirst, percentage, showNext, canProceed } = stepDetails;
|
||||||
|
@ -20,7 +21,10 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className }>
|
||||||
<div className="ppcp-r-navigation">
|
<div className="ppcp-r-navigation">
|
||||||
<div className="ppcp-r-navigation--left">
|
<BusyStateWrapper
|
||||||
|
className="ppcp-r-navigation--left"
|
||||||
|
enabled={ ! isFirst }
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={ isFirst ? onExit : onPrev }
|
onClick={ isFirst ? onExit : onPrev }
|
||||||
|
@ -31,7 +35,7 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
|
||||||
{ title }
|
{ title }
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</BusyStateWrapper>
|
||||||
{ ! isFirst &&
|
{ ! isFirst &&
|
||||||
NextButton( { showNext, isDisabled, onNext, onExit } ) }
|
NextButton( { showNext, isDisabled, onNext, onExit } ) }
|
||||||
<ProgressBar percent={ percentage } />
|
<ProgressBar percent={ percentage } />
|
||||||
|
@ -42,7 +46,7 @@ const Navigation = ( { stepDetails, onNext, onPrev, onExit } ) => {
|
||||||
|
|
||||||
const NextButton = ( { showNext, isDisabled, onNext, onExit } ) => {
|
const NextButton = ( { showNext, isDisabled, onNext, onExit } ) => {
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-navigation--right">
|
<BusyStateWrapper className="ppcp-r-navigation--right">
|
||||||
<Button variant="link" onClick={ onExit }>
|
<Button variant="link" onClick={ onExit }>
|
||||||
{ __( 'Save and exit', 'woocommerce-paypal-payments' ) }
|
{ __( 'Save and exit', 'woocommerce-paypal-payments' ) }
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -55,7 +59,7 @@ const NextButton = ( { showNext, isDisabled, onNext, onExit } ) => {
|
||||||
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
|
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
|
||||||
</Button>
|
</Button>
|
||||||
) }
|
) }
|
||||||
</div>
|
</BusyStateWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import AccordionSection from '../../ReusableComponents/AccordionSection';
|
||||||
|
|
||||||
import AdvancedOptionsForm from './Components/AdvancedOptionsForm';
|
import AdvancedOptionsForm from './Components/AdvancedOptionsForm';
|
||||||
import { CommonHooks } from '../../../data';
|
import { CommonHooks } from '../../../data';
|
||||||
|
import BusyStateWrapper from '../../ReusableComponents/BusyStateWrapper';
|
||||||
|
|
||||||
const StepWelcome = ( { setStep, currentStep } ) => {
|
const StepWelcome = ( { setStep, currentStep } ) => {
|
||||||
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
||||||
|
@ -34,16 +35,18 @@ const StepWelcome = ( { setStep, currentStep } ) => {
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<BusyStateWrapper>
|
||||||
className="ppcp-r-button-activate-paypal"
|
<Button
|
||||||
variant="primary"
|
className="ppcp-r-button-activate-paypal"
|
||||||
onClick={ () => setStep( currentStep + 1 ) }
|
variant="primary"
|
||||||
>
|
onClick={ () => setStep( currentStep + 1 ) }
|
||||||
{ __(
|
>
|
||||||
'Activate PayPal Payments',
|
{ __(
|
||||||
'woocommerce-paypal-payments'
|
'Activate PayPal Payments',
|
||||||
) }
|
'woocommerce-paypal-payments'
|
||||||
</Button>
|
) }
|
||||||
|
</Button>
|
||||||
|
</BusyStateWrapper>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
||||||
<WelcomeDocs
|
<WelcomeDocs
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue