diff --git a/modules/ppcp-settings/images/icon-arrow-down.svg b/modules/ppcp-settings/images/icon-arrow-down.svg new file mode 100644 index 000000000..3adce0c2e --- /dev/null +++ b/modules/ppcp-settings/images/icon-arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/ppcp-settings/images/icon-close.svg b/modules/ppcp-settings/images/icon-close.svg new file mode 100644 index 000000000..cfb1aef48 --- /dev/null +++ b/modules/ppcp-settings/images/icon-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/modules/ppcp-settings/images/icon-dashboard-list.svg b/modules/ppcp-settings/images/icon-dashboard-list.svg new file mode 100644 index 000000000..a9dcb265d --- /dev/null +++ b/modules/ppcp-settings/images/icon-dashboard-list.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/ppcp-settings/images/icon-dashboard-status.svg b/modules/ppcp-settings/images/icon-dashboard-status.svg new file mode 100644 index 000000000..38a142a4c --- /dev/null +++ b/modules/ppcp-settings/images/icon-dashboard-status.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/ppcp-settings/images/icon-dashboard-support.svg b/modules/ppcp-settings/images/icon-dashboard-support.svg new file mode 100644 index 000000000..b1dc452c4 --- /dev/null +++ b/modules/ppcp-settings/images/icon-dashboard-support.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/ppcp-settings/images/icon-refresh.svg b/modules/ppcp-settings/images/icon-refresh.svg new file mode 100644 index 000000000..6a7c9f262 --- /dev/null +++ b/modules/ppcp-settings/images/icon-refresh.svg @@ -0,0 +1,4 @@ + + + + diff --git a/modules/ppcp-settings/resources/css/_variables.scss b/modules/ppcp-settings/resources/css/_variables.scss index 69d1912e1..2c14894d3 100644 --- a/modules/ppcp-settings/resources/css/_variables.scss +++ b/modules/ppcp-settings/resources/css/_variables.scss @@ -1,4 +1,5 @@ $color-white: #fff; +$color-black: #000; $color-blue: #1D35B4; $color-blueberry: #3858E9; $color-gray-900: #1E1E1E; diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss index be4174a0a..d084ce4d7 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss @@ -1,4 +1,4 @@ -button.components-button { +button.components-button, a.components-button { &.is-primary, &.is-secondary { &:not(:disabled) { background-color: $color-blueberry; @@ -22,13 +22,17 @@ button.components-button { &.is-primary { @include font(13, 16, 500); + color:$color-white; } - &.is-secondary { - padding: 6px 12px; - @include font(13, 20, 500); - color: $color-white; - border: none; + &.is-secondary:not(:disabled) { + border-color:$color-blueberry; + background-color:$color-white; + color:$color-blueberry; + &:hover{ + background-color:$color-white; + background:none; + } } &.is-tertiary { diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss new file mode 100644 index 000000000..9f6274085 --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss @@ -0,0 +1,43 @@ +.ppcp-r { + + &__radio-value { + @include hide-input-field; + + &:checked { + + .ppcp-r__radio-presentation { + background: $color-white; + width: 20px; + height: 20px; + border: 6px solid $color-blueberry; + } + } + } + + &__checkbox-value { + @include hide-input-field; + + &:not(:checked) + .ppcp-r__checkbox-presentation img { + display: none; + } + + &:checked { + + .ppcp-r__checkbox-presentation { + width: 20px; + height: 20px; + border: none; + + img { + border-radius: 2px; + } + } + } + } + + &__radio-presentation { + @include fake-input-field(20px); + } + + &__checkbox-presentation { + @include fake-input-field(2px); + } +} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-wrapper.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-wrapper.scss index 6c20b8646..b83a1a94f 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-wrapper.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-wrapper.scss @@ -4,7 +4,7 @@ width: 652px; max-width: 100%; margin: 0 auto; - padding:0 16px; + padding: 0 16px; box-sizing: border-box; } @@ -16,4 +16,36 @@ box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.15); background-color: $color-white; } + + &-settings-card { + background-color: $color-white; + padding: 48px; + border-radius: 8px; + box-shadow: 0 2px 4px 0 #0000001A; + @media screen and (max-width: 480px) { + padding: 24px; + } + + &__header { + display: flex; + align-items: center; + gap: 18px; + padding-bottom: 18px; + border-bottom: 2px solid $color-gray-700; + margin-bottom: 32px; + } + + &__title { + @include font(16, 24, 600); + color: $color-blueberry; + margin: 0 0 4px 0; + display: block; + } + + &__description { + @include font(14, 20, 400); + color: $color-gray-800; + margin: 0; + } + } } diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_tabs.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_tabs.scss new file mode 100644 index 000000000..5eaa4fb56 --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_tabs.scss @@ -0,0 +1,27 @@ +.components-tab-panel__tabs { + width: 100%; + position: relative; + margin:0 0 48px 0; + &::after { + content: ''; + position: absolute; + display: block; + width: 100%; + height: 1px; + background-color: $color-gray-400; + bottom: 0; + left: 0; + z-index: -1; + } + + button { + padding: 16px 20px; + @include font(13, 16, 400); + color: $color-gray-900; + + &.active-tab { + font-weight: 600; + box-shadow: 0px -4px 0px 0px $color-blueberry inset; + } + } +} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_title-badge.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_title-badge.scss new file mode 100644 index 000000000..2490f67e8 --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_title-badge.scss @@ -0,0 +1,15 @@ +.ppcp-r-title-badge{ + @include font(12, 16, 400); + margin-left:12px; + padding:4px 8px; + border-radius: 2px; + white-space: nowrap; + &--positive{ + color:#005C12; + background-color: #EDFAEF; + } + &--negative{ + color:#5c0000; + background-color: #faeded; + } +} diff --git a/modules/ppcp-settings/resources/css/components/screens/dashboard/_tab-dashboard.scss b/modules/ppcp-settings/resources/css/components/screens/dashboard/_tab-dashboard.scss new file mode 100644 index 000000000..f9ac41a0d --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/screens/dashboard/_tab-dashboard.scss @@ -0,0 +1,182 @@ +.ppcp-r-tab-dashboard-todo { + margin: 0 0 48px 0; +} + +.ppcp-r-todo-item { + position: relative; + display: flex; + align-items: center; + gap: 18px; + width: 100%; + + &:not(:last-child) { + border-bottom: 1px solid $color-gray-400; + padding-bottom: 24px; + } + + &:not(:first-child) { + padding-top: 24px; + } + + p { + @include font(14, 20, 400); + } + + &__inner { + position: relative; + display: flex; + align-items: center; + gap: 18px; + } + + &__close { + margin-left: auto; + + &:hover { + cursor: pointer; + color: $color-blueberry; + } + } +} + +.ppcp-r-feature-item { + padding-top: 32px; + border-top: 1px solid $color-gray-400; + + &__title { + @include font(16, 20, 600); + color: $color-black; + display: block; + margin: 0 0 8px 0; + } + + &__description { + @include font(14, 20, 400); + color: $color-gray-800; + margin: 0 0 18px 0; + } + + &:not(:last-child) { + padding-bottom: 32px; + } + + &__buttons { + display: flex; + gap: 18px; + } + + &__notes { + display: flex; + flex-direction: column; + + span { + font-weight: 500; + } + } +} + +.ppcp-r-connection-status { + display: flex; + gap: 32px; + padding-bottom: 48px; + margin-bottom: 48px; + border-bottom: 2px solid $color-gray-500; + + &__status-status { + margin: 0 0 8px 0; + + strong { + @include font(14, 20, 700); + color: $color-black; + } + } + + &__show-all-data { + margin-left: 12px; + + &:hover { + cursor: pointer; + opacity: 0.8; + } + } + + &__status-label { + span { + @include font(12, 16, 400); + color: $color-gray-700; + } + } + + &__data { + display: flex; + flex-direction: column; + gap: 12px; + } + + &__status-row { + display: flex; + align-items: center; + + strong { + @include font(14, 20, 600); + color: $color-gray-800; + margin-right: 12px; + white-space: nowrap; + } + + span { + @include font(14, 20, 400); + color: $color-gray-800; + } + } + + @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; + } + } + } +} + +.ppcp-r-feature-refresh { + display: flex; + gap: 12px; + margin-bottom: 24px; + + &__row { + display: flex; + align-items: center; + } + + &__content { + width: 100%; + + &-title { + @include font(16, 20, 700); + color: $color-black; + display: block; + margin: 0 0 4px 0; + } + + p { + @include font(12, 20, 400); + color: $color-gray-700; + margin: 0; + } + } + + button { + display: flex; + gap: 4px; + @include font(13, 20, 400); + } +} diff --git a/modules/ppcp-settings/resources/css/components/screens/onboarding/_step-welcome.scss b/modules/ppcp-settings/resources/css/components/screens/onboarding/_step-welcome.scss index e9861b900..c37822b41 100644 --- a/modules/ppcp-settings/resources/css/components/screens/onboarding/_step-welcome.scss +++ b/modules/ppcp-settings/resources/css/components/screens/onboarding/_step-welcome.scss @@ -34,6 +34,12 @@ .components-base-control__field { margin: 0 0 24px 0; } + .ppcp-r-toggle-block__toggled-content > button{ + padding: 6px 12px; + @include font(13, 20, 500); + color: $color-white; + border: none; + } } .ppcp-r-welcome-features { @@ -81,3 +87,4 @@ } } } + diff --git a/modules/ppcp-settings/resources/css/style.scss b/modules/ppcp-settings/resources/css/style.scss index a6ce1cb76..822d24f48 100644 --- a/modules/ppcp-settings/resources/css/style.scss +++ b/modules/ppcp-settings/resources/css/style.scss @@ -13,5 +13,8 @@ @import './components/reusable-components/select-box'; @import './components/reusable-components/tab-navigation'; @import './components/reusable-components/navigation'; + @import './components/reusable-components/fields'; + @import './components/reusable-components/title-badge'; @import './components/screens/onboarding'; + @import './components/screens/dashboard/tab-dashboard'; } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Container.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Container.js index b52058d79..e6b83e691 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Container.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Container.js @@ -1,5 +1,18 @@ -const Container = ( props ) => { - return
{ props.children }
; +export const PAGE_ONBOARDING = 'onboarding'; +export const PAGE_SETTINGS = 'settings'; + +const Container = ( { isCard = true, page, children } ) => { + let className = 'ppcp-r-container'; + + if ( isCard ) { + className += ' ppcp-r-container--card'; + } + + if ( page ) { + className += ` ppcp-r-container--${ page }`; + } + + return
{ children }
; }; export default Container; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js new file mode 100644 index 000000000..9872c07bf --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js @@ -0,0 +1,50 @@ +import data from '../../utils/data'; + +export const PayPalCheckbox = ( props ) => { + return ( +
+ + props.handleCheckboxState( e.target.checked, props ) + } + /> + + { data().getImage( 'icon-checkbox.svg' ) } + +
+ ); +}; + +export const PayPalRdb = ( props ) => { + return ( +
+ props.handleRdbState( props.value ) } + /> + +
+ ); +}; + +export const handleCheckboxState = ( checked, props ) => { + let newValue = null; + if ( checked ) { + newValue = [ ...props.currentValue, props.value ]; + props.changeCallback( newValue ); + } else { + newValue = props.currentValue.filter( + ( value ) => value !== props.value + ); + } + props.changeCallback( newValue ); +}; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SelectBox.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SelectBox.js index 739397a13..06a56c5ac 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SelectBox.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SelectBox.js @@ -1,19 +1,7 @@ import data from '../../utils/data'; +import { PayPalCheckbox, PayPalRdb, handleCheckboxState } from './Fields'; const SelectBox = ( props ) => { - const handleCheckboxState = ( checked ) => { - let newValue = null; - if ( checked ) { - newValue = [ ...props.currentValue, props.value ]; - props.changeCallback( newValue ); - } else { - newValue = props.currentValue.filter( - ( value ) => value !== props.value - ); - } - props.changeCallback( newValue ); - }; - let boxClassName = 'ppcp-r-select-box'; if ( @@ -27,34 +15,20 @@ const SelectBox = ( props ) => { return (
{ props.type === 'radio' && ( -
- props.changeCallback( props.value ) } - /> - -
+ ) } { props.type === 'checkbox' && ( -
- - handleCheckboxState( e.target.checked ) - } - /> - - { data().getImage( 'icon-checkbox.svg' ) } - -
+ ) }
{ data().getImage( props.icon ) } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsCard.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsCard.js new file mode 100644 index 000000000..bd0a76449 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsCard.js @@ -0,0 +1,29 @@ +import data from '../../utils/data'; + +const SettingsCard = ( props ) => { + let className = 'ppcp-r-settings-card'; + + if ( props?.className ) { + className += ' ' + props.className; + } + return ( +
+
+ { data().getImage( props.icon ) } +
+ + { props.title } + +

+ { props.description } +

+
+
+
+ { props.children } +
+
+ ); +}; + +export default SettingsCard; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/TitleBadge.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/TitleBadge.js new file mode 100644 index 000000000..25c5c8645 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/TitleBadge.js @@ -0,0 +1,8 @@ +const TitleBadge = ( { text, type } ) => { + const className = 'ppcp-r-title-badge ' + `ppcp-r-title-badge--${ type }`; + return { text }; +}; + +export const TITLE_BADGE_POSITIVE = 'positive'; +export const TITLE_BADGE_NEGATIVE = 'negative'; +export default TitleBadge; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/Dashboard.js b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/Dashboard.js deleted file mode 100644 index 2c095e88b..000000000 --- a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/Dashboard.js +++ /dev/null @@ -1,7 +0,0 @@ -import TabNavigation from '../../ReusableComponents/TabNavigation'; - -const Dashboard = () => { - return
Settings Page
; -}; - -export default Dashboard; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabDashboard.js b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabDashboard.js new file mode 100644 index 000000000..214f7510f --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabDashboard.js @@ -0,0 +1,450 @@ +import SettingsCard from '../../ReusableComponents/SettingsCard'; +import { __ } from '@wordpress/i18n'; +import { + PayPalCheckbox, + handleCheckboxState, +} from '../../ReusableComponents/Fields'; +import { useState } from '@wordpress/element'; +import data from '../../../utils/data'; +import { Button } from '@wordpress/components'; +import TitleBadge, { + TITLE_BADGE_NEGATIVE, + TITLE_BADGE_POSITIVE, +} from '../../ReusableComponents/TitleBadge'; + +const TabDashboard = () => { + const [ todos, setTodos ] = useState( [] ); + const [ todosData, setTodosData ] = useState( todosDataDefault ); + const [ connectionData, setConnectionData ] = useState( { + connectionStatus: true, + showAllData: false, + email: 'bt_us@woocommerce.com', + merchantId: 'AT45V2DGMKLRY', + clientId: 'BAARTJLxtUNN4d2GMB6Eut3suMDYad72xQA-FntdIFuJ6FmFJITxAY8', + } ); + + const showAllData = () => { + setConnectionData( { ...connectionData, showAllData: true } ); + }; + + return ( +
+ { todosData.length > 0 && ( + +
+ { todosData.map( ( todo ) => ( + + ) ) } +
+
+ ) } + + + + { featuresDefault.map( ( feature ) => { + return ( + + ); + } ) } + +
+ ); +}; + +const ConnectionStatus = ( { connectionData, showAllData } ) => { + return ( +
+
+
+ + { __( 'Connection', 'woocommerce-paypal-payments' ) } + + { connectionData.connectionStatus ? ( + + ) : ( + + ) } +
+
+ + { __( + 'PayPal Account Details', + 'woocommerce-paypal-payments' + ) } + +
+
+ { connectionData.connectionStatus && ( +
+
+ + { __( + 'Email address:', + 'woocommerce-paypal-payments' + ) } + + { connectionData.email } + { ! connectionData.showAllData && ( + showAllData() }> + { data().getImage( + 'icon-arrow-down.svg', + 'ppcp-r-connection-status__show-all-data' + ) } + + ) } +
+ { connectionData.showAllData && ( + <> +
+ + { __( + 'Merchant ID:', + 'woocommerce-paypal-payments' + ) } + + { connectionData.merchantId } +
+
+ + { __( + 'Client ID:', + 'woocommerce-paypal-payments' + ) } + + { connectionData.clientId } +
+ + ) } +
+ ) } +
+ ); +}; + +const FeaturesRefresh = () => { + return ( +
+
+ + { __( 'Features', 'woocommerce-paypal-payments' ) } + +

+ { __( + 'After making changes to your PayPal account, click Refresh to update your store features.', + 'woocommerce-paypal-payments' + ) } +

+
+ +
+ ); +}; + +const TodoItem = ( props ) => { + return ( +
+
+ { ' ' } +

{ props.description }

+
+
+ removeTodo( + props.value, + props.todosData, + props.changeTodos + ) + } + > + { data().getImage( 'icon-close.svg' ) } +
+
+ ); +}; + +const FeatureItem = ( { feature } ) => { + const printNotes = () => { + if ( ! feature?.notes ) { + return null; + } + + if ( Array.isArray( feature.notes ) && feature.notes.length === 0 ) { + return null; + } + + return ( + <> +
+
+ + { feature.notes.map( ( note, index ) => { + return { note }; + } ) } + + + ); + }; + + return ( +
+ + { feature.title } + { feature?.featureStatus && ( + + ) } + +

+ { feature.description } + { printNotes() } +

+
+ { feature.buttons.map( ( button ) => { + return ( + + ); + } ) } +
+
+ ); +}; + +const removeTodo = ( todoValue, todosData, changeTodos ) => { + changeTodos( todosData.filter( ( todo ) => todo.value !== todoValue ) ); +}; + +const todosDataDefault = [ + { + value: 'paypal_later_messaging', + description: __( + 'Enable Pay Later messaging', + 'woocommerce-paypal-payments' + ), + }, + { + value: 'capture_authorized_payments', + description: __( + 'Capture authorized payments', + 'woocommerce-paypal-payments' + ), + }, + { + value: 'enable_google_pay', + description: __( 'Enable Google Pay', 'woocommerce-paypal-payments' ), + }, + { + value: 'paypal_shortcut', + description: __( + 'Add PayPal shortcut to the Cart page', + 'woocommerce-paypal-payments' + ), + }, + { + value: 'advanced_cards', + description: __( + 'Add Advanced Cards to Blocks Checkout', + 'woocommerce-paypal-payments' + ), + }, +]; + +const featuresDefault = [ + { + id: 'save_paypal_and_venmo', + title: __( 'Save PayPal and Venmo', 'woocommerce-paypal-payments' ), + description: __( + 'Securely save PayPal and Venmo payment methods for subscriptions or return buyers.', + 'woocommerce-paypal-payments' + ), + buttons: [ + { + type: 'primary', + text: __( 'Configure', 'woocommerce-paypal-payments' ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + }, + { + id: 'advanced_credit_and_debit_cards', + title: __( + 'Advanced Credit and Debit Cards', + 'woocommerce-paypal-payments' + ), + featureStatus: true, + description: __( + 'Process major credit and debit cards including Visa, Mastercard, American Express and Discover.', + 'woocommerce-paypal-payments' + ), + buttons: [ + { + type: 'primary', + text: __( 'Configure', 'woocommerce-paypal-payments' ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + }, + { + id: 'alternative_payment_methods', + title: __( + 'Alternative Payment Methods', + 'woocommerce-paypal-payments' + ), + description: __( + 'Offer global, country-specific payment options for your customers.', + 'woocommerce-paypal-payments' + ), + buttons: [ + { + type: 'primary', + text: __( 'Apply', 'woocommerce-paypal-payments' ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + }, + { + id: 'google_pay', + title: __( 'Google Pay', 'woocommerce-paypal-payments' ), + description: __( + 'Let customers pay using their Google Pay wallet.', + 'woocommerce-paypal-payments' + ), + featureStatus: true, + buttons: [ + { + type: 'primary', + text: __( 'Configure', 'woocommerce-paypal-payments' ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + notes: [ + __( '¹PayPal Q2 Earnings-2021.', 'woocommerce-paypal-payments' ), + ], + }, + { + id: 'apple_pay', + title: __( 'Apple Pay', 'woocommerce-paypal-payments' ), + description: __( + 'Let customers pay using their Apple Pay wallet.', + 'woocommerce-paypal-payments' + ), + buttons: [ + { + type: 'primary', + text: __( + 'Domain registration', + 'woocommerce-paypal-payments' + ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + }, + { + id: 'pay_later_messaging', + title: __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ), + description: __( + 'Let customers know they can buy now and pay later with PayPal. Adding this messaging can boost conversion rates and increase cart sizes by 39%¹, with no extra cost to you—plus, you get paid up front.', + 'woocommerce-paypal-payments' + ), + buttons: [ + { + type: 'primary', + text: __( 'Configure', 'woocommerce-paypal-payments' ), + url: '#', + }, + { + type: 'secondary', + text: __( 'Learn more', 'woocommerce-paypal-payments' ), + url: '#', + }, + ], + }, +]; +export default TabDashboard; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabPaymentMethods.js b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabPaymentMethods.js new file mode 100644 index 000000000..56df4838c --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabPaymentMethods.js @@ -0,0 +1,5 @@ +const TabPaymentMethods = () => { + return
PaymentMethods tab
; +}; + +export default TabPaymentMethods; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabSettings.js b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabSettings.js new file mode 100644 index 000000000..da94c89c8 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabSettings.js @@ -0,0 +1,5 @@ +const TabSettings = () => { + return
Settings tab
; +}; + +export default TabSettings; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabStyling.js new file mode 100644 index 000000000..c61b2b296 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Dashboard/TabStyling.js @@ -0,0 +1,5 @@ +const TabStyling = () => { + return
Styling tab
; +}; + +export default TabStyling; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Onboarding.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Onboarding.js index f2f78246e..767f1c304 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Onboarding.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Onboarding.js @@ -1,15 +1,16 @@ -import Container from '../../ReusableComponents/Container.js'; +import Container, { + PAGE_ONBOARDING, +} from '../../ReusableComponents/Container.js'; import StepWelcome from './StepWelcome.js'; import StepBusiness from './StepBusiness.js'; import StepProducts from './StepProducts.js'; import { useState } from '@wordpress/element'; -import Dashboard from '../Dashboard/Dashboard'; const Onboarding = () => { const [ step, setStep ] = useState( 0 ); return ( - +
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/tabs.js b/modules/ppcp-settings/resources/js/Components/Screens/tabs.js index dea1869d1..3c9a760a1 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/tabs.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/tabs.js @@ -1,6 +1,9 @@ import { __ } from '@wordpress/i18n'; -import Dashboard from './Dashboard/Dashboard'; import Onboarding from './Onboarding/Onboarding'; +import TabDashboard from './Dashboard/TabDashboard'; +import TabPaymentMethods from './Dashboard/TabPaymentMethods'; +import TabSettings from './Dashboard/TabSettings'; +import TabStyling from './Dashboard/TabStyling'; export const getSettingsTabs = () => { const tabs = []; @@ -18,23 +21,25 @@ export const getSettingsTabs = () => { tabs.push( { name: 'dashboard', title: __( 'Dashboard', 'woocommerce-paypal-payments' ), - component: , + component: , } ); tabs.push( { name: 'payment-methods', title: __( 'Payment Methods', 'woocommerce-paypal-payments' ), - component: , + component: , } ); tabs.push( { name: 'settings', title: __( 'Settings', 'woocommerce-paypal-payments' ), + component: , } ); tabs.push( { name: 'styling', title: __( 'Styling', 'woocommerce-paypal-payments' ), + component: , } ); } diff --git a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js index 954801995..e37d145b0 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js @@ -8,6 +8,9 @@ const defaultState = { useManualConnection: false, clientId: '', clientSecret: '', + canUseCasualSelling: false, + canUseVaulting: false, + canUseCardPayments: false, }, }; diff --git a/modules/ppcp-settings/resources/js/data/store.js b/modules/ppcp-settings/resources/js/data/store.js index f5a1a4b13..f416cb94f 100644 --- a/modules/ppcp-settings/resources/js/data/store.js +++ b/modules/ppcp-settings/resources/js/data/store.js @@ -27,4 +27,23 @@ export const initStore = () => { } ); register( store ); + + /* eslint-disable no-console */ + // Provide a debug tool to inspect the Redux store via the JS console. + if ( window.ppcpSettings?.debug && console?.groupCollapsed ) { + window.ppcpSettings.dumpStore = () => { + const storeSelector = `wp.data.select('${ STORE_NAME }')`; + console.group( `[STORE] ${ storeSelector }` ); + + const storeState = wp.data.select( STORE_NAME ); + Object.keys( selectors ).forEach( ( selector ) => { + console.groupCollapsed( `[SELECTOR] .${ selector }()` ); + console.table( storeState[ selector ]() ); + console.groupEnd(); + } ); + + console.groupEnd(); + }; + } + /* eslint-enable no-console */ }; diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index c8bb64144..f34257f79 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -26,7 +26,20 @@ return array( ); }, 'settings.data.onboarding' => static function ( ContainerInterface $container ) : OnboardingProfile { - return new OnboardingProfile(); + $can_use_casual_selling = false; + $can_use_vaulting = $container->has( 'save-payment-methods.eligible' ) && $container->get( 'save-payment-methods.eligible' ); + $can_use_card_payments = $container->has( 'card-fields.eligible' ) && $container->get( 'card-fields.eligible' ); + + // Card payments are disabled for this plugin when WooPayments is active. + if ( class_exists( '\WC_Payments' ) ) { + $can_use_card_payments = false; + } + + return new OnboardingProfile( + $can_use_casual_selling, + $can_use_vaulting, + $can_use_card_payments + ); }, 'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint { return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); diff --git a/modules/ppcp-settings/src/Data/AbstractDataModel.php b/modules/ppcp-settings/src/Data/AbstractDataModel.php index d9cd0fbae..1d711cbe5 100644 --- a/modules/ppcp-settings/src/Data/AbstractDataModel.php +++ b/modules/ppcp-settings/src/Data/AbstractDataModel.php @@ -57,8 +57,9 @@ abstract class AbstractDataModel { * Loads the model data from WordPress options. */ public function load() : void { - $saved_data = get_option( static::OPTION_KEY, array() ); - $this->data = array_merge( $this->data, $saved_data ); + $saved_data = get_option( static::OPTION_KEY, array() ); + $filtered_data = array_intersect_key( $saved_data, $this->data ); + $this->data = array_merge( $this->data, $filtered_data ); } /** @@ -91,8 +92,6 @@ abstract class AbstractDataModel { $setter = "set_$key"; if ( method_exists( $this, $setter ) ) { $this->$setter( $value ); - } else { - $this->data[ $key ] = $value; } } } diff --git a/modules/ppcp-settings/src/Data/OnboardingProfile.php b/modules/ppcp-settings/src/Data/OnboardingProfile.php index 9cfd29d46..c9a45ead0 100644 --- a/modules/ppcp-settings/src/Data/OnboardingProfile.php +++ b/modules/ppcp-settings/src/Data/OnboardingProfile.php @@ -9,12 +9,16 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Data; +use RuntimeException; + /** * Class OnboardingProfile * * This class serves as a container for managing the onboarding profile details - * within the WooCommerce PayPal Commerce plugin. It provides methods to retrieve - * and save the onboarding profile data using WordPress options. + * within the WooCommerce PayPal Commerce plugin. + * + * This profile impacts the onboarding wizard and help to apply default + * settings. The details here should not be used outside the onboarding process. */ class OnboardingProfile extends AbstractDataModel { @@ -25,6 +29,27 @@ class OnboardingProfile extends AbstractDataModel { */ protected const OPTION_KEY = 'woocommerce-ppcp-data-onboarding'; + /** + * Constructor. + * + * @param bool $can_use_casual_selling Whether casual selling is enabled in the store's country. + * @param bool $can_use_vaulting Whether vaulting is enabled in the store's country. + * @param bool $can_use_card_payments Whether credit card payments are possible. + * + * @throws RuntimeException If the OPTION_KEY is not defined in the child class. + */ + public function __construct( + bool $can_use_casual_selling = false, + bool $can_use_vaulting = false, + bool $can_use_card_payments = false + ) { + parent::__construct(); + + $this->data['can_use_casual_selling'] = $can_use_casual_selling; + $this->data['can_use_vaulting'] = $can_use_vaulting; + $this->data['can_use_card_payments'] = $can_use_card_payments; + } + /** * Get default values for the model. * @@ -32,11 +57,14 @@ class OnboardingProfile extends AbstractDataModel { */ protected function get_defaults() : array { return array( - 'step' => 0, - 'use_sandbox' => false, - 'use_manual_connection' => false, - 'client_id' => '', - 'client_secret' => '', + 'step' => 0, + 'use_sandbox' => false, + 'use_manual_connection' => false, + 'client_id' => '', + 'client_secret' => '', + 'can_use_casual_selling' => null, + 'can_use_vaulting' => null, + 'can_use_card_payments' => null, ); } @@ -131,4 +159,31 @@ class OnboardingProfile extends AbstractDataModel { public function set_client_secret( string $client_secret ) : void { $this->data['client_secret'] = sanitize_text_field( $client_secret ); } + + /** + * Gets whether casual selling can be used. + * + * @return bool + */ + public function get_can_use_casual_selling() : bool { + return (bool) $this->data['can_use_casual_selling']; + } + + /** + * Gets whether vaulting can be used. + * + * @return bool + */ + public function get_can_use_vaulting() : bool { + return (bool) $this->data['can_use_vaulting']; + } + + /** + * Gets whether Credit Card payments can be used. + * + * @return bool + */ + public function get_can_use_card_payments() : bool { + return (bool) $this->data['can_use_card_payments']; + } } diff --git a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php index dd8f19e67..e5a38af5d 100644 --- a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php @@ -41,26 +41,38 @@ class OnboardingRestEndpoint extends RestEndpoint { * @var array */ private array $field_map = array( - 'step' => array( + 'step' => array( 'js_name' => 'step', 'sanitize' => 'to_number', ), - 'use_sandbox' => array( + 'use_sandbox' => array( 'js_name' => 'useSandbox', 'sanitize' => 'to_boolean', ), - 'use_manual_connection' => array( + 'use_manual_connection' => array( 'js_name' => 'useManualConnection', 'sanitize' => 'to_boolean', ), - 'client_id' => array( + 'client_id' => array( 'js_name' => 'clientId', 'sanitize' => 'sanitize_text_field', ), - 'client_secret' => array( + 'client_secret' => array( 'js_name' => 'clientSecret', 'sanitize' => 'sanitize_text_field', ), + 'can_use_casual_selling' => array( + 'js_name' => 'canUseCasualSelling', + 'sanitize' => 'read_only', + ), + 'can_use_vaulting' => array( + 'js_name' => 'canUseVaulting', + 'sanitize' => 'read_only', + ), + 'can_use_card_payments' => array( + 'js_name' => 'canUseCardPayments', + 'sanitize' => 'read_only', + ), ); /** diff --git a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php index 63e543d59..9bb98dfac 100644 --- a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php @@ -53,7 +53,11 @@ class RestEndpoint extends WC_REST_Controller { $source_key = $details['js_name'] ?? ''; $sanitation_cb = $details['sanitize'] ?? null; - if ( ! $source_key || ! isset( $params[ $source_key ] ) ) { + if ( + ! $source_key + || ! isset( $params[ $source_key ] ) + || 'read_only' === $sanitation_cb + ) { continue; } @@ -121,5 +125,4 @@ class RestEndpoint extends WC_REST_Controller { protected function to_number( $value ) { return $value !== null ? ( is_numeric( $value ) ? $value + 0 : null ) : null; } - } diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index fdc286521..3533c2394 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -86,6 +86,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { 'assets' => array( 'imagesUrl' => $module_url . '/images/', ), + 'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, ) ); } diff --git a/modules/ppcp-settings/webpack.config.js b/modules/ppcp-settings/webpack.config.js index e0223be80..93cd4afb5 100644 --- a/modules/ppcp-settings/webpack.config.js +++ b/modules/ppcp-settings/webpack.config.js @@ -3,6 +3,7 @@ const path = require( 'path' ); module.exports = { ...defaultConfig, + cache: false, ...{ entry: { index: path.resolve( process.cwd(), 'resources/js', 'index.js' ),