From 63f417d7d29a04ad4143d6c2acaff562d97ec053 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 13 Jan 2025 17:06:05 +0100 Subject: [PATCH 01/63] =?UTF-8?q?=E2=9C=A8=20Brand=20new,=20empty=20?= =?UTF-8?q?=E2=80=9CStyling=E2=80=9D=20store=20in=20Redux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-settings/resources/js/data/index.js | 6 +- .../resources/js/data/styling/action-types.js | 18 +++ .../resources/js/data/styling/actions.js | 70 ++++++++++ .../resources/js/data/styling/constants.js | 28 ++++ .../resources/js/data/styling/controls.js | 23 ++++ .../resources/js/data/styling/hooks.js | 48 +++++++ .../resources/js/data/styling/index.js | 24 ++++ .../resources/js/data/styling/reducer.js | 55 ++++++++ .../resources/js/data/styling/resolvers.js | 36 +++++ .../resources/js/data/styling/selectors.js | 21 +++ modules/ppcp-settings/services.php | 8 ++ .../src/Data/StylingSettings.php | 14 +- .../src/Endpoint/StylingRestEndpoint.php | 126 ++++++++++++++++++ modules/ppcp-settings/src/SettingsModule.php | 1 + 14 files changed, 471 insertions(+), 7 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/data/styling/action-types.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/actions.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/constants.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/controls.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/hooks.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/index.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/reducer.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/resolvers.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/selectors.js create mode 100644 modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php diff --git a/modules/ppcp-settings/resources/js/data/index.js b/modules/ppcp-settings/resources/js/data/index.js index 274aac790..e447ff770 100644 --- a/modules/ppcp-settings/resources/js/data/index.js +++ b/modules/ppcp-settings/resources/js/data/index.js @@ -1,16 +1,20 @@ import { addDebugTools } from './debug'; import * as Onboarding from './onboarding'; import * as Common from './common'; +import * as Styling from './styling'; Onboarding.initStore(); Common.initStore(); +Styling.initStore(); export const OnboardingHooks = Onboarding.hooks; export const CommonHooks = Common.hooks; +export const StylingHooks = Styling.hooks; export const OnboardingStoreName = Onboarding.STORE_NAME; export const CommonStoreName = Common.STORE_NAME; +export const StylingStoreName = Styling.STORE_NAME; export * from './constants'; -addDebugTools( window.ppcpSettings, [ Onboarding, Common ] ); +addDebugTools( window.ppcpSettings, [ Onboarding, Common, Styling ] ); diff --git a/modules/ppcp-settings/resources/js/data/styling/action-types.js b/modules/ppcp-settings/resources/js/data/styling/action-types.js new file mode 100644 index 000000000..d487b1f5f --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/action-types.js @@ -0,0 +1,18 @@ +/** + * Action Types: Define unique identifiers for actions across all store modules. + * + * @file + */ + +export default { + // Transient data. + SET_TRANSIENT: 'STYLE:SET_TRANSIENT', + + // Persistent data. + SET_PERSISTENT: 'STYLE:SET_PERSISTENT', + RESET: 'STYLE:RESET', + HYDRATE: 'STYLE:HYDRATE', + + // Controls - always start with "DO_". + DO_PERSIST_DATA: 'STYLE:DO_PERSIST_DATA', +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/actions.js b/modules/ppcp-settings/resources/js/data/styling/actions.js new file mode 100644 index 000000000..25cf6f04b --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/actions.js @@ -0,0 +1,70 @@ +/** + * Action Creators: Define functions to create action objects. + * + * These functions update state or trigger side effects (e.g., async operations). + * Actions are categorized as Transient, Persistent, or Side effect. + * + * @file + */ + +import { select } from '@wordpress/data'; + +import ACTION_TYPES from './action-types'; +import { STORE_NAME } from './constants'; + +/** + * @typedef {Object} Action An action object that is handled by a reducer or control. + * @property {string} type - The action type. + * @property {Object?} payload - Optional payload for the action. + */ + +/** + * Special. Resets all values in the store to initial defaults. + * + * @return {Action} The action. + */ +export const reset = () => ( { type: ACTION_TYPES.RESET } ); + +/** + * Persistent. Set the full store details during app initialization. + * + * @param {{data: {}, flags?: {}}} payload + * @return {Action} The action. + */ +export const hydrate = ( payload ) => ( { + type: ACTION_TYPES.HYDRATE, + payload, +} ); + +/** + * Transient. Marks the store as "ready", i.e., fully initialized. + * + * @param {boolean} isReady + * @return {Action} The action. + */ +export const setIsReady = ( isReady ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { isReady }, +} ); + +/** + * Persistent. + * + * @param {string} shape + * @return {Action} The action. + */ +export const setShape = ( shape ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { shape }, +} ); + +/** + * Side effect. Triggers the persistence of store data to the server. + * + * @return {Action} The action. + */ +export const persist = function* () { + const data = yield select( STORE_NAME ).persistentData(); + + yield { type: ACTION_TYPES.DO_PERSIST_DATA, data }; +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js new file mode 100644 index 000000000..db1082f33 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -0,0 +1,28 @@ +/** + * Name of the Redux store module. + * + * Used by: Reducer, Selector, Index + * + * @type {string} + */ +export const STORE_NAME = 'wc/paypal/style'; + +/** + * REST path to hydrate data of this module by loading data from the WP DB. + * + * Used by: Resolvers + * See: StylingRestEndpoint.php + * + * @type {string} + */ +export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/styling'; + +/** + * REST path to persist data of this module to the WP DB. + * + * Used by: Controls + * See: StylingRestEndpoint.php + * + * @type {string} + */ +export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/styling'; diff --git a/modules/ppcp-settings/resources/js/data/styling/controls.js b/modules/ppcp-settings/resources/js/data/styling/controls.js new file mode 100644 index 000000000..9295b62bc --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/controls.js @@ -0,0 +1,23 @@ +/** + * Controls: Implement side effects, typically asynchronous operations. + * + * Controls use ACTION_TYPES keys as identifiers. + * They are triggered by corresponding actions and handle external interactions. + * + * @file + */ + +import apiFetch from '@wordpress/api-fetch'; + +import { REST_PERSIST_PATH } from './constants'; +import ACTION_TYPES from './action-types'; + +export const controls = { + async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) { + return await apiFetch( { + path: REST_PERSIST_PATH, + method: 'POST', + data, + } ); + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js new file mode 100644 index 000000000..de08f1124 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -0,0 +1,48 @@ +/** + * Hooks: Provide the main API for components to interact with the store. + * + * These encapsulate store interactions, offering a consistent interface. + * Hooks simplify data access and manipulation for components. + * + * @file + */ + +import { useSelect, useDispatch } from '@wordpress/data'; + +import { STORE_NAME } from './constants'; + +const useTransient = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).transientData()?.[ key ], + [ key ] + ); + +const usePersistent = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).persistentData()?.[ key ], + [ key ] + ); + +const useHooks = () => { + const { persist, setShape } = useDispatch( STORE_NAME ); + + // Read-only flags and derived state. + + // Transient accessors. + const isReady = useTransient( 'isReady' ); + + // Persistent accessors. + const shape = usePersistent( 'shape' ); + + return { + persist, + isReady, + shape, + setShape, + }; +}; + +export const useState = () => { + const { persist, isReady } = useHooks(); + return { persist, isReady }; +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/index.js b/modules/ppcp-settings/resources/js/data/styling/index.js new file mode 100644 index 000000000..28c162f98 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/index.js @@ -0,0 +1,24 @@ +import { createReduxStore, register } from '@wordpress/data'; +import { controls as wpControls } from '@wordpress/data-controls'; + +import { STORE_NAME } from './constants'; +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import * as hooks from './hooks'; +import { resolvers } from './resolvers'; +import { controls } from './controls'; + +export const initStore = () => { + const store = createReduxStore( STORE_NAME, { + reducer, + controls: { ...wpControls, ...controls }, + actions, + selectors, + resolvers, + } ); + + register( store ); +}; + +export { hooks, selectors, STORE_NAME }; diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js new file mode 100644 index 000000000..9e505dd24 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -0,0 +1,55 @@ +/** + * Reducer: Defines store structure and state updates for this module. + * + * Manages both transient (temporary) and persistent (saved) state. + * The initial state must define all properties, as dynamic additions are not supported. + * + * @file + */ + +import { createReducer, createSetters } from '../utils'; +import ACTION_TYPES from './action-types'; + +// Store structure. + +// Transient: Values that are _not_ saved to the DB (like app lifecycle-flags). +const defaultTransient = Object.freeze( { + isReady: false, +} ); + +// Persistent: Values that are loaded from the DB. +const defaultPersistent = Object.freeze( { + shape: 'rect', +} ); + +// Reducer logic. + +const [ setTransient, setPersistent ] = createSetters( + defaultTransient, + defaultPersistent +); + +const reducer = createReducer( defaultTransient, defaultPersistent, { + [ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) => + setTransient( state, payload ), + + [ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) => + setPersistent( state, payload ), + + [ ACTION_TYPES.RESET ]: ( state ) => { + const cleanState = setTransient( + setPersistent( state, defaultPersistent ), + defaultTransient + ); + + // Keep "read-only" details and initialization flags. + cleanState.isReady = true; + + return cleanState; + }, + + [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => + setPersistent( state, payload.data ), +} ); + +export default reducer; diff --git a/modules/ppcp-settings/resources/js/data/styling/resolvers.js b/modules/ppcp-settings/resources/js/data/styling/resolvers.js new file mode 100644 index 000000000..e59794746 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/resolvers.js @@ -0,0 +1,36 @@ +/** + * Resolvers: Handle asynchronous data fetching for the store. + * + * These functions update store state with data from external sources. + * Each resolver corresponds to a specific selector (selector with same name must exist). + * Resolvers are called automatically when selectors request unavailable data. + * + * @file + */ + +import { dispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { apiFetch } from '@wordpress/data-controls'; + +import { STORE_NAME, REST_HYDRATE_PATH } from './constants'; + +export const resolvers = { + /** + * Retrieve settings from the site's REST API. + */ + *persistentData() { + try { + const result = yield apiFetch( { path: REST_HYDRATE_PATH } ); + + yield dispatch( STORE_NAME ).hydrate( result ); + yield dispatch( STORE_NAME ).setIsReady( true ); + } catch ( e ) { + yield dispatch( 'core/notices' ).createErrorNotice( + __( + 'Error retrieving style-details.', + 'woocommerce-paypal-payments' + ) + ); + } + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/selectors.js b/modules/ppcp-settings/resources/js/data/styling/selectors.js new file mode 100644 index 000000000..14334fcf3 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/selectors.js @@ -0,0 +1,21 @@ +/** + * Selectors: Extract specific pieces of state from the store. + * + * These functions provide a consistent interface for accessing store data. + * They allow components to retrieve data without knowing the store structure. + * + * @file + */ + +const EMPTY_OBJ = Object.freeze( {} ); + +const getState = ( state ) => state || EMPTY_OBJ; + +export const persistentData = ( state ) => { + return getState( state ).data || EMPTY_OBJ; +}; + +export const transientData = ( state ) => { + const { data, ...transientState } = getState( state ); + return transientState || EMPTY_OBJ; +}; diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 6fc9d67e3..50afb3fdb 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -24,6 +24,8 @@ use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager; use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator; use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\Settings\Endpoint\StylingRestEndpoint; +use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; return array( 'settings.url' => static function ( ContainerInterface $container ) : string { @@ -64,12 +66,18 @@ return array( $container->get( 'wcgateway.is-send-only-country' ) ); }, + 'settings.data.styling' => static function ( ContainerInterface $container ) : StylingSettings { + return new StylingSettings(); + }, 'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint { return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); }, 'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint { return new CommonRestEndpoint( $container->get( 'settings.data.general' ) ); }, + 'settings.rest.styling' => static function ( ContainerInterface $container ) : StylingRestEndpoint { + return new StylingRestEndpoint( $container->get( 'settings.data.styling' ) ); + }, 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { return new RefreshFeatureStatusEndpoint( $container->get( 'wcgateway.settings' ), diff --git a/modules/ppcp-settings/src/Data/StylingSettings.php b/modules/ppcp-settings/src/Data/StylingSettings.php index 481625ca7..ee942693c 100644 --- a/modules/ppcp-settings/src/Data/StylingSettings.php +++ b/modules/ppcp-settings/src/Data/StylingSettings.php @@ -1,6 +1,6 @@ 'rect', + ); } } diff --git a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php new file mode 100644 index 000000000..97b1158f2 --- /dev/null +++ b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php @@ -0,0 +1,126 @@ + array( + 'js_name' => 'shape', + ), + ); + + /** + * Constructor. + * + * @param StylingSettings $settings The settings instance. + */ + public function __construct( StylingSettings $settings ) { + $this->settings = $settings; + } + + /** + * Configure REST API routes. + */ + public function register_routes() : void { + /** + * GET wc/v3/wc_paypal/styling + */ + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_details' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + + /** + * POST wc/v3/wc_paypal/styling + * { + * // Fields mentioned in $field_map[]['js_name'] + * } + */ + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_details' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + } + + /** + * Returns all styling details. + * + * @return WP_REST_Response The current styling details. + */ + public function get_details() : WP_REST_Response { + $js_data = $this->sanitize_for_javascript( + $this->settings->to_array(), + $this->field_map + ); + + return $this->return_success( + $js_data + ); + } + + /** + * Updates styling details based on the request. + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_REST_Response The updated styling details. + */ + public function update_details( WP_REST_Request $request ) : WP_REST_Response { + $wp_data = $this->sanitize_for_wordpress( + $request->get_params(), + $this->field_map + ); + + $this->settings->from_array( $wp_data ); + $this->settings->save(); + + return $this->get_details(); + } +} diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 59f752545..5bc032dfb 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -180,6 +180,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { $endpoints = array( $container->get( 'settings.rest.onboarding' ), $container->get( 'settings.rest.common' ), + $container->get( 'settings.rest.styling' ), $container->get( 'settings.rest.connect_manual' ), $container->get( 'settings.rest.login_link' ), $container->get( 'settings.rest.webhooks' ), From 0b624eb8bc3f53ae47f81071d18775b8ff2ca3b3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 13 Jan 2025 19:03:33 +0100 Subject: [PATCH 02/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20JS=20configur?= =?UTF-8?q?ation=20for=20styling=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Screens/Overview/TabStyling.js | 20 +-- .../resources/js/data/constants.js | 17 ++- .../resources/js/data/onboarding/constants.js | 23 +++- .../js/data/settings/tab-styling-data.js | 64 ---------- .../resources/js/data/styling/constants.js | 114 ++++++++++++++++++ 5 files changed, 155 insertions(+), 83 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js index 7ddd8be7a..ad931c844 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js @@ -7,11 +7,13 @@ import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'; import { defaultLocationSettings, paymentMethodOptions, - colorOptions, - shapeOptions, - buttonLayoutOptions, - buttonLabelOptions, } from '../../../data/settings/tab-styling-data'; +import { + STYLING_LABELS, + STYLING_COLORS, + STYLING_LAYOUTS, + STYLING_SHAPES, +} from '../../../data'; const TabStyling = () => { const [ location, setLocation ] = useState( 'cart' ); @@ -52,7 +54,7 @@ const TabStyling = () => { }, [] ); - }, [] ); + }, [ locationSettings ] ); const updateButtonSettings = ( key, value ) => { setLocationSettings( { @@ -223,7 +225,7 @@ const SectionButtonLayout = ( { locationSettings, updateButtonStyle } ) => { updateButtonStyle( 'layout', newValue ) } selected={ locationSettings.settings.style.layout } - options={ buttonLayoutOptions } + options={ Object.values( STYLING_LAYOUTS ) } /> ) @@ -242,7 +244,7 @@ const SectionButtonShape = ( { locationSettings, updateButtonStyle } ) => { updateButtonStyle( 'shape', newValue ) } selected={ locationSettings.settings.style.shape } - options={ shapeOptions } + options={ Object.values( STYLING_SHAPES ) } /> ); @@ -258,7 +260,7 @@ const SectionButtonLabel = ( { locationSettings, updateButtonStyle } ) => { } value={ locationSettings.settings.style.label } label={ __( 'Button Label', 'woocommerce-paypal-payments' ) } - options={ buttonLabelOptions } + options={ Object.values( STYLING_LABELS ) } /> ); @@ -274,7 +276,7 @@ const SectionButtonColor = ( { locationSettings, updateButtonStyle } ) => { updateButtonStyle( 'color', newValue ) } value={ locationSettings.settings.style.color } - options={ colorOptions } + options={ Object.values( STYLING_COLORS ) } /> ); diff --git a/modules/ppcp-settings/resources/js/data/constants.js b/modules/ppcp-settings/resources/js/data/constants.js index 5654ad476..fbc8e8e11 100644 --- a/modules/ppcp-settings/resources/js/data/constants.js +++ b/modules/ppcp-settings/resources/js/data/constants.js @@ -1,10 +1,9 @@ -export const BUSINESS_TYPES = { - CASUAL_SELLER: 'casual_seller', - BUSINESS: 'business', -}; +export { BUSINESS_TYPES, PRODUCT_TYPES } from './onboarding/constants'; -export const PRODUCT_TYPES = { - VIRTUAL: 'virtual', - PHYSICAL: 'physical', - SUBSCRIPTIONS: 'subscriptions', -}; +export { + STYLING_LOCATIONS, + STYLING_LABELS, + STYLING_COLORS, + STYLING_LAYOUTS, + STYLING_SHAPES, +} from './styling/constants'; diff --git a/modules/ppcp-settings/resources/js/data/onboarding/constants.js b/modules/ppcp-settings/resources/js/data/onboarding/constants.js index 4b33c6701..7c35ee693 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/constants.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/constants.js @@ -8,7 +8,7 @@ export const STORE_NAME = 'wc/paypal/onboarding'; /** - * REST path to hydrate data of this module by loading data from the WP DB.. + * REST path to hydrate data of this module by loading data from the WP DB. * * Used by: Resolvers * See: OnboardingRestEndpoint.php @@ -26,3 +26,24 @@ export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/onboarding'; * @type {string} */ export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/onboarding'; + +/** + * Onboarding options for StepBusiness + * + * @type {Object} + */ +export const BUSINESS_TYPES = { + CASUAL_SELLER: 'casual_seller', + BUSINESS: 'business', +}; + +/** + * Onboarding options for StepProducts + * + * @type {Object} + */ +export const PRODUCT_TYPES = { + VIRTUAL: 'virtual', + PHYSICAL: 'physical', + SUBSCRIPTIONS: 'subscriptions', +}; diff --git a/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js b/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js index 6bdb4f643..ae0636481 100644 --- a/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js +++ b/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js @@ -96,67 +96,3 @@ export const paymentMethodOptions = [ label: __( 'Apple Pay', 'woocommerce-paypal-payments' ), }, ]; - -export const buttonLabelOptions = [ - { - value: 'paypal', - label: __( 'PayPal', 'woocommerce-paypal-payments' ), - }, - { - value: 'checkout', - label: __( 'Checkout', 'woocommerce-paypal-payments' ), - }, - { - value: 'buynow', - label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ), - }, - { - value: 'pay', - 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: 'rect', - label: __( 'Rectangle', 'woocommerce-paypal-payments' ), - }, -]; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index db1082f33..e5e32efab 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -1,3 +1,5 @@ +import { __ } from '@wordpress/i18n'; + /** * Name of the Redux store module. * @@ -26,3 +28,115 @@ export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/styling'; * @type {string} */ export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/styling'; + +export const STYLING_LOCATIONS = { + cart: { + value: 'cart', + label: __( 'Cart', 'woocommerce-paypal-payments' ), + description: __( + 'Customize the appearance of the PayPal smart buttons on the Cart page and select which additional payment buttons to display in this location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'classic-checkout': { + value: 'classic-checkout', + label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), + description: __( + 'Customize the appearance of the PayPal smart buttons on the Classic Checkout page and choose which additional payment buttons to display in this location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'express-checkout': { + value: 'express-checkout', + label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), + description: __( + 'Customize the appearance of the PayPal smart buttons on the Express Checkout location and choose which additional payment buttons to display in this location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'mini-cart': { + value: 'mini-cart', + label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), + description: __( + 'Customize the appearance of the PayPal smart buttons on the Mini Cart and choose which additional payment buttons to display in this location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'product-page': { + value: 'product-page', + label: __( 'Product Page', 'woocommerce-paypal-payments' ), + description: __( + 'Customize the appearance of the PayPal smart buttons on the Product Page and choose which additional payment buttons to display in this location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, +}; + +export const STYLING_LABELS = { + paypal: { + value: 'paypal', + label: __( 'PayPal', 'woocommerce-paypal-payments' ), + }, + checkout: { + value: 'checkout', + label: __( 'Checkout', 'woocommerce-paypal-payments' ), + }, + buynow: { + value: 'buynow', + label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ), + }, + pay: { + value: 'pay', + label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_COLORS = { + gold: { + value: 'gold', + label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ), + }, + blue: { + value: 'blue', + label: __( 'Blue', 'woocommerce-paypal-payments' ), + }, + silver: { + value: 'silver', + label: __( 'Silver', 'woocommerce-paypal-payments' ), + }, + black: { + value: 'black', + label: __( 'Black', 'woocommerce-paypal-payments' ), + }, + white: { + value: 'white', + label: __( 'White', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_LAYOUTS = { + vertical: { + value: 'vertical', + label: __( 'Vertical', 'woocommerce-paypal-payments' ), + }, + horizontal: { + value: 'horizontal', + label: __( 'Horizontal', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_SHAPES = { + pill: { + value: 'pill', + label: __( 'Pill', 'woocommerce-paypal-payments' ), + }, + rect: { + value: 'rect', + label: __( 'Rectangle', 'woocommerce-paypal-payments' ), + }, +}; From fdffbee0aba883d57990ad97bade50a7b6cdd221 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 13 Jan 2025 19:04:02 +0100 Subject: [PATCH 03/63] =?UTF-8?q?=E2=9C=A8=20New=20DTO=20to=20hold=20styli?= =?UTF-8?q?ng=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/DTO/LocationStylingDTO.php | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 modules/ppcp-settings/src/DTO/LocationStylingDTO.php diff --git a/modules/ppcp-settings/src/DTO/LocationStylingDTO.php b/modules/ppcp-settings/src/DTO/LocationStylingDTO.php new file mode 100644 index 000000000..14e4471ea --- /dev/null +++ b/modules/ppcp-settings/src/DTO/LocationStylingDTO.php @@ -0,0 +1,85 @@ +location = $location; + $this->enabled = $enabled; + $this->methods = $methods; + $this->shape = $shape; + $this->label = $label; + $this->color = $color; + } +} From 98a626dfa355cafe6332c65de854c32071fe5fdc Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 14 Jan 2025 10:28:10 +0100 Subject: [PATCH 04/63] =?UTF-8?q?=F0=9F=9A=A7=20Sample=20reducer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/reducer.js | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index 9e505dd24..3ac82ab6d 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -19,7 +19,41 @@ const defaultTransient = Object.freeze( { // Persistent: Values that are loaded from the DB. const defaultPersistent = Object.freeze( { - shape: 'rect', + cart: { + enabled: true, + methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], + shape: 'rect', + label: 'Pay', + color: 'gold', + }, + 'classic-checkout': { + enabled: true, + methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], + shape: 'rect', + label: 'Checkout', + color: 'gold', + }, + 'express-checkout': { + enabled: true, + methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], + shape: 'rect', + label: 'Checkout', + color: 'gold', + }, + 'mini-cart': { + enabled: true, + methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], + shape: 'rect', + label: 'Pay', + color: 'gold', + }, + product: { + enabled: true, + methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], + shape: 'rect', + label: 'Buy', + color: 'gold', + }, } ); // Reducer logic. From ef57724040c8f173e2221d204ac92836649611b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 14 Jan 2025 13:12:37 +0100 Subject: [PATCH 05/63] =?UTF-8?q?=F0=9F=9A=9A=20Move=20the=20TabStyling=20?= =?UTF-8?q?component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Screens/{Overview => Settings/Tabs}/TabStyling.js | 6 +++--- .../resources/js/Components/Screens/Settings/Tabs/index.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename modules/ppcp-settings/resources/js/Components/Screens/{Overview => Settings/Tabs}/TabStyling.js (98%) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js similarity index 98% rename from modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index ad931c844..671e133bc 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -1,19 +1,19 @@ import { __, sprintf } from '@wordpress/i18n'; import { SelectControl, RadioControl } from '@wordpress/components'; -import { PayPalCheckboxGroup } from '../../ReusableComponents/Fields'; +import { PayPalCheckboxGroup } from '../../../ReusableComponents/Fields'; import { useState, useMemo, useEffect } from '@wordpress/element'; import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'; import { defaultLocationSettings, paymentMethodOptions, -} from '../../../data/settings/tab-styling-data'; +} from '../../../../data/settings/tab-styling-data'; import { STYLING_LABELS, STYLING_COLORS, STYLING_LAYOUTS, STYLING_SHAPES, -} from '../../../data'; +} from '../../../../data'; const TabStyling = () => { const [ location, setLocation ] = useState( 'cart' ); diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/index.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/index.js index fa19dc6e4..85d4cf5bd 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/index.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/index.js @@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n'; import TabOverview from '../../Overview/TabOverview'; import TabPaymentMethods from '../../Overview/TabPaymentMethods'; import TabSettings from '../../Overview/TabSettings'; -import TabStyling from '../../Overview/TabStyling'; +import TabStyling from './TabStyling'; import TabPayLaterMessaging from '../../Overview/TabPayLaterMessaging'; /** From fa67abc8e4c048dbfdd127b8b2cc99c45a4a8cce Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 14 Jan 2025 16:18:54 +0100 Subject: [PATCH 06/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20the=20butt?= =?UTF-8?q?on-preview=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/PaymentButtonPreview.js | 28 +++++++++++++++++++ .../Screens/Settings/Tabs/TabStyling.js | 28 ++----------------- 2 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js new file mode 100644 index 000000000..0c43ba902 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js @@ -0,0 +1,28 @@ +import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js'; + +const PREVIEW_CLIENT_ID = 'test'; +const PREVIEW_MERCHANT_ID = 'QTQX5NP6N9WZU'; + +const PaymentButtonPreview = ( { + style, + components = [ 'buttons', 'googlepay' ], +} ) => { + return ( + + + Error + + + ); +}; + +export default PaymentButtonPreview; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index 671e133bc..98019c93c 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -2,7 +2,6 @@ import { __, sprintf } from '@wordpress/i18n'; import { SelectControl, RadioControl } from '@wordpress/components'; import { PayPalCheckboxGroup } from '../../../ReusableComponents/Fields'; import { useState, useMemo, useEffect } from '@wordpress/element'; -import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'; import { defaultLocationSettings, @@ -14,6 +13,7 @@ import { STYLING_LAYOUTS, STYLING_SHAPES, } from '../../../../data'; +import PaymentButtonPreview from '../Components/PaymentButtonPreview'; const TabStyling = () => { const [ location, setLocation ] = useState( 'cart' ); @@ -127,8 +127,8 @@ const TabStyling = () => {
-
@@ -313,26 +313,4 @@ const SectionButtonTagline = ( { locationSettings, updateButtonStyle } ) => { ); }; -const SectionButtonPreview = ( { locationSettings } ) => { - return ( - - - Error - - - ); -}; - export default TabStyling; From e9644ba02602af6d100c9ef200b98078e712c629 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 14 Jan 2025 16:19:18 +0100 Subject: [PATCH 07/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve=20the=20redu?= =?UTF-8?q?cer=20by=20using=20defined=20constants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/constants.js | 5 +++ .../resources/js/data/styling/reducer.js | 31 ++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index e5e32efab..357fe43a1 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -33,6 +33,7 @@ export const STYLING_LOCATIONS = { cart: { value: 'cart', label: __( 'Cart', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. description: __( 'Customize the appearance of the PayPal smart buttons on the Cart page and select which additional payment buttons to display in this location.', 'wooocommerce-paypal-payments' @@ -42,6 +43,7 @@ export const STYLING_LOCATIONS = { 'classic-checkout': { value: 'classic-checkout', label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. description: __( 'Customize the appearance of the PayPal smart buttons on the Classic Checkout page and choose which additional payment buttons to display in this location.', 'wooocommerce-paypal-payments' @@ -51,6 +53,7 @@ export const STYLING_LOCATIONS = { 'express-checkout': { value: 'express-checkout', label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. description: __( 'Customize the appearance of the PayPal smart buttons on the Express Checkout location and choose which additional payment buttons to display in this location.', 'wooocommerce-paypal-payments' @@ -60,6 +63,7 @@ export const STYLING_LOCATIONS = { 'mini-cart': { value: 'mini-cart', label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), + // translators: %s is the URL to a documentation page. description: __( 'Customize the appearance of the PayPal smart buttons on the Mini Cart and choose which additional payment buttons to display in this location.', 'wooocommerce-paypal-payments' @@ -69,6 +73,7 @@ export const STYLING_LOCATIONS = { 'product-page': { value: 'product-page', label: __( 'Product Page', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. description: __( 'Customize the appearance of the PayPal smart buttons on the Product Page and choose which additional payment buttons to display in this location.', 'wooocommerce-paypal-payments' diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index 3ac82ab6d..b28c3f265 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -9,6 +9,7 @@ import { createReducer, createSetters } from '../utils'; import ACTION_TYPES from './action-types'; +import { STYLING_COLORS, STYLING_SHAPES } from './constants'; // Store structure. @@ -21,38 +22,38 @@ const defaultTransient = Object.freeze( { const defaultPersistent = Object.freeze( { cart: { enabled: true, - methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], - shape: 'rect', + methods: [], label: 'Pay', - color: 'gold', + shape: STYLING_SHAPES.rect.value, + color: STYLING_COLORS.gold.value, }, 'classic-checkout': { enabled: true, - methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], - shape: 'rect', + methods: [], label: 'Checkout', - color: 'gold', + shape: STYLING_SHAPES.rect.value, + color: STYLING_COLORS.gold.value, }, 'express-checkout': { enabled: true, - methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], - shape: 'rect', + methods: [], label: 'Checkout', - color: 'gold', + shape: STYLING_SHAPES.rect.value, + color: STYLING_COLORS.gold.value, }, 'mini-cart': { enabled: true, - methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], - shape: 'rect', + methods: [], label: 'Pay', - color: 'gold', + shape: STYLING_SHAPES.rect.value, + color: STYLING_COLORS.gold.value, }, product: { enabled: true, - methods: [ 'venmo', 'applepay', 'googlepay', 'credit card' ], - shape: 'rect', + methods: [], label: 'Buy', - color: 'gold', + shape: STYLING_SHAPES.rect.value, + color: STYLING_COLORS.gold.value, }, } ); From 491352710e83280b0de67a3fb5c0bc2c2134934a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 15 Jan 2025 12:25:37 +0100 Subject: [PATCH 08/63] =?UTF-8?q?=F0=9F=9A=A7=20Refactor=20the=20Styling?= =?UTF-8?q?=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/css/_variables.scss | 2 + .../settings/_tab-paylater-configurator.scss | 2 +- .../screens/settings/_tab-styling.scss | 65 ++-- .../Components/Styling/LocationSelector.js | 35 ++ .../Components/Styling/PreviewPanel.js | 11 + .../Components/Styling/SettingsPanel.js | 184 +++++++++++ .../Components/Styling/StylingSection.js | 29 ++ .../Screens/Settings/Tabs/TabStyling.js | 308 +----------------- .../resources/js/data/styling/constants.js | 10 +- 9 files changed, 301 insertions(+), 345 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js diff --git a/modules/ppcp-settings/resources/css/_variables.scss b/modules/ppcp-settings/resources/css/_variables.scss index 5ccf191e5..53483adb6 100644 --- a/modules/ppcp-settings/resources/css/_variables.scss +++ b/modules/ppcp-settings/resources/css/_variables.scss @@ -55,6 +55,8 @@ $card-vertical-gap: 48px; --color-gray-100: #{$color-gray-100}; --color-gradient-dark: #{$color-gradient-dark}; + --color-preview-background: #FAF8F5; + --color-separators: #{$color-gray-200}; --color-text-title: #{$color-gray-900}; --color-text-main: #{$color-text-text}; --color-text-teriary: #{$color-text-tertiary}; diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss index ae49bed82..9444f4e18 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss @@ -1,6 +1,6 @@ .ppcp-r-paylater-configurator { display: flex; - border: 1px solid $color-gray-200; + border: 1px solid var(--color-separators); border-radius: 8px; overflow: hidden; diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index c75d6a4c9..87472e377 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -1,61 +1,49 @@ +$width-settings-panel: 422px; + .ppcp-r-styling { display: flex; - border: 1px solid $color-gray-200; + border: 1px solid var(--color-separators); border-radius: 8px; overflow: hidden; - &__section:not(:last-child) { - border-bottom: 1px solid black; - padding-bottom: 24px; - margin-bottom: 28px; - border-bottom: 1px solid $color-gray-600; - } + .ppcp-r-styling__title { + @include font(14, 16, 600); - &__main-title { - @include font(14, 20, 600); - color: $color-gray-800; - margin: 0 0 8px 0; + color: var(--color-text-title); 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; + .header-section .ppcp-r-styling__title { + @include font(16, 20, 600); } - &__preview { - width: calc(100% - 422px); - background-color: #FAF8F5; + /* The settings-panel (left side) */ + .settings-panel { + width: $width-settings-panel; + padding: 48px; + + .ppcp-r-styling__section { + padding-bottom: 24px; + margin-bottom: 28px; + border-bottom: 1px solid var(--color-separators); + } + } + + /* The preview area (right side) */ + .preview-panel { + width: calc(100% - $width-settings-panel); + background-color: var(--color-preview-background); display: flex; align-items: center; - &-inner { + .preview-panel-inner { width: 100%; padding: 24px; } } - &__section--rc { - .ppcp-r-styling__title { - @include font(13, 20, 600); - color: $color-black; - display: block; - margin: 0 0 18px 0; - } - } - - &__section--empty.ppcp-r-styling__section { - padding-bottom: 0; - margin-bottom: 0; - border-bottom: none; - } + /* --- * &__select { label { @@ -118,4 +106,5 @@ } } } + // */ } diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js new file mode 100644 index 000000000..af0b83840 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js @@ -0,0 +1,35 @@ +import { SelectControl } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +import StylingSection from './StylingSection'; + +const LocationSelector = ( { choices = [], location, setLocation } ) => { + // TODO. move to store/hook. + const locationData = choices.find( + ( choice ) => choice.value === location + ); + const { description, link } = locationData || {}; + const locationDescription = sprintf( description, link ); + + return ( + + setLocation( choice ) } + /> +

+ + ); +}; + +export default LocationSelector; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js new file mode 100644 index 000000000..66658f2d6 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js @@ -0,0 +1,11 @@ +import PaymentButtonPreview from '../PaymentButtonPreview'; + +const PreviewPanel = ( { settings } ) => ( +

+
+ +
+
+); + +export default PreviewPanel; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js new file mode 100644 index 000000000..d6571a4c5 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -0,0 +1,184 @@ +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { RadioControl, SelectControl } from '@wordpress/components'; + +import { STYLING_LOCATIONS } from '../../../../../data'; +import { PayPalCheckboxGroup } from '../../../../ReusableComponents/Fields'; +import LocationSelector from './LocationSelector'; +import StylingSection from './StylingSection'; + +const SettingsPanel = () => { + const { location, setLocation } = useState( 'cart' ); + + const currentLocationSettings = { + settings: { shape: '', label: '', color: '' }, + }; + const handleChange = () => {}; + + return ( +
+ + + + + + + + + + +
+ ); +}; + +export default SettingsPanel; + +// ----- +const SectionPaymentMethods = ( { + locationSettings, + updateButtonSettings, +} ) => { + const paymentMethodOptions = []; + + return ( + +
+ + updateButtonSettings( 'paymentMethods', newValue ) + } + currentValue={ locationSettings.paymentMethods } + /> +
+
+ ); +}; + +const SectionButtonLayout = ( { locationSettings, updateButtonStyle } ) => { + const buttonLayoutIsAllowed = + locationSettings.layout && locationSettings.tagline === false; + return ( + buttonLayoutIsAllowed && ( + + + updateButtonStyle( 'layout', newValue ) + } + selected={ locationSettings.layout } + options={ [] } + /> + + ) + ); +}; + +const SectionButtonShape = ( { locationSettings, updateButtonStyle } ) => { + return ( + + + updateButtonStyle( 'shape', newValue ) + } + selected={ locationSettings.shape } + options={ [] } + /> + + ); +}; + +const SectionButtonLabel = ( { locationSettings, updateButtonStyle } ) => { + return ( + + + updateButtonStyle( 'label', newValue ) + } + value={ locationSettings.label } + label={ __( 'Button Label', 'woocommerce-paypal-payments' ) } + options={ [] } + /> + + ); +}; + +const SectionButtonColor = ( { locationSettings, updateButtonStyle } ) => { + return ( + + + updateButtonStyle( 'color', newValue ) + } + value={ locationSettings.color } + options={ [] } + /> + + ); +}; + +const SectionButtonTagline = ( { locationSettings, updateButtonStyle } ) => { + const taglineIsAllowed = + locationSettings.hasOwnProperty( 'tagline' ) && + locationSettings.layout === 'horizontal'; + + return ( + taglineIsAllowed && ( + + { + updateButtonStyle( 'tagline', newValue ); + } } + currentValue={ locationSettings.tagline } + /> + + ) + ); +}; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js new file mode 100644 index 000000000..ae72e5b03 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js @@ -0,0 +1,29 @@ +import classnames from 'classnames'; + +const StylingSection = ( { + title, + className = '', + description = '', + children, +} ) => { + const sectionClasses = classnames( 'ppcp-r-styling__section', className ); + + return ( +
+ { title } + + { description && ( +

+ ) } + + { children } +

+ ); +}; + +export default StylingSection; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index 98019c93c..4a0b8f015 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -1,316 +1,22 @@ -import { __, sprintf } from '@wordpress/i18n'; -import { SelectControl, RadioControl } from '@wordpress/components'; -import { PayPalCheckboxGroup } from '../../../ReusableComponents/Fields'; -import { useState, useMemo, useEffect } from '@wordpress/element'; +import { useState } from '@wordpress/element'; -import { - defaultLocationSettings, - paymentMethodOptions, -} from '../../../../data/settings/tab-styling-data'; -import { - STYLING_LABELS, - STYLING_COLORS, - STYLING_LAYOUTS, - STYLING_SHAPES, -} from '../../../../data'; -import PaymentButtonPreview from '../Components/PaymentButtonPreview'; +import { defaultLocationSettings } from '../../../../data/settings/tab-styling-data'; + +import PreviewPanel from '../Components/Styling/PreviewPanel'; +import SettingsPanel from '../Components/Styling/SettingsPanel'; const TabStyling = () => { - const [ location, setLocation ] = useState( 'cart' ); - const [ canRender, setCanRender ] = useState( false ); const [ locationSettings, setLocationSettings ] = useState( { ...defaultLocationSettings, } ); - // Sometimes buttons won't render. This fixes the timing problem. - useEffect( () => { - const handleDOMContentLoaded = () => setCanRender( true ); - if ( - document.readyState === 'interactive' || - document.readyState === 'complete' - ) { - handleDOMContentLoaded(); - } else { - document.addEventListener( - 'DOMContentLoaded', - handleDOMContentLoaded - ); - } - }, [] ); - - const currentLocationSettings = useMemo( () => { - return locationSettings[ location ]; - }, [ location, locationSettings ] ); - - const locationOptions = useMemo( () => { - return Object.keys( locationSettings ).reduce( - ( locationOptionsData, key ) => { - locationOptionsData.push( { - value: locationSettings[ key ].value, - label: locationSettings[ key ].label, - } ); - - return locationOptionsData; - }, - [] - ); - }, [ locationSettings ] ); - - const updateButtonSettings = ( key, value ) => { - setLocationSettings( { - ...locationSettings, - [ location ]: { - ...currentLocationSettings, - settings: { - ...currentLocationSettings.settings, - [ key ]: value, - }, - }, - } ); - }; - - const updateButtonStyle = ( key, value ) => { - setLocationSettings( { - ...locationSettings, - [ location ]: { - ...currentLocationSettings, - settings: { - ...currentLocationSettings.settings, - style: { - ...currentLocationSettings.settings.style, - [ key ]: value, - }, - }, - }, - } ); - }; - - if ( ! canRender ) { - return <>; - } - return (
-
- - - + - - - - - - -
-
-
- -
-
+
); }; -const TabStylingSection = ( props ) => { - let sectionTitleClassName = 'ppcp-r-styling__section'; - - if ( props?.className ) { - sectionTitleClassName += ` ${ props.className }`; - } - - return ( -
- { props.title } - { props?.description && ( -

- ) } - { props.children } -

- ); -}; - -const SectionIntro = ( { location } ) => { - const { description, descriptionLink } = - defaultLocationSettings[ location ]; - const buttonStyleDescription = sprintf( description, descriptionLink ); - - return ( - - ); -}; - -const SectionLocations = ( { locationOptions, location, setLocation } ) => { - return ( - - setLocation( newLocation ) } - label={ __( 'Locations', 'woocommerce-paypal-payments' ) } - options={ locationOptions } - /> - - ); -}; - -const SectionPaymentMethods = ( { - locationSettings, - updateButtonSettings, -} ) => { - return ( - -
- - updateButtonSettings( 'paymentMethods', newValue ) - } - currentValue={ locationSettings.settings.paymentMethods } - /> -
-
- ); -}; - -const SectionButtonLayout = ( { locationSettings, updateButtonStyle } ) => { - const buttonLayoutIsAllowed = - locationSettings.settings.style?.layout && - locationSettings.settings.style?.tagline === false; - return ( - buttonLayoutIsAllowed && ( - - - updateButtonStyle( 'layout', newValue ) - } - selected={ locationSettings.settings.style.layout } - options={ Object.values( STYLING_LAYOUTS ) } - /> - - ) - ); -}; - -const SectionButtonShape = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'shape', newValue ) - } - selected={ locationSettings.settings.style.shape } - options={ Object.values( STYLING_SHAPES ) } - /> - - ); -}; - -const SectionButtonLabel = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'label', newValue ) - } - value={ locationSettings.settings.style.label } - label={ __( 'Button Label', 'woocommerce-paypal-payments' ) } - options={ Object.values( STYLING_LABELS ) } - /> - - ); -}; - -const SectionButtonColor = ( { locationSettings, updateButtonStyle } ) => { - return ( - - - updateButtonStyle( 'color', newValue ) - } - value={ locationSettings.settings.style.color } - options={ Object.values( STYLING_COLORS ) } - /> - - ); -}; - -const SectionButtonTagline = ( { locationSettings, updateButtonStyle } ) => { - const taglineIsAllowed = - locationSettings.settings.style.hasOwnProperty( 'tagline' ) && - locationSettings.settings.style?.layout === 'horizontal'; - - return ( - taglineIsAllowed && ( - - { - updateButtonStyle( 'tagline', newValue ); - } } - currentValue={ locationSettings.settings.style.tagline } - /> - - ) - ); -}; - export default TabStyling; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index 357fe43a1..92d3714da 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -35,7 +35,7 @@ export const STYLING_LOCATIONS = { label: __( 'Cart', 'woocommerce-paypal-payments' ), // translators: %s is the URL to a documentation page. description: __( - 'Customize the appearance of the PayPal smart buttons on the Cart page and select which additional payment buttons to display in this location.', + 'More details on the Cart page.', 'wooocommerce-paypal-payments' ), link: '#', @@ -45,7 +45,7 @@ export const STYLING_LOCATIONS = { label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), // translators: %s is the URL to a documentation page. description: __( - 'Customize the appearance of the PayPal smart buttons on the Classic Checkout page and choose which additional payment buttons to display in this location.', + 'More details on the Classic Checkout page.', 'wooocommerce-paypal-payments' ), link: '#', @@ -55,7 +55,7 @@ export const STYLING_LOCATIONS = { label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), // translators: %s is the URL to a documentation page. description: __( - 'Customize the appearance of the PayPal smart buttons on the Express Checkout location and choose which additional payment buttons to display in this location.', + 'More details on the Express Checkout location.', 'wooocommerce-paypal-payments' ), link: '#', @@ -65,7 +65,7 @@ export const STYLING_LOCATIONS = { label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), // translators: %s is the URL to a documentation page. description: __( - 'Customize the appearance of the PayPal smart buttons on the Mini Cart and choose which additional payment buttons to display in this location.', + 'More details on the Mini Cart.', 'wooocommerce-paypal-payments' ), link: '#', @@ -75,7 +75,7 @@ export const STYLING_LOCATIONS = { label: __( 'Product Page', 'woocommerce-paypal-payments' ), // translators: %s is the URL to a documentation page. description: __( - 'Customize the appearance of the PayPal smart buttons on the Product Page and choose which additional payment buttons to display in this location.', + 'More details on the Product Page.', 'wooocommerce-paypal-payments' ), link: '#', From 55d1fd3699d18efacd51445934021146930a4666 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 15 Jan 2025 18:43:12 +0100 Subject: [PATCH 09/63] =?UTF-8?q?=F0=9F=9A=A7=20Move=20styling=20data=20to?= =?UTF-8?q?=20a=20dummy=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/PaymentButtonPreview.js | 28 --- .../Components/Styling/LocationSelector.js | 16 +- .../Components/Styling/PreviewPanel.js | 36 +++- .../Components/Styling/SettingsPanel.js | 200 +++++++----------- .../Screens/Settings/Tabs/TabStyling.js | 121 +++++++++-- .../js/data/settings/tab-styling-data.js | 98 --------- .../resources/js/data/styling/constants.js | 19 ++ 7 files changed, 242 insertions(+), 276 deletions(-) delete mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js delete mode 100644 modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js deleted file mode 100644 index 0c43ba902..000000000 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/PaymentButtonPreview.js +++ /dev/null @@ -1,28 +0,0 @@ -import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js'; - -const PREVIEW_CLIENT_ID = 'test'; -const PREVIEW_MERCHANT_ID = 'QTQX5NP6N9WZU'; - -const PaymentButtonPreview = ( { - style, - components = [ 'buttons', 'googlepay' ], -} ) => { - return ( - - - Error - - - ); -}; - -export default PaymentButtonPreview; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js index af0b83840..c05b68727 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js @@ -1,14 +1,14 @@ import { SelectControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; +// Dummy hook. +import { useStylingProps } from '../../Tabs/TabStyling'; + import StylingSection from './StylingSection'; -const LocationSelector = ( { choices = [], location, setLocation } ) => { - // TODO. move to store/hook. - const locationData = choices.find( - ( choice ) => choice.value === location - ); - const { description, link } = locationData || {}; +const LocationSelector = ( { location, setLocation } ) => { + const { locationChoices, locationDetails } = useStylingProps( location ); + const { description, link } = locationDetails || {}; const locationDescription = sprintf( description, link ); return ( @@ -23,9 +23,9 @@ const LocationSelector = ( { choices = [], location, setLocation } ) => { setLocation( choice ) } + onChange={ setLocation } />

diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js index 66658f2d6..8b83785b7 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/PreviewPanel.js @@ -1,11 +1,33 @@ -import PaymentButtonPreview from '../PaymentButtonPreview'; +import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js'; -const PreviewPanel = ( { settings } ) => ( -

-
- +const PREVIEW_CLIENT_ID = 'test'; +const PREVIEW_MERCHANT_ID = 'QTQX5NP6N9WZU'; + +const PreviewPanel = () => { + // TODO: Make those props dynamic based on location style settings. + const style = {}; + const components = [ 'buttons', 'googlepay' ]; + + const providerOptions = { + clientId: PREVIEW_CLIENT_ID, + merchantId: PREVIEW_MERCHANT_ID, + components: components.join( ',' ), + 'disable-funding': 'card', + 'buyer-country': 'US', + currency: 'USD', + }; + + return ( +
+
+ + + Error + + +
-
-); + ); +}; export default PreviewPanel; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index d6571a4c5..e739ab629 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -1,54 +1,28 @@ import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; import { RadioControl, SelectControl } from '@wordpress/components'; -import { STYLING_LOCATIONS } from '../../../../../data'; +// Dummy hook. +import { useStylingLocation, useStylingProps } from '../../Tabs/TabStyling'; + import { PayPalCheckboxGroup } from '../../../../ReusableComponents/Fields'; import LocationSelector from './LocationSelector'; import StylingSection from './StylingSection'; const SettingsPanel = () => { - const { location, setLocation } = useState( 'cart' ); - - const currentLocationSettings = { - settings: { shape: '', label: '', color: '' }, - }; - const handleChange = () => {}; + const { location, setLocation } = useStylingLocation(); return (
- - - - - - - - - + + + + + +
); }; @@ -56,129 +30,115 @@ const SettingsPanel = () => { export default SettingsPanel; // ----- -const SectionPaymentMethods = ( { - locationSettings, - updateButtonSettings, -} ) => { - const paymentMethodOptions = []; +const SectionPaymentMethods = ( { location } ) => { + const { paymentMethods, setPaymentMethods, paymentMethodChoices } = + useStylingProps( location ); return ( -
- - updateButtonSettings( 'paymentMethods', newValue ) - } - currentValue={ locationSettings.paymentMethods } - /> -
+
); }; -const SectionButtonLayout = ( { locationSettings, updateButtonStyle } ) => { - const buttonLayoutIsAllowed = - locationSettings.layout && locationSettings.tagline === false; - return ( - buttonLayoutIsAllowed && ( - - - updateButtonStyle( 'layout', newValue ) - } - selected={ locationSettings.layout } - options={ [] } - /> - - ) - ); -}; +const SectionButtonLayout = ( { location } ) => { + const { supportsLayout, layout, setLayout, layoutChoices } = + useStylingProps( location ); + + if ( ! supportsLayout ) { + return null; + } -const SectionButtonShape = ( { locationSettings, updateButtonStyle } ) => { return ( - updateButtonStyle( 'shape', newValue ) - } - selected={ locationSettings.shape } - options={ [] } + options={ layoutChoices } + selected={ layout } + onChange={ setLayout } /> ); }; -const SectionButtonLabel = ( { locationSettings, updateButtonStyle } ) => { +const SectionButtonShape = ( { location } ) => { + const { shape, setShape, shapeChoices } = useStylingProps( location ); + + return ( + + + + ); +}; + +const SectionButtonLabel = ( { location } ) => { + const { label, setLabel, labelChoices } = useStylingProps( location ); + return ( - updateButtonStyle( 'label', newValue ) - } - value={ locationSettings.label } label={ __( 'Button Label', 'woocommerce-paypal-payments' ) } - options={ [] } + className="ppcp-r-styling__select" + options={ labelChoices } + value={ label } + onChange={ setLabel } /> ); }; -const SectionButtonColor = ( { locationSettings, updateButtonStyle } ) => { +const SectionButtonColor = ( { location } ) => { + const { color, setColor, colorChoices } = useStylingProps( location ); + return ( - updateButtonStyle( 'color', newValue ) - } - value={ locationSettings.color } - options={ [] } + className=" ppcp-r-styling__select" + options={ colorChoices } + value={ color } + onChange={ setColor } /> ); }; -const SectionButtonTagline = ( { locationSettings, updateButtonStyle } ) => { - const taglineIsAllowed = - locationSettings.hasOwnProperty( 'tagline' ) && - locationSettings.layout === 'horizontal'; +const SectionButtonTagline = ( { location } ) => { + const { supportsTagline, tagline, setTagline, taglineChoices } = + useStylingProps( location ); + + if ( ! supportsTagline ) { + return null; + } return ( - taglineIsAllowed && ( - - { - updateButtonStyle( 'tagline', newValue ); - } } - currentValue={ locationSettings.tagline } - /> - - ) + + + ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index 4a0b8f015..07f5ad4d9 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -1,22 +1,113 @@ -import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useState } from '@wordpress/element'; -import { defaultLocationSettings } from '../../../../data/settings/tab-styling-data'; +import { + STYLING_COLORS, + STYLING_LABELS, + STYLING_LAYOUTS, + STYLING_LOCATIONS, + STYLING_PAYMENT_METHODS, + STYLING_SHAPES, +} from '../../../../data/styling/constants'; import PreviewPanel from '../Components/Styling/PreviewPanel'; import SettingsPanel from '../Components/Styling/SettingsPanel'; -const TabStyling = () => { - const [ locationSettings, setLocationSettings ] = useState( { - ...defaultLocationSettings, - } ); - - return ( -
- - - -
- ); -}; +const TabStyling = () => ( +
+ + +
+); export default TabStyling; + +// ---------------------------------------------------------------------------- + +// Temporary "hook" to extract logic before moving it to the Redux store. +export const useStylingLocation = () => { + const [ location, setLocation ] = useState( 'cart' ); + + return { location, setLocation }; +}; + +export const useStylingProps = ( location ) => { + const defaultStyle = { + paymentMethods: [], + color: 'gold', + shape: 'rect', + label: 'paypal', + layout: 'vertical', + tagline: false, + }; + + const [ styles, setStyles ] = useState( { + cart: { ...defaultStyle, label: 'checkout' }, + 'classic-checkout': { ...defaultStyle }, + 'express-checkout': { ...defaultStyle, label: 'pay' }, + 'mini-cart': { ...defaultStyle, label: 'pay' }, + 'product-page': { ...defaultStyle }, + } ); + + const getLocationStyle = useCallback( + ( prop ) => styles[ location ]?.[ prop ], + [ location, styles ] + ); + + const setLocationStyle = useCallback( + ( prop, value ) => { + setStyles( ( prevState ) => ( { + ...prevState, + [ location ]: { + ...prevState[ location ], + [ prop ]: value, + }, + } ) ); + }, + [ location ] + ); + + return { + // Location (drop down). + locationChoices: Object.values( STYLING_LOCATIONS ), + locationDetails: STYLING_LOCATIONS[ location ], + + // Payment methods (checkboxes). + paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), + paymentMethods: getLocationStyle( 'paymentMethods' ), + setPaymentMethods: ( methods ) => + setLocationStyle( 'paymentMethods', methods ), + + // Color (dropdown). + colorChoices: Object.values( STYLING_COLORS ), + color: getLocationStyle( 'color' ), + setColor: ( color ) => setLocationStyle( 'color', color ), + + // Shape (radio). + shapeChoices: Object.values( STYLING_SHAPES ), + shape: getLocationStyle( 'shape' ), + setShape: ( shape ) => setLocationStyle( 'shape', shape ), + + // Label (dropdown). + labelChoices: Object.values( STYLING_LABELS ), + label: getLocationStyle( 'label' ), + setLabel: ( label ) => setLocationStyle( 'label', label ), + + // Layout (radio). + layoutChoices: Object.values( STYLING_LAYOUTS ), + supportsLayout: true, + layout: getLocationStyle( 'layout' ), + setLayout: ( layout ) => setLocationStyle( 'layout', layout ), + + // Tagline (checkbox). + taglineChoices: [ + { + value: 'tagline', + label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), + }, + ], + supportsTagline: true, + tagline: getLocationStyle( 'tagline' ), + setTagline: ( tagline ) => setLocationStyle( 'tagline', tagline ), + }; +}; diff --git a/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js b/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js deleted file mode 100644 index ae0636481..000000000 --- a/modules/ppcp-settings/resources/js/data/settings/tab-styling-data.js +++ /dev/null @@ -1,98 +0,0 @@ -import { __ } from '@wordpress/i18n'; - -const cartAndExpressCheckoutSettings = { - paymentMethods: [], - style: { - shape: 'pill', - label: 'paypal', - color: 'gold', - }, -}; - -const settings = { - paymentMethods: [], - style: { - layout: 'vertical', - shape: cartAndExpressCheckoutSettings.style.shape, - label: cartAndExpressCheckoutSettings.style.label, - color: cartAndExpressCheckoutSettings.style.color, - tagline: false, - }, -}; - -export const defaultLocationSettings = { - cart: { - value: 'cart', - label: __( 'Cart', 'woocommerce-paypal-payments' ), - settings: { ...cartAndExpressCheckoutSettings }, - // translators: %s: Link to Cart page - description: __( - 'Customize the appearance of the PayPal smart buttons on the [MISSING LINK]Cart page and select which additional payment buttons to display in this location.', - 'wooocommerce-paypal-payments' - ), - descriptionLink: '#', - }, - 'classic-checkout': { - value: 'classic-checkout', - label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), - settings: { ...settings }, - // translators: %s: Link to Classic Checkout page - description: __( - 'Customize the appearance of the PayPal smart buttons on the [MISSING LINK]Classic Checkout page and choose which additional payment buttons to display in this location.', - 'wooocommerce-paypal-payments' - ), - descriptionLink: '#', - }, - 'express-checkout': { - value: 'express-checkout', - label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), - settings: { ...cartAndExpressCheckoutSettings }, - // translators: %s: Link to Express Checkout location - description: __( - 'Customize the appearance of the PayPal smart buttons on the [MISSING LINK]Express Checkout location and choose which additional payment buttons to display in this location.', - 'wooocommerce-paypal-payments' - ), - descriptionLink: '#', - }, - 'mini-cart': { - value: 'mini-cart', - label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), - settings: { ...settings }, - // translators: %s: Link to Mini Cart - description: __( - 'Customize the appearance of the PayPal smart buttons on the [MISSING LINK]Mini Cart and choose which additional payment buttons to display in this location.', - 'wooocommerce-paypal-payments' - ), - descriptionLink: '#', - }, - 'product-page': { - value: 'product-page', - label: __( 'Product Page', 'woocommerce-paypal-payments' ), - settings: { ...settings }, - // translators: %s: Link to Product Page - description: __( - 'Customize the appearance of the PayPal smart buttons on the [MISSING LINK]Product Page and choose which additional payment buttons to display in this location.', - 'wooocommerce-paypal-payments' - ), - descriptionLink: '#', - }, -}; - -export const paymentMethodOptions = [ - { - value: 'venmo', - label: __( 'Venmo', 'woocommerce-paypal-payments' ), - }, - { - value: 'paylater', - label: __( 'Pay Later', 'woocommerce-paypal-payments' ), - }, - { - value: 'googlepay', - label: __( 'Google Pay', 'woocommerce-paypal-payments' ), - }, - { - value: 'applepay', - label: __( 'Apple Pay', 'woocommerce-paypal-payments' ), - }, -]; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index 92d3714da..395d80cdb 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -145,3 +145,22 @@ export const STYLING_SHAPES = { label: __( 'Rectangle', 'woocommerce-paypal-payments' ), }, }; + +export const STYLING_PAYMENT_METHODS = { + venmo: { + value: 'venmo', + label: __( 'Venmo', 'woocommerce-paypal-payments' ), + }, + paylater: { + value: 'paylater', + label: __( 'Pay Later', 'woocommerce-paypal-payments' ), + }, + googlepay: { + value: 'googlepay', + label: __( 'Google Pay', 'woocommerce-paypal-payments' ), + }, + applepay: { + value: 'applepay', + label: __( 'Apple Pay', 'woocommerce-paypal-payments' ), + }, +}; From 85794e77ce2a492cdd3b926df8d8ff5fd8ea6211 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 15 Jan 2025 19:41:46 +0100 Subject: [PATCH 10/63] =?UTF-8?q?=F0=9F=92=84=20Improve=20the=20(broken)?= =?UTF-8?q?=20UI=20in=20the=20Styling-tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/css/components/_reusable.scss | 1 + .../reusable-components/_hstack.scss | 20 +++ .../screens/settings/_tab-styling.scss | 51 ++---- .../Components/ReusableComponents/Fields.js | 161 +++++++++++------- .../Components/ReusableComponents/HStack.js | 26 +++ .../Components/Styling/SettingsPanel.js | 68 ++++---- .../resources/js/data/styling/constants.js | 6 + 7 files changed, 198 insertions(+), 135 deletions(-) create mode 100644 modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss create mode 100644 modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js diff --git a/modules/ppcp-settings/resources/css/components/_reusable.scss b/modules/ppcp-settings/resources/css/components/_reusable.scss index 4d4f5c1ba..2de887f57 100644 --- a/modules/ppcp-settings/resources/css/components/_reusable.scss +++ b/modules/ppcp-settings/resources/css/components/_reusable.scss @@ -4,6 +4,7 @@ @import './reusable-components/busy-state'; @import './reusable-components/button'; @import './reusable-components/fields'; +@import './reusable-components/hstack'; @import './reusable-components/navigation'; @import './reusable-components/onboarding-header'; @import './reusable-components/payment-method-icons'; diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss new file mode 100644 index 000000000..e55584d1c --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_hstack.scss @@ -0,0 +1,20 @@ +.components-flex { + display: flex; + -webkit-box-align: stretch; + align-items: stretch; + flex-direction: column; + -webkit-box-pack: center; + justify-content: center; + + .components-h-stack { + flex-direction: row; + justify-content: flex-start; + gap: 32px; + } + + // Fix layout for checkboxes inside a flex-stack. + .components-checkbox-control >.components-base-control__field > .components-flex { + flex-direction: row; + gap: 12px; + } +} diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index 87472e377..61534cc04 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -27,6 +27,16 @@ $width-settings-panel: 422px; padding-bottom: 24px; margin-bottom: 28px; border-bottom: 1px solid var(--color-separators); + + &:last-child { + padding-bottom: 0; + margin-bottom: 0; + border-bottom-style: none; + } + } + + .components-radio-control__option { + min-width: 100px; } } @@ -65,46 +75,7 @@ $width-settings-panel: 422px; 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; - } - } - } - // */ +//*/ } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js index 74336951f..862e057bb 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js @@ -1,107 +1,136 @@ import { CheckboxControl } from '@wordpress/components'; +import classNames from 'classnames'; -export const PayPalCheckbox = ( props ) => { - let isChecked = null; +export const PayPalCheckbox = ( { + currentValue, + label, + value, + checked = null, + disabled = null, + changeCallback, +} ) => { + let isChecked = checked; - if ( Array.isArray( props.currentValue ) ) { - isChecked = props.currentValue.includes( props.value ); - } else { - isChecked = props.currentValue; + if ( null === isChecked ) { + if ( Array.isArray( currentValue ) ) { + isChecked = currentValue.includes( value ); + } else { + isChecked = currentValue; + } } + const onChange = ( newState ) => { + let newValue; + + if ( ! Array.isArray( currentValue ) ) { + newValue = newState; + } else if ( newState ) { + newValue = [ ...currentValue, value ]; + } else { + newValue = currentValue.filter( + ( optionValue ) => optionValue !== value + ); + } + + changeCallback( newValue ); + }; + return (
+ { /* todo: Can we remove the wrapper div? */ } - handleCheckboxState( checked, props ) - } + disabled={ disabled } + onChange={ onChange } />
); }; -export const PayPalCheckboxGroup = ( props ) => { - const renderCheckboxGroup = () => { - return props.value.map( ( checkbox ) => { - return ( - - ); - } ); - }; +export const CheckboxGroup = ( { options, value, onChange } ) => ( + <> + { options.map( ( checkbox ) => ( + + ) ) } + +); - return <>{ renderCheckboxGroup() }; -}; - -export const PayPalRdb = ( props ) => { +export const PayPalRdb = ( { + id, + name, + value, + currentValue, + handleRdbState, +} ) => { return (
+ { /* todo: Can we remove the wrapper div? */ } props.handleRdbState( props.value ) } + id={ id } + checked={ value === currentValue } + name={ name } + value={ value } + onChange={ () => handleRdbState( value ) } />
); }; -export const PayPalRdbWithContent = ( props ) => { - const className = [ 'ppcp-r__radio-wrapper' ]; - - if ( props?.className ) { - className.push( props.className ); - } +export const PayPalRdbWithContent = ( { + className, + id, + name, + label, + description, + value, + currentValue, + handleRdbState, + toggleAdditionalContent, + children, +} ) => { + const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className ); return (
-
- +
+ +
- - { props.description && ( + + { description && (

) }

- { props?.toggleAdditionalContent && - props.children && - props.value === props.currentValue && ( -
- { props.children } -
- ) } + { toggleAdditionalContent && children && value === currentValue && ( +
+ { children } +
+ ) }
); }; - -export const handleCheckboxState = ( checked, props ) => { - let newValue = null; - if ( ! Array.isArray( props.currentValue ) ) { - newValue = checked; - } else if ( checked ) { - newValue = [ ...props.currentValue, props.value ]; - } else { - newValue = props.currentValue.filter( - ( value ) => value !== props.value - ); - } - props.changeCallback( newValue ); -}; diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js new file mode 100644 index 000000000..2c54ac7da --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/HStack.js @@ -0,0 +1,26 @@ +/** + * Temporary component, until the experimental HStack block editor component is stable. + * + * @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs + * @file + */ +import classNames from 'classnames'; + +const HStack = ( { className, spacing = 3, children } ) => { + const wrapperClass = classNames( + 'components-flex components-h-stack', + className + ); + + const styles = { + gap: `calc(${ 4 * spacing }px)`, + }; + + return ( +
+ { children } +
+ ); +}; + +export default HStack; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index e739ab629..f357bb287 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -4,7 +4,8 @@ import { RadioControl, SelectControl } from '@wordpress/components'; // Dummy hook. import { useStylingLocation, useStylingProps } from '../../Tabs/TabStyling'; -import { PayPalCheckboxGroup } from '../../../../ReusableComponents/Fields'; +import { CheckboxGroup } from '../../../../ReusableComponents/Fields'; +import HStack from '../../../../ReusableComponents/HStack'; import LocationSelector from './LocationSelector'; import StylingSection from './StylingSection'; @@ -30,6 +31,7 @@ const SettingsPanel = () => { export default SettingsPanel; // ----- + const SectionPaymentMethods = ( { location } ) => { const { paymentMethods, setPaymentMethods, paymentMethodChoices } = useStylingProps( location ); @@ -39,11 +41,13 @@ const SectionPaymentMethods = ( { location } ) => { title={ __( 'Payment Methods', 'woocommerce-paypal-payments' ) } className="payment-methods" > - + + + ); }; @@ -61,12 +65,13 @@ const SectionButtonLayout = ( { location } ) => { className="button-layout" title={ __( 'Button Layout', 'woocommerce-paypal-payments' ) } > - + + + ); }; @@ -79,12 +84,13 @@ const SectionButtonShape = ( { location } ) => { title={ __( 'Shape', 'woocommerce-paypal-payments' ) } className="button-shape" > - + + + ); }; @@ -93,10 +99,11 @@ const SectionButtonLabel = ( { location } ) => { const { label, setLabel, labelChoices } = useStylingProps( location ); return ( - + { const { color, setColor, colorChoices } = useStylingProps( location ); return ( - + { title={ __( 'Tagline', 'woocommerce-paypal-payments' ) } className="tagline" > - + + + ); }; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index 395d80cdb..f21ac7adf 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -147,6 +147,12 @@ export const STYLING_SHAPES = { }; export const STYLING_PAYMENT_METHODS = { + paypal: { + value: '', + label: __( 'PayPal', 'woocommerce-paypal-payments' ), + checked: true, + disabled: true, + }, venmo: { value: 'venmo', label: __( 'Venmo', 'woocommerce-paypal-payments' ), From 1cac69ce990c35c730496413209b10e07f06ccd1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 14:37:44 +0100 Subject: [PATCH 11/63] =?UTF-8?q?=E2=9C=A8=20Make=20some=20generic=20UI=20?= =?UTF-8?q?components=20themeable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-settings/resources/css/_mixins.scss | 11 ++ .../reusable-components/_fields.scss | 186 +++++++++--------- .../reusable-components/_settings-block.scss | 19 +- .../Components/ReusableComponents/Fields.js | 22 ++- .../SettingsBlocks/SettingsBlockElements.js | 47 +++-- 5 files changed, 169 insertions(+), 116 deletions(-) diff --git a/modules/ppcp-settings/resources/css/_mixins.scss b/modules/ppcp-settings/resources/css/_mixins.scss index d2fac6cd2..29cb3af1c 100644 --- a/modules/ppcp-settings/resources/css/_mixins.scss +++ b/modules/ppcp-settings/resources/css/_mixins.scss @@ -43,3 +43,14 @@ display: flex; gap: $gap; } + +@mixin disabled-state($control-type) { + .components-#{$control-type}-control.is-disabled { + .components-#{$control-type}-control__input, + .components-#{$control-type}-control__label, + .components-base-control__help { + opacity: 0.5; + cursor: default; + } + } +} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss index 1a9cf102c..195367dfb 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss @@ -1,27 +1,96 @@ -.ppcp-r { +.ppcp-r__radio-value { + @include hide-input-field; - &__radio-value { - @include hide-input-field; + &:checked + .ppcp-r__radio-presentation { + position: relative; - &:checked + .ppcp-r__radio-presentation { - position: relative; + &::before { + content: ''; + width: 12px; + height: 12px; + border-radius: 12px; + background-color: $color-blueberry; + display: block; + position: absolute; + transform: translate(-50%, -50%); + left: 50%; + top: 50%; + } + } +} - &::before { - content: ''; - width: 12px; - height: 12px; - border-radius: 12px; - background-color: $color-blueberry; - display: block; - position: absolute; - transform: translate(-50%, -50%); - left: 50%; - top: 50%; - } +.ppcp-r__radio-presentation { + @include fake-input-field(20px); +} + +.ppcp-r__checkbox-presentation { + @include fake-input-field(2px); +} + +.ppcp-r__radio-wrapper { + display: flex; + gap: 18px; + align-items: center; + position: relative; + + label { + @include font(13, 20, 400); + color: $color-gray-800; + } +} + +.ppcp-r__radio-description { + @include font(13, 20, 400); + margin: 0; + color: $color-gray-800; +} + +.ppcp-r__radio-content-additional { + padding-left: 38px; + padding-top: 18px; +} + + +.ppcp-r-app { + @include disabled-state('base'); + @include disabled-state('checkbox'); + + .components-base-control__label { + @include font(13, 16, 600); + color: $color-gray-900; + text-transform: none; + } + + .components-base-control__input { + border: 1px solid $color-gray-700; + border-radius: 2px; + box-shadow: none; + + &:focus { + border-color: $color-blueberry; } } - &__checkbox { + .components-base-control__help { + margin-bottom: 0; + } + + // Text input fields. + input[type='text'] { + @include font(14, 20, 400); + @include primaryFont; + padding: 7px 11px; + border-radius: 2px; + } + + // Select lists. + select { + @include font(14, 20, 400); + padding: 7px 27px 7px 11px; + } + + // Checkboxes. + .components-checkbox-control { position: relative; input { @@ -30,7 +99,7 @@ &:checked { background-color: $color-blueberry; - border-color:$color-blueberry; + border-color: $color-blueberry; } } @@ -43,78 +112,17 @@ } } - &__radio-presentation { - @include fake-input-field(20px); + // Custom styles. + .components-form-toggle.is-checked > .components-form-toggle__track { + background-color: $color-blueberry; } - &__checkbox-presentation { - @include fake-input-field(2px); - } - - &__radio-wrapper { - display: flex; - gap: 18px; - align-items: center; - position: relative; - - label { - @include font(13, 20, 400); - color: $color-gray-800; - } - } - - &__radio-description { - @include font(13, 20, 400); - margin: 0; - color: $color-gray-800; - } - - &__radio-content-additional { - padding-left: 38px; - padding-top: 18px; - } -} - -.components-base-control { - &__label { - color: $color-gray-900; - @include font(13, 16, 600); - text-transform: none; - } - - &__input { - border: 1px solid $color-gray-700; - border-radius: 2px; - box-shadow: none; - - &:focus { - border-color: $color-blueberry; + .ppcp-r-vertical-text-control { + .components-base-control__field { + display: flex; + flex-direction: column; + gap: 0; + margin: 0; } } } - - -input[type='text'] { - padding: 7px 11px; - @include font(14, 20, 400); - @include primaryFont; - border-radius: 2px; -} - -select { - padding: 7px 27px 7px 11px; - @include font(14, 20, 400); -} - -.components-form-toggle.is-checked > .components-form-toggle__track { - background-color: $color-blueberry; -} - -.ppcp-r-vertical-text-control { - .components-base-control__field { - display: flex; - flex-direction: column; - gap: 0; - margin: 0; - } -} diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss index f65c5c9d5..f5ddb5ea1 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss @@ -4,7 +4,7 @@ .ppcp-r-settings-block { display: flex; flex-direction: column; - gap: 16px 0; + gap: var(--block-item-gap, 16px) 0; &.ppcp-r-settings-block__input, &.ppcp-r-settings-block__select { @@ -16,8 +16,8 @@ flex-direction: column; gap: 6px; - &:not(:last-child):not(.ppcp-r-settings-block--accordion__header) { - padding-bottom: 6px; + &:not(:last-child) { + padding-bottom: var(--block-header-gap, 6px); } } @@ -27,6 +27,15 @@ display: block; text-transform: uppercase; + &.style-alt { + @include font(14, 16, 600); + text-transform: none; + } + + &.style-big { + @include font(16, 20, 600); + } + .ppcp-r-title-badge { text-transform: none; margin-left: 6px; @@ -89,8 +98,8 @@ } + .ppcp-r-settings-block:not(.no-gap) { - margin-top: 32px; - padding-top: 32px; + margin-top: var(--block-separator-gap, 32px); + padding-top: var(--block-separator-gap, 32px); border-top: 1px solid var(--color-gray-200); } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js index 862e057bb..d979cffba 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/Fields.js @@ -19,6 +19,8 @@ export const PayPalCheckbox = ( { } } + const className = classNames( { 'is-disabled': disabled } ); + const onChange = ( newState ) => { let newValue; @@ -36,16 +38,14 @@ export const PayPalCheckbox = ( { }; return ( -
- { /* todo: Can we remove the wrapper div? */ } - -
+ ); }; @@ -58,6 +58,8 @@ export const CheckboxGroup = ( { options, value, onChange } ) => ( value={ checkbox.value } checked={ checkbox.checked } disabled={ checkbox.disabled } + description={ checkbox.description } + tooltip={ checkbox.tooltip } currentValue={ value } changeCallback={ onChange } /> diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js index ec3ba31ae..de45a601b 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js @@ -1,9 +1,20 @@ +import classNames from 'classnames'; + // Block Elements -export const Title = ( { children, className = '' } ) => ( - - { children } - -); +export const Title = ( { + children, + altStyle = false, + big = false, + className = '', +} ) => { + className = classNames( 'ppcp-r-settings-block__title', className, { + 'style-alt': altStyle, + 'style-big': big, + } ); + + return { children }; +}; + export const TitleWrapper = ( { children } ) => ( { children } ); @@ -14,13 +25,25 @@ export const SupplementaryLabel = ( { children } ) => ( ); -export const Description = ( { children, className = '' } ) => ( - - { children } - -); +export const Description = ( { children, asHtml = false, className = '' } ) => { + // Don't output anything if description is empty. + if ( ! children ) { + return null; + } + + className = classNames( 'ppcp-r-settings-block__description', className ); + + if ( ! asHtml ) { + return { children }; + } + + return ( + + ); +}; export const Action = ( { children } ) => (
{ children }
From e5eb1a4435c463a251362a95677d4a43e33691f3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 14:39:27 +0100 Subject: [PATCH 12/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Switch=20Styling=20U?= =?UTF-8?q?I=20to=20use=20generic=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/settings/_tab-styling.scss | 52 +++++++------------ .../Components/Styling/StylingSection.js | 34 ++++++------ 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index 61534cc04..d88fe64f7 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -1,21 +1,29 @@ $width-settings-panel: 422px; .ppcp-r-styling { + --block-item-gap: 0; + --block-separator-gap: 24px; + --block-header-gap: 18px; + display: flex; border: 1px solid var(--color-separators); border-radius: 8px; overflow: hidden; - .ppcp-r-styling__title { - @include font(14, 16, 600); + .ppcp-r-settings-block { + &.header-section { + margin-bottom: 22px + } - color: var(--color-text-title); - display: block; - margin: 0 0 18px 0; - } + // Select-fields have a smaller gap between the header and input field. + &.has-select { + --block-header-gap: 8px; + } - .header-section .ppcp-r-styling__title { - @include font(16, 20, 600); + // Above the payment methods is a slightly larger gap. + &.payment-methods { + --block-separator-gap: 28px; + } } /* The settings-panel (left side) */ @@ -28,6 +36,7 @@ $width-settings-panel: 422px; margin-bottom: 28px; border-bottom: 1px solid var(--color-separators); + &.no-gap, &:last-child { padding-bottom: 0; margin-bottom: 0; @@ -35,6 +44,7 @@ $width-settings-panel: 422px; } } + // Horizontal radio buttons have a width of 100px. .components-radio-control__option { min-width: 100px; } @@ -52,30 +62,4 @@ $width-settings-panel: 422px; padding: 24px; } } - - /* --- * - - &__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; - } - } - } -} -//*/ } diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js index ae72e5b03..51290831a 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js @@ -1,28 +1,32 @@ -import classnames from 'classnames'; +import SettingsBlock from '../../../../ReusableComponents/SettingsBlocks/SettingsBlock'; +import { + Description, + Header, + Title, +} from '../../../../ReusableComponents/SettingsBlocks'; const StylingSection = ( { title, + bigTitle = false, className = '', description = '', + separatorAndGap = true, children, } ) => { - const sectionClasses = classnames( 'ppcp-r-styling__section', className ); - return ( -
- { title } - - { description && ( -

- ) } + +

+ + { title } + + { description } +
{ children } -
+ ); }; From 11e6624dfc280bf1a829133e58c42cd879bf954d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 14:39:55 +0100 Subject: [PATCH 13/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20common=20S?= =?UTF-8?q?tyling=20components=20to=20own=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Styling/LocationSelector.js | 36 ++++--- .../Components/Styling/SettingsPanel.js | 93 +++++++------------ .../Styling/StylingSectionWithCheckboxes.js | 39 ++++++++ .../Styling/StylingSectionWithRadiobuttons.js | 39 ++++++++ .../Styling/StylingSectionWithSelect.js | 36 +++++++ 5 files changed, 169 insertions(+), 74 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js index c05b68727..01745fbdc 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js @@ -4,7 +4,9 @@ import { __, sprintf } from '@wordpress/i18n'; // Dummy hook. import { useStylingProps } from '../../Tabs/TabStyling'; +import { Description } from '../../../../ReusableComponents/SettingsBlocks'; import StylingSection from './StylingSection'; +import StylingSectionWithSelect from './StylingSectionWithSelect'; const LocationSelector = ( { location, setLocation } ) => { const { locationChoices, locationDetails } = useStylingProps( location ); @@ -12,23 +14,29 @@ const LocationSelector = ( { location, setLocation } ) => { const locationDescription = sprintf( description, link ); return ( - - + + -

- + > + + { locationDescription } + + + ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index f357bb287..40d418276 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -1,13 +1,12 @@ import { __ } from '@wordpress/i18n'; -import { RadioControl, SelectControl } from '@wordpress/components'; // Dummy hook. import { useStylingLocation, useStylingProps } from '../../Tabs/TabStyling'; -import { CheckboxGroup } from '../../../../ReusableComponents/Fields'; -import HStack from '../../../../ReusableComponents/HStack'; import LocationSelector from './LocationSelector'; -import StylingSection from './StylingSection'; +import StylingSectionWithSelect from './StylingSectionWithSelect'; +import StylingSectionWithCheckboxes from './StylingSectionWithCheckboxes'; +import StylingSectionWithRadiobuttons from './StylingSectionWithRadiobuttons'; const SettingsPanel = () => { const { location, setLocation } = useStylingLocation(); @@ -37,18 +36,13 @@ const SectionPaymentMethods = ( { location } ) => { useStylingProps( location ); return ( - - - - - + options={ paymentMethodChoices } + value={ paymentMethods } + onChange={ setPaymentMethods } + /> ); }; @@ -61,18 +55,13 @@ const SectionButtonLayout = ( { location } ) => { } return ( - - - - - + options={ layoutChoices } + selected={ layout } + onChange={ setLayout } + /> ); }; @@ -80,18 +69,13 @@ const SectionButtonShape = ( { location } ) => { const { shape, setShape, shapeChoices } = useStylingProps( location ); return ( - - - - - + options={ shapeChoices } + selected={ shape } + onChange={ setShape } + /> ); }; @@ -99,16 +83,13 @@ const SectionButtonLabel = ( { location } ) => { const { label, setLabel, labelChoices } = useStylingProps( location ); return ( - - - + options={ labelChoices } + value={ label } + onChange={ setLabel } + /> ); }; @@ -116,16 +97,13 @@ const SectionButtonColor = ( { location } ) => { const { color, setColor, colorChoices } = useStylingProps( location ); return ( - - - + options={ colorChoices } + value={ color } + onChange={ setColor } + /> ); }; @@ -138,17 +116,12 @@ const SectionButtonTagline = ( { location } ) => { } return ( - - - - - + options={ taglineChoices } + value={ tagline } + onChange={ setTagline } + /> ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js new file mode 100644 index 000000000..cc0b259d6 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js @@ -0,0 +1,39 @@ +import classNames from 'classnames'; + +import { CheckboxGroup } from '../../../../ReusableComponents/Fields'; +import HStack from '../../../../ReusableComponents/HStack'; +import StylingSection from './StylingSection'; + +const StylingSectionWithCheckboxes = ( { + title, + className = '', + description = '', + separatorAndGap = true, + options, + value, + onChange, + children, +} ) => { + className = classNames( 'has-checkboxes', className ); + + return ( + + + + + + { children } + + ); +}; + +export default StylingSectionWithCheckboxes; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js new file mode 100644 index 000000000..4bc33326f --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js @@ -0,0 +1,39 @@ +import { RadioControl } from '@wordpress/components'; +import classNames from 'classnames'; + +import HStack from '../../../../ReusableComponents/HStack'; +import StylingSection from './StylingSection'; + +const StylingSectionWithRadiobuttons = ( { + title, + className = '', + description = '', + separatorAndGap = true, + options, + selected, + onChange, + children, +} ) => { + className = classNames( 'has-radio-buttons', className ); + + return ( + + + + + + { children } + + ); +}; + +export default StylingSectionWithRadiobuttons; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js new file mode 100644 index 000000000..c4164d6b8 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js @@ -0,0 +1,36 @@ +import { SelectControl } from '@wordpress/components'; +import classNames from 'classnames'; + +import StylingSection from './StylingSection'; + +const StylingSectionWithSelect = ( { + title, + className = '', + description = '', + separatorAndGap = true, + options, + value, + onChange, + children, +} ) => { + className = classNames( 'has-select', className ); + + return ( + + + + { children } + + ); +}; + +export default StylingSectionWithSelect; From 560f6ed30df17e5ba8b3b24f9319371b19c62f41 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 14:53:58 +0100 Subject: [PATCH 14/63] =?UTF-8?q?=F0=9F=92=84=20Fix=20some=20minor=20CSS?= =?UTF-8?q?=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reusable-components/_settings-block.scss | 11 +++++++++- .../css/components/screens/_settings.scss | 22 ++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss index f5ddb5ea1..8888bf2c0 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-block.scss @@ -4,7 +4,7 @@ .ppcp-r-settings-block { display: flex; flex-direction: column; - gap: var(--block-item-gap, 16px) 0; + gap: var(--block-item-gap, 16px); &.ppcp-r-settings-block__input, &.ppcp-r-settings-block__select { @@ -97,6 +97,15 @@ margin-left: 5px; } + .ppcp-r-settings-block__action { + display: flex; + align-items: center; + + .components-flex { + row-gap: 0; + } + } + + .ppcp-r-settings-block:not(.no-gap) { margin-top: var(--block-separator-gap, 32px); padding-top: var(--block-separator-gap, 32px); diff --git a/modules/ppcp-settings/resources/css/components/screens/_settings.scss b/modules/ppcp-settings/resources/css/components/screens/_settings.scss index 2f536ac96..207817cb4 100644 --- a/modules/ppcp-settings/resources/css/components/screens/_settings.scss +++ b/modules/ppcp-settings/resources/css/components/screens/_settings.scss @@ -22,6 +22,7 @@ &:hover { cursor: pointer; + .ppcp-r-todo-item__inner { .ppcp-r-todo-item__description { color: $color-text-text; @@ -117,7 +118,7 @@ font-weight: 500; } - margin-top:24px; + margin-top: 24px; } } @@ -137,10 +138,6 @@ } } - &__show-all-data { - margin-left: 12px; - } - &__status-label { @include font(11, 22, 600); color: $color-gray-900; @@ -151,12 +148,16 @@ &__status-value { @include font(13, 26, 400); color: $color-text-tertiary; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } &__data { display: flex; flex-direction: column; gap: 12px; + width: 100%; } &__status-toggle--toggled { @@ -168,9 +169,7 @@ &__status-row { display: flex; flex-direction: column; - * { - user-select: none; - } + strong { @include font(14, 24, 600); color: $color-gray-800; @@ -181,11 +180,6 @@ .ppcp-r-connection-status__status-toggle { line-height: 0; } - &--first { - &:hover { - cursor: pointer; - } - } } @media screen and (max-width: 767px) { @@ -195,9 +189,11 @@ } &__status-row { flex-wrap: wrap; + strong { width: 100%; } + span { word-break: break-all; } From 59a4991cf3d2481c995c69d34ad7cf52883ca005 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 14:55:13 +0100 Subject: [PATCH 15/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20a=20compon?= =?UTF-8?q?ent=20from=20settings.scss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../css/components/screens/_settings.scss | 80 +------------------ .../screens/settings/_connection-status.scss | 78 ++++++++++++++++++ 2 files changed, 79 insertions(+), 79 deletions(-) create mode 100644 modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss diff --git a/modules/ppcp-settings/resources/css/components/screens/_settings.scss b/modules/ppcp-settings/resources/css/components/screens/_settings.scss index 207817cb4..0b8802138 100644 --- a/modules/ppcp-settings/resources/css/components/screens/_settings.scss +++ b/modules/ppcp-settings/resources/css/components/screens/_settings.scss @@ -1,4 +1,5 @@ @import './settings/input'; +@import './settings/connection-status'; @import './settings/tab-styling'; @import './settings/tab-paylater-configurator'; @@ -122,85 +123,6 @@ } } -// Connection Status -.ppcp-r-connection-status { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 12px; - - &__status-status { - margin: 0 0 8px 0; - - strong { - @include font(14, 24, 700); - color: $color-black; - } - } - - &__status-label { - @include font(11, 22, 600); - color: $color-gray-900; - display: block; - text-transform: uppercase; - } - - &__status-value { - @include font(13, 26, 400); - color: $color-text-tertiary; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - &__data { - display: flex; - flex-direction: column; - gap: 12px; - width: 100%; - } - - &__status-toggle--toggled { - .ppcp-r-connection-status__show-all-data { - transform: rotate(180deg); - } - } - - &__status-row { - display: flex; - flex-direction: column; - - strong { - @include font(14, 24, 600); - color: $color-gray-800; - margin-right: 12px; - white-space: nowrap; - } - - .ppcp-r-connection-status__status-toggle { - line-height: 0; - } - } - - @media screen and (max-width: 767px) { - flex-wrap: wrap; - &__status { - width: 100%; - } - &__status-row { - flex-wrap: wrap; - - strong { - width: 100%; - } - - span { - word-break: break-all; - } - } - } -} - // Feature Refresh .ppcp-r-feature-refresh { display: flex; diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss new file mode 100644 index 000000000..2bf1417e6 --- /dev/null +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_connection-status.scss @@ -0,0 +1,78 @@ +// Connection Status +.ppcp-r-connection-status { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12px; + + &__status-status { + margin: 0 0 8px 0; + + strong { + @include font(14, 24, 700); + color: $color-black; + } + } + + &__status-label { + @include font(11, 22, 600); + color: $color-gray-900; + display: block; + text-transform: uppercase; + } + + &__status-value { + @include font(13, 26, 400); + color: $color-text-tertiary; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &__data { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; + } + + &__status-toggle--toggled { + .ppcp-r-connection-status__show-all-data { + transform: rotate(180deg); + } + } + + &__status-row { + display: flex; + flex-direction: column; + + strong { + @include font(14, 24, 600); + color: $color-gray-800; + margin-right: 12px; + white-space: nowrap; + } + + .ppcp-r-connection-status__status-toggle { + line-height: 0; + } + } + + @media screen and (max-width: 767px) { + flex-wrap: wrap; + &__status { + width: 100%; + } + &__status-row { + flex-wrap: wrap; + + strong { + width: 100%; + } + + span { + word-break: break-all; + } + } + } +} From 484356dcc00d015ef76320fd0783e8b05b97ba70 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 15:02:25 +0100 Subject: [PATCH 16/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Shorten=20the=20impo?= =?UTF-8?q?rt=20path=20for=20styling=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/Components/Screens/Settings/Tabs/TabStyling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index 07f5ad4d9..f0ee60151 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -8,7 +8,7 @@ import { STYLING_LOCATIONS, STYLING_PAYMENT_METHODS, STYLING_SHAPES, -} from '../../../../data/styling/constants'; +} from '../../../../data'; import PreviewPanel from '../Components/Styling/PreviewPanel'; import SettingsPanel from '../Components/Styling/SettingsPanel'; From c16e5e4c58e17e3c800c3eb14f30bf7876140fca Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 15:03:46 +0100 Subject: [PATCH 17/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20UI=20confi?= =?UTF-8?q?guration=20to=20separate=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/configuration.js | 10 ++ .../resources/js/data/constants.js | 9 -- .../ppcp-settings/resources/js/data/index.js | 2 +- .../js/data/onboarding/configuration.js | 26 +++ .../resources/js/data/onboarding/constants.js | 21 --- .../resources/js/data/onboarding/hooks.js | 2 +- .../js/data/styling/configuration.js | 149 ++++++++++++++++++ .../resources/js/data/styling/constants.js | 144 ----------------- 8 files changed, 187 insertions(+), 176 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/data/configuration.js delete mode 100644 modules/ppcp-settings/resources/js/data/constants.js create mode 100644 modules/ppcp-settings/resources/js/data/onboarding/configuration.js create mode 100644 modules/ppcp-settings/resources/js/data/styling/configuration.js diff --git a/modules/ppcp-settings/resources/js/data/configuration.js b/modules/ppcp-settings/resources/js/data/configuration.js new file mode 100644 index 000000000..0f3608552 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/configuration.js @@ -0,0 +1,10 @@ +export { BUSINESS_TYPES, PRODUCT_TYPES } from './onboarding/configuration'; + +export { + STYLING_LOCATIONS, + STYLING_PAYMENT_METHODS, + STYLING_LABELS, + STYLING_COLORS, + STYLING_LAYOUTS, + STYLING_SHAPES, +} from './styling/configuration'; diff --git a/modules/ppcp-settings/resources/js/data/constants.js b/modules/ppcp-settings/resources/js/data/constants.js deleted file mode 100644 index fbc8e8e11..000000000 --- a/modules/ppcp-settings/resources/js/data/constants.js +++ /dev/null @@ -1,9 +0,0 @@ -export { BUSINESS_TYPES, PRODUCT_TYPES } from './onboarding/constants'; - -export { - STYLING_LOCATIONS, - STYLING_LABELS, - STYLING_COLORS, - STYLING_LAYOUTS, - STYLING_SHAPES, -} from './styling/constants'; diff --git a/modules/ppcp-settings/resources/js/data/index.js b/modules/ppcp-settings/resources/js/data/index.js index e447ff770..959c5f187 100644 --- a/modules/ppcp-settings/resources/js/data/index.js +++ b/modules/ppcp-settings/resources/js/data/index.js @@ -15,6 +15,6 @@ export const OnboardingStoreName = Onboarding.STORE_NAME; export const CommonStoreName = Common.STORE_NAME; export const StylingStoreName = Styling.STORE_NAME; -export * from './constants'; +export * from './configuration'; addDebugTools( window.ppcpSettings, [ Onboarding, Common, Styling ] ); diff --git a/modules/ppcp-settings/resources/js/data/onboarding/configuration.js b/modules/ppcp-settings/resources/js/data/onboarding/configuration.js new file mode 100644 index 000000000..4b31689b5 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/onboarding/configuration.js @@ -0,0 +1,26 @@ +/** + * Configuration for UI components. + * + * @file + */ + +/** + * Onboarding options for StepBusiness + * + * @type {Object} + */ +export const BUSINESS_TYPES = { + CASUAL_SELLER: 'casual_seller', + BUSINESS: 'business', +}; + +/** + * Onboarding options for StepProducts + * + * @type {Object} + */ +export const PRODUCT_TYPES = { + VIRTUAL: 'virtual', + PHYSICAL: 'physical', + SUBSCRIPTIONS: 'subscriptions', +}; diff --git a/modules/ppcp-settings/resources/js/data/onboarding/constants.js b/modules/ppcp-settings/resources/js/data/onboarding/constants.js index 7c35ee693..396726199 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/constants.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/constants.js @@ -26,24 +26,3 @@ export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/onboarding'; * @type {string} */ export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/onboarding'; - -/** - * Onboarding options for StepBusiness - * - * @type {Object} - */ -export const BUSINESS_TYPES = { - CASUAL_SELLER: 'casual_seller', - BUSINESS: 'business', -}; - -/** - * Onboarding options for StepProducts - * - * @type {Object} - */ -export const PRODUCT_TYPES = { - VIRTUAL: 'virtual', - PHYSICAL: 'physical', - SUBSCRIPTIONS: 'subscriptions', -}; diff --git a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js index c4308c0fa..2d1542f68 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js @@ -9,7 +9,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; -import { PRODUCT_TYPES } from '../constants'; +import { PRODUCT_TYPES } from './configuration'; import { STORE_NAME } from './constants'; const useTransient = ( key ) => diff --git a/modules/ppcp-settings/resources/js/data/styling/configuration.js b/modules/ppcp-settings/resources/js/data/styling/configuration.js new file mode 100644 index 000000000..861379e2d --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/styling/configuration.js @@ -0,0 +1,149 @@ +/** + * Configuration for UI components. + * + * @file + */ + +import { __ } from '@wordpress/i18n'; + +export const STYLING_LOCATIONS = { + cart: { + value: 'cart', + label: __( 'Cart', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. + description: __( + 'More details on the Cart page.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'classic-checkout': { + value: 'classic-checkout', + label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. + description: __( + 'More details on the Classic Checkout page.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'express-checkout': { + value: 'express-checkout', + label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. + description: __( + 'More details on the Express Checkout location.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'mini-cart': { + value: 'mini-cart', + label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), + // translators: %s is the URL to a documentation page. + description: __( + 'More details on the Mini Cart.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, + 'product-page': { + value: 'product-page', + label: __( 'Product Page', 'woocommerce-paypal-payments' ), + // translators: %s is the URL to a documentation page. + description: __( + 'More details on the Product Page.', + 'wooocommerce-paypal-payments' + ), + link: '#', + }, +}; + +export const STYLING_LABELS = { + paypal: { + value: 'paypal', + label: __( 'PayPal', 'woocommerce-paypal-payments' ), + }, + checkout: { + value: 'checkout', + label: __( 'Checkout', 'woocommerce-paypal-payments' ), + }, + buynow: { + value: 'buynow', + label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ), + }, + pay: { + value: 'pay', + label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_COLORS = { + gold: { + value: 'gold', + label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ), + }, + blue: { + value: 'blue', + label: __( 'Blue', 'woocommerce-paypal-payments' ), + }, + silver: { + value: 'silver', + label: __( 'Silver', 'woocommerce-paypal-payments' ), + }, + black: { + value: 'black', + label: __( 'Black', 'woocommerce-paypal-payments' ), + }, + white: { + value: 'white', + label: __( 'White', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_LAYOUTS = { + vertical: { + value: 'vertical', + label: __( 'Vertical', 'woocommerce-paypal-payments' ), + }, + horizontal: { + value: 'horizontal', + label: __( 'Horizontal', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_SHAPES = { + pill: { + value: 'pill', + label: __( 'Pill', 'woocommerce-paypal-payments' ), + }, + rect: { + value: 'rect', + label: __( 'Rectangle', 'woocommerce-paypal-payments' ), + }, +}; + +export const STYLING_PAYMENT_METHODS = { + paypal: { + value: '', + label: __( 'PayPal', 'woocommerce-paypal-payments' ), + checked: true, + disabled: true, + }, + venmo: { + value: 'venmo', + label: __( 'Venmo', 'woocommerce-paypal-payments' ), + }, + paylater: { + value: 'paylater', + label: __( 'Pay Later', 'woocommerce-paypal-payments' ), + }, + googlepay: { + value: 'googlepay', + label: __( 'Google Pay', 'woocommerce-paypal-payments' ), + }, + applepay: { + value: 'applepay', + label: __( 'Apple Pay', 'woocommerce-paypal-payments' ), + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/styling/constants.js b/modules/ppcp-settings/resources/js/data/styling/constants.js index f21ac7adf..db1082f33 100644 --- a/modules/ppcp-settings/resources/js/data/styling/constants.js +++ b/modules/ppcp-settings/resources/js/data/styling/constants.js @@ -1,5 +1,3 @@ -import { __ } from '@wordpress/i18n'; - /** * Name of the Redux store module. * @@ -28,145 +26,3 @@ export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/styling'; * @type {string} */ export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/styling'; - -export const STYLING_LOCATIONS = { - cart: { - value: 'cart', - label: __( 'Cart', 'woocommerce-paypal-payments' ), - // translators: %s is the URL to a documentation page. - description: __( - 'More details on the Cart page.', - 'wooocommerce-paypal-payments' - ), - link: '#', - }, - 'classic-checkout': { - value: 'classic-checkout', - label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), - // translators: %s is the URL to a documentation page. - description: __( - 'More details on the Classic Checkout page.', - 'wooocommerce-paypal-payments' - ), - link: '#', - }, - 'express-checkout': { - value: 'express-checkout', - label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), - // translators: %s is the URL to a documentation page. - description: __( - 'More details on the Express Checkout location.', - 'wooocommerce-paypal-payments' - ), - link: '#', - }, - 'mini-cart': { - value: 'mini-cart', - label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), - // translators: %s is the URL to a documentation page. - description: __( - 'More details on the Mini Cart.', - 'wooocommerce-paypal-payments' - ), - link: '#', - }, - 'product-page': { - value: 'product-page', - label: __( 'Product Page', 'woocommerce-paypal-payments' ), - // translators: %s is the URL to a documentation page. - description: __( - 'More details on the Product Page.', - 'wooocommerce-paypal-payments' - ), - link: '#', - }, -}; - -export const STYLING_LABELS = { - paypal: { - value: 'paypal', - label: __( 'PayPal', 'woocommerce-paypal-payments' ), - }, - checkout: { - value: 'checkout', - label: __( 'Checkout', 'woocommerce-paypal-payments' ), - }, - buynow: { - value: 'buynow', - label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ), - }, - pay: { - value: 'pay', - label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ), - }, -}; - -export const STYLING_COLORS = { - gold: { - value: 'gold', - label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ), - }, - blue: { - value: 'blue', - label: __( 'Blue', 'woocommerce-paypal-payments' ), - }, - silver: { - value: 'silver', - label: __( 'Silver', 'woocommerce-paypal-payments' ), - }, - black: { - value: 'black', - label: __( 'Black', 'woocommerce-paypal-payments' ), - }, - white: { - value: 'white', - label: __( 'White', 'woocommerce-paypal-payments' ), - }, -}; - -export const STYLING_LAYOUTS = { - vertical: { - value: 'vertical', - label: __( 'Vertical', 'woocommerce-paypal-payments' ), - }, - horizontal: { - value: 'horizontal', - label: __( 'Horizontal', 'woocommerce-paypal-payments' ), - }, -}; - -export const STYLING_SHAPES = { - pill: { - value: 'pill', - label: __( 'Pill', 'woocommerce-paypal-payments' ), - }, - rect: { - value: 'rect', - label: __( 'Rectangle', 'woocommerce-paypal-payments' ), - }, -}; - -export const STYLING_PAYMENT_METHODS = { - paypal: { - value: '', - label: __( 'PayPal', 'woocommerce-paypal-payments' ), - checked: true, - disabled: true, - }, - venmo: { - value: 'venmo', - label: __( 'Venmo', 'woocommerce-paypal-payments' ), - }, - paylater: { - value: 'paylater', - label: __( 'Pay Later', 'woocommerce-paypal-payments' ), - }, - googlepay: { - value: 'googlepay', - label: __( 'Google Pay', 'woocommerce-paypal-payments' ), - }, - applepay: { - value: 'applepay', - label: __( 'Apple Pay', 'woocommerce-paypal-payments' ), - }, -}; From 8755242530511672eea2d2e9fc7862fb4f339e7a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 15:05:59 +0100 Subject: [PATCH 18/63] =?UTF-8?q?=F0=9F=90=9B=20Fix=20incorrect=20import?= =?UTF-8?q?=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/styling/reducer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index b28c3f265..358d261ff 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -9,7 +9,7 @@ import { createReducer, createSetters } from '../utils'; import ACTION_TYPES from './action-types'; -import { STYLING_COLORS, STYLING_SHAPES } from './constants'; +import { STYLING_COLORS, STYLING_SHAPES } from './configuration'; // Store structure. From bd14ea441ec2d38a6abed9861a5f2c5e9a6bb977 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 17:16:37 +0100 Subject: [PATCH 19/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20dummy=20hook?= =?UTF-8?q?=20into=20redux=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Styling/LocationSelector.js | 8 +- .../Components/Styling/SettingsPanel.js | 21 ++-- .../Screens/Settings/Tabs/TabStyling.js | 102 ------------------ .../resources/js/data/styling/hooks.js | 98 ++++++++++++++++- 4 files changed, 111 insertions(+), 118 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js index 01745fbdc..35c3a6efd 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js @@ -1,15 +1,13 @@ -import { SelectControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -// Dummy hook. -import { useStylingProps } from '../../Tabs/TabStyling'; - +import { StylingHooks } from '../../../../../data'; import { Description } from '../../../../ReusableComponents/SettingsBlocks'; import StylingSection from './StylingSection'; import StylingSectionWithSelect from './StylingSectionWithSelect'; const LocationSelector = ( { location, setLocation } ) => { - const { locationChoices, locationDetails } = useStylingProps( location ); + const { locationChoices, locationDetails } = + StylingHooks.useStylingProps( location ); const { description, link } = locationDetails || {}; const locationDescription = sprintf( description, link ); diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index 40d418276..4826632a9 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -1,15 +1,13 @@ import { __ } from '@wordpress/i18n'; -// Dummy hook. -import { useStylingLocation, useStylingProps } from '../../Tabs/TabStyling'; - +import { StylingHooks } from '../../../../../data'; import LocationSelector from './LocationSelector'; import StylingSectionWithSelect from './StylingSectionWithSelect'; import StylingSectionWithCheckboxes from './StylingSectionWithCheckboxes'; import StylingSectionWithRadiobuttons from './StylingSectionWithRadiobuttons'; const SettingsPanel = () => { - const { location, setLocation } = useStylingLocation(); + const { location, setLocation } = StylingHooks.useStylingLocation(); return (

@@ -33,7 +31,7 @@ export default SettingsPanel; const SectionPaymentMethods = ( { location } ) => { const { paymentMethods, setPaymentMethods, paymentMethodChoices } = - useStylingProps( location ); + StylingHooks.useStylingProps( location ); return ( { const SectionButtonLayout = ( { location } ) => { const { supportsLayout, layout, setLayout, layoutChoices } = - useStylingProps( location ); + StylingHooks.useStylingProps( location ); if ( ! supportsLayout ) { return null; @@ -66,7 +64,8 @@ const SectionButtonLayout = ( { location } ) => { }; const SectionButtonShape = ( { location } ) => { - const { shape, setShape, shapeChoices } = useStylingProps( location ); + const { shape, setShape, shapeChoices } = + StylingHooks.useStylingProps( location ); return ( { }; const SectionButtonLabel = ( { location } ) => { - const { label, setLabel, labelChoices } = useStylingProps( location ); + const { label, setLabel, labelChoices } = + StylingHooks.useStylingProps( location ); return ( { }; const SectionButtonColor = ( { location } ) => { - const { color, setColor, colorChoices } = useStylingProps( location ); + const { color, setColor, colorChoices } = + StylingHooks.useStylingProps( location ); return ( { const SectionButtonTagline = ( { location } ) => { const { supportsTagline, tagline, setTagline, taglineChoices } = - useStylingProps( location ); + StylingHooks.useStylingProps( location ); if ( ! supportsTagline ) { return null; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js index f0ee60151..c1eb5eb19 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabStyling.js @@ -1,15 +1,3 @@ -import { __ } from '@wordpress/i18n'; -import { useCallback, useState } from '@wordpress/element'; - -import { - STYLING_COLORS, - STYLING_LABELS, - STYLING_LAYOUTS, - STYLING_LOCATIONS, - STYLING_PAYMENT_METHODS, - STYLING_SHAPES, -} from '../../../../data'; - import PreviewPanel from '../Components/Styling/PreviewPanel'; import SettingsPanel from '../Components/Styling/SettingsPanel'; @@ -21,93 +9,3 @@ const TabStyling = () => ( ); export default TabStyling; - -// ---------------------------------------------------------------------------- - -// Temporary "hook" to extract logic before moving it to the Redux store. -export const useStylingLocation = () => { - const [ location, setLocation ] = useState( 'cart' ); - - return { location, setLocation }; -}; - -export const useStylingProps = ( location ) => { - const defaultStyle = { - paymentMethods: [], - color: 'gold', - shape: 'rect', - label: 'paypal', - layout: 'vertical', - tagline: false, - }; - - const [ styles, setStyles ] = useState( { - cart: { ...defaultStyle, label: 'checkout' }, - 'classic-checkout': { ...defaultStyle }, - 'express-checkout': { ...defaultStyle, label: 'pay' }, - 'mini-cart': { ...defaultStyle, label: 'pay' }, - 'product-page': { ...defaultStyle }, - } ); - - const getLocationStyle = useCallback( - ( prop ) => styles[ location ]?.[ prop ], - [ location, styles ] - ); - - const setLocationStyle = useCallback( - ( prop, value ) => { - setStyles( ( prevState ) => ( { - ...prevState, - [ location ]: { - ...prevState[ location ], - [ prop ]: value, - }, - } ) ); - }, - [ location ] - ); - - return { - // Location (drop down). - locationChoices: Object.values( STYLING_LOCATIONS ), - locationDetails: STYLING_LOCATIONS[ location ], - - // Payment methods (checkboxes). - paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), - paymentMethods: getLocationStyle( 'paymentMethods' ), - setPaymentMethods: ( methods ) => - setLocationStyle( 'paymentMethods', methods ), - - // Color (dropdown). - colorChoices: Object.values( STYLING_COLORS ), - color: getLocationStyle( 'color' ), - setColor: ( color ) => setLocationStyle( 'color', color ), - - // Shape (radio). - shapeChoices: Object.values( STYLING_SHAPES ), - shape: getLocationStyle( 'shape' ), - setShape: ( shape ) => setLocationStyle( 'shape', shape ), - - // Label (dropdown). - labelChoices: Object.values( STYLING_LABELS ), - label: getLocationStyle( 'label' ), - setLabel: ( label ) => setLocationStyle( 'label', label ), - - // Layout (radio). - layoutChoices: Object.values( STYLING_LAYOUTS ), - supportsLayout: true, - layout: getLocationStyle( 'layout' ), - setLayout: ( layout ) => setLocationStyle( 'layout', layout ), - - // Tagline (checkbox). - taglineChoices: [ - { - value: 'tagline', - label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), - }, - ], - supportsTagline: true, - tagline: getLocationStyle( 'tagline' ), - setTagline: ( tagline ) => setLocationStyle( 'tagline', tagline ), - }; -}; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index de08f1124..45280de83 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -7,9 +7,19 @@ * @file */ +import { __ } from '@wordpress/i18n'; +import { useCallback, useState } from '@wordpress/element'; // Temporary import { useSelect, useDispatch } from '@wordpress/data'; import { STORE_NAME } from './constants'; +import { + STYLING_COLORS, + STYLING_LABELS, + STYLING_LAYOUTS, + STYLING_LOCATIONS, + STYLING_PAYMENT_METHODS, + STYLING_SHAPES, +} from './configuration'; const useTransient = ( key ) => useSelect( @@ -42,7 +52,93 @@ const useHooks = () => { }; }; -export const useState = () => { +export const useStore = () => { const { persist, isReady } = useHooks(); return { persist, isReady }; }; + +export const useStylingLocation = () => { + const [ location, setLocation ] = useState( 'cart' ); + return { location, setLocation }; +}; + +export const useStylingProps = ( location ) => { + const defaultStyle = { + paymentMethods: [], + color: 'gold', + shape: 'rect', + label: 'paypal', + layout: 'vertical', + tagline: false, + }; + + const [ styles, setStyles ] = useState( { + cart: { ...defaultStyle, label: 'checkout' }, + 'classic-checkout': { ...defaultStyle }, + 'express-checkout': { ...defaultStyle, label: 'pay' }, + 'mini-cart': { ...defaultStyle, label: 'pay' }, + 'product-page': { ...defaultStyle }, + } ); + + const getLocationStyle = useCallback( + ( prop ) => styles[ location ]?.[ prop ], + [ location, styles ] + ); + + const setLocationStyle = useCallback( + ( prop, value ) => { + setStyles( ( prevState ) => ( { + ...prevState, + [ location ]: { + ...prevState[ location ], + [ prop ]: value, + }, + } ) ); + }, + [ location ] + ); + + return { + // Location (drop down). + locationChoices: Object.values( STYLING_LOCATIONS ), + locationDetails: STYLING_LOCATIONS[ location ], + + // Payment methods (checkboxes). + paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), + paymentMethods: getLocationStyle( 'paymentMethods' ), + setPaymentMethods: ( methods ) => + setLocationStyle( 'paymentMethods', methods ), + + // Color (dropdown). + colorChoices: Object.values( STYLING_COLORS ), + color: getLocationStyle( 'color' ), + setColor: ( color ) => setLocationStyle( 'color', color ), + + // Shape (radio). + shapeChoices: Object.values( STYLING_SHAPES ), + shape: getLocationStyle( 'shape' ), + setShape: ( shape ) => setLocationStyle( 'shape', shape ), + + // Label (dropdown). + labelChoices: Object.values( STYLING_LABELS ), + label: getLocationStyle( 'label' ), + setLabel: ( label ) => setLocationStyle( 'label', label ), + + // Layout (radio). + layoutChoices: Object.values( STYLING_LAYOUTS ), + supportsLayout: true, + layout: getLocationStyle( 'layout' ), + setLayout: ( layout ) => setLocationStyle( 'layout', layout ), + + // Tagline (checkbox). + taglineChoices: [ + { + value: 'tagline', + label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), + }, + ], + supportsTagline: true, + tagline: getLocationStyle( 'tagline' ), + setTagline: ( tagline ) => setLocationStyle( 'tagline', tagline ), + }; +}; From f07d9bad82e7b0afcacf0cd5af47a6fe31df9411 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 18:20:03 +0100 Subject: [PATCH 20/63] =?UTF-8?q?=E2=9C=A8=20Add=20gemeric=20hook-generato?= =?UTF-8?q?r=20for=20data=20access?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 20 ++---- .../ppcp-settings/resources/js/data/utils.js | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 45280de83..6e6a502ab 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -11,6 +11,7 @@ import { __ } from '@wordpress/i18n'; import { useCallback, useState } from '@wordpress/element'; // Temporary import { useSelect, useDispatch } from '@wordpress/data'; +import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; import { STYLING_COLORS, @@ -21,28 +22,17 @@ import { STYLING_SHAPES, } from './configuration'; -const useTransient = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).transientData()?.[ key ], - [ key ] - ); - -const usePersistent = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).persistentData()?.[ key ], - [ key ] - ); - const useHooks = () => { - const { persist, setShape } = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + const { persist } = useDispatch( STORE_NAME ); // Read-only flags and derived state. // Transient accessors. - const isReady = useTransient( 'isReady' ); + const [ isReady ] = useTransient( 'isReady' ); // Persistent accessors. - const shape = usePersistent( 'shape' ); + const [ shape, setShape ] = usePersistent( 'shape' ); return { persist, diff --git a/modules/ppcp-settings/resources/js/data/utils.js b/modules/ppcp-settings/resources/js/data/utils.js index 45c652862..35d7bbbf9 100644 --- a/modules/ppcp-settings/resources/js/data/utils.js +++ b/modules/ppcp-settings/resources/js/data/utils.js @@ -1,3 +1,6 @@ +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; + /** * Updates an object with new values, filtering based on allowed keys. * @@ -13,6 +16,10 @@ const updateObject = ( oldObject, newValues, allowedKeys = {} ) => ( { ...Object.keys( newValues ).reduce( ( acc, key ) => { if ( key in allowedKeys ) { acc[ key ] = newValues[ key ]; + } else { + console.warn( + `Ignoring unknown key "${ key }" - to use it, add it to the initial store properties in the reducer.` + ); } return acc; }, {} ), @@ -73,3 +80,57 @@ export const createReducer = ( return state; }; }; + +/** + * Returns an object with two hooks: + * - useTransient( prop ) + * - usePersistent( prop ) + * + * Both hooks have a similar syntax to the native "useState( prop )" hook, but provide access to + * a transient or persistent property in the relevant Redux store. + * + * Sample: + * + * const { useTransient } = createHooksForStore( STORE_NAME ); + * const [ isReady, setIsReady ] = useTransient( 'isReady' ); + * + * @param {string} storeName Store name. + * @return {{useTransient, usePersistent}} Store hooks. + */ +export const createHooksForStore = ( storeName ) => { + const createHook = ( selector, dispatcher ) => ( key ) => { + const value = useSelect( + ( select ) => { + const store = select( storeName ); + if ( ! store?.[ selector ] ) { + throw new Error( + `Please create the selector "${ selector }" for store "${ storeName }"` + ); + } + return store[ selector ]()?.[ key ]; + }, + [ key ] + ); + + const actions = useDispatch( storeName ); + + const setValue = useCallback( + ( newValue ) => { + if ( ! actions?.[ dispatcher ] ) { + throw new Error( + `Please create the action "${ dispatcher }" for store "${ storeName }"` + ); + } + actions[ dispatcher ]( key, newValue ); + }, + [ actions, key ] + ); + + return [ value, setValue ]; + }; + + return { + useTransient: createHook( 'transientData', 'setTransient' ), + usePersistent: createHook( 'persistentData', 'setPersistent' ), + }; +}; From 96128ee3e4e7280383b0fbeffe0f7068f75886f7 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 18:25:54 +0100 Subject: [PATCH 21/63] =?UTF-8?q?=E2=9C=A8=20Create=20the=20generic=20sett?= =?UTF-8?q?er-actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/actions.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/modules/ppcp-settings/resources/js/data/styling/actions.js b/modules/ppcp-settings/resources/js/data/styling/actions.js index 25cf6f04b..435c4f2d9 100644 --- a/modules/ppcp-settings/resources/js/data/styling/actions.js +++ b/modules/ppcp-settings/resources/js/data/styling/actions.js @@ -36,6 +36,30 @@ export const hydrate = ( payload ) => ( { payload, } ); +/** + * Generic transient-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setTransient = ( prop, value ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { [ prop ]: value }, +} ); + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); + /** * Transient. Marks the store as "ready", i.e., fully initialized. * From 209b7a7c885a8d3b6513f2172388ebbe09019341 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 18:27:20 +0100 Subject: [PATCH 22/63] =?UTF-8?q?=F0=9F=94=A5=20Clean=20up=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/actions.js | 22 ------------------- .../resources/js/data/styling/hooks.js | 4 +--- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/actions.js b/modules/ppcp-settings/resources/js/data/styling/actions.js index 435c4f2d9..095a54c91 100644 --- a/modules/ppcp-settings/resources/js/data/styling/actions.js +++ b/modules/ppcp-settings/resources/js/data/styling/actions.js @@ -60,28 +60,6 @@ export const setPersistent = ( prop, value ) => ( { payload: { [ prop ]: value }, } ); -/** - * Transient. Marks the store as "ready", i.e., fully initialized. - * - * @param {boolean} isReady - * @return {Action} The action. - */ -export const setIsReady = ( isReady ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isReady }, -} ); - -/** - * Persistent. - * - * @param {string} shape - * @return {Action} The action. - */ -export const setShape = ( shape ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { shape }, -} ); - /** * Side effect. Triggers the persistence of store data to the server. * diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 6e6a502ab..201136695 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n'; import { useCallback, useState } from '@wordpress/element'; // Temporary -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; @@ -37,8 +37,6 @@ const useHooks = () => { return { persist, isReady, - shape, - setShape, }; }; From 0673e5d813b6f7314fd47ee17e1761c662365142 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 18:28:11 +0100 Subject: [PATCH 23/63] =?UTF-8?q?=E2=9C=A8=20Move=20prop=20=E2=80=9Clocati?= =?UTF-8?q?on=E2=80=9D=20into=20Redux=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/styling/hooks.js | 5 ++++- modules/ppcp-settings/resources/js/data/styling/reducer.js | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 201136695..e8863b3e3 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -30,6 +30,7 @@ const useHooks = () => { // Transient accessors. const [ isReady ] = useTransient( 'isReady' ); + const [ location, setLocation ] = useTransient( 'location' ); // Persistent accessors. const [ shape, setShape ] = usePersistent( 'shape' ); @@ -37,6 +38,8 @@ const useHooks = () => { return { persist, isReady, + location, + setLocation, }; }; @@ -46,7 +49,7 @@ export const useStore = () => { }; export const useStylingLocation = () => { - const [ location, setLocation ] = useState( 'cart' ); + const { location, setLocation } = useHooks(); return { location, setLocation }; }; diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index 358d261ff..ec432081f 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -16,6 +16,7 @@ import { STYLING_COLORS, STYLING_SHAPES } from './configuration'; // Transient: Values that are _not_ saved to the DB (like app lifecycle-flags). const defaultTransient = Object.freeze( { isReady: false, + location: 'cart', // Which location is selected in the Styling tab. } ); // Persistent: Values that are loaded from the DB. From 2112769de9a7a0bfcc20549d4b0400fd6d9ae71f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 19:29:16 +0100 Subject: [PATCH 24/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Restructure=20Stylin?= =?UTF-8?q?g=20component=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Styling/Content/ButtonColor.js | 21 +++ .../Components/Styling/Content/ButtonLabel.js | 21 +++ .../Styling/Content/ButtonLayout.js | 25 ++++ .../Components/Styling/Content/ButtonShape.js | 21 +++ .../Styling/{ => Content}/LocationSelector.js | 13 +- .../Styling/Content/PaymentMethods.js | 21 +++ .../Components/Styling/Content/TagLine.js | 25 ++++ .../Components/Styling/Content/index.js | 7 + .../Styling/{ => Layout}/StylingSection.js | 4 +- .../StylingSectionWithCheckboxes.js | 4 +- .../StylingSectionWithRadiobuttons.js | 2 +- .../{ => Layout}/StylingSectionWithSelect.js | 0 .../Components/Styling/Layout/index.js | 4 + .../Components/Styling/SettingsPanel.js | 127 +++--------------- 14 files changed, 171 insertions(+), 124 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js rename modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/{ => Content}/LocationSelector.js (72%) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js rename modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/{ => Layout}/StylingSection.js (76%) rename modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/{ => Layout}/StylingSectionWithCheckboxes.js (83%) rename modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/{ => Layout}/StylingSectionWithRadiobuttons.js (92%) rename modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/{ => Layout}/StylingSectionWithSelect.js (100%) create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/index.js diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js new file mode 100644 index 000000000..acadc3943 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js @@ -0,0 +1,21 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { SelectStylingSection } from '../Layout'; + +const SectionButtonColor = ( { location } ) => { + const { color, setColor, colorChoices } = + StylingHooks.useStylingProps( location ); + + return ( + + ); +}; + +export default SectionButtonColor; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js new file mode 100644 index 000000000..a0763a049 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js @@ -0,0 +1,21 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { SelectStylingSection } from '../Layout'; + +const SectionButtonLabel = ( { location } ) => { + const { label, setLabel, labelChoices } = + StylingHooks.useStylingProps( location ); + + return ( + + ); +}; + +export default SectionButtonLabel; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js new file mode 100644 index 000000000..0b6407ef1 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js @@ -0,0 +1,25 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { RadiobuttonStylingSection } from '../Layout'; + +const SectionButtonLayout = ( { location } ) => { + const { supportsLayout, layout, setLayout, layoutChoices } = + StylingHooks.useStylingProps( location ); + + if ( ! supportsLayout ) { + return null; + } + + return ( + + ); +}; + +export default SectionButtonLayout; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js new file mode 100644 index 000000000..68eaef1dc --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js @@ -0,0 +1,21 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { RadiobuttonStylingSection } from '../Layout'; + +const SectionButtonShape = ( { location } ) => { + const { shape, setShape, shapeChoices } = + StylingHooks.useStylingProps( location ); + + return ( + + ); +}; + +export default SectionButtonShape; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js similarity index 72% rename from modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js index 35c3a6efd..2fd21caa0 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js @@ -1,9 +1,8 @@ import { __, sprintf } from '@wordpress/i18n'; -import { StylingHooks } from '../../../../../data'; -import { Description } from '../../../../ReusableComponents/SettingsBlocks'; -import StylingSection from './StylingSection'; -import StylingSectionWithSelect from './StylingSectionWithSelect'; +import { StylingHooks } from '../../../../../../data'; +import { Description } from '../../../../../ReusableComponents/SettingsBlocks'; +import { SelectStylingSection, StylingSection } from '../Layout'; const LocationSelector = ( { location, setLocation } ) => { const { locationChoices, locationDetails } = @@ -22,9 +21,9 @@ const LocationSelector = ( { location, setLocation } ) => { 'woocommerce-paypal-payments' ) } > - { { locationDescription } - + ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js new file mode 100644 index 000000000..e379f876b --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js @@ -0,0 +1,21 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { CheckboxStylingSection } from '../Layout'; + +const SectionPaymentMethods = ( { location } ) => { + const { paymentMethods, setPaymentMethods, paymentMethodChoices } = + StylingHooks.useStylingProps( location ); + + return ( + + ); +}; + +export default SectionPaymentMethods; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js new file mode 100644 index 000000000..66fa4cd1f --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js @@ -0,0 +1,25 @@ +import { __ } from '@wordpress/i18n'; + +import { StylingHooks } from '../../../../../../data'; +import { CheckboxStylingSection } from '../Layout'; + +const SectionTagline = ( { location } ) => { + const { supportsTagline, tagline, setTagline, taglineChoices } = + StylingHooks.useStylingProps( location ); + + if ( ! supportsTagline ) { + return null; + } + + return ( + + ); +}; + +export default SectionTagline; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js new file mode 100644 index 000000000..27a4e3d56 --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js @@ -0,0 +1,7 @@ +export { default as LocationSelector } from './LocationSelector'; +export { default as ButtonColor } from './ButtonColor'; +export { default as ButtonLabel } from './ButtonLabel'; +export { default as ButtonLayout } from './ButtonLayout'; +export { default as ButtonShape } from './ButtonShape'; +export { default as PaymentMethods } from './PaymentMethods'; +export { default as TagLine } from './TagLine'; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js similarity index 76% rename from modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js index 51290831a..e528361dc 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSection.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js @@ -1,9 +1,9 @@ -import SettingsBlock from '../../../../ReusableComponents/SettingsBlocks/SettingsBlock'; +import SettingsBlock from '../../../../../ReusableComponents/SettingsBlocks/SettingsBlock'; import { Description, Header, Title, -} from '../../../../ReusableComponents/SettingsBlocks'; +} from '../../../../../ReusableComponents/SettingsBlocks'; const StylingSection = ( { title, diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithCheckboxes.js similarity index 83% rename from modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithCheckboxes.js index cc0b259d6..d3ca92db4 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithCheckboxes.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithCheckboxes.js @@ -1,7 +1,7 @@ import classNames from 'classnames'; -import { CheckboxGroup } from '../../../../ReusableComponents/Fields'; -import HStack from '../../../../ReusableComponents/HStack'; +import { CheckboxGroup } from '../../../../../ReusableComponents/Fields'; +import HStack from '../../../../../ReusableComponents/HStack'; import StylingSection from './StylingSection'; const StylingSectionWithCheckboxes = ( { diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithRadiobuttons.js similarity index 92% rename from modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithRadiobuttons.js index 4bc33326f..337e2b30f 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithRadiobuttons.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithRadiobuttons.js @@ -1,7 +1,7 @@ import { RadioControl } from '@wordpress/components'; import classNames from 'classnames'; -import HStack from '../../../../ReusableComponents/HStack'; +import HStack from '../../../../../ReusableComponents/HStack'; import StylingSection from './StylingSection'; const StylingSectionWithRadiobuttons = ( { diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js similarity index 100% rename from modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/StylingSectionWithSelect.js rename to modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/index.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/index.js new file mode 100644 index 000000000..ea856552d --- /dev/null +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/index.js @@ -0,0 +1,4 @@ +export { default as StylingSection } from './StylingSection'; +export { default as CheckboxStylingSection } from './StylingSectionWithCheckboxes'; +export { default as RadiobuttonStylingSection } from './StylingSectionWithRadiobuttons'; +export { default as SelectStylingSection } from './StylingSectionWithSelect'; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index 4826632a9..ad65185cf 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -1,10 +1,13 @@ -import { __ } from '@wordpress/i18n'; - import { StylingHooks } from '../../../../../data'; -import LocationSelector from './LocationSelector'; -import StylingSectionWithSelect from './StylingSectionWithSelect'; -import StylingSectionWithCheckboxes from './StylingSectionWithCheckboxes'; -import StylingSectionWithRadiobuttons from './StylingSectionWithRadiobuttons'; +import { + LocationSelector, + PaymentMethods, + ButtonLayout, + ButtonShape, + ButtonLabel, + ButtonColor, + TagLine, +} from './Content'; const SettingsPanel = () => { const { location, setLocation } = StylingHooks.useStylingLocation(); @@ -15,114 +18,14 @@ const SettingsPanel = () => { location={ location } setLocation={ setLocation } /> - - - - - - + + + + + +
); }; export default SettingsPanel; - -// ----- - -const SectionPaymentMethods = ( { location } ) => { - const { paymentMethods, setPaymentMethods, paymentMethodChoices } = - StylingHooks.useStylingProps( location ); - - return ( - - ); -}; - -const SectionButtonLayout = ( { location } ) => { - const { supportsLayout, layout, setLayout, layoutChoices } = - StylingHooks.useStylingProps( location ); - - if ( ! supportsLayout ) { - return null; - } - - return ( - - ); -}; - -const SectionButtonShape = ( { location } ) => { - const { shape, setShape, shapeChoices } = - StylingHooks.useStylingProps( location ); - - return ( - - ); -}; - -const SectionButtonLabel = ( { location } ) => { - const { label, setLabel, labelChoices } = - StylingHooks.useStylingProps( location ); - - return ( - - ); -}; - -const SectionButtonColor = ( { location } ) => { - const { color, setColor, colorChoices } = - StylingHooks.useStylingProps( location ); - - return ( - - ); -}; - -const SectionButtonTagline = ( { location } ) => { - const { supportsTagline, tagline, setTagline, taglineChoices } = - StylingHooks.useStylingProps( location ); - - if ( ! supportsTagline ) { - return null; - } - - return ( - - ); -}; From f7f140875da7ae6f16b1905c6f47232fb11a6af8 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 19:30:01 +0100 Subject: [PATCH 25/63] =?UTF-8?q?=F0=9F=92=84=20Improve=20UX=20and=20SCSS?= =?UTF-8?q?=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/settings/_tab-styling.scss | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index d88fe64f7..cc60e1619 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -1,18 +1,30 @@ -$width-settings-panel: 422px; - .ppcp-r-styling { --block-item-gap: 0; --block-separator-gap: 24px; --block-header-gap: 18px; + --panel-width: 422px; + --sticky-offset-top: 92px; // 32px admin-bar + 60px TopNavigation height + --preview-height-reduction: 236px; // 32px admin-bar + 60px TopNavigation height + 48px TopNavigation margin + 48px TabList height + 48px TabList margin display: flex; border: 1px solid var(--color-separators); border-radius: 8px; - overflow: hidden; .ppcp-r-settings-block { &.header-section { - margin-bottom: 22px + margin-bottom: 6px + } + + &.location-selector { + position: sticky; + top: var(--sticky-offset-top); + background: var(--ppcp-color-app-bg); + border-bottom: 1px solid var(--color-gray-200); + box-shadow: 0 5px 10px 5px var(--ppcp-color-app-bg); + z-index: 5; + padding-top: 16px; + padding-bottom: var(--block-separator-gap); + margin-bottom: -29px; } // Select-fields have a smaller gap between the header and input field. @@ -28,7 +40,7 @@ $width-settings-panel: 422px; /* The settings-panel (left side) */ .settings-panel { - width: $width-settings-panel; + width: var(--panel-width); padding: 48px; .ppcp-r-styling__section { @@ -52,14 +64,19 @@ $width-settings-panel: 422px; /* The preview area (right side) */ .preview-panel { - width: calc(100% - $width-settings-panel); + width: calc(100% - var(--panel-width)); background-color: var(--color-preview-background); - display: flex; - align-items: center; + z-index: 0; .preview-panel-inner { + position: sticky; + display: flex; + flex-direction: column; + justify-content: center; width: 100%; padding: 24px; + height: calc(100vh - var(--preview-height-reduction)); + top: var(--sticky-offset-top); } } } From 34cc8f8fab66ac1274c50bd631e801792f87b6ae Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 19:30:54 +0100 Subject: [PATCH 26/63] =?UTF-8?q?=F0=9F=9A=A7=20=20Prepare=20Redux=20integ?= =?UTF-8?q?ration=20in=20hooks-file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index e8863b3e3..7db797d3a 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -33,13 +33,51 @@ const useHooks = () => { const [ location, setLocation ] = useTransient( 'location' ); // Persistent accessors. - const [ shape, setShape ] = usePersistent( 'shape' ); + // TODO - this is a dummy implementation. + const defaultStyle = { + paymentMethods: [], + color: 'gold', + shape: 'rect', + label: 'paypal', + layout: 'vertical', + tagline: false, + }; + + // This data-struct is already present in the Redux store via persistentData, e.g. "persistentData.cart.label" + const [ styles, setStyles ] = useState( { + cart: { ...defaultStyle, label: 'checkout' }, + 'classic-checkout': { ...defaultStyle }, + 'express-checkout': { ...defaultStyle, label: 'pay' }, + 'mini-cart': { ...defaultStyle, label: 'pay' }, + 'product-page': { ...defaultStyle }, + } ); + + const getLocationProp = useCallback( + ( prop ) => styles[ location ]?.[ prop ], + [ location, styles ] + ); + + const setLocationProp = useCallback( + ( prop, value ) => { + setStyles( ( prevState ) => ( { + ...prevState, + [ location ]: { + ...prevState[ location ], + [ prop ]: value, + }, + } ) ); + }, + [ location ] + ); + // end of dummy implementation return { persist, isReady, location, setLocation, + getLocationProp, + setLocationProp, }; }; @@ -54,40 +92,7 @@ export const useStylingLocation = () => { }; export const useStylingProps = ( location ) => { - const defaultStyle = { - paymentMethods: [], - color: 'gold', - shape: 'rect', - label: 'paypal', - layout: 'vertical', - tagline: false, - }; - - const [ styles, setStyles ] = useState( { - cart: { ...defaultStyle, label: 'checkout' }, - 'classic-checkout': { ...defaultStyle }, - 'express-checkout': { ...defaultStyle, label: 'pay' }, - 'mini-cart': { ...defaultStyle, label: 'pay' }, - 'product-page': { ...defaultStyle }, - } ); - - const getLocationStyle = useCallback( - ( prop ) => styles[ location ]?.[ prop ], - [ location, styles ] - ); - - const setLocationStyle = useCallback( - ( prop, value ) => { - setStyles( ( prevState ) => ( { - ...prevState, - [ location ]: { - ...prevState[ location ], - [ prop ]: value, - }, - } ) ); - }, - [ location ] - ); + const { getLocationProp, setLocationProp } = useHooks(); return { // Location (drop down). @@ -96,30 +101,30 @@ export const useStylingProps = ( location ) => { // Payment methods (checkboxes). paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), - paymentMethods: getLocationStyle( 'paymentMethods' ), + paymentMethods: getLocationProp( 'paymentMethods' ), setPaymentMethods: ( methods ) => - setLocationStyle( 'paymentMethods', methods ), + setLocationProp( 'paymentMethods', methods ), // Color (dropdown). colorChoices: Object.values( STYLING_COLORS ), - color: getLocationStyle( 'color' ), - setColor: ( color ) => setLocationStyle( 'color', color ), + color: getLocationProp( 'color' ), + setColor: ( color ) => setLocationProp( 'color', color ), // Shape (radio). shapeChoices: Object.values( STYLING_SHAPES ), - shape: getLocationStyle( 'shape' ), - setShape: ( shape ) => setLocationStyle( 'shape', shape ), + shape: getLocationProp( 'shape' ), + setShape: ( shape ) => setLocationProp( 'shape', shape ), // Label (dropdown). labelChoices: Object.values( STYLING_LABELS ), - label: getLocationStyle( 'label' ), - setLabel: ( label ) => setLocationStyle( 'label', label ), + label: getLocationProp( 'label' ), + setLabel: ( label ) => setLocationProp( 'label', label ), // Layout (radio). layoutChoices: Object.values( STYLING_LAYOUTS ), supportsLayout: true, - layout: getLocationStyle( 'layout' ), - setLayout: ( layout ) => setLocationStyle( 'layout', layout ), + layout: getLocationProp( 'layout' ), + setLayout: ( layout ) => setLocationProp( 'layout', layout ), // Tagline (checkbox). taglineChoices: [ @@ -129,7 +134,7 @@ export const useStylingProps = ( location ) => { }, ], supportsTagline: true, - tagline: getLocationStyle( 'tagline' ), - setTagline: ( tagline ) => setLocationStyle( 'tagline', tagline ), + tagline: getLocationProp( 'tagline' ), + setTagline: ( tagline ) => setLocationProp( 'tagline', tagline ), }; }; From 033b6e5b74ca2194e7c1b5bf470bf6d03610eb72 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 20:01:22 +0100 Subject: [PATCH 27/63] =?UTF-8?q?=E2=9C=A8=20Move=20styling=20options=20to?= =?UTF-8?q?=20Redux=20store!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 7db797d3a..5cf07655e 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -8,8 +8,8 @@ */ import { __ } from '@wordpress/i18n'; -import { useCallback, useState } from '@wordpress/element'; // Temporary -import { useDispatch } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; // Temporary +import { useDispatch, useSelect } from '@wordpress/data'; import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; @@ -33,43 +33,29 @@ const useHooks = () => { const [ location, setLocation ] = useTransient( 'location' ); // Persistent accessors. - // TODO - this is a dummy implementation. - const defaultStyle = { - paymentMethods: [], - color: 'gold', - shape: 'rect', - label: 'paypal', - layout: 'vertical', - tagline: false, - }; + const persistentData = useSelect( + ( select ) => select( STORE_NAME ).persistentData(), + [] + ); - // This data-struct is already present in the Redux store via persistentData, e.g. "persistentData.cart.label" - const [ styles, setStyles ] = useState( { - cart: { ...defaultStyle, label: 'checkout' }, - 'classic-checkout': { ...defaultStyle }, - 'express-checkout': { ...defaultStyle, label: 'pay' }, - 'mini-cart': { ...defaultStyle, label: 'pay' }, - 'product-page': { ...defaultStyle }, - } ); + const [ locationStyles, setLocationStyles ] = usePersistent( location ); const getLocationProp = useCallback( - ( prop ) => styles[ location ]?.[ prop ], - [ location, styles ] + ( prop ) => { + return persistentData[ location ]?.[ prop ]; + }, + [ location, persistentData ] ); const setLocationProp = useCallback( ( prop, value ) => { - setStyles( ( prevState ) => ( { - ...prevState, - [ location ]: { - ...prevState[ location ], - [ prop ]: value, - }, - } ) ); + setLocationStyles( { + ...locationStyles, + [ prop ]: value, + } ); }, - [ location ] + [ locationStyles, setLocationStyles ] ); - // end of dummy implementation return { persist, From dac414433fffc8feda3b224f6fd97afdfbf19438 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 20:02:37 +0100 Subject: [PATCH 28/63] =?UTF-8?q?=F0=9F=A5=85=20Add=20detection=20for=20ac?= =?UTF-8?q?cessing=20undefined=20props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/styling/hooks.js | 5 +++++ modules/ppcp-settings/resources/js/data/utils.js | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 5cf07655e..cb0508247 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -42,6 +42,11 @@ const useHooks = () => { const getLocationProp = useCallback( ( prop ) => { + if ( undefined === persistentData[ location ]?.[ prop ] ) { + console.error( + `Trying to access non-existent style property: ${ location }.${ prop }. Possibly wrong style name - review the reducer.` + ); + } return persistentData[ location ]?.[ prop ]; }, [ location, persistentData ] diff --git a/modules/ppcp-settings/resources/js/data/utils.js b/modules/ppcp-settings/resources/js/data/utils.js index 35d7bbbf9..7d3a14af7 100644 --- a/modules/ppcp-settings/resources/js/data/utils.js +++ b/modules/ppcp-settings/resources/js/data/utils.js @@ -107,7 +107,13 @@ export const createHooksForStore = ( storeName ) => { `Please create the selector "${ selector }" for store "${ storeName }"` ); } - return store[ selector ]()?.[ key ]; + const selectorResult = store[ selector ](); + if ( undefined === selectorResult?.[ key ] ) { + console.error( + `Warning: ${ selector }()[${ key }] is undefined in store "${ storeName }". This may indicate a bug.` + ); + } + return selectorResult?.[ key ]; }, [ key ] ); From e5f882cc57904dba2d969226c2741b694cca47fe Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 20:04:20 +0100 Subject: [PATCH 29/63] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Fix=20issues=20in?= =?UTF-8?q?=20the=20defaultPersistent=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/reducer.js | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index ec432081f..7c553091e 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -9,53 +9,69 @@ import { createReducer, createSetters } from '../utils'; import ACTION_TYPES from './action-types'; -import { STYLING_COLORS, STYLING_SHAPES } from './configuration'; +import { + STYLING_COLORS, + STYLING_LABELS, + STYLING_LAYOUTS, + STYLING_LOCATIONS, + STYLING_SHAPES, +} from './configuration'; // Store structure. // Transient: Values that are _not_ saved to the DB (like app lifecycle-flags). const defaultTransient = Object.freeze( { isReady: false, - location: 'cart', // Which location is selected in the Styling tab. + location: STYLING_LOCATIONS.cart.value, // Which location is selected in the Styling tab. } ); // Persistent: Values that are loaded from the DB. const defaultPersistent = Object.freeze( { - cart: { + [ STYLING_LOCATIONS.cart.value ]: Object.freeze( { enabled: true, methods: [], - label: 'Pay', + label: STYLING_LABELS.pay.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - }, - 'classic-checkout': { + layout: STYLING_LAYOUTS.vertical.value, + tagline: false, + } ), + [ STYLING_LOCATIONS[ 'classic-checkout' ].value ]: Object.freeze( { enabled: true, methods: [], - label: 'Checkout', + label: STYLING_LABELS.checkout.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - }, - 'express-checkout': { + layout: STYLING_LAYOUTS.vertical.value, + tagline: false, + } ), + [ STYLING_LOCATIONS[ 'express-checkout' ].value ]: Object.freeze( { enabled: true, methods: [], - label: 'Checkout', + label: STYLING_LABELS.checkout.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - }, - 'mini-cart': { + layout: STYLING_LAYOUTS.vertical.value, + tagline: false, + } ), + [ STYLING_LOCATIONS[ 'mini-cart' ].value ]: Object.freeze( { enabled: true, methods: [], - label: 'Pay', + label: STYLING_LABELS.pay.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - }, - product: { + layout: STYLING_LAYOUTS.vertical.value, + tagline: false, + } ), + [ STYLING_LOCATIONS.product.value ]: Object.freeze( { enabled: true, methods: [], - label: 'Buy', + label: STYLING_LABELS.buynow.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - }, + layout: STYLING_LAYOUTS.vertical.value, + tagline: false, + } ), } ); // Reducer logic. From 510a711caa58fb142a827f73734d038b86b7d88a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 16 Jan 2025 20:04:47 +0100 Subject: [PATCH 30/63] =?UTF-8?q?=F0=9F=90=9B=20Fix=20inconsistent=20prope?= =?UTF-8?q?rty=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/configuration.js | 12 ++++++------ .../ppcp-settings/resources/js/data/styling/hooks.js | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/configuration.js b/modules/ppcp-settings/resources/js/data/styling/configuration.js index 861379e2d..ecb7a3765 100644 --- a/modules/ppcp-settings/resources/js/data/styling/configuration.js +++ b/modules/ppcp-settings/resources/js/data/styling/configuration.js @@ -47,8 +47,8 @@ export const STYLING_LOCATIONS = { ), link: '#', }, - 'product-page': { - value: 'product-page', + product: { + value: 'product', label: __( 'Product Page', 'woocommerce-paypal-payments' ), // translators: %s is the URL to a documentation page. description: __( @@ -113,14 +113,14 @@ export const STYLING_LAYOUTS = { }; export const STYLING_SHAPES = { - pill: { - value: 'pill', - label: __( 'Pill', 'woocommerce-paypal-payments' ), - }, rect: { value: 'rect', label: __( 'Rectangle', 'woocommerce-paypal-payments' ), }, + pill: { + value: 'pill', + label: __( 'Pill', 'woocommerce-paypal-payments' ), + }, }; export const STYLING_PAYMENT_METHODS = { diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index cb0508247..d81ee8746 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -92,9 +92,8 @@ export const useStylingProps = ( location ) => { // Payment methods (checkboxes). paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), - paymentMethods: getLocationProp( 'paymentMethods' ), - setPaymentMethods: ( methods ) => - setLocationProp( 'paymentMethods', methods ), + paymentMethods: getLocationProp( 'methods' ), + setPaymentMethods: ( methods ) => setLocationProp( 'methods', methods ), // Color (dropdown). colorChoices: Object.values( STYLING_COLORS ), From a5ceec2af43361920edac4dd50f8d1f12c168bb6 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 13:04:21 +0100 Subject: [PATCH 31/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Location-prop=20acce?= =?UTF-8?q?ssors=20require=20a=20location=20arg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of using the “location” property, we want to explicitly specify which location props to access --- .../resources/js/data/styling/hooks.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index d81ee8746..946aa4cc7 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -23,8 +23,8 @@ import { } from './configuration'; const useHooks = () => { - const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); - const { persist } = useDispatch( STORE_NAME ); + const { useTransient } = createHooksForStore( STORE_NAME ); + const { persist, setPersistent } = useDispatch( STORE_NAME ); // Read-only flags and derived state. @@ -38,28 +38,28 @@ const useHooks = () => { [] ); - const [ locationStyles, setLocationStyles ] = usePersistent( location ); - const getLocationProp = useCallback( - ( prop ) => { - if ( undefined === persistentData[ location ]?.[ prop ] ) { + ( locatonId, prop ) => { + if ( undefined === persistentData[ locatonId ]?.[ prop ] ) { console.error( - `Trying to access non-existent style property: ${ location }.${ prop }. Possibly wrong style name - review the reducer.` + `Trying to access non-existent style property: ${ locatonId }.${ prop }. Possibly wrong style name - review the reducer.` ); } - return persistentData[ location ]?.[ prop ]; + return persistentData[ locatonId ]?.[ prop ]; }, - [ location, persistentData ] + [ persistentData ] ); const setLocationProp = useCallback( - ( prop, value ) => { - setLocationStyles( { - ...locationStyles, + ( locationId, prop, value ) => { + const updatedStyles = { + ...persistentData[ locationId ], [ prop ]: value, - } ); + }; + + setPersistent( locationId, updatedStyles ); }, - [ locationStyles, setLocationStyles ] + [ persistentData, setPersistent ] ); return { From 18429d91e5d87edd5613d760a0d3e15c94860510 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 13:05:25 +0100 Subject: [PATCH 32/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Split=20the=20stylin?= =?UTF-8?q?g=20hook=20into=20multiple=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Styling/Content/ButtonColor.js | 5 +- .../Components/Styling/Content/ButtonLabel.js | 5 +- .../Styling/Content/ButtonLayout.js | 8 +- .../Components/Styling/Content/ButtonShape.js | 5 +- .../Styling/Content/LocationSelector.js | 13 +-- .../Styling/Content/PaymentMethods.js | 6 +- .../Components/Styling/Content/TagLine.js | 8 +- .../Components/Styling/Content/index.js | 2 +- .../Components/Styling/SettingsPanel.js | 4 +- .../resources/js/data/styling/hooks.js | 97 +++++++++++++------ 10 files changed, 90 insertions(+), 63 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js index acadc3943..29b98069c 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonColor.js @@ -4,14 +4,13 @@ import { StylingHooks } from '../../../../../../data'; import { SelectStylingSection } from '../Layout'; const SectionButtonColor = ( { location } ) => { - const { color, setColor, colorChoices } = - StylingHooks.useStylingProps( location ); + const { color, setColor, choices } = StylingHooks.useColorProps( location ); return ( diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js index a0763a049..de7c1a103 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLabel.js @@ -4,14 +4,13 @@ import { StylingHooks } from '../../../../../../data'; import { SelectStylingSection } from '../Layout'; const SectionButtonLabel = ( { location } ) => { - const { label, setLabel, labelChoices } = - StylingHooks.useStylingProps( location ); + const { label, setLabel, choices } = StylingHooks.useLabelProps( location ); return ( diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js index 0b6407ef1..1acf666f9 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js @@ -4,10 +4,10 @@ import { StylingHooks } from '../../../../../../data'; import { RadiobuttonStylingSection } from '../Layout'; const SectionButtonLayout = ( { location } ) => { - const { supportsLayout, layout, setLayout, layoutChoices } = - StylingHooks.useStylingProps( location ); + const { isAvailable, layout, setLayout, choices } = + StylingHooks.useLayoutProps( location ); - if ( ! supportsLayout ) { + if ( ! isAvailable ) { return null; } @@ -15,7 +15,7 @@ const SectionButtonLayout = ( { location } ) => { diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js index 68eaef1dc..71bc5bebe 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonShape.js @@ -4,14 +4,13 @@ import { StylingHooks } from '../../../../../../data'; import { RadiobuttonStylingSection } from '../Layout'; const SectionButtonShape = ( { location } ) => { - const { shape, setShape, shapeChoices } = - StylingHooks.useStylingProps( location ); + const { shape, setShape, choices } = StylingHooks.useShapeProps( location ); return ( diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js index 2fd21caa0..f5d8231b8 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js @@ -1,14 +1,11 @@ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { StylingHooks } from '../../../../../../data'; import { Description } from '../../../../../ReusableComponents/SettingsBlocks'; import { SelectStylingSection, StylingSection } from '../Layout'; const LocationSelector = ( { location, setLocation } ) => { - const { locationChoices, locationDetails } = - StylingHooks.useStylingProps( location ); - const { description, link } = locationDetails || {}; - const locationDescription = sprintf( description, link ); + const { choices, description } = StylingHooks.useLocationProps( location ); return ( <> @@ -25,13 +22,11 @@ const LocationSelector = ( { location, setLocation } ) => { className="location-selector" title={ __( 'Location', 'woocommerce-paypal-payments' ) } separatorAndGap={ false } - options={ locationChoices } + options={ choices } value={ location } onChange={ setLocation } > - - { locationDescription } - + { description } ); diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js index e379f876b..0397d34c3 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/PaymentMethods.js @@ -4,14 +4,14 @@ import { StylingHooks } from '../../../../../../data'; import { CheckboxStylingSection } from '../Layout'; const SectionPaymentMethods = ( { location } ) => { - const { paymentMethods, setPaymentMethods, paymentMethodChoices } = - StylingHooks.useStylingProps( location ); + const { paymentMethods, setPaymentMethods, choices } = + StylingHooks.usePaymentMethodProps( location ); return ( diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js index 66fa4cd1f..ec292455c 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js @@ -4,10 +4,10 @@ import { StylingHooks } from '../../../../../../data'; import { CheckboxStylingSection } from '../Layout'; const SectionTagline = ( { location } ) => { - const { supportsTagline, tagline, setTagline, taglineChoices } = - StylingHooks.useStylingProps( location ); + const { isAvailable, tagline, setTagline, choices } = + StylingHooks.useTaglineProps( location ); - if ( ! supportsTagline ) { + if ( ! isAvailable ) { return null; } @@ -15,7 +15,7 @@ const SectionTagline = ( { location } ) => { diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js index 27a4e3d56..4529fb9eb 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/index.js @@ -4,4 +4,4 @@ export { default as ButtonLabel } from './ButtonLabel'; export { default as ButtonLayout } from './ButtonLayout'; export { default as ButtonShape } from './ButtonShape'; export { default as PaymentMethods } from './PaymentMethods'; -export { default as TagLine } from './TagLine'; +export { default as Tagline } from './Tagline'; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js index ad65185cf..9151781f7 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/SettingsPanel.js @@ -6,7 +6,7 @@ import { ButtonShape, ButtonLabel, ButtonColor, - TagLine, + Tagline, } from './Content'; const SettingsPanel = () => { @@ -23,7 +23,7 @@ const SettingsPanel = () => { - +
); }; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 946aa4cc7..2e77d41f2 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -7,7 +7,7 @@ * @file */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useCallback } from '@wordpress/element'; // Temporary import { useDispatch, useSelect } from '@wordpress/data'; @@ -82,49 +82,84 @@ export const useStylingLocation = () => { return { location, setLocation }; }; -export const useStylingProps = ( location ) => { +export const useLocationProps = ( location ) => { + const details = STYLING_LOCATIONS[ location ] ?? {}; + + // eslint-disable-next-line @wordpress/valid-sprintf + const description = sprintf( details.description ?? '', details.link ); + + return { + choices: Object.values( STYLING_LOCATIONS ), + details, + description, + }; +}; + +export const usePaymentMethodProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); return { - // Location (drop down). - locationChoices: Object.values( STYLING_LOCATIONS ), - locationDetails: STYLING_LOCATIONS[ location ], + choices: Object.values( STYLING_PAYMENT_METHODS ), + paymentMethods: getLocationProp( location, 'methods' ), + setPaymentMethods: ( methods ) => + setLocationProp( location, 'methods', methods ), + }; +}; - // Payment methods (checkboxes). - paymentMethodChoices: Object.values( STYLING_PAYMENT_METHODS ), - paymentMethods: getLocationProp( 'methods' ), - setPaymentMethods: ( methods ) => setLocationProp( 'methods', methods ), +export const useColorProps = ( location ) => { + const { getLocationProp, setLocationProp } = useHooks(); - // Color (dropdown). - colorChoices: Object.values( STYLING_COLORS ), - color: getLocationProp( 'color' ), - setColor: ( color ) => setLocationProp( 'color', color ), + return { + choices: Object.values( STYLING_COLORS ), + color: getLocationProp( location, 'color' ), + setColor: ( color ) => setLocationProp( location, 'color', color ), + }; +}; - // Shape (radio). - shapeChoices: Object.values( STYLING_SHAPES ), - shape: getLocationProp( 'shape' ), - setShape: ( shape ) => setLocationProp( 'shape', shape ), +export const useShapeProps = ( location ) => { + const { getLocationProp, setLocationProp } = useHooks(); - // Label (dropdown). - labelChoices: Object.values( STYLING_LABELS ), - label: getLocationProp( 'label' ), - setLabel: ( label ) => setLocationProp( 'label', label ), + return { + choices: Object.values( STYLING_SHAPES ), + shape: getLocationProp( location, 'shape' ), + setShape: ( shape ) => setLocationProp( location, 'shape', shape ), + }; +}; - // Layout (radio). - layoutChoices: Object.values( STYLING_LAYOUTS ), - supportsLayout: true, - layout: getLocationProp( 'layout' ), - setLayout: ( layout ) => setLocationProp( 'layout', layout ), +export const useLabelProps = ( location ) => { + const { getLocationProp, setLocationProp } = useHooks(); - // Tagline (checkbox). - taglineChoices: [ + return { + choices: Object.values( STYLING_LABELS ), + label: getLocationProp( location, 'label' ), + setLabel: ( label ) => setLocationProp( location, 'label', label ), + }; +}; + +export const useLayoutProps = ( location ) => { + const { getLocationProp, setLocationProp } = useHooks(); + + return { + choices: Object.values( STYLING_LAYOUTS ), + isAvailable: true, + layout: getLocationProp( location, 'layout' ), + setLayout: ( layout ) => setLocationProp( location, 'layout', layout ), + }; +}; + +export const useTaglineProps = ( location ) => { + const { getLocationProp, setLocationProp } = useHooks(); + + return { + choices: [ { value: 'tagline', label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), }, ], - supportsTagline: true, - tagline: getLocationProp( 'tagline' ), - setTagline: ( tagline ) => setLocationProp( 'tagline', tagline ), + isAvailable: true, + tagline: getLocationProp( location, 'tagline' ), + setTagline: ( tagline ) => + setLocationProp( location, 'tagline', tagline ), }; }; From 4b47084aa20c96dcba52bc9e5e202fae5b61e1cf Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 13:20:15 +0100 Subject: [PATCH 33/63] =?UTF-8?q?=F0=9F=92=84=20Make=20disabled=20option?= =?UTF-8?q?=20more=20distinct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/css/_mixins.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/css/_mixins.scss b/modules/ppcp-settings/resources/css/_mixins.scss index 29cb3af1c..8c83eb436 100644 --- a/modules/ppcp-settings/resources/css/_mixins.scss +++ b/modules/ppcp-settings/resources/css/_mixins.scss @@ -49,7 +49,7 @@ .components-#{$control-type}-control__input, .components-#{$control-type}-control__label, .components-base-control__help { - opacity: 0.5; + opacity: 0.3; cursor: default; } } From 0428dd08aa0be410b500c6bf6f67fa79e6707165 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 13:20:58 +0100 Subject: [PATCH 34/63] =?UTF-8?q?=E2=9C=A8=20Add=20location-specific=20con?= =?UTF-8?q?figuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/configuration.js | 5 +++++ modules/ppcp-settings/resources/js/data/styling/hooks.js | 9 +++++++-- .../ppcp-settings/resources/js/data/styling/reducer.js | 4 ---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/configuration.js b/modules/ppcp-settings/resources/js/data/styling/configuration.js index ecb7a3765..eef2f90bf 100644 --- a/modules/ppcp-settings/resources/js/data/styling/configuration.js +++ b/modules/ppcp-settings/resources/js/data/styling/configuration.js @@ -16,6 +16,7 @@ export const STYLING_LOCATIONS = { 'wooocommerce-paypal-payments' ), link: '#', + props: { layout: false, tagline: false }, }, 'classic-checkout': { value: 'classic-checkout', @@ -26,6 +27,7 @@ export const STYLING_LOCATIONS = { 'wooocommerce-paypal-payments' ), link: '#', + props: { layout: true, tagline: true }, }, 'express-checkout': { value: 'express-checkout', @@ -36,6 +38,7 @@ export const STYLING_LOCATIONS = { 'wooocommerce-paypal-payments' ), link: '#', + props: { layout: false, tagline: false }, }, 'mini-cart': { value: 'mini-cart', @@ -46,6 +49,7 @@ export const STYLING_LOCATIONS = { 'wooocommerce-paypal-payments' ), link: '#', + props: { layout: true, tagline: true }, }, product: { value: 'product', @@ -56,6 +60,7 @@ export const STYLING_LOCATIONS = { 'wooocommerce-paypal-payments' ), link: '#', + props: { layout: true, tagline: true }, }, }; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 2e77d41f2..f2bee3952 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -138,10 +138,11 @@ export const useLabelProps = ( location ) => { export const useLayoutProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const { details } = useLocationProps( location ); return { choices: Object.values( STYLING_LAYOUTS ), - isAvailable: true, + isAvailable: false !== details.props.layout, layout: getLocationProp( location, 'layout' ), setLayout: ( layout ) => setLocationProp( location, 'layout', layout ), }; @@ -149,6 +150,7 @@ export const useLayoutProps = ( location ) => { export const useTaglineProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const { details } = useLocationProps( location ); return { choices: [ @@ -157,7 +159,10 @@ export const useTaglineProps = ( location ) => { label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), }, ], - isAvailable: true, + isAvailable: + false !== details.props.tagline && + STYLING_LAYOUTS.horizontal.value === + getLocationProp( location, 'layout' ), tagline: getLocationProp( location, 'tagline' ), setTagline: ( tagline ) => setLocationProp( location, 'tagline', tagline ), diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index 7c553091e..e41cd1e40 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -33,8 +33,6 @@ const defaultPersistent = Object.freeze( { label: STYLING_LABELS.pay.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - layout: STYLING_LAYOUTS.vertical.value, - tagline: false, } ), [ STYLING_LOCATIONS[ 'classic-checkout' ].value ]: Object.freeze( { enabled: true, @@ -51,8 +49,6 @@ const defaultPersistent = Object.freeze( { label: STYLING_LABELS.checkout.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, - layout: STYLING_LAYOUTS.vertical.value, - tagline: false, } ), [ STYLING_LOCATIONS[ 'mini-cart' ].value ]: Object.freeze( { enabled: true, From 00c00bf9b1719caccef534cd6af8869ceef99df0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 13:27:31 +0100 Subject: [PATCH 35/63] =?UTF-8?q?=F0=9F=92=84=20Improvement=20UX=20for=20t?= =?UTF-8?q?he=20=E2=80=9Ctagline=E2=80=9D=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/settings/_tab-styling.scss | 5 +++++ .../Components/Styling/Content/ButtonLayout.js | 18 +++++++++++------- .../Components/Styling/Content/TagLine.js | 2 +- .../Components/Styling/SettingsPanel.js | 2 -- .../resources/js/data/styling/hooks.js | 5 ++++- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index cc60e1619..071855a64 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -36,6 +36,11 @@ &.payment-methods { --block-separator-gap: 28px; } + + // It has no header; adjusts the gap to the control right above the tagline. + &.tagline { + --block-header-gap: 24px; + } } /* The settings-panel (left side) */ diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js index 1acf666f9..1a284f72a 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/ButtonLayout.js @@ -2,6 +2,7 @@ import { __ } from '@wordpress/i18n'; import { StylingHooks } from '../../../../../../data'; import { RadiobuttonStylingSection } from '../Layout'; +import { Tagline } from './index'; const SectionButtonLayout = ( { location } ) => { const { isAvailable, layout, setLayout, choices } = @@ -12,13 +13,16 @@ const SectionButtonLayout = ( { location } ) => { } return ( - + <> + + + ); }; diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js index ec292455c..54d823b22 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/TagLine.js @@ -13,8 +13,8 @@ const SectionTagline = ( { location } ) => { return ( { @@ -23,7 +22,6 @@ const SettingsPanel = () => { -
); }; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index f2bee3952..2767698d5 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -156,7 +156,10 @@ export const useTaglineProps = ( location ) => { choices: [ { value: 'tagline', - label: __( 'Enable Tagline', 'woocommerce-paypal-payments' ), + label: __( + 'Show tagline below buttons', + 'woocommerce-paypal-payments' + ), }, ], isAvailable: From a7be75b55e41691dbd545005fbd53f52f7d18939 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 15:35:21 +0100 Subject: [PATCH 36/63] =?UTF-8?q?=E2=9C=A8=20Add=20conditional=20logic=20t?= =?UTF-8?q?o=20layout=20&=20tagline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 2767698d5..439b0ec2a 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -139,10 +139,11 @@ export const useLabelProps = ( location ) => { export const useLayoutProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); const { details } = useLocationProps( location ); + const isAvailable = false !== details.props.layout; return { choices: Object.values( STYLING_LAYOUTS ), - isAvailable: false !== details.props.layout, + isAvailable, layout: getLocationProp( location, 'layout' ), setLayout: ( layout ) => setLocationProp( location, 'layout', layout ), }; @@ -152,6 +153,12 @@ export const useTaglineProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); const { details } = useLocationProps( location ); + // Tagline is only available for horizontal layouts. + const isAvailable = + false !== details.props.tagline && + STYLING_LAYOUTS.horizontal.value === + getLocationProp( location, 'layout' ); + return { choices: [ { @@ -162,11 +169,8 @@ export const useTaglineProps = ( location ) => { ), }, ], - isAvailable: - false !== details.props.tagline && - STYLING_LAYOUTS.horizontal.value === - getLocationProp( location, 'layout' ), - tagline: getLocationProp( location, 'tagline' ), + isAvailable, + tagline: isAvailable ? getLocationProp( location, 'tagline' ) : false, setTagline: ( tagline ) => setLocationProp( location, 'tagline', tagline ), }; From 1152079df0c4659f86c110fdba7e08f65b259b5d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 15:35:50 +0100 Subject: [PATCH 37/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Small=20improvements?= =?UTF-8?q?=20on=20reusable=20component=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SettingsBlocks/SettingsBlockElements.js | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js index de45a601b..46048e071 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/SettingsBlockElements.js @@ -7,12 +7,16 @@ export const Title = ( { big = false, className = '', } ) => { - className = classNames( 'ppcp-r-settings-block__title', className, { - 'style-alt': altStyle, - 'style-big': big, - } ); + const elementClasses = classNames( + 'ppcp-r-settings-block__title', + className, + { + 'style-alt': altStyle, + 'style-big': big, + } + ); - return { children }; + return { children }; }; export const TitleWrapper = ( { children } ) => ( @@ -31,15 +35,18 @@ export const Description = ( { children, asHtml = false, className = '' } ) => { return null; } - className = classNames( 'ppcp-r-settings-block__description', className ); + const elementClasses = classNames( + 'ppcp-r-settings-block__description', + className + ); if ( ! asHtml ) { - return { children }; + return { children }; } return ( ); @@ -56,11 +63,18 @@ export const Header = ( { children, className = '' } ) => ( ); // Card Elements -export const Content = ( { children, id = '' } ) => ( -
- { children } -
-); +export const Content = ( { children, className = '', id = '' } ) => { + const elementClasses = classNames( + 'ppcp-r-settings-card__content', + className + ); + + return ( +
+ { children } +
+ ); +}; export const ContentWrapper = ( { children } ) => (
{ children }
From bfcb12c9ea44bebad88396ace70ff4dd31177d1b Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 15:36:26 +0100 Subject: [PATCH 38/63] =?UTF-8?q?=E2=9C=A8=20Wrap=20Styling=20section=20co?= =?UTF-8?q?ntents=20in=20new=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Settings/Components/Styling/Layout/StylingSection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js index e528361dc..b9fefc52e 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSection.js @@ -3,6 +3,7 @@ import { Description, Header, Title, + Content, } from '../../../../../ReusableComponents/SettingsBlocks'; const StylingSection = ( { @@ -25,7 +26,7 @@ const StylingSection = ( { { description } - { children } + { children } ); }; From 48c68c1855710babce94660e9f56d66587cc2072 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 15:36:59 +0100 Subject: [PATCH 39/63] =?UTF-8?q?=F0=9F=92=84=20Remove=20the=20excess=20ma?= =?UTF-8?q?rgin=20below=20select=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Styling/Layout/StylingSectionWithSelect.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js index c4164d6b8..2b0ef8031 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Layout/StylingSectionWithSelect.js @@ -23,6 +23,7 @@ const StylingSectionWithSelect = ( { separatorAndGap={ separatorAndGap } > Date: Fri, 17 Jan 2025 15:38:08 +0100 Subject: [PATCH 40/63] =?UTF-8?q?=F0=9F=92=84=20New=20help=20icon=20instea?= =?UTF-8?q?d=20of=20text=20for=20location=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/settings/_tab-styling.scss | 8 ++++ .../Styling/Content/LocationSelector.js | 13 +++++-- .../js/data/styling/configuration.js | 37 ++++--------------- .../resources/js/data/styling/hooks.js | 4 -- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss index 071855a64..852e21b51 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-styling.scss @@ -25,6 +25,14 @@ padding-top: 16px; padding-bottom: var(--block-separator-gap); margin-bottom: -29px; + + .section-content { + display: flex; + + & > .components-base-control:first-of-type { + width: 100%; + } + } } // Select-fields have a smaller gap between the header and input field. diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js index f5d8231b8..8a6e6b4bc 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Styling/Content/LocationSelector.js @@ -1,11 +1,12 @@ import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; +import { help } from '@wordpress/icons'; import { StylingHooks } from '../../../../../../data'; -import { Description } from '../../../../../ReusableComponents/SettingsBlocks'; import { SelectStylingSection, StylingSection } from '../Layout'; const LocationSelector = ( { location, setLocation } ) => { - const { choices, description } = StylingHooks.useLocationProps( location ); + const { choices, details } = StylingHooks.useLocationProps( location ); return ( <> @@ -26,7 +27,13 @@ const LocationSelector = ( { location, setLocation } ) => { value={ location } onChange={ setLocation } > - { description } + { details.link && ( + From b866ac79f65837349c844d86c1550fa2746c8e5d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 16:34:06 +0100 Subject: [PATCH 49/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve=20location?= =?UTF-8?q?=20naming=20in=20React?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/configuration.js | 12 ++++++------ .../resources/js/data/styling/reducer.js | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/configuration.js b/modules/ppcp-settings/resources/js/data/styling/configuration.js index beed2d832..130d5451b 100644 --- a/modules/ppcp-settings/resources/js/data/styling/configuration.js +++ b/modules/ppcp-settings/resources/js/data/styling/configuration.js @@ -13,20 +13,20 @@ export const STYLING_LOCATIONS = { link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-cart', props: { layout: false, tagline: false }, }, - 'classic-checkout': { - value: 'classic-checkout', + classicCheckout: { + value: 'classicCheckout', label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ), link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-checkout', props: { layout: true, tagline: true }, }, - 'express-checkout': { - value: 'express-checkout', + expressCheckout: { + value: 'expressCheckout', label: __( 'Express Checkout', 'woocommerce-paypal-payments' ), link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-block-express-checkout', props: { layout: false, tagline: false }, }, - 'mini-cart': { - value: 'mini-cart', + miniCart: { + value: 'miniCart', label: __( 'Mini Cart', 'woocommerce-paypel-payements' ), link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-mini-cart', props: { layout: true, tagline: true }, diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index e41cd1e40..5ce6062a9 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -34,7 +34,7 @@ const defaultPersistent = Object.freeze( { shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, } ), - [ STYLING_LOCATIONS[ 'classic-checkout' ].value ]: Object.freeze( { + [ STYLING_LOCATIONS.classicCheckout.value ]: Object.freeze( { enabled: true, methods: [], label: STYLING_LABELS.checkout.value, @@ -43,14 +43,14 @@ const defaultPersistent = Object.freeze( { layout: STYLING_LAYOUTS.vertical.value, tagline: false, } ), - [ STYLING_LOCATIONS[ 'express-checkout' ].value ]: Object.freeze( { + [ STYLING_LOCATIONS.expressCheckout.value ]: Object.freeze( { enabled: true, methods: [], label: STYLING_LABELS.checkout.value, shape: STYLING_SHAPES.rect.value, color: STYLING_COLORS.gold.value, } ), - [ STYLING_LOCATIONS[ 'mini-cart' ].value ]: Object.freeze( { + [ STYLING_LOCATIONS.miniCart.value ]: Object.freeze( { enabled: true, methods: [], label: STYLING_LABELS.pay.value, From 00519d9e2541b6f2d8c90553de976b0e8e66d81d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 16:34:17 +0100 Subject: [PATCH 50/63] =?UTF-8?q?=F0=9F=94=A5=20Clean=20up=20Styling=20hoo?= =?UTF-8?q?ks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 79314e18b..e50896ea3 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -7,8 +7,7 @@ * @file */ -import { __, sprintf } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; // Temporary +import { useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { createHooksForStore } from '../utils'; @@ -26,8 +25,6 @@ const useHooks = () => { const { useTransient } = createHooksForStore( STORE_NAME ); const { persist, setPersistent } = useDispatch( STORE_NAME ); - // Read-only flags and derived state. - // Transient accessors. const [ isReady ] = useTransient( 'isReady' ); const [ location, setLocation ] = useTransient( 'location' ); @@ -39,13 +36,13 @@ const useHooks = () => { ); const getLocationProp = useCallback( - ( locatonId, prop ) => { - if ( undefined === persistentData[ locatonId ]?.[ prop ] ) { + ( locationId, prop ) => { + if ( undefined === persistentData[ locationId ]?.[ prop ] ) { console.error( - `Trying to access non-existent style property: ${ locatonId }.${ prop }. Possibly wrong style name - review the reducer.` + `Trying to access non-existent style property: ${ locationId }.${ prop }. Possibly wrong style name - review the reducer.` ); } - return persistentData[ locatonId ]?.[ prop ]; + return persistentData[ locationId ]?.[ prop ]; }, [ persistentData ] ); From 5e9866cde9147702022ced0c3db90d8df8ac6448 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:05:50 +0100 Subject: [PATCH 51/63] =?UTF-8?q?=E2=9C=A8=20Add=20data=20sanitation=20to?= =?UTF-8?q?=20the=20Redux=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 74 +++++++++++++++---- .../resources/js/data/styling/reducer.js | 29 +++++++- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index e50896ea3..ffcde584f 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -83,52 +83,86 @@ export const useLocationProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); const details = STYLING_LOCATIONS[ location ] ?? {}; + const sanitize = ( value ) => ( undefined === value ? true : !! value ); + return { choices: Object.values( STYLING_LOCATIONS ), details, - isActive: getLocationProp( location, 'enabled' ), - setActive: ( state ) => setLocationProp( location, 'enabled', state ), + isActive: sanitize( getLocationProp( location, 'enabled' ) ), + setActive: ( state ) => + setLocationProp( location, 'enabled', sanitize( state ) ), }; }; export const usePaymentMethodProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const sanitize = ( value ) => { + if ( Array.isArray( value ) ) { + return value; + } + return value ? [ value ] : []; + }; + return { choices: Object.values( STYLING_PAYMENT_METHODS ), - paymentMethods: getLocationProp( location, 'methods' ), + paymentMethods: sanitize( getLocationProp( location, 'methods' ) ), setPaymentMethods: ( methods ) => - setLocationProp( location, 'methods', methods ), + setLocationProp( location, 'methods', sanitize( methods ) ), }; }; export const useColorProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const sanitize = ( value ) => { + const isValidColor = Object.values( STYLING_COLORS ).some( + ( color ) => color.value === value + ); + return isValidColor ? value : STYLING_COLORS.gold.value; + }; + return { choices: Object.values( STYLING_COLORS ), - color: getLocationProp( location, 'color' ), - setColor: ( color ) => setLocationProp( location, 'color', color ), + color: sanitize( getLocationProp( location, 'color' ) ), + setColor: ( color ) => + setLocationProp( location, 'color', sanitize( color ) ), }; }; export const useShapeProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const sanitize = ( value ) => { + const isValidColor = Object.values( STYLING_SHAPES ).some( + ( color ) => color.value === value + ); + return isValidColor ? value : STYLING_SHAPES.rect.value; + }; + return { choices: Object.values( STYLING_SHAPES ), - shape: getLocationProp( location, 'shape' ), - setShape: ( shape ) => setLocationProp( location, 'shape', shape ), + shape: sanitize( getLocationProp( location, 'shape' ) ), + setShape: ( shape ) => + setLocationProp( location, 'shape', sanitize( shape ) ), }; }; export const useLabelProps = ( location ) => { const { getLocationProp, setLocationProp } = useHooks(); + const sanitize = ( value ) => { + const isValidColor = Object.values( STYLING_LABELS ).some( + ( color ) => color.value === value + ); + return isValidColor ? value : STYLING_LABELS.paypal.value; + }; + return { choices: Object.values( STYLING_LABELS ), - label: getLocationProp( location, 'label' ), - setLabel: ( label ) => setLocationProp( location, 'label', label ), + label: sanitize( getLocationProp( location, 'label' ) ), + setLabel: ( label ) => + setLocationProp( location, 'label', sanitize( label ) ), }; }; @@ -137,11 +171,19 @@ export const useLayoutProps = ( location ) => { const { details } = useLocationProps( location ); const isAvailable = false !== details.props.layout; + const sanitize = ( value ) => { + const isValidColor = Object.values( STYLING_LAYOUTS ).some( + ( color ) => color.value === value + ); + return isValidColor ? value : STYLING_LAYOUTS.vertical.value; + }; + return { choices: Object.values( STYLING_LAYOUTS ), isAvailable, - layout: getLocationProp( location, 'layout' ), - setLayout: ( layout ) => setLocationProp( location, 'layout', layout ), + layout: sanitize( getLocationProp( location, 'layout' ) ), + setLayout: ( layout ) => + setLocationProp( location, 'layout', sanitize( layout ) ), }; }; @@ -155,10 +197,14 @@ export const useTaglineProps = ( location ) => { STYLING_LAYOUTS.horizontal.value === getLocationProp( location, 'layout' ); + const sanitize = ( value ) => !! value; + return { isAvailable, - tagline: isAvailable ? getLocationProp( location, 'tagline' ) : false, + tagline: isAvailable + ? sanitize( getLocationProp( location, 'tagline' ) ) + : false, setTagline: ( tagline ) => - setLocationProp( location, 'tagline', tagline ), + setLocationProp( location, 'tagline', sanitize( tagline ) ), }; }; diff --git a/modules/ppcp-settings/resources/js/data/styling/reducer.js b/modules/ppcp-settings/resources/js/data/styling/reducer.js index 5ce6062a9..db92d646b 100644 --- a/modules/ppcp-settings/resources/js/data/styling/reducer.js +++ b/modules/ppcp-settings/resources/js/data/styling/reducer.js @@ -70,6 +70,19 @@ const defaultPersistent = Object.freeze( { } ), } ); +const sanitizeLocation = ( oldDetails, newDetails ) => { + // Skip if provided details are not a plain object. + if ( + ! newDetails || + 'object' !== typeof newDetails || + Array.isArray( newDetails ) + ) { + return oldDetails; + } + + return { ...oldDetails, ...newDetails }; +}; + // Reducer logic. const [ setTransient, setPersistent ] = createSetters( @@ -96,8 +109,20 @@ const reducer = createReducer( defaultTransient, defaultPersistent, { return cleanState; }, - [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => - setPersistent( state, payload.data ), + [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => { + const validData = Object.keys( defaultPersistent ).reduce( + ( data, location ) => { + data[ location ] = sanitizeLocation( + state.data[ location ], + payload.data[ location ] + ); + return data; + }, + {} + ); + + return setPersistent( state, validData ); + }, } ); export default reducer; From 451b45bc10996ca94bdf9822515ca99abc3ca809 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:06:45 +0100 Subject: [PATCH 52/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20the=20locat?= =?UTF-8?q?ion=20styling=20DTO=20with=20new=20props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/DTO/LocationStylingDTO.php | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-settings/src/DTO/LocationStylingDTO.php b/modules/ppcp-settings/src/DTO/LocationStylingDTO.php index 14e4471ea..30b3d0cef 100644 --- a/modules/ppcp-settings/src/DTO/LocationStylingDTO.php +++ b/modules/ppcp-settings/src/DTO/LocationStylingDTO.php @@ -16,46 +16,60 @@ namespace WooCommerce\PayPalCommerce\Settings\DTO; */ class LocationStylingDTO { /** - * The location name, e.g., card, block-checkout, ... + * The location name. * - * @var string + * @var string [cart|classic_checkout|express_checkout|mini_cart|product] */ - public string $location = ''; + public string $location; /** * Whether PayPal payments are enabled on this location. * * @var bool */ - public bool $enabled = false; + public bool $enabled; /** * List of active payment methods, e.g., 'venmo', 'applepay', ... * * @var array */ - public array $methods = array(); + public array $methods; /** * Shape of buttons on this location. * * @var string [rect|pill] */ - public string $shape = 'rect'; + public string $shape; /** * Label of the button on this location. * * @var string */ - public string $label = ''; + public string $label; /** * Color of the button on this location. * * @var string [gold|blue|silver|black|white] */ - public string $color = 'gold'; + public string $color; + + /** + * The button layout + * + * @var string [horizontal|vertical] + */ + public string $layout; + + /** + * Whether to show a tagline below the buttons. + * + * @var bool + */ + public bool $tagline; /** * Constructor. @@ -66,14 +80,18 @@ class LocationStylingDTO { * @param string $shape Shape of buttons on this location. * @param string $label Label of the button on this location. * @param string $color Color of the button on this location. + * @param string $layout Horizontal or vertical button layout. + * @param bool $tagline Whether to show a tagline below the buttons. */ public function __construct( - string $location, - bool $enabled, - array $methods, - string $shape, - string $label, - string $color + string $location = '', + bool $enabled = true, + array $methods = array(), + string $shape = 'rect', + string $label = 'pay', + string $color = 'gold', + string $layout = 'vertical', + bool $tagline = false ) { $this->location = $location; $this->enabled = $enabled; @@ -81,5 +99,7 @@ class LocationStylingDTO { $this->shape = $shape; $this->label = $label; $this->color = $color; + $this->layout = $layout; + $this->tagline = $tagline; } } From 63eb577126e27a533ffab4465417d45bb082d028 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:08:24 +0100 Subject: [PATCH 53/63] =?UTF-8?q?=E2=9C=A8=20Pass=20prop-name=20to=20the?= =?UTF-8?q?=20REST-sanitizer=20callback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/src/Endpoint/RestEndpoint.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php index 6f1eb0e4f..7fba1529c 100644 --- a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php @@ -112,9 +112,9 @@ abstract class RestEndpoint extends WC_REST_Controller { if ( null === $sanitation_cb ) { $sanitized[ $key ] = $value; } elseif ( is_string( $sanitation_cb ) && method_exists( $this, $sanitation_cb ) ) { - $sanitized[ $key ] = $this->{$sanitation_cb}( $value ); + $sanitized[ $key ] = $this->{$sanitation_cb}( $value, $key ); } elseif ( is_callable( $sanitation_cb ) ) { - $sanitized[ $key ] = $sanitation_cb( $value ); + $sanitized[ $key ] = $sanitation_cb( $value, $key ); } } From 0b90921dd43e8af960f012e3c96aa497332c1315 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:09:09 +0100 Subject: [PATCH 54/63] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20minor=20psalm=20warn?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php index 54aced7ef..b055ab3e2 100644 --- a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php @@ -90,7 +90,7 @@ class OnboardingRestEndpoint extends RestEndpoint { public function __construct( OnboardingProfile $profile ) { $this->profile = $profile; - $this->field_map['products']['sanitize'] = fn( $list ) => array_map( 'sanitize_text_field', $list ); + $this->field_map['products']['sanitize'] = static fn( $list ) => array_map( 'sanitize_text_field', $list ); } /** From 72d84546d1cd5e37232495b0fd44b8091db6ac83 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:12:40 +0100 Subject: [PATCH 55/63] =?UTF-8?q?=E2=9C=A8=20Process=20location-styles=20i?= =?UTF-8?q?n=20REST=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Data/StylingSettings.php | 53 +++++++++++++++++- .../src/Endpoint/StylingRestEndpoint.php | 56 ++++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-settings/src/Data/StylingSettings.php b/modules/ppcp-settings/src/Data/StylingSettings.php index ee942693c..9c81aa408 100644 --- a/modules/ppcp-settings/src/Data/StylingSettings.php +++ b/modules/ppcp-settings/src/Data/StylingSettings.php @@ -9,6 +9,8 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Data; +use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO; + /** * Class StylingSettings * @@ -30,7 +32,56 @@ class StylingSettings extends AbstractDataModel { */ protected function get_defaults() : array { return array( - 'shape' => 'rect', + 'cart' => new LocationStylingDTO( 'cart' ), + 'classic_checkout' => new LocationStylingDTO( 'classic_checkout' ), + 'express_checkout' => new LocationStylingDTO( 'express_checkout' ), + 'mini_cart' => new LocationStylingDTO( 'mini_cart' ), + 'product' => new LocationStylingDTO( 'product' ), ); } + + /** + * Get styling details for Cart and Block Cart. + * + * @return LocationStylingDTO + */ + public function get_cart() : LocationStylingDTO { + return $this->data['cart']; + } + + /** + * Get styling details for Classic Checkout. + * + * @return LocationStylingDTO + */ + public function get_classic_checkout() : LocationStylingDTO { + return $this->data['classic_checkout']; + } + + /** + * Get styling details for Express Checkout. + * + * @return LocationStylingDTO + */ + public function get_express_checkout() : LocationStylingDTO { + return $this->data['express_checkout']; + } + + /** + * Get styling details for Mini Cart + * + * @return LocationStylingDTO + */ + public function get_mini_cart() : LocationStylingDTO { + return $this->data['mini_cart']; + } + + /** + * Get styling details for Product Page. + * + * @return LocationStylingDTO + */ + public function get_product() : LocationStylingDTO { + return $this->data['product']; + } } diff --git a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php index 97b1158f2..8ad08f631 100644 --- a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php @@ -13,6 +13,7 @@ use WP_REST_Server; use WP_REST_Response; use WP_REST_Request; use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; +use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO; /** * REST controller for the "Styling" settings tab. @@ -41,8 +42,20 @@ class StylingRestEndpoint extends RestEndpoint { * @var array */ private array $field_map = array( - 'shape' => array( - 'js_name' => 'shape', + 'cart' => array( + 'js_name' => 'cart', + ), + 'classic_checkout' => array( + 'js_name' => 'classicCheckout', + ), + 'express_checkout' => array( + 'js_name' => 'expressCheckout', + ), + 'mini_cart' => array( + 'js_name' => 'miniCart', + ), + 'product' => array( + 'js_name' => 'product', ), ); @@ -53,6 +66,12 @@ class StylingRestEndpoint extends RestEndpoint { */ public function __construct( StylingSettings $settings ) { $this->settings = $settings; + + $this->field_map['cart']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['classic_checkout']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['express_checkout']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['mini_cart']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['product']['sanitize'] = array( $this, 'to_location' ); } /** @@ -123,4 +142,37 @@ class StylingRestEndpoint extends RestEndpoint { return $this->get_details(); } + + /** + * Converts the plain location-style input to a structured DTO. + * + * @param array $data Raw data received from the request. + * @param string $key The field name. + * + * @return LocationStylingDTO + */ + protected function to_location( array $data, string $key ) : LocationStylingDTO { + $is_enabled = ! isset( $data['enabled'] ) || $data['enabled']; + $methods = array(); + $shape = sanitize_text_field( $data['shape'] ?? 'rect' ); + $label = sanitize_text_field( $data['label'] ?? 'pay' ); + $color = sanitize_text_field( $data['color'] ?? 'gold' ); + $layout = sanitize_text_field( $data['layout'] ?? 'vertical' ); + $tagline = isset( $data['tagline'] ) && $data['tagline']; + + if ( isset( $data['methods'] ) && is_array( $data['methods'] ) ) { + $methods = array_map( 'sanitize_text_field', $data['methods'] ); + } + + return new LocationStylingDTO( + $key, + $is_enabled, + $methods, + $shape, + $label, + $color, + $layout, + $tagline, + ); + } } From 57afe4b95e29708233ac5d9f4504b3eac6587050 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 18:40:43 +0100 Subject: [PATCH 56/63] =?UTF-8?q?=E2=9C=A8=20Extract=20sanitizer=20logic?= =?UTF-8?q?=20to=20new=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/services.php | 9 +- .../src/Endpoint/StylingRestEndpoint.php | 74 +++++++-------- .../src/Service/DataSanitizer.php | 89 +++++++++++++++++++ 3 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 modules/ppcp-settings/src/Service/DataSanitizer.php diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 50afb3fdb..117833160 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -26,6 +26,7 @@ use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Settings\Endpoint\StylingRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; +use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer; return array( 'settings.url' => static function ( ContainerInterface $container ) : string { @@ -76,7 +77,10 @@ return array( return new CommonRestEndpoint( $container->get( 'settings.data.general' ) ); }, 'settings.rest.styling' => static function ( ContainerInterface $container ) : StylingRestEndpoint { - return new StylingRestEndpoint( $container->get( 'settings.data.styling' ) ); + return new StylingRestEndpoint( + $container->get( 'settings.data.styling' ), + $container->get( 'settings.service.sanitizer' ) + ); }, 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { return new RefreshFeatureStatusEndpoint( @@ -195,6 +199,9 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ), ); }, + 'settings.service.sanitizer' => static function ( ContainerInterface $container ) : DataSanitizer { + return new DataSanitizer(); + }, 'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint { return new SwitchSettingsUiEndpoint( $container->get( 'woocommerce.logger.woocommerce' ), diff --git a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php index 8ad08f631..450549e43 100644 --- a/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/StylingRestEndpoint.php @@ -14,6 +14,7 @@ use WP_REST_Response; use WP_REST_Request; use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings; use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO; +use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer; /** * REST controller for the "Styling" settings tab. @@ -36,6 +37,13 @@ class StylingRestEndpoint extends RestEndpoint { */ protected StylingSettings $settings; + /** + * Data sanitizer service. + * + * @var DataSanitizer + */ + protected DataSanitizer $sanitizer; + /** * Field mapping for request to profile transformation. * @@ -62,16 +70,33 @@ class StylingRestEndpoint extends RestEndpoint { /** * Constructor. * - * @param StylingSettings $settings The settings instance. + * @param StylingSettings $settings The settings instance. + * @param DataSanitizer $sanitizer Data sanitizer service. */ - public function __construct( StylingSettings $settings ) { - $this->settings = $settings; + public function __construct( StylingSettings $settings, DataSanitizer $sanitizer ) { + $this->settings = $settings; + $this->sanitizer = $sanitizer; - $this->field_map['cart']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['classic_checkout']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['express_checkout']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['mini_cart']['sanitize'] = array( $this, 'to_location' ); - $this->field_map['product']['sanitize'] = array( $this, 'to_location' ); + $this->field_map['cart']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['classic_checkout']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['express_checkout']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['mini_cart']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); + $this->field_map['product']['sanitize'] = array( + $this->sanitizer, + 'sanitize_location_style', + ); } /** @@ -142,37 +167,4 @@ class StylingRestEndpoint extends RestEndpoint { return $this->get_details(); } - - /** - * Converts the plain location-style input to a structured DTO. - * - * @param array $data Raw data received from the request. - * @param string $key The field name. - * - * @return LocationStylingDTO - */ - protected function to_location( array $data, string $key ) : LocationStylingDTO { - $is_enabled = ! isset( $data['enabled'] ) || $data['enabled']; - $methods = array(); - $shape = sanitize_text_field( $data['shape'] ?? 'rect' ); - $label = sanitize_text_field( $data['label'] ?? 'pay' ); - $color = sanitize_text_field( $data['color'] ?? 'gold' ); - $layout = sanitize_text_field( $data['layout'] ?? 'vertical' ); - $tagline = isset( $data['tagline'] ) && $data['tagline']; - - if ( isset( $data['methods'] ) && is_array( $data['methods'] ) ) { - $methods = array_map( 'sanitize_text_field', $data['methods'] ); - } - - return new LocationStylingDTO( - $key, - $is_enabled, - $methods, - $shape, - $label, - $color, - $layout, - $tagline, - ); - } } diff --git a/modules/ppcp-settings/src/Service/DataSanitizer.php b/modules/ppcp-settings/src/Service/DataSanitizer.php new file mode 100644 index 000000000..d7dccbbce --- /dev/null +++ b/modules/ppcp-settings/src/Service/DataSanitizer.php @@ -0,0 +1,89 @@ +sanitize_bool( $data['enabled'] ?? true ); + $shape = $this->sanitize_text( $data['shape'] ?? 'rect' ); + $label = $this->sanitize_text( $data['label'] ?? 'pay' ); + $color = $this->sanitize_text( $data['color'] ?? 'gold' ); + $layout = $this->sanitize_text( $data['layout'] ?? 'vertical' ); + $tagline = $this->sanitize_bool( $data['tagline'] ?? false ); + $methods = $this->sanitize_array( + $data['methods'] ?? array(), + array( $this, 'sanitize_text' ) + ); + + return new LocationStylingDTO( + $location, + $is_enabled, + $methods, + $shape, + $label, + $color, + $layout, + $tagline + ); + } + + /** + * Helper. Ensures the value is a string. + * + * @param mixed $value Value to sanitize. + * @param string $default Default value. + * @return string Sanitized string. + */ + protected function sanitize_text( $value, string $default = '' ) : string { + return sanitize_text_field( $value ?? $default ); + } + + /** + * Helper. Ensures the value is a boolean. + * + * @param mixed $value Value to sanitize. + * @return bool Sanitized boolean. + */ + protected function sanitize_bool( $value ) : bool { + return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); + } + + /** + * Helper. Ensures the value is an array and all items are sanitized. + * + * @param null|array $array Value to sanitize. + * @param callable $sanitize_callback Callback to sanitize each item in the array. + * @return array Array with sanitized items. + */ + protected function sanitize_array( ?array $array, callable $sanitize_callback ) : array { + if ( ! is_array( $array ) ) { + return array(); + } + + return array_map( $sanitize_callback, $array ); + } +} From 605760f5fb67247b8d1cc0af99b28c2f0bbc38b6 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:03:23 +0100 Subject: [PATCH 57/63] =?UTF-8?q?=E2=9C=A8=20Add=20data=20sanitizer=20to?= =?UTF-8?q?=20styling=20data=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/services.php | 4 +- .../src/Data/StylingSettings.php | 71 +++++++++++++++++++ .../src/Service/DataSanitizer.php | 20 +++++- 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 117833160..7bfd3fbff 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -68,7 +68,9 @@ return array( ); }, 'settings.data.styling' => static function ( ContainerInterface $container ) : StylingSettings { - return new StylingSettings(); + return new StylingSettings( + $container->get( 'settings.service.sanitizer' ) + ); }, 'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint { return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); diff --git a/modules/ppcp-settings/src/Data/StylingSettings.php b/modules/ppcp-settings/src/Data/StylingSettings.php index 9c81aa408..32fc3de2f 100644 --- a/modules/ppcp-settings/src/Data/StylingSettings.php +++ b/modules/ppcp-settings/src/Data/StylingSettings.php @@ -9,7 +9,9 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Data; +use RuntimeException; use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO; +use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer; /** * Class StylingSettings @@ -25,6 +27,25 @@ class StylingSettings extends AbstractDataModel { */ protected const OPTION_KEY = 'woocommerce-ppcp-data-styling'; + /** + * Data sanitizer service. + * + * @var DataSanitizer + */ + protected DataSanitizer $sanitizer; + + /** + * Constructor. + * + * @param DataSanitizer $sanitizer Data sanitizer service. + * @throws RuntimeException If the OPTION_KEY is not defined in the child class. + */ + public function __construct( DataSanitizer $sanitizer ) { + $this->sanitizer = $sanitizer; + + parent::__construct(); + } + /** * Get default values for the model. * @@ -49,6 +70,16 @@ class StylingSettings extends AbstractDataModel { return $this->data['cart']; } + /** + * Set styling details for Cart and Block Cart. + * + * @param mixed $styles The new styling details. + * @return void + */ + public function set_cart( $styles ) : void { + $this->data['cart'] = $this->sanitizer->sanitize_location_style( $styles ); + } + /** * Get styling details for Classic Checkout. * @@ -58,6 +89,16 @@ class StylingSettings extends AbstractDataModel { return $this->data['classic_checkout']; } + /** + * Set styling details for Classic Checkout. + * + * @param mixed $styles The new styling details. + * @return void + */ + public function set_classic_checkout( $styles ) : void { + $this->data['classic_checkout'] = $this->sanitizer->sanitize_location_style( $styles ); + } + /** * Get styling details for Express Checkout. * @@ -67,6 +108,16 @@ class StylingSettings extends AbstractDataModel { return $this->data['express_checkout']; } + /** + * Set styling details for Express Checkout. + * + * @param mixed $styles The new styling details. + * @return void + */ + public function set_express_checkout( $styles ) : void { + $this->data['express_checkout'] = $this->sanitizer->sanitize_location_style( $styles ); + } + /** * Get styling details for Mini Cart * @@ -76,6 +127,16 @@ class StylingSettings extends AbstractDataModel { return $this->data['mini_cart']; } + /** + * Set styling details for Mini Cart. + * + * @param mixed $styles The new styling details. + * @return void + */ + public function set_mini_cart( $styles ) : void { + $this->data['mini_cart'] = $this->sanitizer->sanitize_location_style( $styles ); + } + /** * Get styling details for Product Page. * @@ -84,4 +145,14 @@ class StylingSettings extends AbstractDataModel { public function get_product() : LocationStylingDTO { return $this->data['product']; } + + /** + * Set styling details for Product Page. + * + * @param mixed $styles The new styling details. + * @return void + */ + public function set_product( $styles ) : void { + $this->data['product'] = $this->sanitizer->sanitize_location_style( $styles ); + } } diff --git a/modules/ppcp-settings/src/Service/DataSanitizer.php b/modules/ppcp-settings/src/Service/DataSanitizer.php index d7dccbbce..3baab9034 100644 --- a/modules/ppcp-settings/src/Service/DataSanitizer.php +++ b/modules/ppcp-settings/src/Service/DataSanitizer.php @@ -19,11 +19,27 @@ class DataSanitizer { /** * Sanitizes the provided styling data. * - * @param array $data The styling data to sanitize. + * @param mixed $data The styling data to sanitize. * @param ?string $location Name of the location. * @return LocationStylingDTO Styling data. */ - public function sanitize_location_style( array $data, string $location = null ) : LocationStylingDTO { + public function sanitize_location_style( $data, string $location = null ) : LocationStylingDTO { + if ( $data instanceof LocationStylingDTO ) { + if ( $location ) { + $data->location = $location; + } + + return $data; + } + + if ( is_object( $data ) ) { + $data = (array) $data; + } + + if ( ! is_array( $data ) ) { + return new LocationStylingDTO( $location ?? '' ); + } + if ( null === $location ) { $location = $data['location'] ?? ''; } From 7365f75eba0ae896463b3f09be544de1374d3388 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:05:51 +0100 Subject: [PATCH 58/63] =?UTF-8?q?=F0=9F=90=9B=20Add=20missing=20action=20d?= =?UTF-8?q?ispatcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-settings/resources/js/data/styling/actions.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/ppcp-settings/resources/js/data/styling/actions.js b/modules/ppcp-settings/resources/js/data/styling/actions.js index 095a54c91..f7bdb4339 100644 --- a/modules/ppcp-settings/resources/js/data/styling/actions.js +++ b/modules/ppcp-settings/resources/js/data/styling/actions.js @@ -60,6 +60,14 @@ export const setPersistent = ( prop, value ) => ( { payload: { [ prop ]: value }, } ); +/** + * Transient. Changes the "ready-state" of the module. + * + * @param {boolean} state Whether the store is ready to be used. + * @return {Action} The action. + */ +export const setIsReady = ( state ) => setTransient( 'isReady', state ); + /** * Side effect. Triggers the persistence of store data to the server. * From 1726878f554ed5c3eaee43eb7cfc8734d17e3207 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:07:26 +0100 Subject: [PATCH 59/63] =?UTF-8?q?=E2=9C=A8=20Add=20busy-state=20to=20the?= =?UTF-8?q?=20save=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Screens/Settings/Components/Navigation.js | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js index 5f9c19640..b15f1beea 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js @@ -2,27 +2,33 @@ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import TopNavigation from '../../../ReusableComponents/TopNavigation'; -import { StylingHooks } from '../../../../data'; +import { CommonHooks, StylingHooks } from '../../../../data'; +import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper'; const SettingsNavigation = () => { + const { withActivity, isBusy } = CommonHooks.useBusyState(); + + // Todo: Implement other stores here. const { persist: persistStyling } = StylingHooks.useStore(); - const isBusy = false; // TODO: Implement loading state. const handleSaveClick = () => { - persistStyling(); + // Todo: Add other stores here. + withActivity( + 'persist-styling', + 'Save styling details', + persistStyling + ); }; const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' ); return ( - + + + ); }; From cd63b186087d9dda114bb654d8d0ff81e49446a3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:22:01 +0100 Subject: [PATCH 60/63] =?UTF-8?q?=F0=9F=8E=A8=20Minor=20code=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/Components/Screens/Settings/Components/Navigation.js | 4 ++-- modules/ppcp-settings/resources/js/data/styling/hooks.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js index b15f1beea..9023a1f9b 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js @@ -1,12 +1,12 @@ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import TopNavigation from '../../../ReusableComponents/TopNavigation'; import { CommonHooks, StylingHooks } from '../../../../data'; +import TopNavigation from '../../../ReusableComponents/TopNavigation'; import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper'; const SettingsNavigation = () => { - const { withActivity, isBusy } = CommonHooks.useBusyState(); + const { withActivity } = CommonHooks.useBusyState(); // Todo: Implement other stores here. const { persist: persistStyling } = StylingHooks.useStore(); diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index ffcde584f..8227d0124 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -41,8 +41,9 @@ const useHooks = () => { console.error( `Trying to access non-existent style property: ${ locationId }.${ prop }. Possibly wrong style name - review the reducer.` ); + return null; } - return persistentData[ locationId ]?.[ prop ]; + return persistentData[ locationId ][ prop ]; }, [ persistentData ] ); From 10129d49b55c9e142ee70aa41c9f9b661d54c7ca Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:25:10 +0100 Subject: [PATCH 61/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20new=20code?= =?UTF-8?q?=20style=20to=20example=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/_example/actions.js | 41 +++++++++++-------- .../resources/js/data/_example/hooks.js | 27 +++--------- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/_example/actions.js b/modules/ppcp-settings/resources/js/data/_example/actions.js index 7360424f4..59d68d37c 100644 --- a/modules/ppcp-settings/resources/js/data/_example/actions.js +++ b/modules/ppcp-settings/resources/js/data/_example/actions.js @@ -36,28 +36,37 @@ export const hydrate = ( payload ) => ( { payload, } ); +/** + * Generic transient-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setTransient = ( prop, value ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { [ prop ]: value }, +} ); + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); + /** * Transient. Marks the store as "ready", i.e., fully initialized. * * @param {boolean} isReady * @return {Action} The action. */ -export const setIsReady = ( isReady ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isReady }, -} ); - -/** - * Persistent. Sets a sample value. - * TODO: Replace with a real action/property. - * - * @param {string} value - * @return {Action} The action. - */ -export const setSampleValue = ( value ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { sampleValue: value }, -} ); +export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady ); /** * Side effect. Triggers the persistence of store data to the server. diff --git a/modules/ppcp-settings/resources/js/data/_example/hooks.js b/modules/ppcp-settings/resources/js/data/_example/hooks.js index 394fceb7e..b6878c2a9 100644 --- a/modules/ppcp-settings/resources/js/data/_example/hooks.js +++ b/modules/ppcp-settings/resources/js/data/_example/hooks.js @@ -7,39 +7,24 @@ * @file */ -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; +import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; -const useTransient = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).transientData()?.[ key ], - [ key ] - ); - -const usePersistent = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).persistentData()?.[ key ], - [ key ] - ); - const useHooks = () => { - const { - persist, - - // TODO: Replace with real property. - setSampleValue, - } = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + const { persist } = useDispatch( STORE_NAME ); // Read-only flags and derived state. // Nothing here yet. // Transient accessors. - const isReady = useTransient( 'isReady' ); + const [ isReady ] = useTransient( 'isReady' ); // Persistent accessors. // TODO: Replace with real property. - const sampleValue = usePersistent( 'sampleValue' ); + const [ sampleValue, setSampleValue ] = usePersistent( 'sampleValue' ); return { persist, From 4d38c15b2975ee0b9fa53908d46095ec7a9f0db7 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:25:44 +0100 Subject: [PATCH 62/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20new=20code?= =?UTF-8?q?=20style=20to=20onboarding=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/onboarding/actions.js | 73 +++++++++++-------- .../resources/js/data/onboarding/hooks.js | 50 +++++-------- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/onboarding/actions.js b/modules/ppcp-settings/resources/js/data/onboarding/actions.js index e9bf8ed5f..8c6f2999f 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/actions.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/actions.js @@ -36,16 +36,37 @@ export const hydrate = ( payload ) => ( { payload, } ); +/** + * Generic transient-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setTransient = ( prop, value ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { [ prop ]: value }, +} ); + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); + /** * Transient. Marks the onboarding details as "ready", i.e., fully initialized. * * @param {boolean} isReady * @return {Action} The action. */ -export const setIsReady = ( isReady ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isReady }, -} ); +export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady ); /** * Transient. Sets the "manualClientId" value. @@ -53,10 +74,8 @@ export const setIsReady = ( isReady ) => ( { * @param {string} manualClientId * @return {Action} The action. */ -export const setManualClientId = ( manualClientId ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { manualClientId }, -} ); +export const setManualClientId = ( manualClientId ) => + setTransient( 'manualClientId', manualClientId ); /** * Transient. Sets the "manualClientSecret" value. @@ -64,10 +83,8 @@ export const setManualClientId = ( manualClientId ) => ( { * @param {string} manualClientSecret * @return {Action} The action. */ -export const setManualClientSecret = ( manualClientSecret ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { manualClientSecret }, -} ); +export const setManualClientSecret = ( manualClientSecret ) => + setTransient( 'manualClientSecret', manualClientSecret ); /** * Persistent.Set the "onboarding completed" flag which shows or hides the wizard. @@ -75,10 +92,8 @@ export const setManualClientSecret = ( manualClientSecret ) => ( { * @param {boolean} completed * @return {Action} The action. */ -export const setCompleted = ( completed ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { completed }, -} ); +export const setCompleted = ( completed ) => + setPersistent( 'completed', completed ); /** * Persistent. Sets the onboarding wizard to a new step. @@ -86,10 +101,7 @@ export const setCompleted = ( completed ) => ( { * @param {number} step * @return {Action} The action. */ -export const setStep = ( step ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { step }, -} ); +export const setStep = ( step ) => setPersistent( 'step', step ); /** * Persistent. Sets the "isCasualSeller" value. @@ -97,10 +109,8 @@ export const setStep = ( step ) => ( { * @param {boolean} isCasualSeller * @return {Action} The action. */ -export const setIsCasualSeller = ( isCasualSeller ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { isCasualSeller }, -} ); +export const setIsCasualSeller = ( isCasualSeller ) => + setPersistent( 'isCasualSeller', isCasualSeller ); /** * Persistent. Sets the "areOptionalPaymentMethodsEnabled" value. @@ -110,10 +120,11 @@ export const setIsCasualSeller = ( isCasualSeller ) => ( { */ export const setAreOptionalPaymentMethodsEnabled = ( areOptionalPaymentMethodsEnabled -) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { areOptionalPaymentMethodsEnabled }, -} ); +) => + setPersistent( + 'areOptionalPaymentMethodsEnabled', + areOptionalPaymentMethodsEnabled + ); /** * Persistent. Sets the "products" array. @@ -121,10 +132,8 @@ export const setAreOptionalPaymentMethodsEnabled = ( * @param {string[]} products * @return {Action} The action. */ -export const setProducts = ( products ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { products }, -} ); +export const setProducts = ( products ) => + setPersistent( 'products', products ); /** * Side effect. Triggers the persistence of onboarding data to the server. diff --git a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js index 2d1542f68..70999960c 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js @@ -9,32 +9,14 @@ import { useSelect, useDispatch } from '@wordpress/data'; +import { createHooksForStore } from '../utils'; import { PRODUCT_TYPES } from './configuration'; import { STORE_NAME } from './constants'; -const useTransient = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).transientData()?.[ key ], - [ key ] - ); - -const usePersistent = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).persistentData()?.[ key ], - [ key ] - ); - const useHooks = () => { - const { - persist, - setStep, - setCompleted, - setIsCasualSeller, - setManualClientId, - setManualClientSecret, - setAreOptionalPaymentMethodsEnabled, - setProducts, - } = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + const { persist } = useDispatch( STORE_NAME ); // Read-only flags and derived state. const flags = useSelect( ( select ) => select( STORE_NAME ).flags(), [] ); @@ -44,18 +26,22 @@ const useHooks = () => { ); // Transient accessors. - const isReady = useTransient( 'isReady' ); - const manualClientId = useTransient( 'manualClientId' ); - const manualClientSecret = useTransient( 'manualClientSecret' ); + const [ isReady ] = useTransient( 'isReady' ); + const [ manualClientId, setManualClientId ] = + useTransient( 'manualClientId' ); + const [ manualClientSecret, setManualClientSecret ] = + useTransient( 'manualClientSecret' ); // Persistent accessors. - const step = usePersistent( 'step' ); - const completed = usePersistent( 'completed' ); - const isCasualSeller = usePersistent( 'isCasualSeller' ); - const areOptionalPaymentMethodsEnabled = usePersistent( - 'areOptionalPaymentMethodsEnabled' - ); - const products = usePersistent( 'products' ); + const [ step, setStep ] = usePersistent( 'step' ); + const [ completed, setCompleted ] = usePersistent( 'completed' ); + const [ isCasualSeller, setIsCasualSeller ] = + usePersistent( 'isCasualSeller' ); + const [ + areOptionalPaymentMethodsEnabled, + setAreOptionalPaymentMethodsEnabled, + ] = usePersistent( 'areOptionalPaymentMethodsEnabled' ); + const [ products, setProducts ] = usePersistent( 'products' ); const savePersistent = async ( setter, value ) => { setter( value ); From 77ed657394ff35a6275faa0c4769c131e401279f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 17 Jan 2025 19:25:55 +0100 Subject: [PATCH 63/63] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apple=20new=20code?= =?UTF-8?q?=20style=20to=20common=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/common/actions.js | 58 ++++++++++--------- .../resources/js/data/common/hooks.js | 28 +++------ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js index 8f2b14812..cf7837d54 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions.js +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -36,16 +36,37 @@ export const hydrate = ( payload ) => ( { payload, } ); +/** + * Generic transient-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setTransient = ( prop, value ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { [ prop ]: value }, +} ); + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); + /** * Transient. Marks the onboarding details as "ready", i.e., fully initialized. * * @param {boolean} isReady * @return {Action} The action. */ -export const setIsReady = ( isReady ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isReady }, -} ); +export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady ); /** * Transient. Sets the active settings tab. @@ -53,21 +74,8 @@ export const setIsReady = ( isReady ) => ( { * @param {string} activeModal * @return {Action} The action. */ -export const setActiveModal = ( activeModal ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { activeModal }, -} ); - -/** - * Transient. Changes the "saving" flag. - * - * @param {boolean} isSaving - * @return {Action} The action. - */ -export const setIsSaving = ( isSaving ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isSaving }, -} ); +export const setActiveModal = ( activeModal ) => + setTransient( 'activeModal', activeModal ); /** * Transient (Activity): Marks the start of an async activity @@ -107,10 +115,8 @@ export const stopActivity = ( id ) => ( { * @param {boolean} useSandbox * @return {Action} The action. */ -export const setSandboxMode = ( useSandbox ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { useSandbox }, -} ); +export const setSandboxMode = ( useSandbox ) => + setPersistent( 'useSandbox', useSandbox ); /** * Persistent. Toggles the "Manual Connection" mode on or off. @@ -118,10 +124,8 @@ export const setSandboxMode = ( useSandbox ) => ( { * @param {boolean} useManualConnection * @return {Action} The action. */ -export const setManualConnectionMode = ( useManualConnection ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { useManualConnection }, -} ); +export const setManualConnectionMode = ( useManualConnection ) => + setPersistent( 'useManualConnection', useManualConnection ); /** * Side effect. Saves the persistent details to the WP database. diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index 8fcf3f6a9..796d1c50c 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -9,41 +9,31 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { useCallback } from '@wordpress/element'; + +import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; -const useTransient = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).transientData()?.[ key ], - [ key ] - ); - -const usePersistent = ( key ) => - useSelect( - ( select ) => select( STORE_NAME ).persistentData()?.[ key ], - [ key ] - ); - const useHooks = () => { + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); const { persist, - setSandboxMode, - setManualConnectionMode, sandboxOnboardingUrl, productionOnboardingUrl, authenticateWithCredentials, authenticateWithOAuth, - setActiveModal, startWebhookSimulation, checkWebhookSimulationState, } = useDispatch( STORE_NAME ); // Transient accessors. - const isReady = useTransient( 'isReady' ); - const activeModal = useTransient( 'activeModal' ); + const [ isReady ] = useTransient( 'isReady' ); + const [ activeModal, setActiveModal ] = useTransient( 'activeModal' ); // Persistent accessors. - const isSandboxMode = usePersistent( 'useSandbox' ); - const isManualConnectionMode = usePersistent( 'useManualConnection' ); + const [ isSandboxMode, setSandboxMode ] = usePersistent( 'useSandbox' ); + const [ isManualConnectionMode, setManualConnectionMode ] = usePersistent( + 'useManualConnection' + ); const merchant = useSelect( ( select ) => select( STORE_NAME ).merchant(), []