Merge branch 'trunk' into PCP-4077-conditionally-display-payment-methods

This commit is contained in:
Emili Castells Guasch 2025-01-24 11:01:12 +01:00
commit dde1a702ea
135 changed files with 2297 additions and 2246 deletions

View file

@ -45,7 +45,7 @@
}
@mixin disabled-state($control-type) {
.components-#{$control-type}-control.is-disabled {
.components-#{$control-type}-control.ppcp--is-disabled {
.components-#{$control-type}-control__input,
.components-#{$control-type}-control__label,
.components-base-control__help {

View file

@ -16,6 +16,7 @@ $color-text-tertiary: #505050;
$color-text-text: #070707;
$color-border: #AEAEAE;
$color-divider: #F0F0F0;
$color-error-red: #cc1818;
$shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15);
$shadow-selection-box: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
@ -61,4 +62,13 @@ $card-vertical-gap: 48px;
--color-text-main: #{$color-text-text};
--color-text-teriary: #{$color-text-tertiary};
--color-text-description: #{$color-gray-700};
--color-error: #{$color-error-red};
// Default settings-block theme.
--block-item-gap: 16px;
--block-header-gap: 6px;
--block-separator-gap: 32px;
--block-separator-size: 1px;
--block-separator-color: var(--color-gray-200);
--block-action-gap: 16px; // Space between two consecutive action blocks.
}

View file

@ -3,6 +3,7 @@
@import './reusable-components/badge-box';
@import './reusable-components/busy-state';
@import './reusable-components/button';
@import './reusable-components/elements';
@import './reusable-components/fields';
@import './reusable-components/hstack';
@import './reusable-components/navigation';

View file

@ -2,7 +2,7 @@
margin-left: auto;
margin-right: auto;
&__toggler {
.ppcp--toggler {
display: block;
cursor: pointer;
@ -10,19 +10,38 @@
border: 0;
box-shadow: none;
padding: 0;
margin: 24px auto;
text-align: var(--accordion-text-align, center);
width: var(--accordion-width, auto);
margin: var(--accordion-toggler-gap, 24px) auto;
}
&__title-wrapper {
@include font(14, 32, 450);
color: $color-gray-900;
.ppcp--title-wrapper {
display: flex;
align-items: center;
gap: 16px;
}
&__content {
margin: 24px 0 0;
.ppcp--accordion-content {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.2s ease-out, opacity 0.2s ease-out, margin 0.2s ease-out;
opacity: 0;
margin: 0;
> .ppcp--content {
overflow: hidden;
}
&.ppcp--is-open {
grid-template-rows: 1fr;
opacity: 1;
margin: 24px 0 0;
transition: grid-template-rows 0.3s ease-in, opacity 0.3s ease-in, margin 0.3s ease-in;
> .ppcp--content {
// Show the overflow, since the focus-outline can extend outside the content div.
overflow: visible;
}
}
}
}

View file

@ -0,0 +1,71 @@
/**
* General styling for reusable components in the "Elements" folder.
*/
.ppcp--header {
display: flex;
flex-direction: column;
gap: 6px;
&:not(:last-child) {
padding-bottom: var(--block-header-gap, 6px);
}
}
.ppcp--title {
@include font(11, 22, 600);
color: var(--color-text-title);
display: block;
text-transform: uppercase;
&.ppcp--no-caps {
@include font(14, 16, 600);
text-transform: none;
}
&.ppcp--big {
@include font(16, 20, 600);
}
.ppcp-r-title-badge {
text-transform: none;
margin-left: 6px;
}
}
.ppcp--title-extra {
@include font(13, 20, 400);
color: var(--color-text-teriary);
text-transform: none;
margin-left: 5px;
}
.ppcp--title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
.ppcp--description {
@include font(13, 20, 400);
margin: 0;
color: var(--color-text-description);
&:not(:last-child) {
padding-bottom: 1em;
}
a {
color: var(--color-blueberry);
}
strong {
color: var(--color-gray-800);
}
}
.ppcp--action {
& + .ppcp--action {
margin-top: var(--block-action-gap, 16px);
}
}

View file

@ -1,20 +1,31 @@
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
flex-direction: column;
-webkit-box-pack: center;
justify-content: center;
.ppcp-r-app {
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
-webkit-box-pack: center;
.components-h-stack {
flex-direction: row;
justify-content: flex-start;
gap: 32px;
.components-h-stack {
flex-direction: row;
justify-content: flex-start;
gap: 32px;
}
.components-v-stack {
flex-direction: column;
justify-content: center;
}
// Fix layout for checkboxes inside a flex-stack.
.components-checkbox-control > .components-base-control__field > .components-flex {
flex-direction: row;
gap: 12px;
}
}
// Fix layout for checkboxes inside a flex-stack.
.components-checkbox-control >.components-base-control__field > .components-flex {
.ppcp--horizontal {
flex-direction: row;
gap: 12px;
align-items: center;
justify-content: space-between;
}
}

View file

@ -48,11 +48,11 @@
@include font(16, 24, 600);
color: var(--color-text);
.title {
.ppcp--nav-title {
margin-left: 18px;
}
.big {
.ppcp--big {
@include font(20, 28, 400);
}
}
@ -79,7 +79,7 @@
}
}
&.is-scrolled {
&.ppcp--is-scrolled {
box-shadow: 0 -1px 0 0 $color-gray-300 inset, 0 8px 8px 0 rgba(85, 93, 102, .3);
}
@ -90,7 +90,7 @@
row-gap: 8px;
white-space: nowrap;
&--right {
.ppcp-r-navigation--right {
position: absolute;
right: 10px;
z-index: 10;
@ -98,12 +98,12 @@
box-shadow: -5px 0 8px var(--ppcp-color-app-bg);
}
&--progress-bar {
.ppcp-r-navigation--progress-bar {
height: 2px;
}
.components-button.is-title {
.title {
.ppcp--title {
margin-left: 4px;
}
}

View file

@ -16,7 +16,7 @@
padding: 24px 16px 24px 16px;
&.selected {
&.ppcp--selected {
border-color: $color-blueberry;
outline-color: $color-blueberry;
box-shadow: $shadow-selection-box;

View file

@ -6,50 +6,8 @@
flex-direction: column;
gap: var(--block-item-gap, 16px);
&.ppcp-r-settings-block__input,
&.ppcp-r-settings-block__select {
gap: 6px 0;
}
.ppcp-r-settings-block__header {
display: flex;
flex-direction: column;
gap: 6px;
&:not(:last-child) {
padding-bottom: var(--block-header-gap, 6px);
}
}
.ppcp-r-settings-block__title {
@include font(11, 22, 600);
color: var(--color-text-title);
display: block;
text-transform: uppercase;
&.style-alt {
@include font(14, 16, 600);
text-transform: none;
}
&.style-big {
@include font(16, 20, 600);
}
.ppcp-r-title-badge {
text-transform: none;
margin-left: 6px;
}
}
.ppcp-r-settings-block__title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
&.ppcp-r-settings-block__feature {
.ppcp-r-settings-block__title {
.ppcp--title {
@include font(13, 20, 600);
color: var(--color-text-main);
text-transform: none;
@ -61,98 +19,10 @@
}
}
&.ppcp-r-settings-block__toggle {
display: flex;
flex-direction: row;
.ppcp-r-settings-block__title {
@include font(13, 20, 400);
color: var(--color-text-main);
text-transform: none;
}
}
.ppcp-r-settings-block__description {
@include font(13, 20, 400);
margin: 0;
color: var(--color-text-description);
&:not(:last-child) {
padding-bottom: 1em;
}
a {
color: var(--color-blueberry);
}
strong {
color: var(--color-gray-800);
}
}
.ppcp-r-settings-block__supplementary-title-label {
@include font(13, 20, 400);
color: var(--color-text-teriary);
text-transform: none;
margin-left: 5px;
}
.ppcp-r-settings-block__action {
display: flex;
align-items: center;
.components-flex {
row-gap: 0;
}
}
+ .ppcp-r-settings-block:not(.no-gap) {
+ .ppcp-r-settings-block:not(.ppcp--no-gap) {
margin-top: var(--block-separator-gap, 32px);
padding-top: var(--block-separator-gap, 32px);
border-top: 1px solid var(--color-gray-200);
}
// Types
&--toggle-content {
&.ppcp-r-settings-block--content-visible {
.ppcp-r-settings-block__toggle-content {
transform: rotate(180deg);
}
}
.ppcp-r-settings-block__header {
user-select: none;
&:hover {
cursor: pointer;
}
}
}
&--sandbox-connected {
.ppcp-r-settings-block__content {
margin-top: 24px;
}
.ppcp-r-connection-status__data {
margin-bottom: 20px;
}
}
&--connect-sandbox {
button.components-button {
@include small-button;
}
.ppcp-r__radio-content-additional {
@include vertical-layout-event-gap(24px);
align-items: flex-start;
.ppcp-r-vertical-text-control,
input[type='text'] {
width: 100%;
}
}
border-top: var(--block-separator-size, 1px) solid var(--block-separator-color);
}
}
@ -167,28 +37,12 @@
}
}
.ppcp-r-settings-block--toggle-content {
.ppcp-r-settings-block__content {
margin-top: 32px;
}
}
.ppcp-r-settings-block__button {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 50px;
}
.ppcp-r-settings-block__accordion {
> .ppcp-r-accordion {
width: 100%;
.ppcp-r-accordion__toggler {
width: 100%;
margin: 0;
text-align: unset;
}
.ppcp-r-accordion {
--accordion-width: 100%;
--accordion-toggler-gap: 0;
--accordion-text-align: left;
.ppcp--title-wrapper {
justify-content: space-between;
}
}

View file

@ -40,12 +40,15 @@
gap: 24px;
}
.ppcp-r-settings-card__content {
.ppcp--content {
flex: 1;
max-width: var(--card-width-content);
border: 1px solid var(--color-gray-200);
border-radius: 4px;
padding: 24px;
&.ppcp--is-card {
max-width: var(--card-width-content);
border: 1px solid var(--color-gray-200);
border-radius: 4px;
padding: 24px;
}
}
.ppcp-r-settings-card__title {

View file

@ -1,5 +1,6 @@
@import './settings/controls';
@import './settings/input';
@import './settings/connection-status';
@import './settings/tab-settings';
@import './settings/tab-styling';
@import './settings/tab-paylater-configurator';

View file

@ -21,13 +21,18 @@
}
.client-id-error {
color: #cc1818;
margin: -16px 0 24px;
@include font(11, 16, 450);
margin: -16px 0 24px;
color: var(--color-error);
}
.onboarding-advanced-options {
margin-top: 24px;
max-width: 800px;
.ppcp--toggler .ppcp--title-wrapper {
justify-content: center;
}
}
}

View file

@ -1,78 +0,0 @@
// Connection Status
.ppcp-r-connection-status {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
&__status-status {
margin: 0 0 8px 0;
strong {
@include font(14, 24, 700);
color: $color-black;
}
}
&__status-label {
@include font(11, 22, 600);
color: $color-gray-900;
display: block;
text-transform: uppercase;
}
&__status-value {
@include font(13, 26, 400);
color: $color-text-tertiary;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__data {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
}
&__status-toggle--toggled {
.ppcp-r-connection-status__show-all-data {
transform: rotate(180deg);
}
}
&__status-row {
display: flex;
flex-direction: column;
strong {
@include font(14, 24, 600);
color: $color-gray-800;
margin-right: 12px;
white-space: nowrap;
}
.ppcp-r-connection-status__status-toggle {
line-height: 0;
}
}
@media screen and (max-width: 767px) {
flex-wrap: wrap;
&__status {
width: 100%;
}
&__status-row {
flex-wrap: wrap;
strong {
width: 100%;
}
span {
word-break: break-all;
}
}
}
}

View file

@ -0,0 +1,7 @@
.ppcp--static-value {
@include font(13, 26, 400);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -1,5 +1,5 @@
// Fields
.ppcp-r {
.ppcp-r-app {
input[type='text'] {
border-color: $color-gray-700;
width: 100%;
@ -13,13 +13,13 @@
}
// MultiSelect control
.ppcp-r {
&__radio-wrapper {
.ppcp-r-app {
.ppcp-r__radio-wrapper {
align-items: flex-start;
gap: 12px;
}
&__radio-content {
.ppcp-r__radio-content {
display: flex;
flex-direction: column;
gap: 4px;
@ -29,49 +29,47 @@
}
}
&__radio-content-additional {
.ppcp-r__radio-content-additional {
padding-left: 32px;
}
// Select control styles
&__control {
border-radius: 2px;
border-color: $color-gray-700;
min-height: auto;
padding: 0;
}
&__input-container {
padding: 0;
margin: 0;
}
&__value-container {
padding: 0 0 0 7px;
}
&__indicator {
padding: 5px;
}
&__indicator-separator {
display: none;
}
&__value-container--has-value {
.ppcp-r__single-value {
color: $color-gray-800;
// Styles for "react-select" (see `Fields/Select.js`)
.ppcp-r-select {
.ppcp__control {
border-radius: 2px;
border-color: $color-gray-700;
min-height: auto;
padding: 0;
}
}
&__placeholder,
&__single-value {
@include font(13, 20, 400);
}
.ppcp__input-container {
padding: 0;
margin: 0;
}
&__option {
&--is-selected {
background-color: $color-gray-200;
.ppcp__value-container {
padding: 0 0 0 7px;
}
.ppcp__indicator {
padding: 5px;
}
.ppcp__value-container--has-value {
.ppcp__single-value {
color: $color-gray-800;
}
}
.ppcp__placeholder,
.ppcp__single-value {
@include font(13, 20, 400);
}
.ppcp__option {
.ppcp__option--is-selected {
background-color: $color-gray-200;
}
}
}
}

View file

@ -0,0 +1,9 @@
/**
* Used by the "Connection Details" section in the top of the "Settings" tab.
*/
.ppcp--value-list {
--block-item-gap: 0;
--block-separator-gap: 6px;
--block-header-gap: 0;
--block-separator-size: 0;
}

View file

@ -33,7 +33,7 @@
}
// Select-fields have a smaller gap between the header and input field.
&.has-select {
&.ppcp--has-select {
--block-header-gap: 8px;
}
@ -58,7 +58,7 @@
margin-bottom: 28px;
border-bottom: 1px solid var(--color-separators);
&.no-gap,
&.ppcp--no-gap,
&:last-child {
padding-bottom: 0;
margin-bottom: 0;

View file

@ -1,20 +1,15 @@
import { useEffect, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import classNames from 'classnames';
import { OnboardingHooks, CommonHooks } from '../data';
import SpinnerOverlay from './ReusableComponents/SpinnerOverlay';
import SendOnlyMessage from './Screens/SendOnlyMessage';
import OnboardingScreen from './Screens/Onboarding';
import SettingsScreen from './Screens/Settings';
import { initStore as initSettingsStore } from '../data/settings-tab';
import { useSettingsState } from '../data/settings-tab/hooks';
// Initialize the settings store
initSettingsStore();
const SettingsApp = () => {
const onboardingProgress = OnboardingHooks.useSteps();
const { isReady: settingsIsReady } = useSettingsState();
const { isReady: onboardingIsReady, completed: onboardingCompleted } =
OnboardingHooks.useSteps();
const {
isReady: merchantIsReady,
merchant: { isSendOnlyCountry },
@ -33,34 +28,28 @@ const SettingsApp = () => {
}, [] );
const wrapperClass = classNames( 'ppcp-r-app', {
loading: ! onboardingProgress.isReady || ! settingsIsReady,
loading: ! onboardingIsReady,
} );
const Content = useMemo( () => {
if (
! onboardingProgress.isReady ||
! merchantIsReady ||
! settingsIsReady
) {
return (
<SpinnerOverlay
message={ __( 'Loading…', 'woocommerce-paypal-payments' ) }
/>
);
if ( ! onboardingIsReady || ! merchantIsReady ) {
return <SpinnerOverlay />;
}
if ( isSendOnlyCountry ) {
return <SendOnlyMessage />;
}
if ( ! onboardingProgress.completed ) {
if ( ! onboardingCompleted ) {
return <OnboardingScreen />;
}
return <SettingsScreen />;
}, [
isSendOnlyCountry,
merchantIsReady,
onboardingProgress.completed,
onboardingProgress.isReady,
settingsIsReady,
onboardingCompleted,
onboardingIsReady,
] );
return <div className={ wrapperClass }>{ Content }</div>;

View file

@ -1,53 +1,33 @@
import { Icon } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons';
import classNames from 'classnames';
import { useAccordionState } from '../../hooks/useAccordionState';
// Provide defaults for all layout components so the generic version just works.
const DefaultHeader = ( { children, className = '' } ) => (
<div className={ `ppcp-r-accordion__header ${ className }`.trim() }>
{ children }
</div>
);
const DefaultTitleWrapper = ( { children } ) => (
<div className="ppcp-r-accordion__title-wrapper">{ children }</div>
);
const DefaultTitle = ( { children } ) => (
<span className="ppcp-r-accordion__title">{ children }</span>
);
const DefaultAction = ( { children } ) => (
<span className="ppcp-r-accordion__action">{ children }</span>
);
const DefaultDescription = ( { children } ) => (
<div className="ppcp-r-accordion__description">{ children }</div>
);
const AccordionContent = ( { isOpen, children } ) => {
if ( ! isOpen || ! children ) {
return null;
}
return <div className="ppcp-r-accordion__content">{ children }</div>;
};
import {
Content,
Description,
Header,
Title,
Action,
TitleWrapper,
} from './Elements';
const Accordion = ( {
title,
id = '',
noCaps = false,
initiallyOpen = null,
description = '',
children = null,
className = '',
// Layout components can be overridden by the caller
Header = DefaultHeader,
TitleWrapper = DefaultTitleWrapper,
Title = DefaultTitle,
Action = DefaultAction,
Description = DefaultDescription,
} ) => {
const { isOpen, toggleOpen } = useAccordionState( { id, initiallyOpen } );
const wrapperClasses = classNames( 'ppcp-r-accordion', className, {
'ppcp--is-open': isOpen,
} );
const contentClass = classNames( 'ppcp--accordion-content', {
'ppcp--is-open': isOpen,
} );
const icon = isOpen ? chevronUp : chevronDown;
@ -55,22 +35,22 @@ const Accordion = ( {
<div className={ wrapperClasses } { ...( id && { id } ) }>
<button
type="button"
className="ppcp-r-accordion__toggler"
className="ppcp--toggler"
onClick={ toggleOpen }
>
<Header>
<TitleWrapper>
<Title>{ title }</Title>
<Title noCaps={ noCaps }>{ title }</Title>
<Action>
<Icon icon={ icon } />
</Action>
</TitleWrapper>
{ description && (
<Description>{ description }</Description>
) }
<Description>{ description }</Description>
</Header>
</button>
<AccordionContent isOpen={ isOpen }>{ children }</AccordionContent>
<div className={ contentClass }>
<Content asCard={ false }>{ children }</Content>
</div>
</div>
);
};

View file

@ -1,35 +0,0 @@
import { __ } from '@wordpress/i18n';
import { CommonHooks } from '../../data';
const ConnectionInfo = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<div className="ppcp-r-connection-status__data">
<StatusRow
label={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
value={ merchant.id }
/>
<StatusRow
label={ __( 'Email address', 'woocommerce-paypal-payments' ) }
value={ merchant.email }
/>
<StatusRow
label={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
value={ merchant.clientId }
/>
</div>
);
};
export default ConnectionInfo;
const StatusRow = ( { label, value } ) => (
<div className="ppcp-r-connection-status__status-row">
<span className="ppcp-r-connection-status__status-label">
{ label }
</span>
<span className="ppcp-r-connection-status__status-value">
{ value }
</span>
</div>
);

View file

@ -0,0 +1,18 @@
import { Button } from '@wordpress/components';
import { Action } from '../Elements';
const ControlButton = ( {
type = 'secondary',
isBusy,
onClick,
buttonLabel,
} ) => (
<Action>
<Button isBusy={ isBusy } variant={ type } onClick={ onClick }>
{ buttonLabel }
</Button>
</Action>
);
export default ControlButton;

View file

@ -0,0 +1,14 @@
import { Action } from '../Elements';
import { RadioGroup } from '../Fields';
const ControlRadioGroup = ( { options, value, onChange } ) => (
<Action>
<RadioGroup
options={ options }
selected={ value }
onChange={ onChange }
/>
</Action>
);
export default ControlRadioGroup;

View file

@ -0,0 +1,22 @@
import { Select } from '../Fields';
import { Action } from '../Elements';
const ControlSelect = ( {
options,
value,
onChange,
placeholder,
isMulti = false,
} ) => (
<Action>
<Select
isMulti={ isMulti }
options={ options }
value={ value }
placeholder={ placeholder }
onChange={ onChange }
/>
</Action>
);
export default ControlSelect;

View file

@ -0,0 +1,9 @@
import { Action } from '../Elements';
const ControlStaticValue = ( { value } ) => (
<Action>
<div className="ppcp--static-value">{ value }</div>
</Action>
);
export default ControlStaticValue;

View file

@ -0,0 +1,22 @@
import { TextControl } from '@wordpress/components';
import { Action, Description } from '../Elements';
const ControlTextInput = ( {
value,
description,
onChange,
placeholder = '',
} ) => (
<Action>
<TextControl
className="ppcp-r-vertical-text-control"
placeholder={ placeholder }
value={ value }
onChange={ onChange }
/>
<Description>{ description }</Description>
</Action>
);
export default ControlTextInput;

View file

@ -0,0 +1,19 @@
import { ToggleControl } from '@wordpress/components';
import { Action, Description } from '../Elements';
const ControlToggleButton = ( { label, description, value, onChange } ) => (
<Action>
<ToggleControl
className="ppcp--control-toggle"
__nextHasNoMarginBottom={ true }
checked={ value }
onChange={ onChange }
label={ label }
help={
description ? <Description>{ description }</Description> : null
}
/>
</Action>
);
export default ControlToggleButton;

View file

@ -0,0 +1,6 @@
export { default as ControlStaticValue } from './ControlStaticValue';
export { default as ControlTextInput } from './ControlTextInput';
export { default as ControlToggleButton } from './ControlToggleButton';
export { default as ControlButton } from './ControlButton';
export { default as ControlRadioGroup } from './ControlRadioGroup';
export { default as ControlSelect } from './ControlSelect';

View file

@ -0,0 +1,5 @@
const Action = ( { children } ) => (
<div className="ppcp--action">{ children }</div>
);
export default Action;

View file

@ -0,0 +1,15 @@
import classNames from 'classnames';
const Content = ( { children, asCard = true, className = '', id = '' } ) => {
const elementClasses = classNames( 'ppcp--content', className, {
'ppcp--is-card': asCard,
} );
return (
<div id={ id } className={ elementClasses }>
{ children }
</div>
);
};
export default Content;

View file

@ -0,0 +1,5 @@
const ContentWrapper = ( { children } ) => (
<div className="ppcp-r-settings-card__content-wrapper">{ children }</div>
);
export default ContentWrapper;

View file

@ -0,0 +1,23 @@
import classNames from 'classnames';
const Description = ( { children, className = '' } ) => {
// Don't output anything if description is empty.
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--description', className );
if ( 'string' !== typeof children ) {
return <span className={ elementClasses }>{ children }</span>;
}
return (
<span
className={ elementClasses }
dangerouslySetInnerHTML={ { __html: children } }
/>
);
};
export default Description;

View file

@ -0,0 +1,13 @@
import classNames from 'classnames';
const Header = ( { children, className = '' } ) => {
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--header', className );
return <div className={ elementClasses }>{ children }</div>;
};
export default Header;

View file

@ -0,0 +1,16 @@
import classNames from 'classnames';
const Title = ( { children, noCaps = false, big = false, className = '' } ) => {
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--title', className, {
'ppcp--no-caps': noCaps,
'ppcp--big': big,
} );
return <span className={ elementClasses }>{ children }</span>;
};
export default Title;

View file

@ -0,0 +1,9 @@
const TitleExtra = ( { children } ) => {
if ( ! children ) {
return null;
}
return <span className="ppcp--title-extra">{ children }</span>;
};
export default TitleExtra;

View file

@ -0,0 +1,5 @@
const TitleWrapper = ( { children } ) => (
<span className="ppcp--title-wrapper">{ children }</span>
);
export default TitleWrapper;

View file

@ -0,0 +1,13 @@
/**
* Static elements used to build UI layouts.
*/
export { default as Action } from './Action';
export { default as Content } from './Content';
export { default as ContentWrapper } from './ContentWrapper';
export { default as Description } from './Description';
export { default as Header } from './Header';
export { default as Title } from './Title';
export { default as TitleExtra } from './TitleExtra';
export { default as TitleWrapper } from './TitleWrapper';
export { default as Separator } from './Separator';

View file

@ -1,138 +0,0 @@
import { CheckboxControl } from '@wordpress/components';
import classNames from 'classnames';
export const PayPalCheckbox = ( {
currentValue,
label,
value,
checked = null,
disabled = null,
changeCallback,
} ) => {
let isChecked = checked;
if ( null === isChecked ) {
if ( Array.isArray( currentValue ) ) {
isChecked = currentValue.includes( value );
} else {
isChecked = currentValue;
}
}
const className = classNames( { 'is-disabled': disabled } );
const onChange = ( newState ) => {
let newValue;
if ( ! Array.isArray( currentValue ) ) {
newValue = newState;
} else if ( newState ) {
newValue = [ ...currentValue, value ];
} else {
newValue = currentValue.filter(
( optionValue ) => optionValue !== value
);
}
changeCallback( newValue );
};
return (
<CheckboxControl
label={ label }
value={ value }
checked={ isChecked }
disabled={ disabled }
onChange={ onChange }
className={ className }
/>
);
};
export const CheckboxGroup = ( { options, value, onChange } ) => (
<>
{ options.map( ( checkbox ) => (
<PayPalCheckbox
key={ checkbox.value }
label={ checkbox.label }
value={ checkbox.value }
checked={ checkbox.checked }
disabled={ checkbox.disabled }
description={ checkbox.description }
tooltip={ checkbox.tooltip }
currentValue={ value }
changeCallback={ onChange }
/>
) ) }
</>
);
export const PayPalRdb = ( {
id,
name,
value,
currentValue,
handleRdbState,
} ) => {
return (
<div className="ppcp-r__radio">
{ /* todo: Can we remove the wrapper div? */ }
<input
className="ppcp-r__radio-value"
type="radio"
id={ id }
checked={ value === currentValue }
name={ name }
value={ value }
onChange={ () => handleRdbState( value ) }
/>
<span className="ppcp-r__radio-presentation"></span>
</div>
);
};
export const PayPalRdbWithContent = ( {
className,
id,
name,
label,
description,
value,
currentValue,
handleRdbState,
toggleAdditionalContent,
children,
} ) => {
const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className );
return (
<div className="ppcp-r__radio-outer-wrapper">
<div className={ wrapperClasses }>
<PayPalRdb
id={ id }
name={ name }
value={ value }
currentValue={ currentValue }
handleRdbState={ handleRdbState }
/>
<div className="ppcp-r__radio-content">
<label htmlFor={ id }>{ label }</label>
{ description && (
<p
className="ppcp-r__radio-description"
dangerouslySetInnerHTML={ {
__html: description,
} }
/>
) }
</div>
</div>
{ toggleAdditionalContent && children && value === currentValue && (
<div className="ppcp-r__radio-content-additional">
{ children }
</div>
) }
</div>
);
};

View file

@ -0,0 +1,37 @@
import { CheckboxControl } from '@wordpress/components';
import classNames from 'classnames';
const Checkbox = ( {
label,
value,
checked = null,
disabled = null,
onChange,
changeCallback, // deprecated.
} ) => {
const className = classNames( { 'ppcp--is-disabled': disabled } );
const handleChange = ( isChecked ) => {
if ( onChange ) {
onChange( value, isChecked );
} else if ( changeCallback ) {
console.warn(
'Deprecated prop, use "onChange" instead of "changeCallback"'
);
changeCallback( value, isChecked );
}
};
return (
<CheckboxControl
label={ label }
value={ value }
checked={ checked }
disabled={ disabled }
onChange={ handleChange }
className={ className }
/>
);
};
export default Checkbox;

View file

@ -0,0 +1,42 @@
import { PayPalCheckbox } from './index';
const CheckboxGroup = ( { options, value, onChange } ) => {
const handleChange = ( key, checked ) => {
const getNewValue = () => {
if ( checked ) {
return [ ...value, key ];
}
return value.filter( ( val ) => val !== key );
};
onChange( getNewValue() );
};
return (
<>
{ options.map(
( {
value: itemValue,
label,
checked,
disabled,
description,
tooltip,
} ) => (
<PayPalCheckbox
key={ itemValue }
value={ itemValue }
label={ label }
checked={ checked }
disabled={ disabled }
description={ description }
tooltip={ tooltip }
changeCallback={ handleChange }
/>
)
) }
</>
);
};
export default CheckboxGroup;

View file

@ -0,0 +1,100 @@
import classNames from 'classnames';
import { PayPalCheckbox, PayPalRdb } from './index';
const OptionSelector = ( {
multiSelect = false,
options,
value,
onChange,
} ) => (
<div className="ppcp-r-select-box-wrapper">
{ options.map(
( { value: itemValue, title, description, contents } ) => {
let isSelected;
if ( Array.isArray( value ) ) {
isSelected = value.includes( itemValue );
} else {
isSelected = value === itemValue;
}
return (
<OptionItem
key={ itemValue }
itemTitle={ title }
itemDescription={ description }
itemValue={ itemValue }
onChange={ onChange }
isMulti={ multiSelect }
isSelected={ isSelected }
>
{ contents }
</OptionItem>
);
}
) }
</div>
);
export default OptionSelector;
const OptionItem = ( {
itemTitle,
itemDescription,
itemValue,
onChange,
isMulti,
isSelected,
children,
} ) => {
const boxClassName = classNames( 'ppcp-r-select-box', {
'ppcp--selected': isSelected,
} );
return (
<div className={ boxClassName }>
<InputField
value={ itemValue }
isRadio={ ! isMulti }
onChange={ onChange }
isSelected={ isSelected }
/>
<div className="ppcp-r-select-box__content">
<div className="ppcp-r-select-box__content-inner">
<span className="ppcp-r-select-box__title">
{ itemTitle }
</span>
<p className="ppcp-r-select-box__description">
{ itemDescription }
</p>
{ children && (
<div className="ppcp-r-select-box__additional-content">
{ children }
</div>
) }
</div>
</div>
</div>
);
};
const InputField = ( { value, onChange, isRadio, isSelected } ) => {
if ( isRadio ) {
return (
<PayPalRdb
value={ value }
onChange={ onChange }
checked={ isSelected }
/>
);
}
return (
<PayPalCheckbox
value={ value }
onChange={ onChange }
checked={ isSelected }
/>
);
};

View file

@ -0,0 +1,42 @@
import { useCallback } from '@wordpress/element';
const RadioButton = ( {
id,
name,
value,
currentValue,
checked = null, // alternative to currentValue.
onChange,
handleRdbState, // deprecated
} ) => {
const handleChange = useCallback( () => {
if ( onChange ) {
onChange( value );
} else if ( handleRdbState ) {
console.warn(
'Deprecated prop, use "onChange" instead of "handleRdbState"'
);
handleRdbState( value );
}
}, [ handleRdbState, onChange, value ] );
const radioProps = {
className: 'ppcp-r__radio-value',
type: 'radio',
onChange: handleChange,
checked: null === checked ? value === currentValue : checked,
id,
name,
value,
};
return (
<div className="ppcp-r__radio">
{ /* todo: Can we remove the wrapper div? */ }
<input { ...radioProps } />
<span className="ppcp-r__radio-presentation"></span>
</div>
);
};
export default RadioButton;

View file

@ -0,0 +1,50 @@
import classNames from 'classnames';
import { PayPalRdb } from './index';
const RadioButtonWithContent = ( {
className,
id,
name,
label,
description,
value,
currentValue,
handleRdbState,
toggleAdditionalContent,
children,
} ) => {
const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className );
return (
<div className="ppcp-r__radio-outer-wrapper">
<div className={ wrapperClasses }>
<PayPalRdb
id={ id }
name={ name }
value={ value }
currentValue={ currentValue }
handleRdbState={ handleRdbState }
/>
<div className="ppcp-r__radio-content">
<label htmlFor={ id }>{ label }</label>
{ description && (
<p
className="ppcp-r__radio-description"
dangerouslySetInnerHTML={ {
__html: description,
} }
/>
) }
</div>
</div>
{ toggleAdditionalContent && children && value === currentValue && (
<div className="ppcp-r__radio-content-additional">
{ children }
</div>
) }
</div>
);
};
export default RadioButtonWithContent;

View file

@ -0,0 +1,13 @@
import { RadioControl } from '@wordpress/components';
const RadioGroup = ( { options, selected, onChange } ) => {
return (
<RadioControl
options={ options }
onChange={ onChange }
selected={ selected }
/>
);
};
export default RadioGroup;

View file

@ -0,0 +1,84 @@
/**
* TODO: Replace this with the WordPress select control once V2 with multi-select is ready.
*
* This component has a lot of compatibility logic to (a) make the ReactSelect component look like
* a WordPress select component, and (b) convert values from Redux-format (value-strings) to
* ReactSelect values (objects containing value and label). When switching to the
* SelectControl from `@wordpress/components`, we can remove a lot of this code.
*
* @see https://wordpress.github.io/gutenberg/?path=/story/components-customselectcontrol-v2--multiple-selection
* @file
*/
import { default as ReactSelect, components } from 'react-select';
import { Icon } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons';
import { useCallback, useEffect, useState } from '@wordpress/element';
const DropdownIndicator = ( props ) => (
<components.DropdownIndicator { ...props }>
<Icon icon={ props.selectProps.menuIsOpen ? chevronUp : chevronDown } />
</components.DropdownIndicator>
);
const IndicatorSeparator = () => null;
// Convert a plain value string/array to react-select objects.
const toInternalValue = ( selected, options ) => {
if ( Array.isArray( selected ) ) {
return selected.map( ( value ) =>
options.find( ( option ) => option.value === value )
);
}
return options.find( ( option ) => option.value === selected );
};
// Convert react-select object(s) to a plain value string/array.
const toStoreValue = ( selected ) => {
if ( ! selected ) {
return null;
}
if ( Array.isArray( selected ) ) {
return selected.map( ( value ) => value.value );
}
return selected.value;
};
const Select = ( { options, value, onChange, isMulti, placeholder } ) => {
const [ internalValue, setInternalValue ] = useState(
toInternalValue( value, options )
);
const onInternalValueChange = useCallback(
( selected ) => {
setInternalValue( selected );
if ( Array.isArray( selected ) ) {
return onChange( selected.map( ( option ) => option.id ) );
}
return onChange( selected.id );
},
[ onChange ]
);
// Forward changes of the internal ReactSelect value to the onChange callback.
useEffect( () => {
onChange( toStoreValue( internalValue ) );
}, [ internalValue, onChange ] );
return (
<ReactSelect
className="ppcp-r-select"
classNamePrefix="ppcp"
isMulti={ isMulti }
options={ options }
value={ internalValue }
onChange={ onInternalValueChange }
placeholder={ placeholder }
components={ { DropdownIndicator, IndicatorSeparator } }
/>
);
};
export default Select;

View file

@ -0,0 +1,11 @@
/**
* Generic input fields.
*/
export { default as PayPalCheckbox } from './Checkbox';
export { default as CheckboxGroup } from './CheckboxGroup';
export { default as RadioGroup } from './RadioGroup';
export { default as PayPalRdb } from './RadioButton';
export { default as PayPalRdbWithContent } from './RadioContent';
export { default as OptionSelector } from './OptionSelector';
export { default as Select } from './Select';

View file

@ -1,68 +0,0 @@
import data from '../../utils/data';
import { PayPalCheckbox, PayPalRdb } from './Fields';
const SelectBox = ( props ) => {
let boxClassName = 'ppcp-r-select-box';
if (
props.value === props.currentValue ||
( Array.isArray( props.currentValue ) &&
props.currentValue.includes( props.value ) )
) {
boxClassName += ' selected';
}
const handleClick = () => {
if ( props.type === 'checkbox' ) {
let newValue;
if ( Array.isArray( props.currentValue ) ) {
if ( props.currentValue.includes( props.value ) ) {
newValue = props.currentValue.filter(
( optionValue ) => optionValue !== props.value
);
} else {
newValue = [ ...props.currentValue, props.value ];
}
} else {
newValue = ! props.currentValue;
}
props.changeCallback( newValue );
}
};
return (
<div
className={ boxClassName }
onClick={ props.type === 'checkbox' ? handleClick : undefined }
>
{ props.type === 'radio' && (
<PayPalRdb
{ ...{
...props,
handleRdbState: props.changeCallback,
} }
/>
) }
{ props.type === 'checkbox' && <PayPalCheckbox { ...props } /> }
<div className="ppcp-r-select-box__content">
<div className="ppcp-r-select-box__content-inner">
<span className="ppcp-r-select-box__title">
{ props.title }
</span>
<p className="ppcp-r-select-box__description">
{ props.description }
</p>
{ props.children && (
<div className="ppcp-r-select-box__additional-content">
{ props.children }
</div>
) }
</div>
</div>
</div>
);
};
export default SelectBox;

View file

@ -1,5 +0,0 @@
const SelectBoxWrapper = ( props ) => {
return <div className="ppcp-r-select-box-wrapper">{ props.children }</div>;
};
export default SelectBoxWrapper;

View file

@ -0,0 +1,47 @@
import classNames from 'classnames';
import { Description, Header, Title, TitleExtra, Content } from './Elements';
const SettingsBlock = ( {
className,
children,
title,
titleSuffix,
description,
horizontalLayout = false,
separatorAndGap = true,
} ) => {
const blockClassName = classNames( 'ppcp-r-settings-block', className, {
'ppcp--no-gap': ! separatorAndGap,
'ppcp--horizontal': horizontalLayout,
} );
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => {
if ( ! blockTitle && ! blockDescription ) {
return null;
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
};
return (
<div className={ blockClassName }>
<BlockTitle
blockTitle={ title }
blockSuffix={ titleSuffix }
blockDescription={ description }
/>
<Content asCard={ false }>{ children }</Content>
</div>
);
};
export default SettingsBlock;

View file

@ -1,27 +0,0 @@
import Accordion from '../AccordionSection';
import SettingsBlock from './SettingsBlock';
import {
Header,
Title,
Action,
Description,
TitleWrapper,
} from './SettingsBlockElements';
const SettingsAccordion = ( { title, description, children, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__accordion">
<Accordion
title={ title }
description={ description }
Header={ Header }
TitleWrapper={ TitleWrapper }
Title={ Title }
Action={ Action }
Description={ Description }
>
{ children }
</Accordion>
</SettingsBlock>
);
export default SettingsAccordion;

View file

@ -1,27 +0,0 @@
import { Button } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Action, Description, Header, Title } from './SettingsBlockElements';
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
<Header>
<Title>{ title }</Title>
<Description>{ description }</Description>
</Header>
<Action>
<Button
isBusy={ props.actionProps?.isBusy }
variant={ props.actionProps?.buttonType }
onClick={
props.actionProps?.callback
? () => props.actionProps.callback()
: undefined
}
>
{ props?.actionProps?.value }
</Button>
</Action>
</SettingsBlock>
);
export default ButtonSettingsBlock;

View file

@ -1,6 +1,7 @@
import { Button } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Header, Title, Action, Description } from './SettingsBlockElements';
import { Header, Title, Action, Description } from '../Elements';
import SettingsBlock from '../SettingsBlock';
import TitleBadge from '../TitleBadge';
const FeatureSettingsBlock = ( { title, description, ...props } ) => {

View file

@ -1,61 +0,0 @@
import { TextControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import {
Title,
Action,
Description,
SupplementaryLabel,
} from './SettingsBlockElements';
const DEFAULT_ELEMENT_ORDER = [ 'title', 'action', 'description' ];
const ELEMENT_RENDERERS = {
title: ( { title, supplementaryLabel } ) => (
<Title>
{ title }
{ supplementaryLabel && (
<SupplementaryLabel>{ supplementaryLabel }</SupplementaryLabel>
) }
</Title>
),
action: ( { actionProps } ) => (
<Action>
<TextControl
className="ppcp-r-vertical-text-control"
placeholder={ actionProps?.placeholder }
value={ actionProps?.value }
onChange={ ( newValue ) =>
actionProps?.callback( actionProps?.key, newValue )
}
/>
</Action>
),
description: ( { description } ) => (
<Description>{ description }</Description>
),
};
const InputSettingsBlock = ( {
title,
description,
supplementaryLabel,
order = DEFAULT_ELEMENT_ORDER,
...props
} ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__input">
{ order.map( ( elementKey ) => {
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
return RenderElement ? (
<RenderElement
key={ elementKey }
title={ title }
description={ description }
supplementaryLabel={ supplementaryLabel }
actionProps={ props.actionProps }
/>
) : null;
} ) }
</SettingsBlock>
);
export default InputSettingsBlock;

View file

@ -1,5 +1,6 @@
import { ToggleControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import SettingsBlock from '../SettingsBlock';
import PaymentMethodIcon from '../PaymentMethodIcon';
import data from '../../../utils/data';

View file

@ -1,4 +1,4 @@
import SettingsBlock from './SettingsBlock';
import SettingsBlock from '../SettingsBlock';
import PaymentMethodItemBlock from './PaymentMethodItemBlock';
import { usePaymentMethods } from '../../../data/payment/hooks';

View file

@ -1,42 +0,0 @@
import SettingsBlock from './SettingsBlock';
import { Header, Title, Description } from './SettingsBlockElements';
import { PayPalRdbWithContent } from '../Fields';
const RadioSettingsBlock = ( {
title,
description,
options = [],
...props
} ) => (
<SettingsBlock
{ ...props }
className="ppcp-r-settings-block__radio ppcp-r-settings-block--expert-rdb"
>
<Header>
<Title>{ title }</Title>
<Description>{ description }</Description>
</Header>
{ options.map( ( option ) => (
<PayPalRdbWithContent
key={ option.id }
id={ option.id }
name={ props.actionProps?.name }
value={ option.value }
currentValue={ props.actionProps?.currentValue }
handleRdbState={ ( newValue ) =>
props.actionProps?.callback(
props.actionProps?.key,
newValue
)
}
label={ option.label }
description={ option.description }
toggleAdditionalContent={ true }
>
{ option.additionalContent }
</PayPalRdbWithContent>
) ) }
</SettingsBlock>
);
export default RadioSettingsBlock;

View file

@ -1,53 +0,0 @@
import Select, { components } from 'react-select';
import data from '../../../utils/data';
import SettingsBlock from './SettingsBlock';
import { Title, Action, Description } from './SettingsBlockElements';
const DEFAULT_ELEMENT_ORDER = [ 'title', 'action', 'description' ];
const DropdownIndicator = ( props ) => (
<components.DropdownIndicator { ...props }>
{ data().getImage( 'icon-arrow-down.svg' ) }
</components.DropdownIndicator>
);
const ELEMENT_RENDERERS = {
title: ( { title } ) => <Title>{ title }</Title>,
action: ( { actionProps } ) => (
<Action>
<Select
className="ppcp-r-multiselect"
classNamePrefix="ppcp-r"
isMulti={ actionProps?.isMulti }
options={ actionProps?.options }
components={ { DropdownIndicator } }
/>
</Action>
),
description: ( { description } ) => (
<Description>{ description }</Description>
),
};
const SelectSettingsBlock = ( {
title,
description,
order = DEFAULT_ELEMENT_ORDER,
...props
} ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__select">
{ order.map( ( elementKey ) => {
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
return RenderElement ? (
<RenderElement
key={ elementKey }
title={ title }
description={ description }
actionProps={ props.actionProps }
/>
) : null;
} ) }
</SettingsBlock>
);
export default SelectSettingsBlock;

View file

@ -1,11 +0,0 @@
import classNames from 'classnames';
const SettingsBlock = ( { className, children, separatorAndGap = true } ) => {
const blockClassName = classNames( 'ppcp-r-settings-block', className, {
'no-gap': ! separatorAndGap,
} );
return <div className={ blockClassName }>{ children }</div>;
};
export default SettingsBlock;

View file

@ -1,81 +0,0 @@
import classNames from 'classnames';
// Block Elements
export const Title = ( {
children,
altStyle = false,
big = false,
className = '',
} ) => {
const elementClasses = classNames(
'ppcp-r-settings-block__title',
className,
{
'style-alt': altStyle,
'style-big': big,
}
);
return <span className={ elementClasses }>{ children }</span>;
};
export const TitleWrapper = ( { children } ) => (
<span className="ppcp-r-settings-block__title-wrapper">{ children }</span>
);
export const SupplementaryLabel = ( { children } ) => (
<span className="ppcp-r-settings-block__supplementary-title-label">
{ children }
</span>
);
export const Description = ( { children, asHtml = false, className = '' } ) => {
// Don't output anything if description is empty.
if ( ! children ) {
return null;
}
const elementClasses = classNames(
'ppcp-r-settings-block__description',
className
);
if ( ! asHtml ) {
return <span className={ elementClasses }>{ children }</span>;
}
return (
<span
className={ elementClasses }
dangerouslySetInnerHTML={ { __html: children } }
/>
);
};
export const Action = ( { children } ) => (
<div className="ppcp-r-settings-block__action">{ children }</div>
);
export const Header = ( { children, className = '' } ) => (
<div className={ `ppcp-r-settings-block__header ${ className }`.trim() }>
{ children }
</div>
);
// Card Elements
export const Content = ( { children, className = '', id = '' } ) => {
const elementClasses = classNames(
'ppcp-r-settings-card__content',
className
);
return (
<div id={ id } className={ elementClasses }>
{ children }
</div>
);
};
export const ContentWrapper = ( { children } ) => (
<div className="ppcp-r-settings-card__content-wrapper">{ children }</div>
);

View file

@ -1,27 +0,0 @@
import { ToggleControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Header, Title, Action, Description } from './SettingsBlockElements';
const ToggleSettingsBlock = ( { title, description, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__toggle">
<Action>
<ToggleControl
className="ppcp-r-settings-block__toggle-control"
__nextHasNoMarginBottom={ true }
checked={ props.actionProps?.value }
onChange={ ( newValue ) =>
props.actionProps?.callback(
props.actionProps?.key,
newValue
)
}
/>
</Action>
<Header>
{ title && <Title>{ title }</Title> }
{ description && <Description>{ description }</Description> }
</Header>
</SettingsBlock>
);
export default ToggleSettingsBlock;

View file

@ -1,20 +1,4 @@
export { default as SettingsBlock } from './SettingsBlock';
export { default as ButtonSettingsBlock } from './ButtonSettingsBlock';
export { default as InputSettingsBlock } from './InputSettingsBlock';
export { default as SelectSettingsBlock } from './SelectSettingsBlock';
export { default as AccordionSettingsBlock } from './AccordionSettingsBlock';
export { default as ToggleSettingsBlock } from './ToggleSettingsBlock';
export { default as RadioSettingsBlock } from './RadioSettingsBlock';
export { default as PaymentMethodsBlock } from './PaymentMethodsBlock';
export { default as PaymentMethodItemBlock } from './PaymentMethodItemBlock';
export {
Title,
TitleWrapper,
SupplementaryLabel,
Description,
Action,
Content,
ContentWrapper,
Header,
} from './SettingsBlockElements';
export { default as TodoSettingsBlock } from './TodoSettingsBlock';
export { default as FeatureSettingsBlock } from './FeatureSettingsBlock';

View file

@ -1,6 +1,6 @@
import classNames from 'classnames';
import { Content, ContentWrapper } from './SettingsBlocks';
import { Content, ContentWrapper } from './Elements';
const SettingsCard = ( {
id,

View file

@ -1,6 +1,11 @@
import { __ } from '@wordpress/i18n';
import { Spinner } from '@wordpress/components';
const SpinnerOverlay = ( { message = '' } ) => {
const SpinnerOverlay = ( { message = null } ) => {
if ( null === message ) {
message = __( 'Loading…', 'woocommerce-paypal-payments' );
}
return (
<div className="ppcp-r-spinner-overlay">
{ message && (

View file

@ -20,10 +20,10 @@ const TopNavigation = ( {
const { isScrolled } = useIsScrolled();
const className = classNames( 'ppcp-r-navigation-container', {
'is-scrolled': isScrolled,
'ppcp--is-scrolled': isScrolled,
} );
const titleClassName = classNames( 'title', {
big: isMainTitle,
const titleClassName = classNames( 'ppcp--nav-title', {
'ppcp--big': isMainTitle,
} );
const handleTitleClick = useCallback( () => {

View file

@ -3,7 +3,7 @@ import { __, sprintf } from '@wordpress/i18n';
import BadgeBox, {
BADGE_BOX_TITLE_BIG,
} from '../../../ReusableComponents/BadgeBox';
import Separator from '../../../ReusableComponents/Separator';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
import OptionalPaymentMethods from './OptionalPaymentMethods';

View file

@ -1,7 +1,7 @@
import { __, sprintf } from '@wordpress/i18n';
import BadgeBox from '../../../ReusableComponents/BadgeBox';
import Separator from '../../../ReusableComponents/Separator';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
const AcdcOptionalPaymentMethods = ( {

View file

@ -1,4 +1,4 @@
import Separator from '../../../ReusableComponents/Separator';
import { Separator } from '../../../ReusableComponents/Elements';
import SandboxConnectionForm from './SandboxConnectionForm';
import ManualConnectionForm from './ManualConnectionForm';

View file

@ -3,7 +3,7 @@ import { __, sprintf } from '@wordpress/i18n';
import BadgeBox, {
BADGE_BOX_TITLE_BIG,
} from '../../../ReusableComponents/BadgeBox';
import Separator from '../../../ReusableComponents/Separator';
import { Separator } from '../../../ReusableComponents/Elements';
import PricingTitleBadge from '../../../ReusableComponents/PricingTitleBadge';
import OptionalPaymentMethods from './OptionalPaymentMethods';

View file

@ -54,8 +54,8 @@ const ConnectionButton = ( {
removeCompleteHandler,
} = useHandleOnboardingButton( isSandbox );
const buttonClassName = classNames( 'ppcp-r-connection-button', className, {
'sandbox-mode': isSandbox,
'live-mode': ! isSandbox,
'ppcp--mode-sandbox': isSandbox,
'ppcp--mode-live': ! isSandbox,
} );
const environment = isSandbox ? 'sandbox' : 'production';

View file

@ -157,7 +157,7 @@ const ManualConnectionForm = () => {
value={ manualClientId }
onChange={ setManualClientId }
className={ classNames( {
'has-error': ! clientValid,
'ppcp--has-error': ! clientValid,
} ) }
/>
{ clientValid || (

View file

@ -1,27 +1,29 @@
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import SelectBoxWrapper from '../../../ReusableComponents/SelectBoxWrapper';
import SelectBox from '../../../ReusableComponents/SelectBox';
import { OptionSelector } from '../../../ReusableComponents/Fields';
import { OnboardingHooks, BUSINESS_TYPES } from '../../../../data';
import OnboardingHeader from '../Components/OnboardingHeader';
const BUSINESS_RADIO_GROUP_NAME = 'business';
const getBusinessType = ( isCasualSeller ) => {
if ( isCasualSeller === null ) {
return '';
}
return isCasualSeller
? BUSINESS_TYPES.CASUAL_SELLER
: BUSINESS_TYPES.BUSINESS;
};
const StepBusiness = ( {} ) => {
const { isCasualSeller, setIsCasualSeller } = OnboardingHooks.useBusiness();
const [ businessChoice, setBusinessChoice ] = useState(
getBusinessType( isCasualSeller )
);
const handleSellerTypeChange = ( value ) =>
setIsCasualSeller( BUSINESS_TYPES.CASUAL_SELLER === value );
const getCurrentValue = () => {
if ( isCasualSeller === null ) {
return '';
}
return isCasualSeller
? BUSINESS_TYPES.CASUAL_SELLER
: BUSINESS_TYPES.BUSINESS;
};
useEffect( () => {
setIsCasualSeller( BUSINESS_TYPES.CASUAL_SELLER === businessChoice );
}, [ businessChoice, setIsCasualSeller ] );
return (
<div className="ppcp-r-page-business">
@ -32,43 +34,34 @@ const StepBusiness = ( {} ) => {
) }
/>
<div className="ppcp-r-inner-container">
<SelectBoxWrapper>
<SelectBox
title={ __(
'Business',
'woocommerce-paypal-payments'
) }
description={ __(
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
'woocommerce-paypal-payments'
) }
name={ BUSINESS_RADIO_GROUP_NAME }
value={ BUSINESS_TYPES.BUSINESS }
changeCallback={ handleSellerTypeChange }
currentValue={ getCurrentValue() }
checked={ isCasualSeller === false }
type="radio"
></SelectBox>
<SelectBox
title={ __(
'Personal Account',
'woocommerce-paypal-payments'
) }
description={ __(
'Ideal for those who primarily make purchases or send personal transactions to family and friends.',
'woocommerce-paypal-payments'
) }
name={ BUSINESS_RADIO_GROUP_NAME }
value={ BUSINESS_TYPES.CASUAL_SELLER }
changeCallback={ handleSellerTypeChange }
currentValue={ getCurrentValue() }
checked={ isCasualSeller === true }
type="radio"
></SelectBox>
</SelectBoxWrapper>
<OptionSelector
multiSelect={ false }
options={ businessChoices }
onChange={ setBusinessChoice }
value={ businessChoice }
/>
</div>
</div>
);
};
const businessChoices = [
{
value: BUSINESS_TYPES.BUSINESS,
title: __( 'Business', 'woocommerce-paypal-payments' ),
description: __(
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
'woocommerce-paypal-payments'
),
},
{
value: BUSINESS_TYPES.CASUAL_SELLER,
title: __( 'Personal Account', 'woocommerce-paypal-payments' ),
description: __(
'Ideal for those who primarily make purchases or send personal transactions to family and friends.',
'woocommerce-paypal-payments'
),
},
];
export default StepBusiness;

View file

@ -1,71 +1,26 @@
import { __ } from '@wordpress/i18n';
import { CommonHooks, OnboardingHooks } from '../../../../data';
import SelectBoxWrapper from '../../../ReusableComponents/SelectBoxWrapper';
import SelectBox from '../../../ReusableComponents/SelectBox';
import { OptionSelector } from '../../../ReusableComponents/Fields';
import PricingDescription from '../../../ReusableComponents/PricingDescription';
import OnboardingHeader from '../Components/OnboardingHeader';
import OptionalPaymentMethods from '../Components/OptionalPaymentMethods';
const OPM_RADIO_GROUP_NAME = 'optional-payment-methods';
const StepPaymentMethods = ( {} ) => {
const {
areOptionalPaymentMethodsEnabled,
setAreOptionalPaymentMethodsEnabled,
} = OnboardingHooks.useOptionalPaymentMethods();
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
let screenTitle = __(
'Add optional payment methods to your Checkout',
'woocommerce-paypal-payments'
);
if ( 'US' === storeCountry ) {
screenTitle = __(
'Add Expanded Checkout for More Ways to Pay',
'woocommerce-paypal-payments'
);
}
const { optionalMethods, setOptionalMethods } =
OnboardingHooks.useOptionalPaymentMethods();
return (
<div className="ppcp-r-page-optional-payment-methods">
<OnboardingHeader title={ screenTitle } />
<OnboardingHeader title={ <PaymentStepTitle /> } />
<div className="ppcp-r-inner-container">
<SelectBoxWrapper>
<SelectBox
title={ __(
'Available with additional application',
'woocommerce-paypal-payments'
) }
description={
<OptionalPaymentMethods
useAcdc={ true }
isFastlane={ true }
isPayLater={ true }
storeCountry={ storeCountry }
storeCurrency={ storeCurrency }
/>
}
name={ OPM_RADIO_GROUP_NAME }
value={ true }
changeCallback={ setAreOptionalPaymentMethodsEnabled }
currentValue={ areOptionalPaymentMethodsEnabled }
type="radio"
></SelectBox>
<SelectBox
title={ __(
'No thanks, I prefer to use a different provider for processing credit cards, digital wallets, and local payment methods',
'woocommerce-paypal-payments'
) }
name={ OPM_RADIO_GROUP_NAME }
value={ false }
changeCallback={ setAreOptionalPaymentMethodsEnabled }
currentValue={ areOptionalPaymentMethodsEnabled }
type="radio"
></SelectBox>
</SelectBoxWrapper>
<OptionSelector
multiSelect={ false }
options={ methodChoices }
onChange={ setOptionalMethods }
value={ optionalMethods }
/>
<PricingDescription />
</div>
</div>
@ -73,3 +28,51 @@ const StepPaymentMethods = ( {} ) => {
};
export default StepPaymentMethods;
const PaymentStepTitle = () => {
const { storeCountry } = CommonHooks.useWooSettings();
if ( 'US' === storeCountry ) {
return __(
'Add Expanded Checkout for More Ways to Pay',
'woocommerce-paypal-payments'
);
}
return __(
'Add optional payment methods to your Checkout',
'woocommerce-paypal-payments'
);
};
const OptionalMethodDescription = () => {
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
return (
<OptionalPaymentMethods
useAcdc={ true }
isFastlane={ true }
isPayLater={ true }
storeCountry={ storeCountry }
storeCurrency={ storeCurrency }
/>
);
};
const methodChoices = [
{
value: true,
title: __(
'Available with additional application',
'woocommerce-paypal-payments'
),
description: <OptionalMethodDescription />,
},
{
title: __(
'No thanks, I prefer to use a different provider for processing credit cards, digital wallets, and local payment methods',
'woocommerce-paypal-payments'
),
value: false,
},
];

View file

@ -1,15 +1,53 @@
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import SelectBox from '../../../ReusableComponents/SelectBox';
import SelectBoxWrapper from '../../../ReusableComponents/SelectBoxWrapper';
import { OptionSelector } from '../../../ReusableComponents/Fields';
import { OnboardingHooks, PRODUCT_TYPES } from '../../../../data';
import OnboardingHeader from '../Components/OnboardingHeader';
const PRODUCTS_CHECKBOX_GROUP_NAME = 'products';
const StepProducts = () => {
const { products, setProducts } = OnboardingHooks.useProducts();
const { canUseSubscriptions } = OnboardingHooks.useFlags();
const [ optionState, setOptionState ] = useState( null );
const [ productChoices, setProductChoices ] = useState( [] );
useEffect( () => {
const initChoices = () => {
if ( optionState === canUseSubscriptions ) {
return;
}
let choices = productChoicesFull;
// Remove subscription details, if not available.
if ( ! canUseSubscriptions ) {
choices = choices.filter(
( { value } ) => value !== PRODUCT_TYPES.SUBSCRIPTIONS
);
setProducts(
products.filter(
( value ) => value !== PRODUCT_TYPES.SUBSCRIPTIONS
)
);
}
setProductChoices( choices );
setOptionState( canUseSubscriptions );
};
initChoices();
}, [ canUseSubscriptions, optionState, products, setProducts ] );
const handleChange = ( key, checked ) => {
const getNewValue = () => {
if ( checked ) {
return [ ...products, key ];
}
return products.filter( ( val ) => val !== key );
};
setProducts( getNewValue() );
};
return (
<div className="ppcp-r-page-products">
@ -20,104 +58,70 @@ const StepProducts = () => {
) }
/>
<div className="ppcp-r-inner-container">
<SelectBoxWrapper>
<SelectBox
title={ __( 'Virtual', 'woocommerce-paypal-payments' ) }
description={ __(
'Items do not require shipping.',
'woocommerce-paypal-payments'
) }
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
value={ PRODUCT_TYPES.VIRTUAL }
changeCallback={ setProducts }
currentValue={ products }
type="checkbox"
>
<ul className="ppcp-r-services">
<li>
{ __(
'Services',
'woocommerce-paypal-payments'
) }
</li>
<li>
{ __(
'Downloadable',
'woocommerce-paypal-payments'
) }
</li>
<li>
{ __(
'Bookings',
'woocommerce-paypal-payments'
) }
</li>
<li>
{ __(
'Deposits',
'woocommerce-paypal-payments'
) }
</li>
</ul>
</SelectBox>
<SelectBox
title={ __(
'Physical Goods',
'woocommerce-paypal-payments'
) }
description={ __(
'Items require shipping.',
'woocommerce-paypal-payments'
) }
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
value={ PRODUCT_TYPES.PHYSICAL }
changeCallback={ setProducts }
currentValue={ products }
type="checkbox"
>
<ul className="ppcp-r-services">
<li>
{ __( 'Goods', 'woocommerce-paypal-payments' ) }
</li>
<li>
{ __(
'Deliveries',
'woocommerce-paypal-payments'
) }
</li>
</ul>
</SelectBox>
{ canUseSubscriptions && (
<SelectBox
title={ __(
'Subscriptions',
'woocommerce-paypal-payments'
) }
description={ __(
'Recurring payments for either physical goods or services.',
'woocommerce-paypal-payments'
) }
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
value={ PRODUCT_TYPES.SUBSCRIPTIONS }
changeCallback={ setProducts }
currentValue={ products }
type="checkbox"
>
<a
target="__blank"
href="https://woocommerce.com/document/woocommerce-paypal-payments/#subscriptions-faq"
>
{ __(
'WooCommerce Subscriptions',
'woocommerce-paypal-payments'
) }
</a>
</SelectBox>
) }
</SelectBoxWrapper>
<OptionSelector
multiSelect={ true }
options={ productChoices }
onChange={ handleChange }
value={ products }
/>
</div>
</div>
);
};
export default StepProducts;
const DetailsVirtual = () => (
<ul className="ppcp-r-services">
<li>{ __( 'Services', 'woocommerce-paypal-payments' ) }</li>
<li>{ __( 'Downloadable', 'woocommerce-paypal-payments' ) }</li>
<li>{ __( 'Bookings', 'woocommerce-paypal-payments' ) }</li>
<li>{ __( 'Deposits', 'woocommerce-paypal-payments' ) }</li>
</ul>
);
const DetailsPhysical = () => (
<ul className="ppcp-r-services">
<li>{ __( 'Goods', 'woocommerce-paypal-payments' ) }</li>
<li>{ __( 'Deliveries', 'woocommerce-paypal-payments' ) }</li>
</ul>
);
const DetailsSubscriptions = () => (
<a
target="__blank"
href="https://woocommerce.com/document/woocommerce-paypal-payments/#subscriptions-faq"
>
{ __( 'WooCommerce Subscriptions', 'woocommerce-paypal-payments' ) }
</a>
);
const productChoicesFull = [
{
value: PRODUCT_TYPES.VIRTUAL,
title: __( 'Virtual', 'woocommerce-paypal-payments' ),
description: __(
'Items do not require shipping.',
'woocommerce-paypal-payments'
),
contents: <DetailsVirtual />,
},
{
value: PRODUCT_TYPES.PHYSICAL,
title: __( 'Physical Goods', 'woocommerce-paypal-payments' ),
description: __(
'Items require shipping.',
'woocommerce-paypal-payments'
),
contents: <DetailsPhysical />,
},
{
value: PRODUCT_TYPES.SUBSCRIPTIONS,
title: __( 'Subscriptions', 'woocommerce-paypal-payments' ),
description: __(
'Recurring payments for either physical goods or services.',
'woocommerce-paypal-payments'
),
contents: <DetailsSubscriptions />,
},
];

View file

@ -2,8 +2,8 @@ import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import PaymentMethodIcons from '../../../ReusableComponents/PaymentMethodIcons';
import Separator from '../../../ReusableComponents/Separator';
import AccordionSection from '../../../ReusableComponents/AccordionSection';
import { Separator } from '../../../ReusableComponents/Elements';
import Accordion from '../../../ReusableComponents/AccordionSection';
import { CommonHooks } from '../../../../data';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
import OnboardingHeader from '../Components/OnboardingHeader';
@ -55,16 +55,17 @@ const StepWelcome = ( { setStep, currentStep } ) => {
storeCountry={ storeCountry }
/>
<Separator text={ __( 'or', 'woocommerce-paypal-payments' ) } />
<AccordionSection
<Accordion
title={ __(
'See advanced options',
'woocommerce-paypal-payments'
) }
className="onboarding-advanced-options"
noCaps={ true }
id="advanced-options"
>
<AdvancedOptionsForm />
</AccordionSection>
</Accordion>
</div>
);
};

View file

@ -1,7 +1,7 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../ReusableComponents/SettingsCard';
import PaymentMethodsBlock from '../../ReusableComponents/SettingsBlocks/PaymentMethodsBlock';
import { PaymentMethodsBlock } from '../../ReusableComponents/SettingsBlocks';
import { PaymentHooks } from '../../../data';
import { useActiveModal } from '../../../data/common/hooks';
import Modal from './TabSettingsElements/Blocks/Modal';

View file

@ -1,33 +0,0 @@
import ConnectionStatus from './TabSettingsElements/ConnectionStatus';
import CommonSettings from './TabSettingsElements/CommonSettings';
import ExpertSettings from './TabSettingsElements/ExpertSettings';
import { useSettings } from '../../../data/settings-tab/hooks';
const TabSettings = () => {
const { settings, setSettings } = useSettings();
const updateFormValue = ( key, value ) => {
setSettings( {
...settings,
[ key ]: value,
} );
};
return (
<>
<div className="ppcp-r-settings">
<ConnectionStatus />
<CommonSettings
settings={ settings }
updateFormValue={ updateFormValue }
/>
<ExpertSettings
settings={ settings }
updateFormValue={ updateFormValue }
/>
</div>
</>
);
};
export default TabSettings;

View file

@ -1,14 +1,12 @@
import { __, sprintf } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import {
AccordionSettingsBlock,
RadioSettingsBlock,
InputSettingsBlock,
} from '../../../../ReusableComponents/SettingsBlocks';
import {
sandboxData,
productionData,
} from '../../../../../data/settings/connection-details-data';
ControlTextInput,
ControlRadioGroup,
} from '../../../../ReusableComponents/Controls';
import Accordion from '../../../../ReusableComponents/AccordionSection';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock';
const ConnectionDetails = ( { settings, updateFormValue } ) => {
const isSandbox = settings.sandboxConnected;
@ -20,22 +18,192 @@ const ConnectionDetails = ( { settings, updateFormValue } ) => {
const modeKey = isSandbox ? 'productionMode' : 'sandboxMode';
return (
<AccordionSettingsBlock
<Accordion
title={ modeConfig.title }
description={ modeConfig.description }
>
<RadioSettingsBlock
<SettingsBlock
title={ modeConfig.connectTitle }
description={ modeConfig.connectDescription }
options={ modeConfig.options }
actionProps={ {
key: modeKey,
currentValue: settings[ modeKey ],
callback: updateFormValue,
} }
/>
</AccordionSettingsBlock>
>
<ControlRadioGroup
options={ modeConfig.options }
value={ settings[ modeKey ] }
onChange={ updateFormValue }
/>
</SettingsBlock>
</Accordion>
);
};
export default ConnectionDetails;
// Helper logic, refactor this when possible.
/**
* Generates options for the environment mode settings.
*
* @param {Object} config - Configuration for the mode.
* @param {Object} settings - Current settings.
* @param {Function} updateFormValue - Callback to update settings.
* @return {Array} Options array.
*/
const generateOptions = ( config, settings, updateFormValue ) => [
{
id: `${ config.mode }_mode`,
value: `${ config.mode }_mode`,
label: config.labelTitle,
description: config.labelDescription,
additionalContent: (
<Button
variant="primary"
onClick={ () => {
updateFormValue( `${ config.mode }Connected`, true );
if ( config.mode === 'production' ) {
global.ppcpSettings.startOnboarding();
}
} }
>
{ config.buttonText }
</Button>
),
},
{
id: 'manual_connect',
value: 'manual_connect',
label: __( 'Manual Connect', 'woocommerce-paypal-payments' ),
description: sprintf(
__(
'For advanced users: Connect a custom PayPal REST app for full control over your integration. For more information on creating a PayPal REST application, <a target="_blank" href="%s">click here</a>.',
'woocommerce-paypal-payments'
),
'#'
),
additionalContent: (
<>
<ControlTextInput
title={ config.clientIdTitle }
// Input field props.
value={ settings[ `${ config.mode }ClientId` ] }
onChange={ updateFormValue }
placeholder={ __(
'Enter Client ID',
'woocommerce-paypal-payments'
) }
/>
<ControlTextInput
title={ config.secretKeyTitle }
// Input field props.
value={ settings[ `${ config.mode }SecretKey` ] }
onChange={ updateFormValue }
placeholder={ __(
'Enter Secret Key',
'woocommerce-paypal-payments'
) }
/>
<Button
variant="primary"
onClick={ () =>
updateFormValue(
`${ config.mode }ManuallyConnected`,
true
)
}
>
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
</Button>
</>
),
},
];
/**
* Generates data for a given mode (sandbox or production).
*
* @param {Object} config - Configuration for the mode.
* @param {Object} settings - Current settings.
* @param {Function} updateFormValue - Callback to update settings.
* @return {Object} Mode configuration.
*/
const generateModeData = ( config, settings, updateFormValue ) => ( {
title: config.title,
description: config.description,
connectTitle: __(
`Connect ${ config.label } Account`, // TODO: Avoid variables inside __() translation literal.
'woocommerce-paypal-payments'
),
connectDescription: config.connectDescription,
options: generateOptions( config, settings, updateFormValue ),
} );
const sandboxData = ( { settings = {}, updateFormValue = () => {} } ) =>
generateModeData(
{
mode: 'sandbox',
label: 'Sandbox',
title: __( 'Sandbox', 'woocommerce-paypal-payments' ),
description: __(
"Test your site in PayPal's Sandbox environment.",
'woocommerce-paypal-payments'
),
connectDescription: __(
'Connect a PayPal Sandbox account in order to test your website. Transactions made will not result in actual money movement. Do not fulfil orders completed in Sandbox mode.',
'woocommerce-paypal-payments'
),
labelTitle: __( 'Sandbox Mode', 'woocommerce-paypal-payments' ),
labelDescription: __(
'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'
),
buttonText: __(
'Connect Sandbox Account',
'woocommerce-paypal-payments'
),
clientIdTitle: __(
'Sandbox Client ID',
'woocommerce-paypal-payments'
),
secretKeyTitle: __(
'Sandbox Secret Key',
'woocommerce-paypal-payments'
),
},
settings,
updateFormValue
);
const productionData = ( { settings = {}, updateFormValue = () => {} } ) =>
generateModeData(
{
mode: 'production',
label: 'Live',
title: __( 'Live Payments', 'woocommerce-paypal-payments' ),
description: __(
'Your site is currently configured in Sandbox mode to test payments. When you are ready, launch your site and receive live payments via PayPal.',
'woocommerce-paypal-payments'
),
connectDescription: __(
'Connect a live PayPal account to launch your site and receive live payments via PayPal. PayPal will guide you through the setup process.',
'woocommerce-paypal-payments'
),
labelTitle: __( 'Production Mode', 'woocommerce-paypal-payments' ),
labelDescription: __(
'Activate Production mode to connect your live account and receive live payments via PayPal. Stay connected in Sandbox mode to continue testing payments before going live.',
'woocommerce-paypal-payments'
),
buttonText: __(
'Set up and connect live PayPal Account',
'woocommerce-paypal-payments'
),
clientIdTitle: __(
'Live Account Client ID',
'woocommerce-paypal-payments'
),
secretKeyTitle: __(
'Live Account Secret Key',
'woocommerce-paypal-payments'
),
},
settings,
updateFormValue
);

View file

@ -1,49 +0,0 @@
import { __ } from '@wordpress/i18n';
import {
Header,
SettingsBlock,
Title,
Description,
ToggleSettingsBlock,
} from '../../../../ReusableComponents/SettingsBlocks';
const OrderIntent = ( { updateFormValue, settings } ) => {
return (
<SettingsBlock>
<Header>
<Title>
{ __( 'Order Intent', 'woocommerce-paypal-payments' ) }
</Title>
<Description>
{ __(
'Choose between immediate capture or authorization-only, with manual capture in the Order section.',
'woocommerce-paypal-payments'
) }
</Description>
</Header>
<ToggleSettingsBlock
title={ __( 'Authorize Only', 'woocommerce-paypal-payments' ) }
actionProps={ {
callback: updateFormValue,
key: 'authorizeOnly',
value: settings.authorizeOnly,
} }
/>
<ToggleSettingsBlock
title={ __(
'Capture Virtual-Only Orders',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'captureVirtualOnlyOrders',
value: settings.captureVirtualOnlyOrders,
} }
/>
</SettingsBlock>
);
};
export default OrderIntent;

View file

@ -1,10 +1,48 @@
import { __ } from '@wordpress/i18n';
import {
AccordionSettingsBlock,
SelectSettingsBlock,
} from '../../../../ReusableComponents/SettingsBlocks';
const creditCardExamples = [
import Accordion from '../../../../ReusableComponents/AccordionSection';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock';
import { ControlSelect } from '../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../data';
const OtherSettings = () => {
const { disabledCards, setDisabledCards } = SettingsHooks.useSettings();
return (
<Accordion
title={ __(
'Other payment method settings',
'woocommerce-paypal-payments'
) }
description={ __(
'Modify the checkout experience for alternative payment methods, credit cards, and digital wallets.',
'woocommerce-paypal-payments'
) }
>
<SettingsBlock
title={ __(
'Disable specific credit cards',
'woocommerce-paypal-payments'
) }
description={ __(
"If left blank, PayPal and other buttons will present in the user's detected language. Enter a language here to force all buttons to display in that language.",
'woocommerce-paypal-payments'
) }
>
<ControlSelect
options={ disabledCardChoices }
value={ disabledCards }
onChange={ setDisabledCards }
isMulti={ true }
/>
</SettingsBlock>
</Accordion>
);
};
export default OtherSettings;
const disabledCardChoices = [
{ value: '', label: __( 'Select', 'woocommerce-paypal-payments' ) },
{
value: 'mastercard',
@ -21,39 +59,3 @@ const creditCardExamples = [
label: __( 'Diners Club', 'woocommerce-paypal-payments' ),
},
];
const OtherSettings = ( { settings, updateFormValue } ) => {
return (
<AccordionSettingsBlock
title={ __(
'Other payment method settings',
'woocommerce-paypal-payments'
) }
description={ __(
'Modify the checkout experience for alternative payment methods, credit cards, and digital wallets.',
'woocommerce-paypal-payments'
) }
>
<SelectSettingsBlock
title={ __(
'Disable specific credit cards',
'woocommerce-paypal-payments'
) }
description={ __(
"If left blank, PayPal and other buttons will present in the user's detected language. Enter a language here to force all buttons to display in that language.",
'woocommerce-paypal-payments'
) }
actionProps={ {
options: creditCardExamples,
value: settings.buttonLanguage,
callback: updateFormValue,
key: 'buttonLanguage',
isMulti: true,
} }
order={ [ 'title', 'description', 'action' ] }
/>
</AccordionSettingsBlock>
);
};
export default OtherSettings;

View file

@ -1,28 +1,41 @@
import { __ } from '@wordpress/i18n';
import {
AccordionSettingsBlock,
RadioSettingsBlock,
ToggleSettingsBlock,
InputSettingsBlock,
SelectSettingsBlock,
} from '../../../../ReusableComponents/SettingsBlocks';
const PaypalSettings = ( { updateFormValue, settings } ) => {
import {
ControlRadioGroup,
ControlToggleButton,
ControlTextInput,
ControlSelect,
} from '../../../../ReusableComponents/Controls';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock';
import Accordion from '../../../../ReusableComponents/AccordionSection';
import { SettingsHooks } from '../../../../../data';
const PaypalSettings = () => {
const {
savePaypalAndVenmo,
setSavePaypalAndVenmo,
subtotalAdjustment,
setSubtotalAdjustment,
brandName,
setBrandName,
softDescriptor,
setSoftDescriptor,
landingPage,
setLandingPage,
buttonLanguage,
setButtonLanguage,
} = SettingsHooks.useSettings();
return (
<AccordionSettingsBlock
className="ppcp-r-settings-block--settings"
<Accordion
className="ppcp--paypal-settings"
title={ __( 'PayPal Settings', 'woocommerce-paypal-payments' ) }
description={ __(
'Modify the PayPal checkout experience.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'payNowExperience',
value: settings.payNowExperience,
} }
>
<RadioSettingsBlock
<SettingsBlock
title={ __(
'Subtotal mismatch fallback',
'woocommerce-paypal-payments'
@ -31,93 +44,64 @@ const PaypalSettings = ( { updateFormValue, settings } ) => {
'Due to differences in how WooCommerce and PayPal calculates taxes, some transactions may fail due to a rounding error. This settings determines the fallback behavior.',
'woocommerce-paypal-payments'
) }
options={ [
{
id: 'add_a_correction',
value: 'add_a_correction',
label: __(
'Add a correction',
'woocommerce-paypal-payments'
),
description: __(
'Adds an additional line item with the missing amount.',
'woocommerce-paypal-payments'
),
},
{
id: 'do_not_send_line_items',
value: 'do_not_send_line_items',
label: __(
'Do not send line items',
'woocommerce-paypal-payments'
),
description: __(
'Resubmit the transaction without line item details.',
'woocommerce-paypal-payments'
),
},
] }
actionProps={ {
name: 'paypal_settings_mismatch',
key: 'subtotalMismatchFallback',
currentValue: settings.subtotalMismatchFallback,
callback: updateFormValue,
} }
/>
>
<ControlRadioGroup
options={ subtotalAdjustmentChoices }
value={ subtotalAdjustment }
onChange={ setSubtotalAdjustment }
/>
</SettingsBlock>
<ToggleSettingsBlock
title={ __(
'Instant payments only',
'woocommerce-paypal-payments'
) }
description={ __(
'If enabled, PayPal will not allow buyers to use funding sources that take additional time to complete, such as eChecks.',
'woocommerce-paypal-payments'
) }
actionProps={ {
value: settings.savePaypalAndVenmo,
callback: updateFormValue,
key: 'savePaypalAndVenmo',
} }
/>
<SettingsBlock>
<ControlToggleButton
label={ __(
'Instant payments only',
'woocommerce-paypal-payments'
) }
description={ __(
'If enabled, PayPal will not allow buyers to use funding sources that take additional time to complete, such as eChecks.',
'woocommerce-paypal-payments'
) }
value={ savePaypalAndVenmo }
onChange={ setSavePaypalAndVenmo }
/>
</SettingsBlock>
<InputSettingsBlock
<SettingsBlock
title={ __( 'Brand name', 'woocommerce-paypal-payments' ) }
description={ __(
'What business name to show to your buyers during checkout and on receipts.',
'woocommerce-paypal-payments'
) }
actionProps={ {
value: settings.brandName,
callback: updateFormValue,
key: 'brandName',
placeholder: __(
>
<ControlTextInput
value={ brandName }
onChange={ setBrandName }
placeholder={ __(
'Brand name',
'woocommerce-paypal-payments'
),
} }
order={ [ 'title', 'description', 'action' ] }
/>
) }
/>
</SettingsBlock>
<InputSettingsBlock
<SettingsBlock
title={ __( 'Soft Descriptor', 'woocommerce-paypal-payments' ) }
description={ __(
"The dynamic text used to construct the statement descriptor that appears on a payer's card statement. Applies to PayPal and Credit Card transactions. Max value of 22 characters.",
'woocommerce-paypal-payments'
) }
actionProps={ {
value: settings.softDescriptor,
callback: updateFormValue,
key: 'softDescriptor',
placeholder: __(
>
<ControlTextInput
value={ softDescriptor }
onChange={ setSoftDescriptor }
placeholder={ __(
'Soft Descriptor',
'woocommerce-paypal-payments'
),
} }
order={ [ 'title', 'description', 'action' ] }
/>
) }
/>
</SettingsBlock>
<RadioSettingsBlock
<SettingsBlock
title={ __(
'PayPal landing page',
'woocommerce-paypal-payments'
@ -126,71 +110,32 @@ const PaypalSettings = ( { updateFormValue, settings } ) => {
'Determine which experience a buyer sees when they click the PayPal button.',
'woocommerce-paypal-payments'
) }
options={ [
{
id: 'no_preference',
value: 'no_reference',
label: __(
'No preference',
'woocommerce-paypal-payments'
),
description: __(
'Shows the buyer the PayPal login for a recognized PayPal buyer.',
'woocommerce-paypal-payments'
),
},
{
id: 'login_page',
value: 'login_page',
label: __(
'Login page',
'woocommerce-paypal-payments'
),
description: __(
'Always show the buyer the PayPal login screen.',
'woocommerce-paypal-payments'
),
},
{
id: 'guest_checkout_page',
value: 'guest_checkout_page',
label: __(
'Guest checkout page',
'woocommerce-paypal-payments'
),
description: __(
'Always show the buyer the guest checkout fields first.',
'woocommerce-paypal-payments'
),
},
] }
actionProps={ {
name: 'paypal_settings_landing',
key: 'paypalLandingPage',
currentValue: settings.paypalLandingPage,
callback: updateFormValue,
} }
/>
>
<ControlRadioGroup
options={ landingPageChoices }
value={ landingPage }
onChange={ setLandingPage }
/>
</SettingsBlock>
<SelectSettingsBlock
<SettingsBlock
title={ __( 'Button Language', 'woocommerce-paypal-payments' ) }
description={ __(
"If left blank, PayPal and other buttons will present in the user's detected language. Enter a language here to force all buttons to display in that language.",
'woocommerce-paypal-payments'
) }
actionProps={ {
value: settings.buttonLanguage,
callback: updateFormValue,
options: languagesExample,
key: 'buttonLanguage',
placeholder: __(
>
<ControlSelect
options={ languagesExample }
value={ buttonLanguage }
onChange={ setButtonLanguage }
placeholder={ __(
'Browser language',
'woocommerce-paypal-payments'
),
} }
order={ [ 'title', 'description', 'action' ] }
/>
</AccordionSettingsBlock>
) }
/>
</SettingsBlock>
</Accordion>
);
};
@ -201,4 +146,50 @@ const languagesExample = [
{ value: 'it', label: 'Italian' },
];
const subtotalAdjustmentChoices = [
{
value: 'correction',
label: __( 'Add a correction', 'woocommerce-paypal-payments' ),
description: __(
'Adds an additional line item with the missing amount.',
'woocommerce-paypal-payments'
),
},
{
value: 'no_details',
label: __( 'Do not send line items', 'woocommerce-paypal-payments' ),
description: __(
'Resubmit the transaction without line item details.',
'woocommerce-paypal-payments'
),
},
];
const landingPageChoices = [
{
value: 'any',
label: __( 'No preference', 'woocommerce-paypal-payments' ),
description: __(
'Shows the buyer the PayPal login for a recognized PayPal buyer.',
'woocommerce-paypal-payments'
),
},
{
value: 'login',
label: __( 'Login page', 'woocommerce-paypal-payments' ),
description: __(
'Always show the buyer the PayPal login screen.',
'woocommerce-paypal-payments'
),
},
{
value: 'guest_checkout',
label: __( 'Guest checkout page', 'woocommerce-paypal-payments' ),
description: __(
'Always show the buyer the guest checkout fields first.',
'woocommerce-paypal-payments'
),
},
];
export default PaypalSettings;

View file

@ -1,74 +0,0 @@
import { __, sprintf } from '@wordpress/i18n';
import {
Header,
SettingsBlock,
ToggleSettingsBlock,
Title,
Description,
} from '../../../../ReusableComponents/SettingsBlocks';
const SavePaymentMethods = ( { updateFormValue, settings } ) => {
return (
<SettingsBlock className="ppcp-r-settings-block--save-payment-methods">
<Header>
<Title>
{ __(
'Save payment methods',
'woocommerce-paypal-payments'
) }
</Title>
<Description>
{ __(
"Securely store customers' payment methods for future payments and subscriptions, simplifying checkout and enabling recurring transactions.",
'woocommerce-paypal-payments'
) }
</Description>
</Header>
<ToggleSettingsBlock
title={ __(
'Save PayPal and Venmo',
'woocommerce-paypal-payments'
) }
description={
<div
dangerouslySetInnerHTML={ {
__html: sprintf(
/* translators: 1: URL to Pay Later documentation, 2: URL to Alternative Payment Methods documentation */
__(
'Securely store your customers\' PayPal accounts for a seamless checkout experience. <br />This will disable all <a target="_blank" rel="noreferrer" href="%1$s">Pay Later</a> features and <a target="_blank" rel="noreferrer" href="%2$s">Alternative Payment Methods</a> on your site.',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
),
} }
/>
}
actionProps={ {
value: settings.savePaypalAndVenmo,
callback: updateFormValue,
key: 'savePaypalAndVenmo',
} }
/>
<ToggleSettingsBlock
title={ __(
'Save Credit and Debit Cards',
'woocommerce-paypal-payments'
) }
description={ __(
"Securely store your customer's credit card.",
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'saveCreditCardAndDebitCard',
value: settings.saveCreditCardAndDebitCard,
} }
/>
</SettingsBlock>
);
};
export default SavePaymentMethods;

View file

@ -1,9 +1,8 @@
import { __ } from '@wordpress/i18n';
import { CommonHooks } from '../../../../../../data';
import {
SettingsBlock,
Title,
} from '../../../../../ReusableComponents/SettingsBlocks';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import { Title } from '../../../../../ReusableComponents/Elements';
const HooksTableBlock = () => {
const { webhooks } = CommonHooks.useWebhooks();

View file

@ -1,13 +1,15 @@
import { useState } from '@wordpress/element';
import { STORE_NAME } from '../../../../../../data/common';
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { STORE_NAME } from '../../../../../../data/common';
import { ControlButton } from '../../../../../ReusableComponents/Controls';
import {
NOTIFICATION_ERROR,
NOTIFICATION_SUCCESS,
} from '../../../../../ReusableComponents/Icons';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const ResubscribeBlock = () => {
const { createSuccessNotice, createErrorNotice } =
@ -47,7 +49,7 @@ const ResubscribeBlock = () => {
};
return (
<ButtonSettingsBlock
<SettingsBlock
title={ __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
@ -56,17 +58,18 @@ const ResubscribeBlock = () => {
'Click to remove the current webhook subscription and subscribe again, for example, if the website domain or URL structure changed.',
'woocommerce-paypal-payments'
) }
separatorAndGap={ false }
actionProps={ {
buttonType: 'secondary',
isBusy: resubscribing,
callback: () => startResubscribingWebhooks(),
value: __(
horizontalLayout={ true }
>
<ControlButton
type={ 'secondary' }
isBusy={ resubscribing }
onClick={ () => startResubscribingWebhooks() }
buttonLabel={ __(
'Resubscribe webhooks',
'woocommerce-paypal-payments'
),
} }
/>
) }
/>
</SettingsBlock>
);
};

View file

@ -1,13 +1,15 @@
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { ButtonSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
import { useDispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { ControlButton } from '../../../../../ReusableComponents/Controls';
import { CommonHooks } from '../../../../../../data';
import {
NOTIFICATION_ERROR,
NOTIFICATION_SUCCESS,
} from '../../../../../ReusableComponents/Icons';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const SimulationBlock = () => {
const {
@ -107,20 +109,24 @@ const SimulationBlock = () => {
};
return (
<ButtonSettingsBlock
<SettingsBlock
title={ __( 'Test webhooks', 'woocommerce-paypal-payments' ) }
description={ __(
'Send a test-webhook from PayPal to confirm that webhooks are being received and processed correctly.',
'woocommerce-paypal-payments'
) }
separatorAndGap={ false }
actionProps={ {
buttonType: 'secondary',
isBusy: simulating,
callback: () => startSimulation( 30 ),
value: __( 'Simulate webhooks', 'woocommerce-paypal-payments' ),
} }
/>
horizontalLayout={ true }
>
<ControlButton
type={ 'secondary' }
isBusy={ simulating }
onClick={ () => startSimulation( 30 ) }
buttonLabel={ __(
'Simulate webhooks',
'woocommerce-paypal-payments'
) }
/>
</SettingsBlock>
);
};
export default SimulationBlock;

View file

@ -1,68 +1,53 @@
import { __ } from '@wordpress/i18n';
import {
AccordionSettingsBlock,
Description,
Header,
Title,
ToggleSettingsBlock,
} from '../../../../../ReusableComponents/SettingsBlocks';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
import { __, sprintf } from '@wordpress/i18n';
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import Accordion from '../../../../../ReusableComponents/AccordionSection';
import SimulationBlock from './SimulationBlock';
import ResubscribeBlock from './ResubscribeBlock';
import HooksTableBlock from './HooksTableBlock';
import { SettingsHooks } from '../../../../../../data';
const Troubleshooting = () => {
const { logging, setLogging } = SettingsHooks.useSettings();
const Troubleshooting = ( { updateFormValue, settings } ) => {
return (
<AccordionSettingsBlock
className="ppcp-r-settings-block--troubleshooting"
<Accordion
className="ppcp--troubleshooting"
title={ __( 'Troubleshooting', 'woocommerce-paypal-payments' ) }
description={ __(
'Access tools to help debug and resolve issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'payNowExperience',
value: settings.payNowExperience,
} }
>
<ToggleSettingsBlock
title={ __( 'Logging', 'woocommerce-paypal-payments' ) }
description={ __(
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'logging',
value: settings.logging,
} }
/>
<SettingsBlock>
<Header>
<Title>
{ __( 'Webhooks', 'woocommerce-paypal-payments' ) }
</Title>
<Description>
{ __(
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the',
'woocommerce-paypal-payments'
) }{ ' ' }
<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status">
{ __(
'Webhook Status documentation',
'woocommerce-paypal-payments'
) }
</a>
.
</Description>
</Header>
<ControlToggleButton
label={ __( 'Logging', 'woocommerce-paypal-payments' ) }
description={ __(
'Log additional debugging information in the WooCommerce logs that can assist technical staff to determine issues.',
'woocommerce-paypal-payments'
) }
value={ logging }
onChange={ setLogging }
/>
</SettingsBlock>
<SettingsBlock
title={ __( 'Webhooks', 'woocommerce-paypal-payments' ) }
description={ sprintf(
__(
'The following PayPal webhooks are subscribed. More information about the webhooks is available in the <a href="%s">Webhook Status documentation</a>.',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status'
) }
>
<HooksTableBlock />
<ResubscribeBlock />
<SimulationBlock />
</SettingsBlock>
</AccordionSettingsBlock>
</Accordion>
);
};

View file

@ -1,68 +0,0 @@
import { __ } from '@wordpress/i18n';
import {
InputSettingsBlock,
ToggleSettingsBlock,
} from '../../../ReusableComponents/SettingsBlocks';
import SettingsCard from '../../../ReusableComponents/SettingsCard';
import OrderIntent from './Blocks/OrderIntent';
import SavePaymentMethods from './Blocks/SavePaymentMethods';
const CommonSettings = ( { updateFormValue, settings } ) => {
return (
<SettingsCard
icon="icon-settings-common.svg"
title={ __( 'Common settings', 'woocommerce-paypal-payments' ) }
className="ppcp-r-settings-card ppcp-r-settings-card--common-settings"
description={ __(
'Customize key features to tailor your PayPal experience.',
'woocommerce-paypal-payments'
) }
>
<InputSettingsBlock
title="Invoice Prefix"
supplementaryLabel={ __(
'(Recommended)',
'woocommerce-paypal-payments'
) }
description="Add a unique prefix to invoice numbers for site-specific tracking (recommended)."
actionProps={ {
callback: updateFormValue,
key: 'invoicePrefix',
value: settings.invoicePrefix,
placeholder: __(
'Input prefix',
'woocommerce-paypal-payments'
),
} }
/>
<OrderIntent
settings={ settings }
updateFormValue={ updateFormValue }
/>
<SavePaymentMethods
updateFormValue={ updateFormValue }
settings={ settings }
/>
<ToggleSettingsBlock
title={ __(
'Pay Now Experience',
'woocommerce-paypal-payments'
) }
description={ __(
'Let PayPal customers skip the Order Review page by selecting shipping options directly within PayPal.',
'woocommerce-paypal-payments'
) }
actionProps={ {
callback: updateFormValue,
key: 'payNowExperience',
value: settings.payNowExperience,
} }
/>
</SettingsCard>
);
};
export default CommonSettings;

View file

@ -1,49 +0,0 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../../ReusableComponents/SettingsCard';
import { CommonHooks } from '../../../../data';
import TitleBadge, {
TITLE_BADGE_NEGATIVE,
TITLE_BADGE_POSITIVE,
} from '../../../ReusableComponents/TitleBadge';
import ConnectionInfo from '../../../ReusableComponents/ConnectionInfo';
const ConnectionStatus = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<SettingsCard
className="ppcp-r-tab-overview-support"
title={ __( 'Connection status', 'woocommerce-paypal-payments' ) }
description={ __(
'Your PayPal account connection details',
'woocommerce-paypal-payments'
) }
>
<div className="ppcp-r-connection-status">
<div className="ppcp-r-connection-status__status">
<div className="ppcp-r-connection-status__status-status">
{ merchant.isConnected ? (
<TitleBadge
type={ TITLE_BADGE_POSITIVE }
text={ __(
'Active',
'woocommerce-paypal-payments'
) }
/>
) : (
<TitleBadge
type={ TITLE_BADGE_NEGATIVE }
text={ __(
'Not Activated',
'woocommerce-paypal-payments'
) }
/>
) }
</div>
</div>
{ merchant.isConnected && (
<ConnectionInfo connectionStatusDataDefault={ merchant } />
) }
</div>
</SettingsCard>
);
};
export default ConnectionStatus;

View file

@ -1,31 +1,19 @@
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { CommonHooks, StylingHooks } from '../../../../data';
import TopNavigation from '../../../ReusableComponents/TopNavigation';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
import { useSaveSettings } from '../../../../hooks/useSaveSettings';
const SettingsNavigation = () => {
const { withActivity } = CommonHooks.useBusyState();
// Todo: Implement other stores here.
const { persist: persistStyling } = StylingHooks.useStore();
const handleSaveClick = () => {
// Todo: Add other stores here.
withActivity(
'persist-styling',
'Save styling details',
persistStyling
);
};
const { persistAll } = useSaveSettings();
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
return (
<TopNavigation title={ title } exitOnTitleClick={ true }>
<BusyStateWrapper>
<Button variant="primary" onClick={ handleSaveClick }>
<Button variant="primary" onClick={ persistAll }>
{ __( 'Save', 'woocommerce-paypal-payments' ) }
</Button>
</BusyStateWrapper>

View file

@ -0,0 +1,29 @@
import { __ } from '@wordpress/i18n';
import { ControlTextInput } from '../../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../../data';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const InvoicePrefix = () => {
const { invoicePrefix, setInvoicePrefix } = SettingsHooks.useSettings();
return (
<SettingsBlock
title="Invoice Prefix"
titleSuffix={ __( '(Recommended)', 'woocommerce-paypal-payments' ) }
className="ppcp--invoice-prefix"
>
<ControlTextInput
placeholder={ __(
'Input prefix',
'woocommerce-paypal-payments'
) }
onChange={ setInvoicePrefix }
value={ invoicePrefix }
description="Add a unique prefix to invoice numbers for site-specific tracking (recommended)."
/>
</SettingsBlock>
);
};
export default InvoicePrefix;

View file

@ -0,0 +1,42 @@
import { __ } from '@wordpress/i18n';
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import { SettingsHooks } from '../../../../../../data';
const OrderIntent = () => {
const {
authorizeOnly,
setAuthorizeOnly,
captureVirtualOnlyOrders,
setCaptureVirtualOnlyOrders,
} = SettingsHooks.useSettings();
return (
<SettingsBlock
title={ __( 'Order Intent', 'woocommerce-paypal-payments' ) }
description={ __(
'Choose between immediate capture or authorization-only, with manual capture in the Order section.',
'woocommerce-paypal-payments'
) }
className="ppcp--order-intent"
>
<ControlToggleButton
label={ __( 'Authorize Only', 'woocommerce-paypal-payments' ) }
onChange={ setAuthorizeOnly }
value={ authorizeOnly }
/>
<ControlToggleButton
label={ __(
'Capture Virtual-Only Orders',
'woocommerce-paypal-payments'
) }
onChange={ setCaptureVirtualOnlyOrders }
value={ captureVirtualOnlyOrders }
/>
</SettingsBlock>
);
};
export default OrderIntent;

View file

@ -0,0 +1,29 @@
import { __ } from '@wordpress/i18n';
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../../data';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const PayNowExperience = () => {
const { payNowExperience, setPayNowExperience } =
SettingsHooks.useSettings();
return (
<SettingsBlock className="ppcp--pay-now-experience">
<ControlToggleButton
label={ __(
'Pay Now Experience',
'woocommerce-paypal-payments'
) }
description={ __(
'Let PayPal customers skip the Order Review page by selecting shipping options directly within PayPal.',
'woocommerce-paypal-payments'
) }
onChange={ setPayNowExperience }
value={ payNowExperience }
/>
</SettingsBlock>
);
};
export default PayNowExperience;

View file

@ -0,0 +1,61 @@
import { __, sprintf } from '@wordpress/i18n';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../../data';
const SavePaymentMethods = () => {
const {
savePaypalAndVenmo,
setSavePaypalAndVenmo,
saveCardDetails,
setSaveCardDetails,
} = SettingsHooks.useSettings();
return (
<SettingsBlock
title={ __(
'Save payment methods',
'woocommerce-paypal-payments'
) }
description={ __(
"Securely store customers' payment methods for future payments and subscriptions, simplifying checkout and enabling recurring transactions.",
'woocommerce-paypal-payments'
) }
className="ppcp--save-payment-methods"
>
<ControlToggleButton
label={ __(
'Save PayPal and Venmo',
'woocommerce-paypal-payments'
) }
description={ sprintf(
/* translators: 1: URL to Pay Later documentation, 2: URL to Alternative Payment Methods documentation */
__(
'Securely store your customers\' PayPal accounts for a seamless checkout experience. <br />This will disable all <a target="_blank" rel="noreferrer" href="%1$s">Pay Later</a> features and <a target="_blank" rel="noreferrer" href="%2$s">Alternative Payment Methods</a> on your site.',
'woocommerce-paypal-payments'
),
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
) }
value={ savePaypalAndVenmo }
onChange={ setSavePaypalAndVenmo }
/>
<ControlToggleButton
label={ __(
'Save Credit and Debit Cards',
'woocommerce-paypal-payments'
) }
description={ __(
"Securely store your customer's credit card.",
'woocommerce-paypal-payments'
) }
onChange={ setSaveCardDetails }
value={ saveCardDetails }
/>
</SettingsBlock>
);
};
export default SavePaymentMethods;

View file

@ -0,0 +1,26 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../../../ReusableComponents/SettingsCard';
import OrderIntent from './Blocks/OrderIntent';
import SavePaymentMethods from './Blocks/SavePaymentMethods';
import InvoicePrefix from './Blocks/InvoicePrefix';
import PayNowExperience from './Blocks/PayNowExperience';
const CommonSettings = () => (
<SettingsCard
icon="icon-settings-common.svg"
title={ __( 'Common settings', 'woocommerce-paypal-payments' ) }
className="ppcp-r-settings-card ppcp-r-settings-card--common-settings"
description={ __(
'Customize key features to tailor your PayPal experience.',
'woocommerce-paypal-payments'
) }
>
<InvoicePrefix />
<OrderIntent />
<SavePaymentMethods />
<PayNowExperience />
</SettingsCard>
);
export default CommonSettings;

View file

@ -0,0 +1,50 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../../../ReusableComponents/SettingsCard';
import { CommonHooks } from '../../../../../data';
import ConnectionStatusBadge from './Parts/ConnectionStatusBadge';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock';
import { ControlStaticValue } from '../../../../ReusableComponents/Controls';
const ConnectionStatus = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<SettingsCard
className="ppcp-connection-details ppcp--value-list"
title={ __( 'Connection status', 'woocommerce-paypal-payments' ) }
description={ __(
'Your PayPal account connection details',
'woocommerce-paypal-payments'
) }
>
<SettingsBlock>
<ControlStaticValue
value={
<ConnectionStatusBadge
isActive={ merchant.isConnected }
isSandbox={ merchant.isSandbox }
/>
}
/>
</SettingsBlock>
<SettingsBlock
title={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
>
<ControlStaticValue value={ merchant.id } />
</SettingsBlock>
<SettingsBlock
title={ __( 'Email address', 'woocommerce-paypal-payments' ) }
>
<ControlStaticValue value={ merchant.email } />
</SettingsBlock>
<SettingsBlock
title={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
>
<ControlStaticValue value={ merchant.clientId } />
</SettingsBlock>
</SettingsCard>
);
};
export default ConnectionStatus;

View file

@ -1,15 +1,18 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../../ReusableComponents/SettingsCard';
import SettingsCard from '../../../../ReusableComponents/SettingsCard';
import {
Content,
ContentWrapper,
} from '../../../ReusableComponents/SettingsBlocks';
import ConnectionDetails from './Blocks/ConnectionDetails';
import Troubleshooting from './Blocks/Troubleshooting/Troubleshooting';
import PaypalSettings from './Blocks/PaypalSettings';
import OtherSettings from './Blocks/OtherSettings';
} from '../../../../ReusableComponents/Elements';
import ConnectionDetails from '../../../Overview/TabSettingsElements/Blocks/ConnectionDetails';
import Troubleshooting from '../../../Overview/TabSettingsElements/Blocks/Troubleshooting/Troubleshooting';
import PaypalSettings from '../../../Overview/TabSettingsElements/Blocks/PaypalSettings';
import OtherSettings from '../../../Overview/TabSettingsElements/Blocks/OtherSettings';
const ExpertSettings = () => {
const settings = {}; // dummy object
const updateFormValue = () => {}; // dummy function
const ExpertSettings = ( { updateFormValue, settings } ) => {
return (
<SettingsCard
icon="icon-settings-expert.svg"

View file

@ -0,0 +1,25 @@
import { __ } from '@wordpress/i18n';
import TitleBadge, {
TITLE_BADGE_NEGATIVE,
TITLE_BADGE_POSITIVE,
} from '../../../../../ReusableComponents/TitleBadge';
const ConnectionStatusBadge = ( { isActive, isSandbox } ) => {
if ( isActive ) {
const label = isSandbox
? __( 'Sandbox Mode', 'woocommerce-paypal-payments' )
: __( 'Active', 'woocommerce-paypal-payments' );
return <TitleBadge type={ TITLE_BADGE_POSITIVE } text={ label } />;
}
return (
<TitleBadge
type={ TITLE_BADGE_NEGATIVE }
text={ __( 'Not Connected', 'woocommerce-paypal-payments' ) }
/>
);
};
export default ConnectionStatusBadge;

View file

@ -1,10 +1,10 @@
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlocks/SettingsBlock';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import {
Description,
Header,
Title,
Content,
} from '../../../../../ReusableComponents/SettingsBlocks';
} from '../../../../../ReusableComponents/Elements';
const StylingSection = ( {
title,
@ -26,7 +26,9 @@ const StylingSection = ( {
<Description>{ description }</Description>
</Header>
<Content className="section-content">{ children }</Content>
<Content asCard={ false } className="section-content">
{ children }
</Content>
</SettingsBlock>
);
};

View file

@ -14,7 +14,7 @@ const StylingSectionWithCheckboxes = ( {
onChange,
children,
} ) => {
className = classNames( 'has-checkboxes', className );
className = classNames( 'ppcp--has-checkboxes', className );
return (
<StylingSection

View file

@ -14,7 +14,7 @@ const StylingSectionWithRadiobuttons = ( {
onChange,
children,
} ) => {
className = classNames( 'has-radio-buttons', className );
className = classNames( 'ppcp--has-radio-buttons', className );
return (
<StylingSection

View file

@ -13,7 +13,7 @@ const StylingSectionWithSelect = ( {
onChange,
children,
} ) => {
className = classNames( 'has-select', className );
className = classNames( 'ppcp--has-select', className );
return (
<StylingSection

Some files were not shown because too many files have changed in this diff Show more