diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index aca3bed0a..2a6de82d8 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -22,7 +22,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRendere use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; return array( - 'api.sandbox-host' => static function ( ContainerInterface $container ): string { + 'api.sandbox-host' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); @@ -36,7 +36,7 @@ return array( } return CONNECT_WOO_SANDBOX_URL; }, - 'api.production-host' => static function ( ContainerInterface $container ): string { + 'api.production-host' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); @@ -51,7 +51,7 @@ return array( } return CONNECT_WOO_URL; }, - 'api.host' => static function ( ContainerInterface $container ): string { + 'api.host' => static function ( ContainerInterface $container ): string { $environment = $container->get( 'onboarding.environment' ); /** @@ -63,7 +63,7 @@ return array( ? (string) $container->get( 'api.sandbox-host' ) : (string) $container->get( 'api.production-host' ); }, - 'api.paypal-host' => function( ContainerInterface $container ) : string { + 'api.paypal-host' => function( ContainerInterface $container ) : string { $environment = $container->get( 'onboarding.environment' ); /** * The current environment. @@ -76,7 +76,7 @@ return array( return $container->get( 'api.paypal-host-production' ); }, - 'api.paypal-website-url' => function( ContainerInterface $container ) : string { + 'api.paypal-website-url' => function( ContainerInterface $container ) : string { $environment = $container->get( 'onboarding.environment' ); assert( $environment instanceof Environment ); if ( $environment->current_environment_is( Environment::SANDBOX ) ) { @@ -86,7 +86,7 @@ return array( }, - 'api.bearer' => static function ( ContainerInterface $container ): Bearer { + 'api.bearer' => static function ( ContainerInterface $container ): Bearer { $state = $container->get( 'onboarding.state' ); @@ -113,16 +113,16 @@ return array( $settings ); }, - 'onboarding.state' => function( ContainerInterface $container ) : State { + 'onboarding.state' => function( ContainerInterface $container ) : State { $settings = $container->get( 'wcgateway.settings' ); return new State( $settings ); }, - 'onboarding.environment' => function( ContainerInterface $container ) : Environment { + 'onboarding.environment' => function( ContainerInterface $container ) : Environment { $settings = $container->get( 'wcgateway.settings' ); return new Environment( $settings ); }, - 'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets { + 'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets { $state = $container->get( 'onboarding.state' ); $login_seller_endpoint = $container->get( 'onboarding.endpoint.login-seller' ); return new OnboardingAssets( @@ -135,14 +135,14 @@ return array( ); }, - 'onboarding.url' => static function ( ContainerInterface $container ): string { + 'onboarding.url' => static function ( ContainerInterface $container ): string { return plugins_url( '/modules/ppcp-onboarding/', dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint { + 'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint { $request_data = $container->get( 'button.request-data' ); $login_seller_production = $container->get( 'api.endpoint.login-seller-production' ); @@ -162,7 +162,7 @@ return array( new Cache( 'ppcp-client-credentials-cache' ) ); }, - 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { + 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { return new UpdateSignupLinksEndpoint( $container->get( 'wcgateway.settings' ), $container->get( 'button.request-data' ), @@ -172,10 +172,10 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'onboarding.signup-link-cache' => static function( ContainerInterface $container ): Cache { + 'onboarding.signup-link-cache' => static function( ContainerInterface $container ): Cache { return new Cache( 'ppcp-paypal-signup-link' ); }, - 'onboarding.signup-link-ids' => static function ( ContainerInterface $container ): array { + 'onboarding.signup-link-ids' => static function ( ContainerInterface $container ): array { return array( 'production-ppcp', 'production-express_checkout', @@ -183,12 +183,12 @@ return array( 'sandbox-express_checkout', ); }, - 'onboarding.render-send-only-notice' => static function( ContainerInterface $container ) { + 'onboarding.render-send-only-notice' => static function( ContainerInterface $container ) { return new OnboardingSendOnlyNoticeRenderer( $container->get( 'wcgateway.send-only-message' ) ); }, - 'onboarding.render' => static function ( ContainerInterface $container ) : OnboardingRenderer { + 'onboarding.render' => static function ( ContainerInterface $container ) : OnboardingRenderer { $partner_referrals = $container->get( 'api.endpoint.partner-referrals-production' ); $partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' ); $partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' ); @@ -204,14 +204,14 @@ return array( $logger ); }, - 'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer { + 'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer { return new OnboardingOptionsRenderer( $container->get( 'onboarding.url' ), $container->get( 'api.shop.country' ), $container->get( 'wcgateway.settings' ) ); }, - 'onboarding.rest' => static function( $container ) : OnboardingRESTController { + 'onboarding.rest' => static function( $container ) : OnboardingRESTController { return new OnboardingRESTController( $container ); }, ); diff --git a/modules/ppcp-settings/resources/css/_mixins.scss b/modules/ppcp-settings/resources/css/_mixins.scss index d2fac6cd2..8c83eb436 100644 --- a/modules/ppcp-settings/resources/css/_mixins.scss +++ b/modules/ppcp-settings/resources/css/_mixins.scss @@ -43,3 +43,14 @@ display: flex; gap: $gap; } + +@mixin disabled-state($control-type) { + .components-#{$control-type}-control.is-disabled { + .components-#{$control-type}-control__input, + .components-#{$control-type}-control__label, + .components-base-control__help { + opacity: 0.3; + cursor: default; + } + } +} diff --git a/modules/ppcp-settings/resources/css/_variables.scss b/modules/ppcp-settings/resources/css/_variables.scss index 5ccf191e5..53483adb6 100644 --- a/modules/ppcp-settings/resources/css/_variables.scss +++ b/modules/ppcp-settings/resources/css/_variables.scss @@ -55,6 +55,8 @@ $card-vertical-gap: 48px; --color-gray-100: #{$color-gray-100}; --color-gradient-dark: #{$color-gradient-dark}; + --color-preview-background: #FAF8F5; + --color-separators: #{$color-gray-200}; --color-text-title: #{$color-gray-900}; --color-text-main: #{$color-text-text}; --color-text-teriary: #{$color-text-tertiary}; diff --git a/modules/ppcp-settings/resources/css/components/_reusable.scss b/modules/ppcp-settings/resources/css/components/_reusable.scss index 4d4f5c1ba..2de887f57 100644 --- a/modules/ppcp-settings/resources/css/components/_reusable.scss +++ b/modules/ppcp-settings/resources/css/components/_reusable.scss @@ -4,6 +4,7 @@ @import './reusable-components/busy-state'; @import './reusable-components/button'; @import './reusable-components/fields'; +@import './reusable-components/hstack'; @import './reusable-components/navigation'; @import './reusable-components/onboarding-header'; @import './reusable-components/payment-method-icons'; diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss index 1a9cf102c..195367dfb 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss @@ -1,27 +1,96 @@ -.ppcp-r { +.ppcp-r__radio-value { + @include hide-input-field; - &__radio-value { - @include hide-input-field; + &:checked + .ppcp-r__radio-presentation { + position: relative; - &:checked + .ppcp-r__radio-presentation { - position: relative; + &::before { + content: ''; + width: 12px; + height: 12px; + border-radius: 12px; + background-color: $color-blueberry; + display: block; + position: absolute; + transform: translate(-50%, -50%); + left: 50%; + top: 50%; + } + } +} - &::before { - content: ''; - width: 12px; - height: 12px; - border-radius: 12px; - background-color: $color-blueberry; - display: block; - position: absolute; - transform: translate(-50%, -50%); - left: 50%; - top: 50%; - } +.ppcp-r__radio-presentation { + @include fake-input-field(20px); +} + +.ppcp-r__checkbox-presentation { + @include fake-input-field(2px); +} + +.ppcp-r__radio-wrapper { + display: flex; + gap: 18px; + align-items: center; + position: relative; + + label { + @include font(13, 20, 400); + color: $color-gray-800; + } +} + +.ppcp-r__radio-description { + @include font(13, 20, 400); + margin: 0; + color: $color-gray-800; +} + +.ppcp-r__radio-content-additional { + padding-left: 38px; + padding-top: 18px; +} + + +.ppcp-r-app { + @include disabled-state('base'); + @include disabled-state('checkbox'); + + .components-base-control__label { + @include font(13, 16, 600); + color: $color-gray-900; + text-transform: none; + } + + .components-base-control__input { + border: 1px solid $color-gray-700; + border-radius: 2px; + box-shadow: none; + + &:focus { + border-color: $color-blueberry; } } - &__checkbox { + .components-base-control__help { + margin-bottom: 0; + } + + // Text input fields. + input[type='text'] { + @include font(14, 20, 400); + @include primaryFont; + padding: 7px 11px; + border-radius: 2px; + } + + // Select lists. + select { + @include font(14, 20, 400); + padding: 7px 27px 7px 11px; + } + + // Checkboxes. + .components-checkbox-control { position: relative; input { @@ -30,7 +99,7 @@ &:checked { background-color: $color-blueberry; - border-color:$color-blueberry; + border-color: $color-blueberry; } } @@ -43,78 +112,17 @@ } } - &__radio-presentation { - @include fake-input-field(20px); + // Custom styles. + .components-form-toggle.is-checked > .components-form-toggle__track { + background-color: $color-blueberry; } - &__checkbox-presentation { - @include fake-input-field(2px); - } - - &__radio-wrapper { - display: flex; - gap: 18px; - align-items: center; - position: relative; - - label { - @include font(13, 20, 400); - color: $color-gray-800; - } - } - - &__radio-description { - @include font(13, 20, 400); - margin: 0; - color: $color-gray-800; - } - - &__radio-content-additional { - padding-left: 38px; - padding-top: 18px; - } -} - -.components-base-control { - &__label { - color: $color-gray-900; - @include font(13, 16, 600); - text-transform: none; - } - - &__input { - border: 1px solid $color-gray-700; - border-radius: 2px; - box-shadow: none; - - &:focus { - border-color: $color-blueberry; + .ppcp-r-vertical-text-control { + .components-base-control__field { + display: flex; + flex-direction: column; + gap: 0; + margin: 0; } } } - - -input[type='text'] { - padding: 7px 11px; - @include font(14, 20, 400); - @include primaryFont; - border-radius: 2px; -} - -select { - padding: 7px 27px 7px 11px; - @include font(14, 20, 400); -} - -.components-form-toggle.is-checked > .components-form-toggle__track { - background-color: $color-blueberry; -} - -.ppcp-r-vertical-text-control { - .components-base-control__field { - display: flex; - flex-direction: column; - gap: 0; - margin: 0; - } -} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss new file mode 100644 index 000000000..e55584d1c --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss @@ -0,0 +1,20 @@ +.components-flex { + display: flex; + -webkit-box-align: stretch; + align-items: stretch; + flex-direction: column; + -webkit-box-pack: center; + justify-content: center; + + .components-h-stack { + flex-direction: row; + justify-content: flex-start; + gap: 32px; + } + + // Fix layout for checkboxes inside a flex-stack. + .components-checkbox-control >.components-base-control__field > .components-flex { + flex-direction: row; + gap: 12px; + } +} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss index f65c5c9d5..8888bf2c0 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss @@ -4,7 +4,7 @@ .ppcp-r-settings-block { display: flex; flex-direction: column; - gap: 16px 0; + gap: var(--block-item-gap, 16px); &.ppcp-r-settings-block__input, &.ppcp-r-settings-block__select { @@ -16,8 +16,8 @@ flex-direction: column; gap: 6px; - &:not(:last-child):not(.ppcp-r-settings-block--accordion__header) { - padding-bottom: 6px; + &:not(:last-child) { + padding-bottom: var(--block-header-gap, 6px); } } @@ -27,6 +27,15 @@ 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; @@ -88,9 +97,18 @@ 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) { - margin-top: 32px; - padding-top: 32px; + margin-top: var(--block-separator-gap, 32px); + padding-top: var(--block-separator-gap, 32px); border-top: 1px solid var(--color-gray-200); } diff --git a/modules/ppcp-settings/resources/css/components/screens/_settings.scss b/modules/ppcp-settings/resources/css/components/screens/_settings.scss index 2f536ac96..0b8802138 100644 --- a/modules/ppcp-settings/resources/css/components/screens/_settings.scss +++ b/modules/ppcp-settings/resources/css/components/screens/_settings.scss @@ -1,4 +1,5 @@ @import './settings/input'; +@import './settings/connection-status'; @import './settings/tab-styling'; @import './settings/tab-paylater-configurator'; @@ -22,6 +23,7 @@ &:hover { cursor: pointer; + .ppcp-r-todo-item__inner { .ppcp-r-todo-item__description { color: $color-text-text; @@ -117,91 +119,7 @@ font-weight: 500; } - margin-top:24px; - } -} - -// 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; - } - } - - &__show-all-data { - margin-left: 12px; - } - - &__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; - } - - &__data { - display: flex; - flex-direction: column; - gap: 12px; - } - - &__status-toggle--toggled { - .ppcp-r-connection-status__show-all-data { - transform: rotate(180deg); - } - } - - &__status-row { - display: flex; - flex-direction: column; - * { - user-select: none; - } - 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; - } - &--first { - &:hover { - cursor: pointer; - } - } - } - - @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; - } - } + margin-top: 24px; } } diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss new file mode 100644 index 000000000..2bf1417e6 --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss @@ -0,0 +1,78 @@ +// 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; + } + } + } +} diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss index ae49bed82..9444f4e18 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss @@ -1,6 +1,6 @@ .ppcp-r-paylater-configurator { display: flex; - border: 1px solid $color-gray-200; + border: 1px solid var(--color-separators); border-radius: 8px; overflow: hidden; diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index c75d6a4c9..8645e7dad 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -1,121 +1,96 @@ .ppcp-r-styling { + --block-item-gap: 0; + --block-separator-gap: 24px; + --block-header-gap: 18px; + --panel-width: 422px; + --sticky-offset-top: 92px; // 32px admin-bar + 60px TopNavigation height + --preview-height-reduction: 236px; // 32px admin-bar + 60px TopNavigation height + 48px TopNavigation margin + 48px TabList height + 48px TabList margin + display: flex; - border: 1px solid $color-gray-200; + border: 1px solid var(--color-separators); border-radius: 8px; - overflow: hidden; - &__section:not(:last-child) { - border-bottom: 1px solid black; - padding-bottom: 24px; - margin-bottom: 28px; - border-bottom: 1px solid $color-gray-600; + .ppcp-r-settings-block { + &.header-section { + margin-bottom: 6px + } + + &.location-selector { + position: sticky; + top: var(--sticky-offset-top); + background: var(--ppcp-color-app-bg); + z-index: 5; + padding: 16px 10px 8px; + margin: 0 -10px -8px; + + .section-content { + display: flex; + + & > .components-base-control:first-of-type { + width: 100%; + } + } + } + + // Select-fields have a smaller gap between the header and input field. + &.has-select { + --block-header-gap: 8px; + } + + // Above the payment methods is a slightly larger gap. + &.payment-methods { + --block-separator-gap: 28px; + } + + // It has no header; adjusts the gap to the control right above the tagline. + &.tagline { + --block-header-gap: 24px; + } } - &__main-title { - @include font(14, 20, 600); - color: $color-gray-800; - margin: 0 0 8px 0; - display: block; - } - - &__description { - @include font(13, 20, 400); - color: $color-gray-800; - margin: 0 0 18px 0; - } - - &__settings { - width: 422px; - background-color: $color-white; + /* The settings-panel (left side) */ + .settings-panel { + width: var(--panel-width); padding: 48px; + + .ppcp-r-styling__section { + padding-bottom: 24px; + margin-bottom: 28px; + border-bottom: 1px solid var(--color-separators); + + &.no-gap, + &:last-child { + padding-bottom: 0; + margin-bottom: 0; + border-bottom-style: none; + } + } + + // Horizontal radio buttons have a width of 100px. + .components-radio-control__option { + min-width: 100px; + } } - &__preview { - width: calc(100% - 422px); - background-color: #FAF8F5; - display: flex; - align-items: center; + /* The preview area (right side) */ + .preview-panel { + width: calc(100% - var(--panel-width)); + background-color: var(--color-preview-background); + z-index: 0; - &-inner { + .preview-panel-inner { + position: sticky; + display: flex; + flex-direction: column; + justify-content: center; width: 100%; padding: 24px; - } - } + height: calc(100vh - var(--preview-height-reduction)); + top: var(--sticky-offset-top); - &__section--rc { - .ppcp-r-styling__title { - @include font(13, 20, 600); - color: $color-black; - display: block; - margin: 0 0 18px 0; - } - } - - &__section--empty.ppcp-r-styling__section { - padding-bottom: 0; - margin-bottom: 0; - border-bottom: none; - } - - &__select { - label { - @include font(13, 16, 600); - color: $color-black; - margin: 0; - text-transform: none; - } - - select { - @include font(13, 20, 400); - } - } - - .ppcp-r__checkbox { - .components-checkbox-control { - &__label { - @include font(13, 20, 400); - color: $color-black; - } - } - - .components-flex { - gap: 12px; - } - } - - &__payment-method-checkboxes { - display: flex; - flex-direction: column; - gap: 24px; - } -} - -.ppcp-r { - &__horizontal-control { - .components-flex { - flex-direction: row; - justify-content: flex-start; - gap: 32px; - } - - - .components-radio-control { - &__option { - gap: 12px; - - input { - margin: 0; - } - - label { - @include font(13, 20, 400); - color: $color-black; - } - } - - input { - margin: 0; - } + // Disable interactions with the preview. + pointer-events: none; + user-select: none; } } } diff --git a/modules/ppcp-settings/resources/js/Components/App.js b/modules/ppcp-settings/resources/js/Components/App.js index 69aaabe19..6548f4b1e 100644 --- a/modules/ppcp-settings/resources/js/Components/App.js +++ b/modules/ppcp-settings/resources/js/Components/App.js @@ -1,15 +1,20 @@ 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: merchantIsReady, merchant: { isSendOnlyCountry }, @@ -21,41 +26,41 @@ const SettingsApp = () => { event.stopImmediatePropagation(); return undefined; }; - window.addEventListener( 'beforeunload', suppressBeforeUnload ); - return () => { window.removeEventListener( 'beforeunload', suppressBeforeUnload ); }; }, [] ); const wrapperClass = classNames( 'ppcp-r-app', { - loading: ! onboardingProgress.isReady, + loading: ! onboardingProgress.isReady || ! settingsIsReady, } ); const Content = useMemo( () => { - if ( ! onboardingProgress.isReady || ! merchantIsReady ) { + if ( + ! onboardingProgress.isReady || + ! merchantIsReady || + ! settingsIsReady + ) { return ( ); } - if ( isSendOnlyCountry ) { return ; } - if ( ! onboardingProgress.completed ) { return ; } - return ; }, [ isSendOnlyCountry, merchantIsReady, onboardingProgress.completed, onboardingProgress.isReady, + settingsIsReady, ] ); return
{ Content }
; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js index 74336951f..d979cffba 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js @@ -1,107 +1,138 @@ import { CheckboxControl } from '@wordpress/components'; +import classNames from 'classnames'; -export const PayPalCheckbox = ( props ) => { - let isChecked = null; +export const PayPalCheckbox = ( { + currentValue, + label, + value, + checked = null, + disabled = null, + changeCallback, +} ) => { + let isChecked = checked; - if ( Array.isArray( props.currentValue ) ) { - isChecked = props.currentValue.includes( props.value ); - } else { - isChecked = props.currentValue; + 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 ( -
- - handleCheckboxState( checked, props ) - } - /> -
+ ); }; -export const PayPalCheckboxGroup = ( props ) => { - const renderCheckboxGroup = () => { - return props.value.map( ( checkbox ) => { - return ( - - ); - } ); - }; +export const CheckboxGroup = ( { options, value, onChange } ) => ( + <> + { options.map( ( checkbox ) => ( + + ) ) } + +); - return <>{ renderCheckboxGroup() }; -}; - -export const PayPalRdb = ( props ) => { +export const PayPalRdb = ( { + id, + name, + value, + currentValue, + handleRdbState, +} ) => { return (
+ { /* todo: Can we remove the wrapper div? */ } props.handleRdbState( props.value ) } + id={ id } + checked={ value === currentValue } + name={ name } + value={ value } + onChange={ () => handleRdbState( value ) } />
); }; -export const PayPalRdbWithContent = ( props ) => { - const className = [ 'ppcp-r__radio-wrapper' ]; - - if ( props?.className ) { - className.push( props.className ); - } +export const PayPalRdbWithContent = ( { + className, + id, + name, + label, + description, + value, + currentValue, + handleRdbState, + toggleAdditionalContent, + children, +} ) => { + const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className ); return (
-
- +
+ +
- - { props.description && ( + + { description && (

) }

- { props?.toggleAdditionalContent && - props.children && - props.value === props.currentValue && ( -
- { props.children } -
- ) } + { toggleAdditionalContent && children && value === currentValue && ( +
+ { children } +
+ ) }
); }; - -export const handleCheckboxState = ( checked, props ) => { - let newValue = null; - if ( ! Array.isArray( props.currentValue ) ) { - newValue = checked; - } else if ( checked ) { - newValue = [ ...props.currentValue, props.value ]; - } else { - newValue = props.currentValue.filter( - ( value ) => value !== props.value - ); - } - props.changeCallback( newValue ); -}; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js new file mode 100644 index 000000000..2c54ac7da --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js @@ -0,0 +1,26 @@ +/** + * Temporary component, until the experimental HStack block editor component is stable. + * + * @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs + * @file + */ +import classNames from 'classnames'; + +const HStack = ( { className, spacing = 3, children } ) => { + const wrapperClass = classNames( + 'components-flex components-h-stack', + className + ); + + const styles = { + gap: `calc(${ 4 * spacing }px)`, + }; + + return ( +
+ { children } +
+ ); +}; + +export default HStack; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js index ec3ba31ae..46048e071 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js @@ -1,9 +1,24 @@ +import classNames from 'classnames'; + // Block Elements -export const Title = ( { children, className = '' } ) => ( - - { children } - -); +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 { children }; +}; + export const TitleWrapper = ( { children } ) => ( { children } ); @@ -14,13 +29,28 @@ export const SupplementaryLabel = ( { children } ) => ( ); -export const Description = ( { children, className = '' } ) => ( - - { children } - -); +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 { children }; + } + + return ( + + ); +}; export const Action = ( { children } ) => (
{ children }
@@ -33,11 +63,18 @@ export const Header = ( { children, className = '' } ) => ( ); // Card Elements -export const Content = ( { children, id = '' } ) => ( -
- { children } -
-); +export const Content = ( { children, className = '', id = '' } ) => { + const elementClasses = classNames( + 'ppcp-r-settings-card__content', + className + ); + + return ( +
+ { children } +
+ ); +}; export const ContentWrapper = ( { children } ) => (
{ children }
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettings.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettings.js index 1b471fe1e..80d025af8 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettings.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettings.js @@ -1,31 +1,16 @@ -import { useState } from '@wordpress/element'; 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 ] = useState( { - invoicePrefix: '', - authorizeOnly: false, - captureVirtualOnlyOrders: false, - savePaypalAndVenmo: false, - saveCreditCardAndDebitCard: false, - payNowExperience: false, - sandboxAccountCredentials: false, - sandboxMode: null, - sandboxEnabled: false, - sandboxClientId: '', - sandboxSecretKey: '', - sandboxConnected: false, - logging: false, - subtotalMismatchFallback: null, - brandName: '', - softDescriptor: '', - paypalLandingPage: null, - buttonLanguage: '', - } ); + const { settings, setSettings } = useSettings(); + const updateFormValue = ( key, value ) => { - setSettings( { ...settings, [ key ]: value } ); + setSettings( { + ...settings, + [ key ]: value, + } ); }; return ( diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js deleted file mode 100644 index 7ddd8be7a..000000000 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js +++ /dev/null @@ -1,336 +0,0 @@ -import { __, sprintf } from '@wordpress/i18n'; -import { SelectControl, RadioControl } from '@wordpress/components'; -import { PayPalCheckboxGroup } from '../../ReusableComponents/Fields'; -import { useState, useMemo, useEffect } from '@wordpress/element'; -import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'; - -import { - defaultLocationSettings, - paymentMethodOptions, - colorOptions, - shapeOptions, - buttonLayoutOptions, - buttonLabelOptions, -} from '../../../data/settings/tab-styling-data'; - -const TabStyling = () => { - const [ location, setLocation ] = useState( 'cart' ); - const [ canRender, setCanRender ] = useState( false ); - const [ locationSettings, setLocationSettings ] = useState( { - ...defaultLocationSettings, - } ); - - // Sometimes buttons won't render. This fixes the timing problem. - useEffect( () => { - const handleDOMContentLoaded = () => setCanRender( true ); - if ( - document.readyState === 'interactive' || - document.readyState === 'complete' - ) { - handleDOMContentLoaded(); - } else { - document.addEventListener( - 'DOMContentLoaded', - handleDOMContentLoaded - ); - } - }, [] ); - - const currentLocationSettings = useMemo( () => { - return locationSettings[ location ]; - }, [ location, locationSettings ] ); - - const locationOptions = useMemo( () => { - return Object.keys( locationSettings ).reduce( - ( locationOptionsData, key ) => { - locationOptionsData.push( { - value: locationSettings[ key ].value, - label: locationSettings[ key ].label, - } ); - - return locationOptionsData; - }, - [] - ); - }, [] ); - - const updateButtonSettings = ( key, value ) => { - setLocationSettings( { - ...locationSettings, - [ location ]: { - ...currentLocationSettings, - settings: { - ...currentLocationSettings.settings, - [ key ]: value, - }, - }, - } ); - }; - - const updateButtonStyle = ( key, value ) => { - setLocationSettings( { - ...locationSettings, - [ location ]: { - ...currentLocationSettings, - settings: { - ...currentLocationSettings.settings, - style: { - ...currentLocationSettings.settings.style, - [ key ]: value, - }, - }, - }, - } ); - }; - - if ( ! canRender ) { - return <>; - } - - return ( -
-
- - - - - - - - - - -
-
-
- -
-
-
- ); -}; - -const TabStylingSection = ( props ) => { - let sectionTitleClassName = 'ppcp-r-styling__section'; - - if ( props?.className ) { - sectionTitleClassName += ` ${ props.className }`; - } - - return ( -
- { props.title } - { props?.description && ( -

- ) } - { props.children } -

- ); -}; - -const SectionIntro = ( { location } ) => { - const { description, descriptionLink } = - defaultLocationSettings[ location ]; - const buttonStyleDescription = sprintf( description, descriptionLink ); - - return ( - - ); -}; - -const SectionLocations = ( { locationOptions, location, setLocation } ) => { - return ( - - setLocation( newLocation ) } - label={ __( 'Locations', 'woocommerce-paypal-payments' ) } - options={ locationOptions } - /> - - ); -}; - -const SectionPaymentMethods = ( { - locationSettings, - updateButtonSettings, -} ) => { - return ( - -
- - updateButtonSettings( 'paymentMethods', newValue ) - } - currentValue={ locationSettings.settings.paymentMethods } - /> -
-
- ); -}; - -const SectionButtonLayout = ( { locationSettings, updateButtonStyle } ) => { - const buttonLayoutIsAllowed = - locationSettings.settings.style?.layout && - locationSettings.settings.style?.tagline === false; - return ( - buttonLayoutIsAllowed && ( - - - updateButtonStyle( 'layout', newValue ) - } - selected={ locationSettings.settings.style.layout } - options={ buttonLayoutOptions } - /> - - ) - ); -}; - -const SectionButtonShape = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'shape', newValue ) - } - selected={ locationSettings.settings.style.shape } - options={ shapeOptions } - /> - - ); -}; - -const SectionButtonLabel = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'label', newValue ) - } - value={ locationSettings.settings.style.label } - label={ __( 'Button Label', 'woocommerce-paypal-payments' ) } - options={ buttonLabelOptions } - /> - - ); -}; - -const SectionButtonColor = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'color', newValue ) - } - value={ locationSettings.settings.style.color } - options={ colorOptions } - /> - - ); -}; - -const SectionButtonTagline = ( { locationSettings, updateButtonStyle } ) => { - const taglineIsAllowed = - locationSettings.settings.style.hasOwnProperty( 'tagline' ) && - locationSettings.settings.style?.layout === 'horizontal'; - - return ( - taglineIsAllowed && ( - - { - updateButtonStyle( 'tagline', newValue ); - } } - currentValue={ locationSettings.settings.style.tagline } - /> - - ) - ); -}; - -const SectionButtonPreview = ( { locationSettings } ) => { - return ( - - - Error - - - ); -}; - -export default TabStyling; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js index f195ea1b1..9023a1f9b 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js @@ -1,16 +1,34 @@ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { CommonHooks, StylingHooks } from '../../../../data'; import TopNavigation from '../../../ReusableComponents/TopNavigation'; +import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper'; 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 title = __( 'PayPal Payments', 'woocommerce-paypal-payments' ); return ( - + + + ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js new file mode 100644 index 000000000..29b98069c --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js @@ -0,0 +1,20 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { SelectStylingSection } from '../Layout'; + +const SectionButtonColor = ( { location } ) => { + const { color, setColor, choices } = StylingHooks.useColorProps( location ); + + return ( + + ); +}; + +export default SectionButtonColor; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js new file mode 100644 index 000000000..de7c1a103 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js @@ -0,0 +1,20 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { SelectStylingSection } from '../Layout'; + +const SectionButtonLabel = ( { location } ) => { + const { label, setLabel, choices } = StylingHooks.useLabelProps( location ); + + return ( + + ); +}; + +export default SectionButtonLabel; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js new file mode 100644 index 000000000..1a284f72a --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js @@ -0,0 +1,29 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { RadiobuttonStylingSection } from '../Layout'; +import { Tagline } from './index'; + +const SectionButtonLayout = ( { location } ) => { + const { isAvailable, layout, setLayout, choices } = + StylingHooks.useLayoutProps( location ); + + if ( ! isAvailable ) { + return null; + } + + return ( + <> + + + + ); +}; + +export default SectionButtonLayout; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js new file mode 100644 index 000000000..71bc5bebe --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js @@ -0,0 +1,20 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { RadiobuttonStylingSection } from '../Layout'; + +const SectionButtonShape = ( { location } ) => { + const { shape, setShape, choices } = StylingHooks.useShapeProps( location ); + + return ( + + ); +}; + +export default SectionButtonShape; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js new file mode 100644 index 000000000..a747e93b3 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js @@ -0,0 +1,62 @@ +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { help } from '@wordpress/icons'; + +import { StylingHooks } from '../../../../../../data'; +import { + SelectStylingSection, + StylingSection, + CheckboxStylingSection, +} from '../Layout'; + +const LocationSelector = ( { location, setLocation } ) => { + const { choices, details, isActive, setActive } = + StylingHooks.useLocationProps( location ); + + const activateCheckbox = { + value: 'active', + label: __( + 'Enable payment methods in this location', + 'woocommerce-paypal-payments' + ), + }; + + return ( + <> + + + { details.link && ( +