mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Create sidebar for styling screen and initalize preview - no success
This commit is contained in:
parent
06e74e74f4
commit
5f83517509
44 changed files with 3070 additions and 90 deletions
|
@ -13,6 +13,7 @@
|
||||||
"@wordpress/scripts": "^30.3.0"
|
"@wordpress/scripts": "^30.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@paypal/paypal-js": "^8.1.2",
|
||||||
"@woocommerce/settings": "^1.0.0",
|
"@woocommerce/settings": "^1.0.0",
|
||||||
"react-select": "^5.8.3"
|
"react-select": "^5.8.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ button.components-button, a.components-button {
|
||||||
|
|
||||||
&:not(:disabled) {
|
&:not(:disabled) {
|
||||||
background-color: $color-blueberry;
|
background-color: $color-blueberry;
|
||||||
|
color:$color-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,23 +21,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__checkbox-value {
|
&__checkbox {
|
||||||
@include hide-input-field;
|
position: relative;
|
||||||
|
|
||||||
&:not(:checked) + .ppcp-r__checkbox-presentation img {
|
input {
|
||||||
display: none;
|
margin: 0;
|
||||||
|
border-color: $color-gray-600;
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: $color-blueberry;
|
||||||
|
border-color:$color-blueberry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked {
|
.components-checkbox-control__input-container {
|
||||||
+ .ppcp-r__checkbox-presentation {
|
margin: 0;
|
||||||
width: 20px;
|
}
|
||||||
height: 20px;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
img {
|
.components-base-control__field {
|
||||||
border-radius: 2px;
|
margin: 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
padding:0 24px 28px 24px;
|
padding: 0 24px 28px 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,36 @@
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.components-radio-control {
|
||||||
|
.components-flex {
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
@include font(14, 20, 400);
|
||||||
|
color: $color-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__option {
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
border-color: $color-gray-700;
|
||||||
|
margin-right: 0;
|
||||||
|
&:checked {
|
||||||
|
border: 1px solid $color-gray-700;
|
||||||
|
background-color: $color-white;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: translate(3px, 3px);
|
||||||
|
border-width: 6px;
|
||||||
|
border-color: $color-blueberry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ppcp-r-modal__field-row--save button.is-primary {
|
.ppcp-r-modal__field-row--save button.is-primary {
|
||||||
|
|
|
@ -18,10 +18,6 @@
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
@include font(14, 20, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -37,6 +33,16 @@
|
||||||
color: $color-blueberry;
|
color: $color-blueberry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ppcp-r__checkbox {
|
||||||
|
.components-flex {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
label{
|
||||||
|
@include font(13, 20, 400);
|
||||||
|
color:$color-blueberry;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ppcp-r-feature-item {
|
.ppcp-r-feature-item {
|
||||||
|
@ -108,18 +114,20 @@
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status-toggle--toggled{
|
&__status-toggle--toggled {
|
||||||
.ppcp-r-connection-status__show-all-data{
|
.ppcp-r-connection-status__show-all-data {
|
||||||
transform:rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status-row {
|
&__status-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
*{
|
|
||||||
|
* {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
@include font(14, 24, 600);
|
@include font(14, 24, 600);
|
||||||
color: $color-gray-800;
|
color: $color-gray-800;
|
||||||
|
@ -131,11 +139,13 @@
|
||||||
@include font(14, 24, 400);
|
@include font(14, 24, 400);
|
||||||
color: $color-gray-800;
|
color: $color-gray-800;
|
||||||
}
|
}
|
||||||
.ppcp-r-connection-status__status-toggle{
|
|
||||||
|
.ppcp-r-connection-status__status-toggle {
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
&--first{
|
|
||||||
&:hover{
|
&--first {
|
||||||
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,11 +158,13 @@
|
||||||
}
|
}
|
||||||
&__status-row {
|
&__status-row {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
strong{
|
|
||||||
|
strong {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
span{
|
|
||||||
word-break:break-all;
|
span {
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
.ppcp-r-styling {
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid $color-gray-200;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__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;
|
||||||
|
padding: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__preview {
|
||||||
|
width: calc(100% - 422px);
|
||||||
|
background-color: #FAF8F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section--rc{
|
||||||
|
.ppcp-r-styling__title {
|
||||||
|
@include font(13, 20, 600);
|
||||||
|
color: $color-black;
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 18px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
@import './components/screens/overview/tab-overview';
|
@import './components/screens/overview/tab-overview';
|
||||||
@import './components/screens/overview/tab-payment-methods';
|
@import './components/screens/overview/tab-payment-methods';
|
||||||
@import './components/screens/overview/tab-settings';
|
@import './components/screens/overview/tab-settings';
|
||||||
|
@import './components/screens/overview/tab-styling';
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './components/reusable-components/payment-method-modal';
|
@import './components/reusable-components/payment-method-modal';
|
||||||
|
|
|
@ -1,25 +1,38 @@
|
||||||
import data from '../../utils/data';
|
import { CheckboxControl } from '@wordpress/components';
|
||||||
|
|
||||||
export const PayPalCheckbox = ( props ) => {
|
export const PayPalCheckbox = ( props ) => {
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r__checkbox">
|
<div className="ppcp-r__checkbox">
|
||||||
<input
|
<CheckboxControl
|
||||||
className="ppcp-r__checkbox-value"
|
label={ props?.label ? props.label : '' }
|
||||||
type="checkbox"
|
|
||||||
checked={ props.currentValue.includes( props.value ) }
|
|
||||||
name={ props.name }
|
|
||||||
value={ props.value }
|
value={ props.value }
|
||||||
onChange={ ( e ) =>
|
checked={ props.currentValue.includes( props.value ) }
|
||||||
props.handleCheckboxState( e.target.checked, props )
|
onChange={ ( checked ) =>
|
||||||
|
handleCheckboxState( checked, props )
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span className="ppcp-r__checkbox-presentation">
|
|
||||||
{ data().getImage( 'icon-checkbox.svg' ) }
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PayPalCheckboxGroup = ( props ) => {
|
||||||
|
const renderCheckboxGroup = () => {
|
||||||
|
return props.value.map( ( checkbox ) => {
|
||||||
|
return (
|
||||||
|
<PayPalCheckbox
|
||||||
|
label={ checkbox.label }
|
||||||
|
value={ checkbox.value }
|
||||||
|
key={ checkbox.value }
|
||||||
|
currentValue={ props.currentValue }
|
||||||
|
changeCallback={ props.changeCallback }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{ renderCheckboxGroup() }</>;
|
||||||
|
};
|
||||||
|
|
||||||
export const PayPalRdb = ( props ) => {
|
export const PayPalRdb = ( props ) => {
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r__radio">
|
<div className="ppcp-r__radio">
|
||||||
|
@ -75,7 +88,6 @@ export const handleCheckboxState = ( checked, props ) => {
|
||||||
let newValue = null;
|
let newValue = null;
|
||||||
if ( checked ) {
|
if ( checked ) {
|
||||||
newValue = [ ...props.currentValue, props.value ];
|
newValue = [ ...props.currentValue, props.value ];
|
||||||
props.changeCallback( newValue );
|
|
||||||
} else {
|
} else {
|
||||||
newValue = props.currentValue.filter(
|
newValue = props.currentValue.filter(
|
||||||
( value ) => value !== props.value
|
( value ) => value !== props.value
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import data from '../../utils/data';
|
import data from '../../utils/data';
|
||||||
import { PayPalCheckbox, PayPalRdb, handleCheckboxState } from './Fields';
|
import { PayPalCheckbox, PayPalRdb } from './Fields';
|
||||||
|
|
||||||
const SelectBox = ( props ) => {
|
const SelectBox = ( props ) => {
|
||||||
let boxClassName = 'ppcp-r-select-box';
|
let boxClassName = 'ppcp-r-select-box';
|
||||||
|
@ -24,10 +24,7 @@ const SelectBox = ( props ) => {
|
||||||
) }
|
) }
|
||||||
{ props.type === 'checkbox' && (
|
{ props.type === 'checkbox' && (
|
||||||
<PayPalCheckbox
|
<PayPalCheckbox
|
||||||
{ ...{
|
{ ...props }
|
||||||
...props,
|
|
||||||
handleCheckboxState,
|
|
||||||
} }
|
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
<div className="ppcp-r-select-box__content">
|
<div className="ppcp-r-select-box__content">
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
import PaymentMethodModal from '../../../ReusableComponents/PaymentMethodModal';
|
import PaymentMethodModal from '../../../ReusableComponents/PaymentMethodModal';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import {
|
|
||||||
PayPalRdb,
|
|
||||||
PayPalRdbWithContent,
|
|
||||||
} from '../../../ReusableComponents/Fields';
|
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
|
import { RadioControl } from '@wordpress/components';
|
||||||
|
|
||||||
const THREED_SECURE_GROUP_NAME = 'threed-secure';
|
|
||||||
const ModalAcdc = ( { setModalIsVisible } ) => {
|
const ModalAcdc = ( { setModalIsVisible } ) => {
|
||||||
const [ threeDSecure, setThreeDSecure ] = useState( 'no-3d-secure' );
|
const [ threeDSecure, setThreeDSecure ] = useState( 'no-3d-secure' );
|
||||||
|
const acdcOptions = [
|
||||||
|
{
|
||||||
|
label: __( 'No 3D Secure', 'woocommerce-paypal-payments' ),
|
||||||
|
value: 'no-3d-secure',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __( 'Only when required', 'woocommerce-paypal-payments' ),
|
||||||
|
value: 'only-required-3d-secure',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __(
|
||||||
|
'Always require 3D Secure',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
value: 'always-3d-secure',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaymentMethodModal
|
<PaymentMethodModal
|
||||||
|
@ -30,39 +43,10 @@ const ModalAcdc = ( { setModalIsVisible } ) => {
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<div className="ppcp-r-modal__field-rows ppcp-r-modal__field-rows--acdc">
|
<div className="ppcp-r-modal__field-rows ppcp-r-modal__field-rows--acdc">
|
||||||
<PayPalRdbWithContent
|
<RadioControl
|
||||||
id="no-3d-secure"
|
onChange={ setThreeDSecure }
|
||||||
name={ THREED_SECURE_GROUP_NAME }
|
selected={ threeDSecure }
|
||||||
value="no-3d-secure"
|
options={ acdcOptions }
|
||||||
currentValue={ threeDSecure }
|
|
||||||
handleRdbState={ setThreeDSecure }
|
|
||||||
label={ __(
|
|
||||||
'No 3D Secure',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
/>
|
|
||||||
<PayPalRdbWithContent
|
|
||||||
id="only-required-3d-secure"
|
|
||||||
name={ THREED_SECURE_GROUP_NAME }
|
|
||||||
value="only-required-3d-secure"
|
|
||||||
currentValue={ threeDSecure }
|
|
||||||
handleRdbState={ setThreeDSecure }
|
|
||||||
label={ __(
|
|
||||||
'Only when required',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PayPalRdbWithContent
|
|
||||||
id="always-3d-secure"
|
|
||||||
name={ THREED_SECURE_GROUP_NAME }
|
|
||||||
value="always-3d-secure"
|
|
||||||
currentValue={ threeDSecure }
|
|
||||||
handleRdbState={ setThreeDSecure }
|
|
||||||
label={ __(
|
|
||||||
'Always require 3D Secure',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="ppcp-r-modal__field-row ppcp-r-modal__field-row--save">
|
<div className="ppcp-r-modal__field-row ppcp-r-modal__field-row--save">
|
||||||
|
|
|
@ -2,7 +2,6 @@ import SettingsCard from '../../ReusableComponents/SettingsCard';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import {
|
import {
|
||||||
PayPalCheckbox,
|
PayPalCheckbox,
|
||||||
handleCheckboxState,
|
|
||||||
} from '../../ReusableComponents/Fields';
|
} from '../../ReusableComponents/Fields';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import data from '../../../utils/data';
|
import data from '../../../utils/data';
|
||||||
|
@ -41,7 +40,7 @@ const TabOverview = () => {
|
||||||
value={ todo.value }
|
value={ todo.value }
|
||||||
currentValue={ todos }
|
currentValue={ todos }
|
||||||
changeCallback={ setTodos }
|
changeCallback={ setTodos }
|
||||||
description={ todo.description }
|
label={ todo.description }
|
||||||
changeTodos={ setTodosData }
|
changeTodos={ setTodosData }
|
||||||
todosData={ todosData }
|
todosData={ todosData }
|
||||||
/>
|
/>
|
||||||
|
@ -144,10 +143,8 @@ const TodoItem = ( props ) => {
|
||||||
<PayPalCheckbox
|
<PayPalCheckbox
|
||||||
{ ...{
|
{ ...{
|
||||||
...props,
|
...props,
|
||||||
handleCheckboxState,
|
|
||||||
} }
|
} }
|
||||||
/>{ ' ' }
|
/>{ ' ' }
|
||||||
<p>{ props.description }</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="ppcp-r-todo-item__close"
|
className="ppcp-r-todo-item__close"
|
||||||
|
|
|
@ -24,7 +24,6 @@ const TabSettings = () => {
|
||||||
buttonLanguage: '',
|
buttonLanguage: '',
|
||||||
} );
|
} );
|
||||||
const updateFormValue = ( key, value ) => {
|
const updateFormValue = ( key, value ) => {
|
||||||
console.log( key, value );
|
|
||||||
setSettings( { ...settings, [ key ]: value } );
|
setSettings( { ...settings, [ key ]: value } );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,256 @@
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import { SelectControl, RadioControl } from '@wordpress/components';
|
||||||
|
import { PayPalCheckboxGroup } from '../../ReusableComponents/Fields';
|
||||||
|
import { useState, useMemo, useEffect } from '@wordpress/element';
|
||||||
|
import {
|
||||||
|
defaultLocationSettings,
|
||||||
|
paymentMethodOptions,
|
||||||
|
colorOptions,
|
||||||
|
shapeOptions,
|
||||||
|
buttonLayoutOptions,
|
||||||
|
buttonLabelOptions,
|
||||||
|
} from '../../../data/settings/tab-styling-data';
|
||||||
|
import Renderer from '../../../utils/renderer';
|
||||||
|
|
||||||
const TabStyling = () => {
|
const TabStyling = () => {
|
||||||
return <div>Styling tab</div>;
|
useEffect( () => {
|
||||||
|
generatePreview();
|
||||||
|
}, [] );
|
||||||
|
|
||||||
|
const [ location, setLocation ] = useState( 'cart' );
|
||||||
|
const [ locationSettings, setLocationSettings ] = useState( {
|
||||||
|
...defaultLocationSettings,
|
||||||
|
} );
|
||||||
|
|
||||||
|
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 ) => {
|
||||||
|
const updatedLocationSettings = { ...currentLocationSettings };
|
||||||
|
|
||||||
|
updatedLocationSettings.settings = {
|
||||||
|
...updatedLocationSettings.settings,
|
||||||
|
...{ [ key ]: value },
|
||||||
|
};
|
||||||
|
|
||||||
|
setLocationSettings( {
|
||||||
|
...locationSettings,
|
||||||
|
[ location ]: { ...updatedLocationSettings },
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ppcp-r-styling">
|
||||||
|
<div className="ppcp-r-styling__settings">
|
||||||
|
<SectionIntro />
|
||||||
|
<TabStylingSection className="ppcp-r-styling__section--rc">
|
||||||
|
<SelectControl
|
||||||
|
className="ppcp-r-styling__select"
|
||||||
|
value={ location }
|
||||||
|
onChange={ ( newLocation ) =>
|
||||||
|
setLocation( newLocation )
|
||||||
|
}
|
||||||
|
label={ __(
|
||||||
|
'Locations',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
options={ locationOptions }
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
<TabStylingSection
|
||||||
|
title={ __(
|
||||||
|
'Payment Methods',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
className="ppcp-r-styling__section--rc"
|
||||||
|
>
|
||||||
|
<div className="ppcp-r-styling__payment-method-checkboxes">
|
||||||
|
<PayPalCheckboxGroup
|
||||||
|
value={ paymentMethodOptions }
|
||||||
|
changeCallback={ ( newValue ) =>
|
||||||
|
updateButtonSettings(
|
||||||
|
'paymentMethods',
|
||||||
|
newValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
currentValue={
|
||||||
|
currentLocationSettings.settings.paymentMethods
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabStylingSection>
|
||||||
|
{ currentLocationSettings.settings?.buttonLayout && (
|
||||||
|
<TabStylingSection
|
||||||
|
className="ppcp-r-styling__section--rc"
|
||||||
|
title={ __(
|
||||||
|
'Button Layout',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
<RadioControl
|
||||||
|
className="ppcp-r__horizontal-control"
|
||||||
|
onChange={ ( newValue ) =>
|
||||||
|
updateButtonSettings( 'buttonLayout', newValue )
|
||||||
|
}
|
||||||
|
selected={
|
||||||
|
currentLocationSettings.settings.buttonLayout
|
||||||
|
}
|
||||||
|
options={ buttonLayoutOptions }
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
) }
|
||||||
|
<TabStylingSection
|
||||||
|
title={ __( 'Shape', 'woocommerce-paypal-payments' ) }
|
||||||
|
className="ppcp-r-styling__section--rc"
|
||||||
|
>
|
||||||
|
<RadioControl
|
||||||
|
className="ppcp-r__horizontal-control"
|
||||||
|
onChange={ ( newValue ) =>
|
||||||
|
updateButtonSettings( 'shape', newValue )
|
||||||
|
}
|
||||||
|
selected={ currentLocationSettings.settings.shape }
|
||||||
|
options={ shapeOptions }
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
<TabStylingSection>
|
||||||
|
<SelectControl
|
||||||
|
className="ppcp-r-styling__select"
|
||||||
|
onChange={ ( newValue ) =>
|
||||||
|
updateButtonSettings( 'buttonLabel', newValue )
|
||||||
|
}
|
||||||
|
selected={
|
||||||
|
currentLocationSettings.settings.buttonLabel
|
||||||
|
}
|
||||||
|
label={ __(
|
||||||
|
'Button Label',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
options={ buttonLabelOptions }
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
<TabStylingSection>
|
||||||
|
<SelectControl
|
||||||
|
className="ppcp-r-styling__select"
|
||||||
|
label={ __(
|
||||||
|
'Button Color',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
options={ colorOptions }
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
{ currentLocationSettings.settings?.tagLine && (
|
||||||
|
<TabStylingSection
|
||||||
|
title={ __( 'Tagline', 'woocommerce-paypal-payments' ) }
|
||||||
|
className="ppcp-r-styling__section--rc"
|
||||||
|
>
|
||||||
|
<PayPalCheckboxGroup
|
||||||
|
value={ [
|
||||||
|
{
|
||||||
|
value: 'enable-tagline',
|
||||||
|
label: __(
|
||||||
|
'Enable Tagline',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] }
|
||||||
|
changeCallback={ ( newValue ) =>
|
||||||
|
updateButtonSettings( 'tagLine', newValue )
|
||||||
|
}
|
||||||
|
currentValue={
|
||||||
|
currentLocationSettings.settings.tagLine
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabStylingSection>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="ppcp-r-styling-preview"
|
||||||
|
className="ppcp-r-styling__preview"
|
||||||
|
>
|
||||||
|
<div className="ppcp-preview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TabStylingSection = ( props ) => {
|
||||||
|
let sectionTitleClassName = 'ppcp-r-styling__section';
|
||||||
|
|
||||||
|
if ( props?.className ) {
|
||||||
|
sectionTitleClassName += ` ${ props.className }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ sectionTitleClassName }>
|
||||||
|
<span className="ppcp-r-styling__title">{ props.title }</span>
|
||||||
|
{ props?.description && (
|
||||||
|
<p
|
||||||
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: props.description,
|
||||||
|
} }
|
||||||
|
className="ppcp-r-styling__description"
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
{ props.children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SectionIntro = () => {
|
||||||
|
const buttonStyleDescription = sprintf(
|
||||||
|
// translators: %s: Link to Classic checkout page
|
||||||
|
__(
|
||||||
|
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Classic Checkout page</a>. Checkout Buttons must be enabled to display the PayPal gateway on the Checkout page.'
|
||||||
|
),
|
||||||
|
'#'
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<TabStylingSection
|
||||||
|
className="ppcp-r-styling__section--rc ppcp-r-styling__section--empty"
|
||||||
|
title={ __( 'Button Styling', 'wooocommerce-paypal-payments' ) }
|
||||||
|
description={ buttonStyleDescription }
|
||||||
|
></TabStylingSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatePreview = () => {
|
||||||
|
const settings = {
|
||||||
|
button: {
|
||||||
|
wrapper: '#ppcp-r-styling-preview',
|
||||||
|
style: {
|
||||||
|
color: 'gold',
|
||||||
|
shape: 'rect',
|
||||||
|
label: 'paypal',
|
||||||
|
tagline: false,
|
||||||
|
layout: 'horizontal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
separate_buttons: {},
|
||||||
|
};
|
||||||
|
const renderer = new Renderer(
|
||||||
|
null,
|
||||||
|
settings,
|
||||||
|
( data, actions ) => actions.reject(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
jQuery( document ).trigger( 'ppcp_paypal_render_preview', settings );
|
||||||
|
|
||||||
|
renderer.render( {} );
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TabStyling;
|
export default TabStyling;
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
paymentMethods: [],
|
||||||
|
buttonLayout: 'horizontal',
|
||||||
|
shape: null,
|
||||||
|
buttonLabel: 'paypal',
|
||||||
|
buttonColor: 'gold',
|
||||||
|
tagLine: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const cartAndExpressCheckoutSettings = {
|
||||||
|
paymentMethods: [],
|
||||||
|
shape: null,
|
||||||
|
buttonLabel: 'paypal',
|
||||||
|
buttonColor: 'gold',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultLocationSettings = {
|
||||||
|
cart: {
|
||||||
|
value: 'cart',
|
||||||
|
label: __( 'Cart', 'woocommerce-paypal-payments' ),
|
||||||
|
settings: { ...cartAndExpressCheckoutSettings },
|
||||||
|
},
|
||||||
|
'classic-checkout': {
|
||||||
|
value: 'classic-checkout',
|
||||||
|
label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ),
|
||||||
|
settings: { ...settings },
|
||||||
|
},
|
||||||
|
'express-checkout': {
|
||||||
|
value: 'express-checkout',
|
||||||
|
label: __( 'Express Checkout', 'woocommerce-paypal-payments' ),
|
||||||
|
settings: { ...cartAndExpressCheckoutSettings },
|
||||||
|
},
|
||||||
|
'mini-cart': {
|
||||||
|
value: 'mini-cart',
|
||||||
|
label: __( 'Mini Cart', 'woocommerce-paypel-payements' ),
|
||||||
|
settings: { ...settings },
|
||||||
|
},
|
||||||
|
'product-page': {
|
||||||
|
value: 'product-page',
|
||||||
|
label: __( 'Product Page', 'woocommerce-paypal-payments' ),
|
||||||
|
settings: { ...settings },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const paymentMethodOptions = [
|
||||||
|
{
|
||||||
|
value: 'venmo',
|
||||||
|
label: __( 'Venmo', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'pay-later',
|
||||||
|
label: __( 'Pay Later', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'debit-or-credit-card',
|
||||||
|
label: __( 'Debit or Credit Card', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'google-pay',
|
||||||
|
label: __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'apple-pay',
|
||||||
|
label: __( 'Apple Pay', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const buttonLabelOptions = [
|
||||||
|
{
|
||||||
|
value: 'paypal',
|
||||||
|
label: __( 'PayPal', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'checkout',
|
||||||
|
label: __( 'Checkout', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'paypal-buy-now',
|
||||||
|
label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'pay-with-paypal',
|
||||||
|
label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const colorOptions = [
|
||||||
|
{
|
||||||
|
value: 'gold',
|
||||||
|
label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'blue',
|
||||||
|
label: __( 'Blue', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'silver',
|
||||||
|
label: __( 'Silver', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'black',
|
||||||
|
label: __( 'Black', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'white',
|
||||||
|
label: __( 'White', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const buttonLayoutOptions = [
|
||||||
|
{
|
||||||
|
label: __( 'Vertical', 'woocommerce-paypal-payments' ),
|
||||||
|
value: 'vertical',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __( 'Horizontal', 'woocommerce-paypal-payments' ),
|
||||||
|
value: 'horizontal',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const shapeOptions = [
|
||||||
|
{
|
||||||
|
value: 'pill',
|
||||||
|
label: __( 'Pill', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'rectangle',
|
||||||
|
label: __( 'Rectangle', 'woocommerce-paypal-payments' ),
|
||||||
|
},
|
||||||
|
];
|
129
modules/ppcp-settings/resources/js/utils/Helper/ApmButtons.js
Normal file
129
modules/ppcp-settings/resources/js/utils/Helper/ApmButtons.js
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
export const apmButtonsInit = ( config, selector = '.ppcp-button-apm' ) => {
|
||||||
|
let selectorInContainer = selector;
|
||||||
|
|
||||||
|
if ( window.ppcpApmButtons ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( config && config.button ) {
|
||||||
|
// If it's separate gateways, modify wrapper to account for the individual buttons as individual APMs.
|
||||||
|
const wrapper = config.button.wrapper;
|
||||||
|
const isSeparateGateways =
|
||||||
|
jQuery( wrapper ).children( 'div[class^="item-"]' ).length > 0;
|
||||||
|
|
||||||
|
if ( isSeparateGateways ) {
|
||||||
|
selector += `, ${ wrapper } div[class^="item-"]`;
|
||||||
|
selectorInContainer += `, div[class^="item-"]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.ppcpApmButtons = new ApmButtons( selector, selectorInContainer );
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ApmButtons {
|
||||||
|
constructor( selector, selectorInContainer ) {
|
||||||
|
this.selector = selector;
|
||||||
|
this.selectorInContainer = selectorInContainer;
|
||||||
|
this.containers = [];
|
||||||
|
|
||||||
|
// Reloads button containers.
|
||||||
|
this.reloadContainers();
|
||||||
|
|
||||||
|
// Refresh button layout.
|
||||||
|
jQuery( window )
|
||||||
|
.resize( () => {
|
||||||
|
this.refresh();
|
||||||
|
} )
|
||||||
|
.resize();
|
||||||
|
|
||||||
|
jQuery( document ).on( 'ppcp-smart-buttons-init', () => {
|
||||||
|
this.refresh();
|
||||||
|
} );
|
||||||
|
|
||||||
|
jQuery( document ).on(
|
||||||
|
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
|
||||||
|
( ev, data ) => {
|
||||||
|
this.refresh();
|
||||||
|
setTimeout( this.refresh.bind( this ), 200 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Observes for new buttons.
|
||||||
|
new MutationObserver(
|
||||||
|
this.observeElementsCallback.bind( this )
|
||||||
|
).observe( document.body, { childList: true, subtree: true } );
|
||||||
|
}
|
||||||
|
|
||||||
|
observeElementsCallback( mutationsList, observer ) {
|
||||||
|
const observeSelector =
|
||||||
|
this.selector +
|
||||||
|
', .widget_shopping_cart, .widget_shopping_cart_content';
|
||||||
|
|
||||||
|
let shouldReload = false;
|
||||||
|
for ( const mutation of mutationsList ) {
|
||||||
|
if ( mutation.type === 'childList' ) {
|
||||||
|
mutation.addedNodes.forEach( ( node ) => {
|
||||||
|
if ( node.matches && node.matches( observeSelector ) ) {
|
||||||
|
shouldReload = true;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( shouldReload ) {
|
||||||
|
this.reloadContainers();
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadContainers() {
|
||||||
|
jQuery( this.selector ).each( ( index, el ) => {
|
||||||
|
const parent = jQuery( el ).parent();
|
||||||
|
if ( ! this.containers.some( ( $el ) => $el.is( parent ) ) ) {
|
||||||
|
this.containers.push( parent );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
for ( const container of this.containers ) {
|
||||||
|
const $container = jQuery( container );
|
||||||
|
|
||||||
|
// Check width and add classes
|
||||||
|
const width = $container.width();
|
||||||
|
|
||||||
|
$container.removeClass(
|
||||||
|
'ppcp-width-500 ppcp-width-300 ppcp-width-min'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( width >= 500 ) {
|
||||||
|
$container.addClass( 'ppcp-width-500' );
|
||||||
|
} else if ( width >= 300 ) {
|
||||||
|
$container.addClass( 'ppcp-width-300' );
|
||||||
|
} else {
|
||||||
|
$container.addClass( 'ppcp-width-min' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check first apm button
|
||||||
|
const $firstElement = $container.children( ':visible' ).first();
|
||||||
|
|
||||||
|
// Assign margins to buttons
|
||||||
|
$container.find( this.selectorInContainer ).each( ( index, el ) => {
|
||||||
|
const $el = jQuery( el );
|
||||||
|
|
||||||
|
if ( $el.is( $firstElement ) ) {
|
||||||
|
$el.css( 'margin-top', `0px` );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minMargin = 11; // Minimum margin.
|
||||||
|
const height = $el.height();
|
||||||
|
const margin = Math.max(
|
||||||
|
minMargin,
|
||||||
|
Math.round( height * 0.3 )
|
||||||
|
);
|
||||||
|
$el.css( 'margin-top', `${ margin }px` );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { disable, enable, isDisabled } from './ButtonDisabler';
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common Bootstrap methods to avoid code repetition.
|
||||||
|
*/
|
||||||
|
export default class BootstrapHelper {
|
||||||
|
static handleButtonStatus( bs, options ) {
|
||||||
|
options = options || {};
|
||||||
|
options.wrapper = options.wrapper || bs.gateway.button.wrapper;
|
||||||
|
|
||||||
|
const wasDisabled = isDisabled( options.wrapper );
|
||||||
|
const shouldEnable = bs.shouldEnable();
|
||||||
|
|
||||||
|
// Handle enable / disable
|
||||||
|
if ( shouldEnable && wasDisabled ) {
|
||||||
|
bs.renderer.enableSmartButtons( options.wrapper );
|
||||||
|
enable( options.wrapper );
|
||||||
|
} else if ( ! shouldEnable && ! wasDisabled ) {
|
||||||
|
bs.renderer.disableSmartButtons( options.wrapper );
|
||||||
|
disable( options.wrapper, options.formSelector || null );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( wasDisabled !== ! shouldEnable ) {
|
||||||
|
jQuery( options.wrapper ).trigger( 'ppcp_buttons_enabled_changed', [
|
||||||
|
shouldEnable,
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static shouldEnable( bs, options ) {
|
||||||
|
options = options || {};
|
||||||
|
if ( typeof options.isDisabled === 'undefined' ) {
|
||||||
|
options.isDisabled = bs.gateway.button.is_disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bs.shouldRender() && options.isDisabled !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateScriptData( bs, newData ) {
|
||||||
|
const newObj = merge( bs.gateway, newData );
|
||||||
|
|
||||||
|
const isChanged =
|
||||||
|
JSON.stringify( bs.gateway ) !== JSON.stringify( newObj );
|
||||||
|
|
||||||
|
bs.gateway = newObj;
|
||||||
|
|
||||||
|
if ( isChanged ) {
|
||||||
|
jQuery( document.body ).trigger( 'ppcp_script_data_changed', [
|
||||||
|
newObj,
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* @param selectorOrElement
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
const getElement = ( selectorOrElement ) => {
|
||||||
|
if ( typeof selectorOrElement === 'string' ) {
|
||||||
|
return document.querySelector( selectorOrElement );
|
||||||
|
}
|
||||||
|
return selectorOrElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerEnabled = ( selectorOrElement, element ) => {
|
||||||
|
jQuery( document ).trigger( 'ppcp-enabled', {
|
||||||
|
handler: 'ButtonsDisabler.setEnabled',
|
||||||
|
action: 'enable',
|
||||||
|
selector: selectorOrElement,
|
||||||
|
element,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerDisabled = ( selectorOrElement, element ) => {
|
||||||
|
jQuery( document ).trigger( 'ppcp-disabled', {
|
||||||
|
handler: 'ButtonsDisabler.setEnabled',
|
||||||
|
action: 'disable',
|
||||||
|
selector: selectorOrElement,
|
||||||
|
element,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setEnabled = ( selectorOrElement, enable, form = null ) => {
|
||||||
|
const element = getElement( selectorOrElement );
|
||||||
|
|
||||||
|
if ( ! element ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( enable ) {
|
||||||
|
jQuery( element )
|
||||||
|
.removeClass( 'ppcp-disabled' )
|
||||||
|
.off( 'mouseup' )
|
||||||
|
.find( '> *' )
|
||||||
|
.css( 'pointer-events', '' );
|
||||||
|
|
||||||
|
triggerEnabled( selectorOrElement, element );
|
||||||
|
} else {
|
||||||
|
jQuery( element )
|
||||||
|
.addClass( 'ppcp-disabled' )
|
||||||
|
.on( 'mouseup', function ( event ) {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
|
if ( form ) {
|
||||||
|
// Trigger form submit to show the error message
|
||||||
|
const $form = jQuery( form );
|
||||||
|
if (
|
||||||
|
$form
|
||||||
|
.find( '.single_add_to_cart_button' )
|
||||||
|
.hasClass( 'disabled' )
|
||||||
|
) {
|
||||||
|
$form.find( ':submit' ).trigger( 'click' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
.find( '> *' )
|
||||||
|
.css( 'pointer-events', 'none' );
|
||||||
|
|
||||||
|
triggerDisabled( selectorOrElement, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isDisabled = ( selectorOrElement ) => {
|
||||||
|
const element = getElement( selectorOrElement );
|
||||||
|
|
||||||
|
if ( ! element ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jQuery( element ).hasClass( 'ppcp-disabled' );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const disable = ( selectorOrElement, form = null ) => {
|
||||||
|
setEnabled( selectorOrElement, false, form );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const enable = ( selectorOrElement ) => {
|
||||||
|
setEnabled( selectorOrElement, true );
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
|
||||||
|
|
||||||
|
const REFRESH_BUTTON_EVENT = 'ppcp_refresh_payment_buttons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a refresh of the payment buttons.
|
||||||
|
* This function dispatches a custom event that the button components listen for.
|
||||||
|
*
|
||||||
|
* Use this function on the front-end to update payment buttons after the checkout form was updated.
|
||||||
|
*/
|
||||||
|
export function refreshButtons() {
|
||||||
|
document.dispatchEvent( new Event( REFRESH_BUTTON_EVENT ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up event listeners for various cart and checkout update events.
|
||||||
|
* When these events occur, it triggers a refresh of the payment buttons.
|
||||||
|
*
|
||||||
|
* @param {Function} refresh - Callback responsible to re-render the payment button.
|
||||||
|
*/
|
||||||
|
export function setupButtonEvents( refresh ) {
|
||||||
|
const miniCartInitDelay = 1000;
|
||||||
|
const debouncedRefresh = debounce( refresh, 50 );
|
||||||
|
|
||||||
|
// Listen for our custom refresh event.
|
||||||
|
document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh );
|
||||||
|
|
||||||
|
// Listen for cart and checkout update events.
|
||||||
|
// Note: we need jQuery here, because WooCommerce uses jQuery.trigger() to dispatch the events.
|
||||||
|
window
|
||||||
|
.jQuery( 'body' )
|
||||||
|
.on( 'updated_cart_totals', debouncedRefresh )
|
||||||
|
.on( 'updated_checkout', debouncedRefresh );
|
||||||
|
|
||||||
|
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
|
||||||
|
setTimeout( () => {
|
||||||
|
document.body.addEventListener(
|
||||||
|
'wc_fragments_loaded',
|
||||||
|
debouncedRefresh
|
||||||
|
);
|
||||||
|
document.body.addEventListener(
|
||||||
|
'wc_fragments_refreshed',
|
||||||
|
debouncedRefresh
|
||||||
|
);
|
||||||
|
}, miniCartInitDelay );
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
class CartHelper {
|
||||||
|
constructor( cartItemKeys = [] ) {
|
||||||
|
this.cartItemKeys = cartItemKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndpoint() {
|
||||||
|
let ajaxUrl = '/?wc-ajax=%%endpoint%%';
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof wc_cart_fragments_params !== 'undefined' &&
|
||||||
|
wc_cart_fragments_params.wc_ajax_url
|
||||||
|
) {
|
||||||
|
ajaxUrl = wc_cart_fragments_params.wc_ajax_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ajaxUrl.toString().replace( '%%endpoint%%', 'remove_from_cart' );
|
||||||
|
}
|
||||||
|
|
||||||
|
addFromPurchaseUnits( purchaseUnits ) {
|
||||||
|
for ( const purchaseUnit of purchaseUnits || [] ) {
|
||||||
|
for ( const item of purchaseUnit.items || [] ) {
|
||||||
|
if ( ! item.cart_item_key ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.cartItemKeys.push( item.cart_item_key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromCart() {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
if ( ! this.cartItemKeys || ! this.cartItemKeys.length ) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numRequests = this.cartItemKeys.length;
|
||||||
|
let numResponses = 0;
|
||||||
|
|
||||||
|
const tryToResolve = () => {
|
||||||
|
numResponses++;
|
||||||
|
if ( numResponses >= numRequests ) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for ( const cartItemKey of this.cartItemKeys ) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append( 'cart_item_key', cartItemKey );
|
||||||
|
|
||||||
|
if ( ! cartItemKey ) {
|
||||||
|
tryToResolve();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch( this.getEndpoint(), {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: params,
|
||||||
|
} )
|
||||||
|
.then( function ( res ) {
|
||||||
|
return res.json();
|
||||||
|
} )
|
||||||
|
.then( () => {
|
||||||
|
tryToResolve();
|
||||||
|
} )
|
||||||
|
.catch( () => {
|
||||||
|
tryToResolve();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CartHelper;
|
|
@ -0,0 +1,55 @@
|
||||||
|
import Spinner from './Spinner';
|
||||||
|
import FormValidator from './FormValidator';
|
||||||
|
import ErrorHandler from '../ErrorHandler';
|
||||||
|
|
||||||
|
const validateCheckoutForm = function ( config ) {
|
||||||
|
return new Promise( async ( resolve, reject ) => {
|
||||||
|
try {
|
||||||
|
const spinner = new Spinner();
|
||||||
|
const errorHandler = new ErrorHandler(
|
||||||
|
config.labels.error.generic,
|
||||||
|
document.querySelector( '.woocommerce-notices-wrapper' )
|
||||||
|
);
|
||||||
|
|
||||||
|
const formSelector =
|
||||||
|
config.context === 'checkout'
|
||||||
|
? 'form.checkout'
|
||||||
|
: 'form#order_review';
|
||||||
|
const formValidator = config.early_checkout_validation_enabled
|
||||||
|
? new FormValidator(
|
||||||
|
config.ajax.validate_checkout.endpoint,
|
||||||
|
config.ajax.validate_checkout.nonce
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if ( ! formValidator ) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
formValidator
|
||||||
|
.validate( document.querySelector( formSelector ) )
|
||||||
|
.then( ( errors ) => {
|
||||||
|
if ( errors.length > 0 ) {
|
||||||
|
spinner.unblock();
|
||||||
|
errorHandler.clear();
|
||||||
|
errorHandler.messages( errors );
|
||||||
|
|
||||||
|
// fire WC event for other plugins
|
||||||
|
jQuery( document.body ).trigger( 'checkout_error', [
|
||||||
|
errorHandler.currentHtml(),
|
||||||
|
] );
|
||||||
|
|
||||||
|
reject();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} catch ( error ) {
|
||||||
|
console.error( error );
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export default validateCheckoutForm;
|
|
@ -0,0 +1,48 @@
|
||||||
|
export const PaymentMethods = {
|
||||||
|
PAYPAL: 'ppcp-gateway',
|
||||||
|
CARDS: 'ppcp-credit-card-gateway',
|
||||||
|
OXXO: 'ppcp-oxxo-gateway',
|
||||||
|
CARD_BUTTON: 'ppcp-card-button-gateway',
|
||||||
|
GOOGLEPAY: 'ppcp-googlepay',
|
||||||
|
APPLEPAY: 'ppcp-applepay',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of valid context values that the button can have.
|
||||||
|
*
|
||||||
|
* The "context" describes the placement or page where a payment button might be displayed.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const PaymentContext = {
|
||||||
|
Cart: 'cart', // Classic cart.
|
||||||
|
Checkout: 'checkout', // Classic checkout.
|
||||||
|
BlockCart: 'cart-block', // Block cart.
|
||||||
|
BlockCheckout: 'checkout-block', // Block checkout.
|
||||||
|
Product: 'product', // Single product page.
|
||||||
|
MiniCart: 'mini-cart', // Mini cart available on all pages except checkout & cart.
|
||||||
|
PayNow: 'pay-now', // Pay for order, via admin generated link.
|
||||||
|
Preview: 'preview', // Layout preview on settings page.
|
||||||
|
|
||||||
|
// Contexts that use blocks to render payment methods.
|
||||||
|
Blocks: [ 'cart-block', 'checkout-block' ],
|
||||||
|
|
||||||
|
// Contexts that display "classic" payment gateways.
|
||||||
|
Gateways: [ 'checkout', 'pay-now' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ORDER_BUTTON_SELECTOR = '#place_order';
|
||||||
|
|
||||||
|
export const getCurrentPaymentMethod = () => {
|
||||||
|
const el = document.querySelector( 'input[name="payment_method"]:checked' );
|
||||||
|
if ( ! el ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return el.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSavedCardSelected = () => {
|
||||||
|
const savedCardList = document.querySelector( '#saved-credit-card' );
|
||||||
|
return savedCardList && savedCardList.value !== '';
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { keysToCamelCase } from './Utils';
|
||||||
|
|
||||||
|
const processAxoConfig = ( config ) => {
|
||||||
|
const scriptOptions = {};
|
||||||
|
const sdkClientToken = config?.axo?.sdk_client_token;
|
||||||
|
const uuid = uuidv4().replace( /-/g, '' );
|
||||||
|
if ( sdkClientToken && config?.user?.is_logged !== true ) {
|
||||||
|
scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken;
|
||||||
|
scriptOptions[ 'data-client-metadata-id' ] = uuid;
|
||||||
|
}
|
||||||
|
return scriptOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const processUserIdToken = ( config ) => {
|
||||||
|
const userIdToken = config?.save_payment_methods?.id_token;
|
||||||
|
return userIdToken && config?.user?.is_logged === true
|
||||||
|
? { 'data-user-id-token': userIdToken }
|
||||||
|
: {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const processConfig = ( config ) => {
|
||||||
|
let scriptOptions = keysToCamelCase( config.url_params );
|
||||||
|
if ( config.script_attributes ) {
|
||||||
|
scriptOptions = merge( scriptOptions, config.script_attributes );
|
||||||
|
}
|
||||||
|
const axoOptions = processAxoConfig( config );
|
||||||
|
const userIdTokenOptions = processUserIdToken( config );
|
||||||
|
return merge.all( [ scriptOptions, axoOptions, userIdTokenOptions ] );
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
const dccInputFactory = ( original ) => {
|
||||||
|
const styles = window.getComputedStyle( original );
|
||||||
|
const newElement = document.createElement( 'span' );
|
||||||
|
|
||||||
|
newElement.setAttribute( 'id', original.id );
|
||||||
|
newElement.setAttribute( 'class', original.className );
|
||||||
|
|
||||||
|
Object.values( styles ).forEach( ( prop ) => {
|
||||||
|
if (
|
||||||
|
! styles[ prop ] ||
|
||||||
|
! isNaN( prop ) ||
|
||||||
|
prop === 'background-image'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newElement.style.setProperty( prop, '' + styles[ prop ] );
|
||||||
|
} );
|
||||||
|
return newElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dccInputFactory;
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Common Form utility methods
|
||||||
|
*/
|
||||||
|
export default class FormHelper {
|
||||||
|
static getPrefixedFields( formElement, prefix ) {
|
||||||
|
const formData = new FormData( formElement );
|
||||||
|
const fields = {};
|
||||||
|
|
||||||
|
for ( const [ name, value ] of formData.entries() ) {
|
||||||
|
if ( ! prefix || name.startsWith( prefix ) ) {
|
||||||
|
fields[ name ] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getFilteredFields( formElement, exactFilters, prefixFilters ) {
|
||||||
|
const formData = new FormData( formElement );
|
||||||
|
const fields = {};
|
||||||
|
const counters = {};
|
||||||
|
|
||||||
|
for ( let [ name, value ] of formData.entries() ) {
|
||||||
|
// Handle array format
|
||||||
|
if ( name.indexOf( '[]' ) !== -1 ) {
|
||||||
|
const k = name;
|
||||||
|
counters[ k ] = counters[ k ] || 0;
|
||||||
|
name = name.replace( '[]', `[${ counters[ k ] }]` );
|
||||||
|
counters[ k ]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! name ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( exactFilters && exactFilters.indexOf( name ) !== -1 ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
prefixFilters &&
|
||||||
|
prefixFilters.some( ( prefixFilter ) =>
|
||||||
|
name.startsWith( prefixFilter )
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[ name ] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
28
modules/ppcp-settings/resources/js/utils/Helper/FormSaver.js
Normal file
28
modules/ppcp-settings/resources/js/utils/Helper/FormSaver.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export default class FormSaver {
|
||||||
|
constructor( url, nonce ) {
|
||||||
|
this.url = url;
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
async save( form ) {
|
||||||
|
const formData = new FormData( form );
|
||||||
|
|
||||||
|
const res = await fetch( this.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: this.nonce,
|
||||||
|
form_encoded: new URLSearchParams( formData ).toString(),
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if ( ! data.success ) {
|
||||||
|
throw Error( data.data.message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
export default class FormValidator {
|
||||||
|
constructor( url, nonce ) {
|
||||||
|
this.url = url;
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate( form ) {
|
||||||
|
const formData = new FormData( form );
|
||||||
|
|
||||||
|
const res = await fetch( this.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: this.nonce,
|
||||||
|
form_encoded: new URLSearchParams( formData ).toString(),
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if ( ! data.success ) {
|
||||||
|
if ( data.data.refresh ) {
|
||||||
|
jQuery( document.body ).trigger( 'update_checkout' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( data.data.errors ) {
|
||||||
|
return data.data.errors;
|
||||||
|
}
|
||||||
|
throw Error( data.data.message );
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
92
modules/ppcp-settings/resources/js/utils/Helper/Hiding.js
Normal file
92
modules/ppcp-settings/resources/js/utils/Helper/Hiding.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* @param selectorOrElement
|
||||||
|
* @return {Element}
|
||||||
|
*/
|
||||||
|
const getElement = ( selectorOrElement ) => {
|
||||||
|
if ( typeof selectorOrElement === 'string' ) {
|
||||||
|
return document.querySelector( selectorOrElement );
|
||||||
|
}
|
||||||
|
return selectorOrElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerHidden = ( handler, selectorOrElement, element ) => {
|
||||||
|
jQuery( document ).trigger( 'ppcp-hidden', {
|
||||||
|
handler,
|
||||||
|
action: 'hide',
|
||||||
|
selector: selectorOrElement,
|
||||||
|
element,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerShown = ( handler, selectorOrElement, element ) => {
|
||||||
|
jQuery( document ).trigger( 'ppcp-shown', {
|
||||||
|
handler,
|
||||||
|
action: 'show',
|
||||||
|
selector: selectorOrElement,
|
||||||
|
element,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isVisible = ( element ) => {
|
||||||
|
return !! (
|
||||||
|
element.offsetWidth ||
|
||||||
|
element.offsetHeight ||
|
||||||
|
element.getClientRects().length
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setVisible = ( selectorOrElement, show, important = false ) => {
|
||||||
|
const element = getElement( selectorOrElement );
|
||||||
|
if ( ! element ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentValue = element.style.getPropertyValue( 'display' );
|
||||||
|
|
||||||
|
if ( ! show ) {
|
||||||
|
if ( currentValue === 'none' ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.style.setProperty(
|
||||||
|
'display',
|
||||||
|
'none',
|
||||||
|
important ? 'important' : ''
|
||||||
|
);
|
||||||
|
triggerHidden( 'Hiding.setVisible', selectorOrElement, element );
|
||||||
|
} else {
|
||||||
|
if ( currentValue === 'none' ) {
|
||||||
|
element.style.removeProperty( 'display' );
|
||||||
|
triggerShown( 'Hiding.setVisible', selectorOrElement, element );
|
||||||
|
}
|
||||||
|
|
||||||
|
// still not visible (if something else added display: none in CSS)
|
||||||
|
if ( ! isVisible( element ) ) {
|
||||||
|
element.style.setProperty( 'display', 'block' );
|
||||||
|
triggerShown( 'Hiding.setVisible', selectorOrElement, element );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setVisibleByClass = ( selectorOrElement, show, hiddenClass ) => {
|
||||||
|
const element = getElement( selectorOrElement );
|
||||||
|
if ( ! element ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( show ) {
|
||||||
|
element.classList.remove( hiddenClass );
|
||||||
|
triggerShown( 'Hiding.setVisibleByClass', selectorOrElement, element );
|
||||||
|
} else {
|
||||||
|
element.classList.add( hiddenClass );
|
||||||
|
triggerHidden( 'Hiding.setVisibleByClass', selectorOrElement, element );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hide = ( selectorOrElement, important = false ) => {
|
||||||
|
setVisible( selectorOrElement, false, important );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const show = ( selectorOrElement ) => {
|
||||||
|
setVisible( selectorOrElement, true );
|
||||||
|
};
|
179
modules/ppcp-settings/resources/js/utils/Helper/LocalStorage.js
Normal file
179
modules/ppcp-settings/resources/js/utils/Helper/LocalStorage.js
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
function checkLocalStorageAvailability() {
|
||||||
|
try {
|
||||||
|
const testKey = '__ppcp_test__';
|
||||||
|
localStorage.setItem( testKey, 'test' );
|
||||||
|
localStorage.removeItem( testKey );
|
||||||
|
return true;
|
||||||
|
} catch ( e ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeKey( name ) {
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace( /[^a-z0-9_-]/g, '_' );
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeEntry( serialized ) {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse( serialized );
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: payload.data,
|
||||||
|
expires: payload.expires || 0,
|
||||||
|
};
|
||||||
|
} catch ( e ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeEntry( data, timeToLive ) {
|
||||||
|
const payload = {
|
||||||
|
data,
|
||||||
|
expires: calculateExpiration( timeToLive ),
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify( payload );
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateExpiration( timeToLive ) {
|
||||||
|
return timeToLive ? Date.now() + timeToLive * 1000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable class for handling data storage in the browser's local storage,
|
||||||
|
* with optional expiration.
|
||||||
|
*
|
||||||
|
* Can be extended for module specific logic.
|
||||||
|
*
|
||||||
|
* @see GooglePaySession
|
||||||
|
*/
|
||||||
|
export class LocalStorage {
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
#group = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {null|boolean}
|
||||||
|
*/
|
||||||
|
#canUseLocalStorage = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} group - Group name for all storage keys managed by this instance.
|
||||||
|
*/
|
||||||
|
constructor( group ) {
|
||||||
|
this.#group = sanitizeKey( group ) + ':';
|
||||||
|
this.#removeExpired();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all items in the current group that have reached the expiry date.
|
||||||
|
*/
|
||||||
|
#removeExpired() {
|
||||||
|
if ( ! this.canUseLocalStorage ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys( localStorage ).forEach( ( key ) => {
|
||||||
|
if ( ! key.startsWith( this.#group ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = deserializeEntry( localStorage.getItem( key ) );
|
||||||
|
if ( entry && entry.expires > 0 && entry.expires < Date.now() ) {
|
||||||
|
localStorage.removeItem( key );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes the given entry name and adds the group prefix.
|
||||||
|
*
|
||||||
|
* @throws {Error} If the name is empty after sanitization.
|
||||||
|
* @param {string} name - Entry name.
|
||||||
|
* @return {string} Prefixed and sanitized entry name.
|
||||||
|
*/
|
||||||
|
#entryKey( name ) {
|
||||||
|
const sanitizedName = sanitizeKey( name );
|
||||||
|
|
||||||
|
if ( sanitizedName.length === 0 ) {
|
||||||
|
throw new Error( 'Name cannot be empty after sanitization' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${ this.#group }${ sanitizedName }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates, whether localStorage is available.
|
||||||
|
*
|
||||||
|
* @return {boolean} True means the localStorage API is available.
|
||||||
|
*/
|
||||||
|
get canUseLocalStorage() {
|
||||||
|
if ( null === this.#canUseLocalStorage ) {
|
||||||
|
this.#canUseLocalStorage = checkLocalStorageAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#canUseLocalStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores data in the browser's local storage, with an optional timeout.
|
||||||
|
*
|
||||||
|
* @param {string} name - Name of the item in the storage.
|
||||||
|
* @param {any} data - The data to store.
|
||||||
|
* @param {number} [timeToLive=0] - Lifespan in seconds. 0 means the data won't expire.
|
||||||
|
* @throws {Error} If local storage is not available.
|
||||||
|
*/
|
||||||
|
set( name, data, timeToLive = 0 ) {
|
||||||
|
if ( ! this.canUseLocalStorage ) {
|
||||||
|
throw new Error( 'Local storage is not available' );
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = serializeEntry( data, timeToLive );
|
||||||
|
const entryKey = this.#entryKey( name );
|
||||||
|
|
||||||
|
localStorage.setItem( entryKey, entry );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves previously stored data from the browser's local storage.
|
||||||
|
*
|
||||||
|
* @param {string} name - Name of the stored item.
|
||||||
|
* @return {any|null} The stored data, or null when no valid entry is found or it has expired.
|
||||||
|
* @throws {Error} If local storage is not available.
|
||||||
|
*/
|
||||||
|
get( name ) {
|
||||||
|
if ( ! this.canUseLocalStorage ) {
|
||||||
|
throw new Error( 'Local storage is not available' );
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemKey = this.#entryKey( name );
|
||||||
|
const entry = deserializeEntry( localStorage.getItem( itemKey ) );
|
||||||
|
|
||||||
|
if ( ! entry ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified entry from the browser's local storage.
|
||||||
|
*
|
||||||
|
* @param {string} name - Name of the stored item.
|
||||||
|
* @throws {Error} If local storage is not available.
|
||||||
|
*/
|
||||||
|
clear( name ) {
|
||||||
|
if ( ! this.canUseLocalStorage ) {
|
||||||
|
throw new Error( 'Local storage is not available' );
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemKey = this.#entryKey( name );
|
||||||
|
localStorage.removeItem( itemKey );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { refreshButtons } from './ButtonRefreshHelper';
|
||||||
|
|
||||||
|
const DEFAULT_TRIGGER_ELEMENT_SELECTOR = '.woocommerce-checkout-payment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MultistepCheckoutHelper class ensures the initialization of payment buttons
|
||||||
|
* on websites using a multistep checkout plugin. These plugins usually hide the
|
||||||
|
* payment button on page load up and reveal it later using JS. During the
|
||||||
|
* invisibility period of wrappers, some payment buttons fail to initialize,
|
||||||
|
* so we wait for the payment element to be visible.
|
||||||
|
*
|
||||||
|
* @property {HTMLElement} form - Checkout form element.
|
||||||
|
* @property {HTMLElement} triggerElement - Element, which visibility we need to detect.
|
||||||
|
* @property {boolean} isVisible - Whether the triggerElement is visible.
|
||||||
|
*/
|
||||||
|
class MultistepCheckoutHelper {
|
||||||
|
/**
|
||||||
|
* Selector that defines the HTML element we are waiting to become visible.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
#triggerElementSelector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval (in milliseconds) in which the visibility of the trigger element is checked.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
#intervalTime = 150;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval ID returned by the setInterval() method.
|
||||||
|
* @type {number|false}
|
||||||
|
*/
|
||||||
|
#intervalId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector passed to the constructor that identifies the checkout form
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
#formSelector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} formSelector - Selector of the checkout form
|
||||||
|
* @param {string} triggerElementSelector - Optional. Selector of the dependant element.
|
||||||
|
*/
|
||||||
|
constructor( formSelector, triggerElementSelector = '' ) {
|
||||||
|
this.#formSelector = formSelector;
|
||||||
|
this.#triggerElementSelector =
|
||||||
|
triggerElementSelector || DEFAULT_TRIGGER_ELEMENT_SELECTOR;
|
||||||
|
this.#intervalId = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Start the visibility checker after a brief delay. This allows eventual multistep plugins to
|
||||||
|
dynamically prepare the checkout page, so we can decide whether this helper is needed.
|
||||||
|
*/
|
||||||
|
setTimeout( () => {
|
||||||
|
if ( this.form && ! this.isVisible ) {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
}, 250 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The checkout form element.
|
||||||
|
* @return {Element|null} - Form element or null.
|
||||||
|
*/
|
||||||
|
get form() {
|
||||||
|
return document.querySelector( this.#formSelector );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element which must be visible before payment buttons should be initialized.
|
||||||
|
* @return {Element|null} - Trigger element or null.
|
||||||
|
*/
|
||||||
|
get triggerElement() {
|
||||||
|
return this.form?.querySelector( this.#triggerElementSelector );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the visibility of the payment button wrapper.
|
||||||
|
* @return {boolean} - returns boolean value on the basis of visibility of element.
|
||||||
|
*/
|
||||||
|
get isVisible() {
|
||||||
|
const box = this.triggerElement?.getBoundingClientRect();
|
||||||
|
|
||||||
|
return !! ( box && box.width && box.height );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the observation of the DOM, initiates monitoring the checkout form.
|
||||||
|
* To ensure multiple calls to start don't create multiple intervals, we first call stop.
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
this.stop();
|
||||||
|
this.#intervalId = setInterval(
|
||||||
|
() => this.checkElement(),
|
||||||
|
this.#intervalTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the observation of the checkout form.
|
||||||
|
* Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
if ( this.#intervalId ) {
|
||||||
|
clearInterval( this.#intervalId );
|
||||||
|
this.#intervalId = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the trigger element is visible.
|
||||||
|
* If visible, it initialises the payment buttons and stops the observation.
|
||||||
|
*/
|
||||||
|
checkElement() {
|
||||||
|
if ( this.isVisible ) {
|
||||||
|
refreshButtons();
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MultistepCheckoutHelper;
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { loadScript } from '@paypal/paypal-js';
|
||||||
|
import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler';
|
||||||
|
import widgetBuilder from '../Renderer/WidgetBuilder';
|
||||||
|
import { processConfig } from './ConfigProcessor';
|
||||||
|
|
||||||
|
const loadedScripts = new Map();
|
||||||
|
const scriptPromises = new Map();
|
||||||
|
|
||||||
|
const handleDataClientIdAttribute = async ( scriptOptions, config ) => {
|
||||||
|
if (
|
||||||
|
config.data_client_id?.set_attribute &&
|
||||||
|
config.vault_v3_enabled !== true
|
||||||
|
) {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
dataClientIdAttributeHandler(
|
||||||
|
scriptOptions,
|
||||||
|
config.data_client_id,
|
||||||
|
( paypal ) => {
|
||||||
|
widgetBuilder.setPaypal( paypal );
|
||||||
|
resolve( paypal );
|
||||||
|
},
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPayPalScript = async ( namespace, config ) => {
|
||||||
|
if ( ! namespace ) {
|
||||||
|
throw new Error( 'Namespace is required' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( loadedScripts.has( namespace ) ) {
|
||||||
|
console.log( `Script already loaded for namespace: ${ namespace }` );
|
||||||
|
return loadedScripts.get( namespace );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( scriptPromises.has( namespace ) ) {
|
||||||
|
console.log(
|
||||||
|
`Script loading in progress for namespace: ${ namespace }`
|
||||||
|
);
|
||||||
|
return scriptPromises.get( namespace );
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptOptions = {
|
||||||
|
...processConfig( config ),
|
||||||
|
'data-namespace': namespace,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataClientIdResult = await handleDataClientIdAttribute(
|
||||||
|
scriptOptions,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
if ( dataClientIdResult ) {
|
||||||
|
return dataClientIdResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptPromise = new Promise( ( resolve, reject ) => {
|
||||||
|
loadScript( scriptOptions )
|
||||||
|
.then( ( script ) => {
|
||||||
|
widgetBuilder.setPaypal( script );
|
||||||
|
loadedScripts.set( namespace, script );
|
||||||
|
console.log( `Script loaded for namespace: ${ namespace }` );
|
||||||
|
resolve( script );
|
||||||
|
} )
|
||||||
|
.catch( ( error ) => {
|
||||||
|
console.error(
|
||||||
|
`Failed to load script for namespace: ${ namespace }`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
reject( error );
|
||||||
|
} )
|
||||||
|
.finally( () => {
|
||||||
|
scriptPromises.delete( namespace );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
scriptPromises.set( namespace, scriptPromise );
|
||||||
|
return scriptPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadAndRenderPayPalScript = async (
|
||||||
|
namespace,
|
||||||
|
options,
|
||||||
|
renderFunction,
|
||||||
|
renderTarget
|
||||||
|
) => {
|
||||||
|
if ( ! namespace ) {
|
||||||
|
throw new Error( 'Namespace is required' );
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptOptions = {
|
||||||
|
...options,
|
||||||
|
'data-namespace': namespace,
|
||||||
|
};
|
||||||
|
|
||||||
|
const script = await loadScript( scriptOptions );
|
||||||
|
widgetBuilder.setPaypal( script );
|
||||||
|
await renderFunction( script, renderTarget );
|
||||||
|
};
|
196
modules/ppcp-settings/resources/js/utils/Helper/PayerData.js
Normal file
196
modules/ppcp-settings/resources/js/utils/Helper/PayerData.js
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/**
|
||||||
|
* Name details.
|
||||||
|
*
|
||||||
|
* @typedef {Object} NameDetails
|
||||||
|
* @property {string} [given_name] - First name, e.g. "John".
|
||||||
|
* @property {string} [surname] - Last name, e.g. "Doe".
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postal address details.
|
||||||
|
*
|
||||||
|
* @typedef {Object} AddressDetails
|
||||||
|
* @property {string} [country_code] - Country code (2-letter).
|
||||||
|
* @property {string} [address_line_1] - Address details, line 1 (street, house number).
|
||||||
|
* @property {string} [address_line_2] - Address details, line 2.
|
||||||
|
* @property {string} [admin_area_1] - State or region.
|
||||||
|
* @property {string} [admin_area_2] - State or region.
|
||||||
|
* @property {string} [postal_code] - Zip code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phone details.
|
||||||
|
*
|
||||||
|
* @typedef {Object} PhoneDetails
|
||||||
|
* @property {string} [phone_type] - Type, usually 'HOME'
|
||||||
|
* @property {{national_number: string}} [phone_number] - Phone number details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payer details.
|
||||||
|
*
|
||||||
|
* @typedef {Object} PayerDetails
|
||||||
|
* @property {string} [email_address] - Email address for billing communication.
|
||||||
|
* @property {PhoneDetails} [phone] - Phone number for billing communication.
|
||||||
|
* @property {NameDetails} [name] - Payer's name.
|
||||||
|
* @property {AddressDetails} [address] - Postal billing address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Map checkout fields to PayerData object properties.
|
||||||
|
const FIELD_MAP = {
|
||||||
|
'#billing_email': [ 'email_address' ],
|
||||||
|
'#billing_last_name': [ 'name', 'surname' ],
|
||||||
|
'#billing_first_name': [ 'name', 'given_name' ],
|
||||||
|
'#billing_country': [ 'address', 'country_code' ],
|
||||||
|
'#billing_address_1': [ 'address', 'address_line_1' ],
|
||||||
|
'#billing_address_2': [ 'address', 'address_line_2' ],
|
||||||
|
'#billing_state': [ 'address', 'admin_area_1' ],
|
||||||
|
'#billing_city': [ 'address', 'admin_area_2' ],
|
||||||
|
'#billing_postcode': [ 'address', 'postal_code' ],
|
||||||
|
'#billing_phone': [ 'phone' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizePayerDetails( details ) {
|
||||||
|
return {
|
||||||
|
email_address: details.email_address,
|
||||||
|
phone: details.phone,
|
||||||
|
name: {
|
||||||
|
surname: details.name?.surname,
|
||||||
|
given_name: details.name?.given_name,
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
country_code: details.address?.country_code,
|
||||||
|
address_line_1: details.address?.address_line_1,
|
||||||
|
address_line_2: details.address?.address_line_2,
|
||||||
|
admin_area_1: details.address?.admin_area_1,
|
||||||
|
admin_area_2: details.address?.admin_area_2,
|
||||||
|
postal_code: details.address?.postal_code,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePayerDetails( firstPayer, secondPayer ) {
|
||||||
|
const mergeNestedObjects = ( target, source ) => {
|
||||||
|
for ( const [ key, value ] of Object.entries( source ) ) {
|
||||||
|
if ( null !== value && undefined !== value ) {
|
||||||
|
if ( 'object' === typeof value ) {
|
||||||
|
target[ key ] = mergeNestedObjects(
|
||||||
|
target[ key ] || {},
|
||||||
|
value
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
target[ key ] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
return mergeNestedObjects(
|
||||||
|
normalizePayerDetails( firstPayer ),
|
||||||
|
normalizePayerDetails( secondPayer )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheckoutBillingDetails() {
|
||||||
|
const getElementValue = ( selector ) =>
|
||||||
|
document.querySelector( selector )?.value;
|
||||||
|
|
||||||
|
const setNestedValue = ( obj, path, value ) => {
|
||||||
|
let current = obj;
|
||||||
|
for ( let i = 0; i < path.length - 1; i++ ) {
|
||||||
|
current = current[ path[ i ] ] = current[ path[ i ] ] || {};
|
||||||
|
}
|
||||||
|
current[ path[ path.length - 1 ] ] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = {};
|
||||||
|
|
||||||
|
Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => {
|
||||||
|
const value = getElementValue( selector );
|
||||||
|
if ( value ) {
|
||||||
|
setNestedValue( data, path, value );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( data.phone && 'string' === typeof data.phone ) {
|
||||||
|
data.phone = {
|
||||||
|
phone_type: 'HOME',
|
||||||
|
phone_number: { national_number: data.phone },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCheckoutBillingDetails( payer ) {
|
||||||
|
const setValue = ( path, field, value ) => {
|
||||||
|
if ( null === value || undefined === value || ! field ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'phone' === path[ 0 ] && 'object' === typeof value ) {
|
||||||
|
value = value.phone_number?.national_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
field.value = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNestedValue = ( obj, path ) =>
|
||||||
|
path.reduce( ( current, key ) => current?.[ key ], obj );
|
||||||
|
|
||||||
|
Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => {
|
||||||
|
const value = getNestedValue( payer, path );
|
||||||
|
const element = document.querySelector( selector );
|
||||||
|
|
||||||
|
setValue( path, element, value );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWooCommerceCustomerDetails() {
|
||||||
|
// Populated on server-side with details about the current WooCommerce customer.
|
||||||
|
return window?.PayPalCommerceGateway?.payer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSessionBillingDetails() {
|
||||||
|
// Populated by JS via `setSessionBillingDetails()`
|
||||||
|
return window._PpcpPayerSessionDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores customer details in the current JS context for use in the same request.
|
||||||
|
* Details that are set are not persisted during navigation.
|
||||||
|
*
|
||||||
|
* @param {unknown} details - New payer details
|
||||||
|
*/
|
||||||
|
export function setSessionBillingDetails( details ) {
|
||||||
|
if ( ! details || 'object' !== typeof details ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window._PpcpPayerSessionDetails = normalizePayerDetails( details );
|
||||||
|
}
|
||||||
|
|
||||||
|
export function payerData() {
|
||||||
|
const payer = getWooCommerceCustomerDetails() ?? getSessionBillingDetails();
|
||||||
|
|
||||||
|
if ( ! payer ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = getCheckoutBillingDetails();
|
||||||
|
|
||||||
|
if ( formData ) {
|
||||||
|
return mergePayerDetails( payer, formData );
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizePayerDetails( payer );
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPayerData( payerDetails, updateCheckoutForm = false ) {
|
||||||
|
setSessionBillingDetails( payerDetails );
|
||||||
|
|
||||||
|
if ( updateCheckoutForm ) {
|
||||||
|
setCheckoutBillingDetails( payerDetails );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* Helper function used by PaymentButton instances.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of recognized event names for payment button events.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const ButtonEvents = Object.freeze( {
|
||||||
|
INVALIDATE: 'ppcp_invalidate_methods',
|
||||||
|
RENDER: 'ppcp_render_method',
|
||||||
|
REDRAW: 'ppcp_redraw_method',
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} defaultId - Default wrapper ID.
|
||||||
|
* @param {string} miniCartId - Wrapper inside the mini-cart.
|
||||||
|
* @param {string} smartButtonId - ID of the smart button wrapper.
|
||||||
|
* @param {string} blockId - Block wrapper ID (express checkout, block cart).
|
||||||
|
* @param {string} gatewayId - Gateway wrapper ID (classic checkout).
|
||||||
|
* @return {{MiniCart, Gateway, Block, SmartButton, Default}} List of all wrapper IDs, by context.
|
||||||
|
*/
|
||||||
|
export function combineWrapperIds(
|
||||||
|
defaultId = '',
|
||||||
|
miniCartId = '',
|
||||||
|
smartButtonId = '',
|
||||||
|
blockId = '',
|
||||||
|
gatewayId = ''
|
||||||
|
) {
|
||||||
|
const sanitize = ( id ) => id.replace( /^#/, '' );
|
||||||
|
|
||||||
|
return {
|
||||||
|
Default: sanitize( defaultId ),
|
||||||
|
SmartButton: sanitize( smartButtonId ),
|
||||||
|
Block: sanitize( blockId ),
|
||||||
|
Gateway: sanitize( gatewayId ),
|
||||||
|
MiniCart: sanitize( miniCartId ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns full payment button styles by combining the global ppcpConfig with
|
||||||
|
* payment-method-specific styling provided via buttonConfig.
|
||||||
|
*
|
||||||
|
* @param {Object} ppcpConfig - Global plugin configuration.
|
||||||
|
* @param {Object} buttonConfig - Payment method specific configuration.
|
||||||
|
* @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context.
|
||||||
|
*/
|
||||||
|
export function combineStyles( ppcpConfig, buttonConfig ) {
|
||||||
|
return {
|
||||||
|
Default: {
|
||||||
|
...ppcpConfig.style,
|
||||||
|
...buttonConfig.style,
|
||||||
|
},
|
||||||
|
MiniCart: {
|
||||||
|
...ppcpConfig.mini_cart_style,
|
||||||
|
...buttonConfig.mini_cart_style,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the given event name is a valid Payment Button event.
|
||||||
|
*
|
||||||
|
* @param {string} event - The event name to verify.
|
||||||
|
* @return {boolean} True, if the event name is valid.
|
||||||
|
*/
|
||||||
|
export function isValidButtonEvent( event ) {
|
||||||
|
const buttonEventValues = Object.values( ButtonEvents );
|
||||||
|
|
||||||
|
return buttonEventValues.includes( event );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a payment button event.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for dispatching the event.
|
||||||
|
* @param {string} options.event - Event to dispatch.
|
||||||
|
* @param {string} [options.paymentMethod] - Optional. Name of payment method, to target a specific button only.
|
||||||
|
* @throws {Error} Throws an error if the event is invalid.
|
||||||
|
*/
|
||||||
|
export function dispatchButtonEvent( { event, paymentMethod = '' } ) {
|
||||||
|
if ( ! isValidButtonEvent( event ) ) {
|
||||||
|
throw new Error( `Invalid event: ${ event }` );
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEventName = paymentMethod
|
||||||
|
? `${ event }-${ paymentMethod }`
|
||||||
|
: event;
|
||||||
|
|
||||||
|
document.body.dispatchEvent( new Event( fullEventName ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event listener for the provided button event.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for the event listener.
|
||||||
|
* @param {string} options.event - Event to observe.
|
||||||
|
* @param {string} [options.paymentMethod] - The payment method name (optional).
|
||||||
|
* @param {Function} options.callback - The callback function to execute when the event is triggered.
|
||||||
|
* @throws {Error} Throws an error if the event is invalid.
|
||||||
|
*/
|
||||||
|
export function observeButtonEvent( { event, paymentMethod = '', callback } ) {
|
||||||
|
if ( ! isValidButtonEvent( event ) ) {
|
||||||
|
throw new Error( `Invalid event: ${ event }` );
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEventName = paymentMethod
|
||||||
|
? `${ event }-${ paymentMethod }`
|
||||||
|
: event;
|
||||||
|
|
||||||
|
document.body.addEventListener( fullEventName, callback );
|
||||||
|
}
|
128
modules/ppcp-settings/resources/js/utils/Helper/ScriptLoading.js
Normal file
128
modules/ppcp-settings/resources/js/utils/Helper/ScriptLoading.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import dataClientIdAttributeHandler from '../DataClientIdAttributeHandler';
|
||||||
|
import { loadScript } from '@paypal/paypal-js';
|
||||||
|
import widgetBuilder from '../Renderer/WidgetBuilder';
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
import { keysToCamelCase } from './Utils';
|
||||||
|
import { getCurrentPaymentMethod } from './CheckoutMethodState';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
// This component may be used by multiple modules. This assures that options are shared between all instances.
|
||||||
|
const scriptOptionsMap = {};
|
||||||
|
|
||||||
|
const getNamespaceOptions = ( namespace ) => {
|
||||||
|
if ( ! scriptOptionsMap[ namespace ] ) {
|
||||||
|
scriptOptionsMap[ namespace ] = {
|
||||||
|
isLoading: false,
|
||||||
|
onLoadedCallbacks: [],
|
||||||
|
onErrorCallbacks: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return scriptOptionsMap[ namespace ];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPaypalScript = ( config, onLoaded, onError = null ) => {
|
||||||
|
const dataNamespace = config?.data_namespace || '';
|
||||||
|
const options = getNamespaceOptions( dataNamespace );
|
||||||
|
|
||||||
|
// If PayPal is already loaded for this namespace, call the onLoaded callback and return.
|
||||||
|
if ( typeof window.paypal !== 'undefined' && ! dataNamespace ) {
|
||||||
|
onLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the onLoaded callback to the onLoadedCallbacks stack.
|
||||||
|
options.onLoadedCallbacks.push( onLoaded );
|
||||||
|
if ( onError ) {
|
||||||
|
options.onErrorCallbacks.push( onError );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if it's still loading.
|
||||||
|
if ( options.isLoading ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
options.isLoading = true;
|
||||||
|
|
||||||
|
const resetState = () => {
|
||||||
|
options.isLoading = false;
|
||||||
|
options.onLoadedCallbacks = [];
|
||||||
|
options.onErrorCallbacks = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback to be called once the PayPal script is loaded.
|
||||||
|
const callback = ( paypal ) => {
|
||||||
|
widgetBuilder.setPaypal( paypal );
|
||||||
|
|
||||||
|
for ( const onLoadedCallback of options.onLoadedCallbacks ) {
|
||||||
|
onLoadedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetState();
|
||||||
|
};
|
||||||
|
const errorCallback = ( err ) => {
|
||||||
|
for ( const onErrorCallback of options.onErrorCallbacks ) {
|
||||||
|
onErrorCallback( err );
|
||||||
|
}
|
||||||
|
|
||||||
|
resetState();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build the PayPal script options.
|
||||||
|
let scriptOptions = keysToCamelCase( config.url_params );
|
||||||
|
if ( config.script_attributes ) {
|
||||||
|
scriptOptions = merge( scriptOptions, config.script_attributes );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Axo SDK options
|
||||||
|
const sdkClientToken = config?.axo?.sdk_client_token;
|
||||||
|
const uuid = uuidv4().replace( /-/g, '' );
|
||||||
|
if ( sdkClientToken && config?.user?.is_logged !== true ) {
|
||||||
|
scriptOptions[ 'data-sdk-client-token' ] = sdkClientToken;
|
||||||
|
scriptOptions[ 'data-client-metadata-id' ] = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PayPal script for special case with data-client-token
|
||||||
|
if (
|
||||||
|
config.data_client_id?.set_attribute &&
|
||||||
|
config.vault_v3_enabled !== '1'
|
||||||
|
) {
|
||||||
|
dataClientIdAttributeHandler(
|
||||||
|
scriptOptions,
|
||||||
|
config.data_client_id,
|
||||||
|
callback,
|
||||||
|
errorCallback
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds data-user-id-token to script options.
|
||||||
|
const userIdToken = config?.save_payment_methods?.id_token;
|
||||||
|
if ( userIdToken && config?.user?.is_logged === true ) {
|
||||||
|
scriptOptions[ 'data-user-id-token' ] = userIdToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds data-namespace to script options.
|
||||||
|
if ( dataNamespace ) {
|
||||||
|
scriptOptions.dataNamespace = dataNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PayPal script
|
||||||
|
loadScript( scriptOptions ).then( callback ).catch( errorCallback );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPaypalScriptPromise = ( config ) => {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
loadPaypalScript( config, resolve, reject );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPaypalJsScript = ( options, buttons, container ) => {
|
||||||
|
loadScript( options ).then( ( paypal ) => {
|
||||||
|
paypal.Buttons( buttons ).render( container );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPaypalJsScriptPromise = ( options ) => {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
loadScript( options ).then( resolve ).catch( reject );
|
||||||
|
} );
|
||||||
|
};
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { paypalAddressToWc } from '../../../../../ppcp-blocks/resources/js/Helper/Address.js';
|
||||||
|
import { convertKeysToSnakeCase } from '../../../../../ppcp-blocks/resources/js/Helper/Helper.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the shipping option change in PayPal.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @param actions
|
||||||
|
* @param config
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const handleShippingOptionsChange = async ( data, actions, config ) => {
|
||||||
|
try {
|
||||||
|
const shippingOptionId = data.selectedShippingOption?.id;
|
||||||
|
|
||||||
|
if ( shippingOptionId ) {
|
||||||
|
await fetch(
|
||||||
|
config.ajax.update_customer_shipping.shipping_options.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-WC-Store-API-Nonce':
|
||||||
|
config.ajax.update_customer_shipping.wp_rest_nonce,
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
rate_id: shippingOptionId,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then( ( response ) => {
|
||||||
|
return response.json();
|
||||||
|
} )
|
||||||
|
.then( ( cardData ) => {
|
||||||
|
const shippingMethods =
|
||||||
|
document.querySelectorAll( '.shipping_method' );
|
||||||
|
|
||||||
|
shippingMethods.forEach( function ( method ) {
|
||||||
|
if ( method.value === shippingOptionId ) {
|
||||||
|
method.checked = true;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! config.data_client_id.has_subscriptions ) {
|
||||||
|
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.ajax.update_shipping.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch ( e ) {
|
||||||
|
console.error( e );
|
||||||
|
|
||||||
|
actions.reject();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the shipping address change in PayPal.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @param actions
|
||||||
|
* @param config
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const handleShippingAddressChange = async ( data, actions, config ) => {
|
||||||
|
try {
|
||||||
|
const address = paypalAddressToWc(
|
||||||
|
convertKeysToSnakeCase( data.shippingAddress )
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve current cart contents
|
||||||
|
await fetch(
|
||||||
|
config.ajax.update_customer_shipping.shipping_address.cart_endpoint
|
||||||
|
)
|
||||||
|
.then( ( response ) => {
|
||||||
|
return response.json();
|
||||||
|
} )
|
||||||
|
.then( ( cartData ) => {
|
||||||
|
// Update shipping address in the cart data
|
||||||
|
cartData.shipping_address.address_1 = address.address_1;
|
||||||
|
cartData.shipping_address.address_2 = address.address_2;
|
||||||
|
cartData.shipping_address.city = address.city;
|
||||||
|
cartData.shipping_address.state = address.state;
|
||||||
|
cartData.shipping_address.postcode = address.postcode;
|
||||||
|
cartData.shipping_address.country = address.country;
|
||||||
|
|
||||||
|
// Send update request
|
||||||
|
return fetch(
|
||||||
|
config.ajax.update_customer_shipping.shipping_address
|
||||||
|
.update_customer_endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-WC-Store-API-Nonce':
|
||||||
|
config.ajax.update_customer_shipping
|
||||||
|
.wp_rest_nonce,
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
shipping_address: cartData.shipping_address,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then( function ( res ) {
|
||||||
|
return res.json();
|
||||||
|
} )
|
||||||
|
.then( function ( customerData ) {
|
||||||
|
jQuery( '.cart_totals .shop_table' ).load(
|
||||||
|
location.href +
|
||||||
|
' ' +
|
||||||
|
'.cart_totals .shop_table' +
|
||||||
|
'>*',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
const res = await fetch( config.ajax.update_shipping.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.ajax.update_shipping.nonce,
|
||||||
|
order_id: data.orderID,
|
||||||
|
} ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if ( ! json.success ) {
|
||||||
|
throw new Error( json.data.message );
|
||||||
|
}
|
||||||
|
} catch ( e ) {
|
||||||
|
console.error( e );
|
||||||
|
|
||||||
|
actions.reject();
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
class SimulateCart {
|
||||||
|
constructor( endpoint, nonce ) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param onResolve
|
||||||
|
* @param {Product[]} products
|
||||||
|
* @return {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
simulate( onResolve, products ) {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
fetch( this.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: this.nonce,
|
||||||
|
products,
|
||||||
|
} ),
|
||||||
|
} )
|
||||||
|
.then( ( result ) => {
|
||||||
|
return result.json();
|
||||||
|
} )
|
||||||
|
.then( ( result ) => {
|
||||||
|
if ( ! result.success ) {
|
||||||
|
reject( result.data );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = onResolve( result.data );
|
||||||
|
resolve( resolved );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SimulateCart;
|
25
modules/ppcp-settings/resources/js/utils/Helper/Spinner.js
Normal file
25
modules/ppcp-settings/resources/js/utils/Helper/Spinner.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
class Spinner {
|
||||||
|
constructor( target = 'form.woocommerce-checkout' ) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTarget( target ) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
jQuery( this.target ).block( {
|
||||||
|
message: null,
|
||||||
|
overlayCSS: {
|
||||||
|
background: '#fff',
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock() {
|
||||||
|
jQuery( this.target ).unblock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spinner;
|
20
modules/ppcp-settings/resources/js/utils/Helper/Style.js
Normal file
20
modules/ppcp-settings/resources/js/utils/Helper/Style.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
export const normalizeStyleForFundingSource = ( style, fundingSource ) => {
|
||||||
|
const commonProps = {};
|
||||||
|
[ 'shape', 'height' ].forEach( ( prop ) => {
|
||||||
|
if ( style[ prop ] ) {
|
||||||
|
commonProps[ prop ] = style[ prop ];
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
switch ( fundingSource ) {
|
||||||
|
case 'paypal':
|
||||||
|
return style;
|
||||||
|
case 'paylater':
|
||||||
|
return {
|
||||||
|
color: style.color,
|
||||||
|
...commonProps,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return commonProps;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
export const isChangePaymentPage = () => {
|
||||||
|
const urlParams = new URLSearchParams( window.location.search );
|
||||||
|
return urlParams.has( 'change_payment_method' );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPlanIdFromVariation = ( variation ) => {
|
||||||
|
let subscription_plan = '';
|
||||||
|
PayPalCommerceGateway.variable_paypal_subscription_variations.forEach(
|
||||||
|
( element ) => {
|
||||||
|
const obj = {};
|
||||||
|
variation.forEach( ( { name, value } ) => {
|
||||||
|
Object.assign( obj, {
|
||||||
|
[ name.replace( 'attribute_', '' ) ]: value,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
if (
|
||||||
|
JSON.stringify( obj ) ===
|
||||||
|
JSON.stringify( element.attributes ) &&
|
||||||
|
element.subscription_plan !== ''
|
||||||
|
) {
|
||||||
|
subscription_plan = element.subscription_plan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return subscription_plan;
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
import Product from '../Entity/Product';
|
||||||
|
class UpdateCart {
|
||||||
|
constructor( endpoint, nonce ) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param onResolve
|
||||||
|
* @param {Product[]} products
|
||||||
|
* @param {Object} options
|
||||||
|
* @return {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
update( onResolve, products, options = {} ) {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
fetch( this.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: this.nonce,
|
||||||
|
products,
|
||||||
|
...options,
|
||||||
|
} ),
|
||||||
|
} )
|
||||||
|
.then( ( result ) => {
|
||||||
|
return result.json();
|
||||||
|
} )
|
||||||
|
.then( ( result ) => {
|
||||||
|
if ( ! result.success ) {
|
||||||
|
reject( result.data );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = onResolve( result.data );
|
||||||
|
resolve( resolved );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateCart;
|
69
modules/ppcp-settings/resources/js/utils/Helper/Utils.js
Normal file
69
modules/ppcp-settings/resources/js/utils/Helper/Utils.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
export const toCamelCase = ( str ) => {
|
||||||
|
return str.replace( /([-_]\w)/g, function ( match ) {
|
||||||
|
return match[ 1 ].toUpperCase();
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const keysToCamelCase = ( obj ) => {
|
||||||
|
const output = {};
|
||||||
|
for ( const key in obj ) {
|
||||||
|
if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {
|
||||||
|
output[ toCamelCase( key ) ] = obj[ key ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const strAddWord = ( str, word, separator = ',' ) => {
|
||||||
|
const arr = str.split( separator );
|
||||||
|
if ( ! arr.includes( word ) ) {
|
||||||
|
arr.push( word );
|
||||||
|
}
|
||||||
|
return arr.join( separator );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const strRemoveWord = ( str, word, separator = ',' ) => {
|
||||||
|
const arr = str.split( separator );
|
||||||
|
const index = arr.indexOf( word );
|
||||||
|
if ( index !== -1 ) {
|
||||||
|
arr.splice( index, 1 );
|
||||||
|
}
|
||||||
|
return arr.join( separator );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const throttle = ( func, limit ) => {
|
||||||
|
let inThrottle, lastArgs, lastContext;
|
||||||
|
|
||||||
|
function execute() {
|
||||||
|
inThrottle = true;
|
||||||
|
func.apply( this, arguments );
|
||||||
|
setTimeout( () => {
|
||||||
|
inThrottle = false;
|
||||||
|
if ( lastArgs ) {
|
||||||
|
const nextArgs = lastArgs;
|
||||||
|
const nextContext = lastContext;
|
||||||
|
lastArgs = lastContext = null;
|
||||||
|
execute.apply( nextContext, nextArgs );
|
||||||
|
}
|
||||||
|
}, limit );
|
||||||
|
}
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
if ( ! inThrottle ) {
|
||||||
|
execute.apply( this, arguments );
|
||||||
|
} else {
|
||||||
|
lastArgs = arguments;
|
||||||
|
lastContext = this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Utils = {
|
||||||
|
toCamelCase,
|
||||||
|
keysToCamelCase,
|
||||||
|
strAddWord,
|
||||||
|
strRemoveWord,
|
||||||
|
throttle,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Utils;
|
264
modules/ppcp-settings/resources/js/utils/renderer.js
Normal file
264
modules/ppcp-settings/resources/js/utils/renderer.js
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
import { loadScript } from '@paypal/paypal-js';
|
||||||
|
import { keysToCamelCase } from './Helper/Utils';
|
||||||
|
import widgetBuilder from './widget-builder';
|
||||||
|
import { normalizeStyleForFundingSource } from './Helper/Style';
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
constructor(
|
||||||
|
creditCardRenderer,
|
||||||
|
defaultSettings,
|
||||||
|
onSmartButtonClick,
|
||||||
|
onSmartButtonsInit
|
||||||
|
) {
|
||||||
|
this.defaultSettings = defaultSettings;
|
||||||
|
this.creditCardRenderer = creditCardRenderer;
|
||||||
|
this.onSmartButtonClick = onSmartButtonClick;
|
||||||
|
this.onSmartButtonsInit = onSmartButtonsInit;
|
||||||
|
|
||||||
|
this.buttonsOptions = {};
|
||||||
|
this.onButtonsInitListeners = {};
|
||||||
|
|
||||||
|
this.renderedSources = new Set();
|
||||||
|
|
||||||
|
this.reloadEventName = 'ppcp-reload-buttons';
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
contextConfig,
|
||||||
|
settingsOverride = {},
|
||||||
|
contextConfigOverride = () => {}
|
||||||
|
) {
|
||||||
|
const settings = merge( this.defaultSettings, settingsOverride );
|
||||||
|
const enabledSeparateGateways = Object.fromEntries(
|
||||||
|
Object.entries( settings.separate_buttons ).filter(
|
||||||
|
( [ s, data ] ) => document.querySelector( data.wrapper )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const hasEnabledSeparateGateways =
|
||||||
|
Object.keys( enabledSeparateGateways ).length !== 0;
|
||||||
|
|
||||||
|
if ( ! hasEnabledSeparateGateways ) {
|
||||||
|
console.log( 'RENDER 1', settings );
|
||||||
|
this.renderButtons(
|
||||||
|
settings.button.wrapper,
|
||||||
|
settings.button.style,
|
||||||
|
contextConfig,
|
||||||
|
hasEnabledSeparateGateways
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// render each button separately
|
||||||
|
for ( const fundingSource of paypal
|
||||||
|
.getFundingSources()
|
||||||
|
.filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) {
|
||||||
|
const style = normalizeStyleForFundingSource(
|
||||||
|
settings.button.style,
|
||||||
|
fundingSource
|
||||||
|
);
|
||||||
|
console.log( 'RENDER' );
|
||||||
|
this.renderButtons(
|
||||||
|
settings.button.wrapper,
|
||||||
|
style,
|
||||||
|
contextConfig,
|
||||||
|
hasEnabledSeparateGateways,
|
||||||
|
fundingSource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.creditCardRenderer ) {
|
||||||
|
this.creditCardRenderer.render(
|
||||||
|
settings.hosted_fields.wrapper,
|
||||||
|
contextConfigOverride
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( const [ fundingSource, data ] of Object.entries(
|
||||||
|
enabledSeparateGateways
|
||||||
|
) ) {
|
||||||
|
this.renderButtons(
|
||||||
|
data.wrapper,
|
||||||
|
data.style,
|
||||||
|
contextConfig,
|
||||||
|
hasEnabledSeparateGateways,
|
||||||
|
fundingSource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons(
|
||||||
|
wrapper,
|
||||||
|
style,
|
||||||
|
contextConfig,
|
||||||
|
hasEnabledSeparateGateways,
|
||||||
|
fundingSource = null
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
! document.querySelector( wrapper ) ||
|
||||||
|
this.isAlreadyRendered(
|
||||||
|
wrapper,
|
||||||
|
fundingSource,
|
||||||
|
hasEnabledSeparateGateways
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Try to render registered buttons again in case they were removed from the DOM by an external source.
|
||||||
|
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( fundingSource ) {
|
||||||
|
contextConfig.fundingSource = fundingSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
let venmoButtonClicked = false;
|
||||||
|
|
||||||
|
const buttonsOptions = () => {
|
||||||
|
const options = {
|
||||||
|
style,
|
||||||
|
...contextConfig,
|
||||||
|
onClick: ( data, actions ) => {
|
||||||
|
if ( this.onSmartButtonClick ) {
|
||||||
|
this.onSmartButtonClick( data, actions );
|
||||||
|
}
|
||||||
|
|
||||||
|
venmoButtonClicked = false;
|
||||||
|
if ( data.fundingSource === 'venmo' ) {
|
||||||
|
venmoButtonClicked = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInit: ( data, actions ) => {
|
||||||
|
if ( this.onSmartButtonsInit ) {
|
||||||
|
this.onSmartButtonsInit( data, actions );
|
||||||
|
}
|
||||||
|
this.handleOnButtonsInit( wrapper, data, actions );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery( document )
|
||||||
|
.off( this.reloadEventName, wrapper )
|
||||||
|
.on(
|
||||||
|
this.reloadEventName,
|
||||||
|
wrapper,
|
||||||
|
( event, settingsOverride = {}, triggeredFundingSource ) => {
|
||||||
|
// Only accept events from the matching funding source
|
||||||
|
if (
|
||||||
|
fundingSource &&
|
||||||
|
triggeredFundingSource &&
|
||||||
|
triggeredFundingSource !== fundingSource
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = merge(
|
||||||
|
this.defaultSettings,
|
||||||
|
settingsOverride
|
||||||
|
);
|
||||||
|
let scriptOptions = keysToCamelCase( settings.url_params );
|
||||||
|
scriptOptions = merge(
|
||||||
|
scriptOptions,
|
||||||
|
settings.script_attributes
|
||||||
|
);
|
||||||
|
|
||||||
|
loadScript( scriptOptions ).then( ( paypal ) => {
|
||||||
|
widgetBuilder.setPaypal( paypal );
|
||||||
|
widgetBuilder.registerButtons(
|
||||||
|
[ wrapper, fundingSource ],
|
||||||
|
buttonsOptions()
|
||||||
|
);
|
||||||
|
widgetBuilder.renderAll();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderedSources.add( wrapper + ( fundingSource ?? '' ) );
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof paypal !== 'undefined' &&
|
||||||
|
typeof paypal.Buttons !== 'undefined'
|
||||||
|
) {
|
||||||
|
widgetBuilder.registerButtons(
|
||||||
|
[ wrapper, fundingSource ],
|
||||||
|
buttonsOptions()
|
||||||
|
);
|
||||||
|
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isVenmoButtonClickedWhenVaultingIsEnabled = ( venmoButtonClicked ) => {
|
||||||
|
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldEnableShippingCallback = () => {
|
||||||
|
const needShipping =
|
||||||
|
this.defaultSettings.needShipping ||
|
||||||
|
this.defaultSettings.context === 'product';
|
||||||
|
return (
|
||||||
|
this.defaultSettings.should_handle_shipping_in_paypal &&
|
||||||
|
needShipping
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isAlreadyRendered( wrapper, fundingSource ) {
|
||||||
|
return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
disableCreditCardFields() {
|
||||||
|
this.creditCardRenderer.disableFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
enableCreditCardFields() {
|
||||||
|
this.creditCardRenderer.enableFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
onButtonsInit( wrapper, handler, reset ) {
|
||||||
|
this.onButtonsInitListeners[ wrapper ] = reset
|
||||||
|
? []
|
||||||
|
: this.onButtonsInitListeners[ wrapper ] || [];
|
||||||
|
this.onButtonsInitListeners[ wrapper ].push( handler );
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnButtonsInit( wrapper, data, actions ) {
|
||||||
|
this.buttonsOptions[ wrapper ] = {
|
||||||
|
data,
|
||||||
|
actions,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( this.onButtonsInitListeners[ wrapper ] ) {
|
||||||
|
for ( const handler of this.onButtonsInitListeners[ wrapper ] ) {
|
||||||
|
if ( typeof handler === 'function' ) {
|
||||||
|
handler( {
|
||||||
|
wrapper,
|
||||||
|
...this.buttonsOptions[ wrapper ],
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disableSmartButtons( wrapper ) {
|
||||||
|
if ( ! this.buttonsOptions[ wrapper ] ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.buttonsOptions[ wrapper ].actions.disable();
|
||||||
|
} catch ( err ) {
|
||||||
|
console.log( 'Failed to disable buttons: ' + err );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableSmartButtons( wrapper ) {
|
||||||
|
if ( ! this.buttonsOptions[ wrapper ] ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.buttonsOptions[ wrapper ].actions.enable();
|
||||||
|
} catch ( err ) {
|
||||||
|
console.log( 'Failed to enable buttons: ' + err );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Renderer;
|
179
modules/ppcp-settings/resources/js/utils/widget-builder.js
Normal file
179
modules/ppcp-settings/resources/js/utils/widget-builder.js
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
/**
|
||||||
|
* Handles the registration and rendering of PayPal widgets: Buttons and Messages.
|
||||||
|
* To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource].
|
||||||
|
*/
|
||||||
|
class WidgetBuilder {
|
||||||
|
constructor() {
|
||||||
|
this.paypal = null;
|
||||||
|
this.buttons = new Map();
|
||||||
|
this.messages = new Map();
|
||||||
|
|
||||||
|
this.renderEventName = 'ppcp-render';
|
||||||
|
|
||||||
|
document.ppcpWidgetBuilderStatus = () => {
|
||||||
|
console.log( {
|
||||||
|
buttons: this.buttons,
|
||||||
|
messages: this.messages,
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery( document )
|
||||||
|
.off( this.renderEventName )
|
||||||
|
.on( this.renderEventName, () => {
|
||||||
|
this.renderAll();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
setPaypal( paypal ) {
|
||||||
|
this.paypal = paypal;
|
||||||
|
jQuery( document ).trigger( 'ppcp-paypal-loaded', paypal );
|
||||||
|
}
|
||||||
|
|
||||||
|
registerButtons( wrapper, options ) {
|
||||||
|
wrapper = this.sanitizeWrapper( wrapper );
|
||||||
|
|
||||||
|
this.buttons.set( this.toKey( wrapper ), {
|
||||||
|
wrapper,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons( wrapper ) {
|
||||||
|
wrapper = this.sanitizeWrapper( wrapper );
|
||||||
|
|
||||||
|
if ( ! this.buttons.has( this.toKey( wrapper ) ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.hasRendered( wrapper ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = this.buttons.get( this.toKey( wrapper ) );
|
||||||
|
const btn = this.paypal.Buttons( entry.options );
|
||||||
|
|
||||||
|
if ( ! btn.isEligible() ) {
|
||||||
|
this.buttons.delete( this.toKey( wrapper ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = this.buildWrapperTarget( wrapper );
|
||||||
|
|
||||||
|
if ( ! target ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.render( target );
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAllButtons() {
|
||||||
|
for ( const [ wrapper, entry ] of this.buttons ) {
|
||||||
|
this.renderButtons( wrapper );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMessages( wrapper, options ) {
|
||||||
|
this.messages.set( wrapper, {
|
||||||
|
wrapper,
|
||||||
|
options,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMessages( wrapper ) {
|
||||||
|
if ( ! this.messages.has( wrapper ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = this.messages.get( wrapper );
|
||||||
|
|
||||||
|
if ( this.hasRendered( wrapper ) ) {
|
||||||
|
const element = document.querySelector( wrapper );
|
||||||
|
element.setAttribute( 'data-pp-amount', entry.options.amount );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = this.paypal.Messages( entry.options );
|
||||||
|
|
||||||
|
btn.render( entry.wrapper );
|
||||||
|
|
||||||
|
// watchdog to try to handle some strange cases where the wrapper may not be present
|
||||||
|
setTimeout( () => {
|
||||||
|
if ( ! this.hasRendered( wrapper ) ) {
|
||||||
|
btn.render( entry.wrapper );
|
||||||
|
}
|
||||||
|
}, 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAllMessages() {
|
||||||
|
for ( const [ wrapper, entry ] of this.messages ) {
|
||||||
|
this.renderMessages( wrapper );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAll() {
|
||||||
|
this.renderAllButtons();
|
||||||
|
this.renderAllMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRendered( wrapper ) {
|
||||||
|
let selector = wrapper;
|
||||||
|
|
||||||
|
if ( Array.isArray( wrapper ) ) {
|
||||||
|
selector = wrapper[ 0 ];
|
||||||
|
for ( const item of wrapper.slice( 1 ) ) {
|
||||||
|
selector += ' .item-' + item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = document.querySelector( selector );
|
||||||
|
return element && element.hasChildNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeWrapper( wrapper ) {
|
||||||
|
if ( Array.isArray( wrapper ) ) {
|
||||||
|
wrapper = wrapper.filter( ( item ) => !! item );
|
||||||
|
if ( wrapper.length === 1 ) {
|
||||||
|
wrapper = wrapper[ 0 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildWrapperTarget( wrapper ) {
|
||||||
|
let target = wrapper;
|
||||||
|
|
||||||
|
if ( Array.isArray( wrapper ) ) {
|
||||||
|
const $wrapper = jQuery( wrapper[ 0 ] );
|
||||||
|
|
||||||
|
if ( ! $wrapper.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemClass = 'item-' + wrapper[ 1 ];
|
||||||
|
|
||||||
|
// Check if the parent element exists and it doesn't already have the div with the class
|
||||||
|
let $item = $wrapper.find( '.' + itemClass );
|
||||||
|
|
||||||
|
if ( ! $item.length ) {
|
||||||
|
$item = jQuery( `<div class="${ itemClass }"></div>` );
|
||||||
|
$wrapper.append( $item );
|
||||||
|
}
|
||||||
|
|
||||||
|
target = $item.get( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! jQuery( target ).length ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
toKey( wrapper ) {
|
||||||
|
if ( Array.isArray( wrapper ) ) {
|
||||||
|
return JSON.stringify( wrapper );
|
||||||
|
}
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default window.widgetBuilder;
|
|
@ -7,8 +7,26 @@ module.exports = {
|
||||||
...{
|
...{
|
||||||
entry: {
|
entry: {
|
||||||
index: path.resolve( process.cwd(), 'resources/js', 'index.js' ),
|
index: path.resolve( process.cwd(), 'resources/js', 'index.js' ),
|
||||||
switchSettingsUi: path.resolve( process.cwd(), 'resources/js', 'switchSettingsUi.js' ),
|
switchSettingsUi: path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
'resources/js',
|
||||||
|
'switchSettingsUi.js'
|
||||||
|
),
|
||||||
style: path.resolve( process.cwd(), 'resources/css', 'style.scss' ),
|
style: path.resolve( process.cwd(), 'resources/css', 'style.scss' ),
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
...defaultConfig.resolve,
|
||||||
|
...{
|
||||||
|
alias: {
|
||||||
|
...defaultConfig.resolve.alias,
|
||||||
|
...{
|
||||||
|
ppcpButton: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../ppcp-button/resources/js'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1874,6 +1874,13 @@
|
||||||
"@parcel/watcher-win32-ia32" "2.5.0"
|
"@parcel/watcher-win32-ia32" "2.5.0"
|
||||||
"@parcel/watcher-win32-x64" "2.5.0"
|
"@parcel/watcher-win32-x64" "2.5.0"
|
||||||
|
|
||||||
|
"@paypal/paypal-js@^8.1.2":
|
||||||
|
version "8.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.1.2.tgz#3b6199e1c9e3b2a3e444309e0d0ff63556e3ef86"
|
||||||
|
integrity sha512-EKshGSWRxLWU1NyPB9P1TiOkPajVmpTo5I9HuZKoSv8y2uk0XIskXqMkAJ/Y9qAg9iJyP102Jb/atX63tTy24w==
|
||||||
|
dependencies:
|
||||||
|
promise-polyfill "^8.3.0"
|
||||||
|
|
||||||
"@pkgr/core@^0.1.0":
|
"@pkgr/core@^0.1.0":
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
||||||
|
@ -9365,6 +9372,11 @@ progress@2.0.3, progress@^2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||||
|
|
||||||
|
promise-polyfill@^8.3.0:
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
|
||||||
|
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
|
||||||
|
|
||||||
prompts@^2.0.1, prompts@^2.4.2:
|
prompts@^2.0.1, prompts@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue