From e0c5f8acc18ba62ced43604f17601601328b9982 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Tue, 19 Sep 2023 16:17:49 +0200 Subject: [PATCH 01/20] Fix: order metadata changes from admin not applied with HPOS active. When an order is open in admin and changes are made to custom field keys or values, or values are added in "Add New Custom Field", and "Update" in the order is clicked (without having clicked "Update" in the modified fields or "Add Custom Field"), the field changes should be applied to the order anyway. That was happening when the posts table is authoritative but not when the orders table is. --- .../class-wc-meta-box-order-data.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php index 79bae985d1f..531c76175b1 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php @@ -8,6 +8,7 @@ * @version 2.2.0 */ +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Automattic\WooCommerce\Utilities\OrderUtil; if ( ! defined( 'ABSPATH' ) ) { @@ -708,6 +709,37 @@ class WC_Meta_Box_Order_Data { $props['customer_note'] = sanitize_textarea_field( wp_unslash( $_POST['customer_note'] ) ); } + // Metadata (only needed when HPOS table is in use). + if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) { + $order_meta = $order->get_meta_data(); + + $order_meta = + array_combine( + array_map( fn( $meta ) => $meta->id, $order_meta ), + array_map( fn( $meta ) => $meta, $order_meta ) + ); + + // phpcs:disable WordPress.Security.ValidatedSanitizedInput + + foreach ( ( $_POST['meta'] ?? array() ) as $request_meta_id => $request_meta_data ) { + $request_meta_id = wp_unslash( $request_meta_id ); + $request_meta_key = wp_unslash( $request_meta_data['key'] ); + $request_meta_value = wp_unslash( $request_meta_data['value'] ); + if ( array_key_exists( $request_meta_id, $order_meta ) && + ( $order_meta[ $request_meta_id ]->key !== $request_meta_key || $order_meta[ $request_meta_id ]->value !== $request_meta_value ) ) { + $order->update_meta_data( $request_meta_key, $request_meta_value, $request_meta_id ); + } + } + + $request_new_key = wp_unslash( $_POST['metakeyinput'] ?? '' ); + $request_new_value = wp_unslash( $_POST['metavalue'] ?? '' ); + if ( '' !== $request_new_key ) { + $order->add_meta_data( $request_new_key, $request_new_value ); + } + + // phpcs:enable WordPress.Security.ValidatedSanitizedInput + } + // Save order data. $order->set_props( $props ); $order->set_status( wc_clean( wp_unslash( $_POST['order_status'] ) ), '', true ); From 451521bc05ac0c2426d5561d90baf7c966e33411 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 19 Sep 2023 14:48:50 +0000 Subject: [PATCH 02/20] Add changefile(s) from automation for the following project(s): woocommerce --- .../changelog/fix-metadata-not-updated-on-order-update | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-metadata-not-updated-on-order-update diff --git a/plugins/woocommerce/changelog/fix-metadata-not-updated-on-order-update b/plugins/woocommerce/changelog/fix-metadata-not-updated-on-order-update new file mode 100644 index 00000000000..0f9f0a5e20d --- /dev/null +++ b/plugins/woocommerce/changelog/fix-metadata-not-updated-on-order-update @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix changes in order custom fields made from admin not being applied when using the order Update button with HPOS active. From 463e095353fa079ca2ac6de2a6d850cdfcc7735a Mon Sep 17 00:00:00 2001 From: Jorge Torres Date: Tue, 19 Sep 2023 15:57:09 +0100 Subject: [PATCH 03/20] Add missing order type handling in HPOS sync --- .../DataStores/Orders/DataSynchronizer.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php index c559b251703..b0f31dcddd9 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php @@ -251,12 +251,15 @@ class DataSynchronizer implements BatchProcessorInterface { } if ( $this->custom_orders_table_is_authoritative() ) { - $missing_orders_count_sql = " + $missing_orders_count_sql = $wpdb->prepare( + " SELECT COUNT(1) FROM $wpdb->posts posts INNER JOIN $orders_table orders ON posts.id=orders.id WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "' AND orders.status not in ( 'auto-draft' ) -"; + AND orders.type IN ($order_post_type_placeholder)", + $order_post_types + ); $operator = '>'; } else { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholder is prepared. @@ -374,13 +377,16 @@ ORDER BY posts.ID ASC", // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare break; case self::ID_TYPE_MISSING_IN_POSTS_TABLE: - $sql = " + $sql = $wpdb->prepare( + " SELECT posts.ID FROM $wpdb->posts posts INNER JOIN $orders_table orders ON posts.id=orders.id WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "' AND orders.status not in ( 'auto-draft' ) -ORDER BY posts.id ASC -"; +AND orders.type IN ($order_post_type_placeholders) +ORDER BY posts.id ASC", + $order_post_types + ); break; case self::ID_TYPE_DIFFERENT_UPDATE_DATE: $operator = $this->custom_orders_table_is_authoritative() ? '>' : '<'; From ba3e6229f535b7ef1b6b5fc94923b2b2dc04387c Mon Sep 17 00:00:00 2001 From: Jorge Torres Date: Tue, 19 Sep 2023 15:57:13 +0100 Subject: [PATCH 04/20] Add changelog --- .../changelog/fix-hpos-pending-sync-count-order-types | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-hpos-pending-sync-count-order-types diff --git a/plugins/woocommerce/changelog/fix-hpos-pending-sync-count-order-types b/plugins/woocommerce/changelog/fix-hpos-pending-sync-count-order-types new file mode 100644 index 00000000000..80db4ec992f --- /dev/null +++ b/plugins/woocommerce/changelog/fix-hpos-pending-sync-count-order-types @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Address missing order type handling in HPOS compatibility mode sync. From 72f8943b6c68d19f7c56cf83371e7326c6176420 Mon Sep 17 00:00:00 2001 From: Barry Hughes <3594411+barryhughes@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:03:52 -0700 Subject: [PATCH 05/20] Regression test for `woocommerce_get_customer_details` ajax endpoint. (#40273) * Regression test for `woocommerce_get_customer_details` ajax endpoint. * Remove unnecesssary code manipulating customer selector. * Clean-up test customer. --- .../changelog/add-regression-test-user-meta | 5 ++ .../e2e-pw/tests/merchant/order-edit.spec.js | 69 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 plugins/woocommerce/changelog/add-regression-test-user-meta diff --git a/plugins/woocommerce/changelog/add-regression-test-user-meta b/plugins/woocommerce/changelog/add-regression-test-user-meta new file mode 100644 index 00000000000..52ede0eec5e --- /dev/null +++ b/plugins/woocommerce/changelog/add-regression-test-user-meta @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Adds regression test for https://github.com/woocommerce/woocommerce/pull/40221. + + diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js index 94ae4cef3ac..270f93ba851 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/order-edit.spec.js @@ -86,6 +86,75 @@ test.describe( 'Edit order', () => { '2018-12-14' ); } ); + + test( 'can load billing details', async ( { page, baseURL } ) => { + let customerId = 0; + + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + + await api + .post( 'customers', { + email: 'archie123@email.addr', + first_name: 'Archie', + last_name: 'Greenback', + username: 'big.archie', + billing: { + first_name: 'Archibald', + last_name: 'Greenback', + company: 'Automattic', + country: 'US', + address_1: 'address1', + address_2: 'address2', + city: 'San Francisco', + state: 'CA', + postcode: '94107', + phone: '123456789', + email: 'archie123@email.addr', + } + } ) + .then( ( response ) => { + customerId = response.data.id; + } ); + + // Open our test order and select the customer we just created. + await page.goto( `wp-admin/post.php?post=${orderId}&action=edit` ); + + // Simulate the ajax `woocommerce_get_customer_details` call normally done inside meta-boxes-order.js. + const response = await page.evaluate( + async ( customerId ) => { + const simulateCustomerDetailsCall = new Promise( ( resolve ) => { + jQuery.ajax( { + url: woocommerce_admin_meta_boxes.ajax_url, + data: { + user_id : customerId, + action : 'woocommerce_get_customer_details', + security: woocommerce_admin_meta_boxes.get_customer_details_nonce + }, + type: 'POST', + success: function( response ) { + resolve( response ); + } + } ); + } ); + + return await simulateCustomerDetailsCall; + }, + customerId + ); + + // Response should contain billing address info, but should not contain user meta data. + expect( 'billing' in response ).toBeTruthy(); + expect( response.billing.first_name ).toContain( 'Archibald' ); + expect( response.meta_data ).toBeUndefined(); + + // Clean-up. + await api.delete( `customers/${ customerId }`, { force: true } ); + } ); } ); test.describe( 'Edit order > Downloadable product permissions', () => { From 10bb0cc82211cc79904730858137f16ac12c77cc Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:47:05 +1000 Subject: [PATCH 06/20] fix/cys ui feedback 12 sep (#40155) --- .../assembler-hub/resizable-frame.jsx | 3 +- .../customize-store/assembler-hub/style.scss | 7 +-- .../components/choice/choice.scss | 1 + .../design-with-ai/pages/LookAndFeel.tsx | 2 +- .../design-with-ai/pages/ToneOfVoice.tsx | 5 +- .../client/customize-store/style.scss | 6 ++- plugins/woocommerce-admin/client/index.js | 3 ++ .../derive-wp-admin-background-colours.ts | 48 +++++++++++++++++++ .../changelog/fix-cys-visual-tweaks-12-sep | 4 ++ 9 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 plugins/woocommerce-admin/client/utils/derive-wp-admin-background-colours.ts create mode 100644 plugins/woocommerce/changelog/fix-cys-visual-tweaks-12-sep diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/resizable-frame.jsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/resizable-frame.jsx index 0af6db93f08..ef9d8e66c64 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/resizable-frame.jsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/resizable-frame.jsx @@ -180,13 +180,12 @@ function ResizableFrame( { left: 0, }, visible: { - opacity: 1, + opacity: 0.6, left: -10, }, active: { opacity: 1, left: -10, - scaleY: 1.3, }, }; const currentResizeHandleVariant = ( () => { diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss b/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss index ae92f2895ea..c10c0e30c8a 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss @@ -431,7 +431,8 @@ .edit-site-layout__canvas { bottom: 16px; top: 16px; - padding: 0 16px; + left: 12px; // the default styles for this undersizes the width by 24px so we want to center this + padding: 0 4px 0 16px; } .edit-site-resizable-frame__handle { @@ -442,13 +443,13 @@ padding: 0 10px; align-items: flex-start; gap: 10px; - background: #d7defb; + background: var(--wp-admin-theme-color-background-25); left: 5px !important; border-radius: 4px; height: 20px; .components-popover__content { - color: #1d35b4; + color: var(--wp-admin-theme-color-darker-20); font-size: 0.75rem; font-style: normal; font-weight: 500; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/components/choice/choice.scss b/plugins/woocommerce-admin/client/customize-store/design-with-ai/components/choice/choice.scss index e90b762ba9d..71788874562 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/components/choice/choice.scss +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/components/choice/choice.scss @@ -35,6 +35,7 @@ font-weight: 500; line-height: 20px; /* 125% */ letter-spacing: -0.24px; + margin-bottom: 8px; } p { diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/LookAndFeel.tsx b/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/LookAndFeel.tsx index ec77bf542cf..f6f748d8f39 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/LookAndFeel.tsx +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/LookAndFeel.tsx @@ -33,7 +33,7 @@ export const LookAndFeel = ( { title: __( 'Contemporary', 'woocommerce' ), key: 'Contemporary' as const, subtitle: __( - 'Clean lines, neutral colors, sleek and modern look', + 'Clean lines, neutral colors, sleek and modern look.', 'woocommerce' ), }, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/ToneOfVoice.tsx b/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/ToneOfVoice.tsx index b832a0d29cc..16ef43026f3 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/ToneOfVoice.tsx +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/pages/ToneOfVoice.tsx @@ -77,7 +77,10 @@ export const ToneOfVoice = ( {

- { __( 'How would you like to sound?', 'woocommerce' ) } + { __( + 'Which writing style do you prefer?', + 'woocommerce' + ) }

{ choices.map( ( { title, subtitle, key } ) => { diff --git a/plugins/woocommerce-admin/client/customize-store/style.scss b/plugins/woocommerce-admin/client/customize-store/style.scss index b6fb0524d48..66e6c91a481 100644 --- a/plugins/woocommerce-admin/client/customize-store/style.scss +++ b/plugins/woocommerce-admin/client/customize-store/style.scss @@ -20,6 +20,10 @@ body.woocommerce-customize-store.js.is-fullscreen-mode { margin-top: 0 !important; height: 100%; + + & > div.tour-kit.woocommerce-tour-kit > div > div.tour-kit-spotlight.is-visible { + outline: 99999px solid rgba(0, 0, 0, 0.15); + } } .woocommerce-cys-layout { @@ -78,7 +82,7 @@ body.woocommerce-customize-store.js.is-fullscreen-mode { padding: 12px; width: 404px; border-radius: 2px; - background: var(--gutenberg-transparent-blueberry, rgba(56, 88, 233, 0.04)); + background: var(--wp-admin-theme-color-background-04, rgba(168, 168, 170, 0.301)); color: var(--gutenberg-gray-800, #2f2f2f); p { diff --git a/plugins/woocommerce-admin/client/index.js b/plugins/woocommerce-admin/client/index.js index 9feefdbba9e..07a2190c324 100644 --- a/plugins/woocommerce-admin/client/index.js +++ b/plugins/woocommerce-admin/client/index.js @@ -19,6 +19,7 @@ import { EmbeddedBodyLayout } from './embedded-body-layout'; import { WcAdminPaymentsGatewaysBannerSlot } from './payments/payments-settings-banner-slotfill'; import { WcAdminConflictErrorSlot } from './settings/conflict-error-slotfill.js'; import './xstate.js'; +import { deriveWpAdminBackgroundColours } from './utils/derive-wp-admin-background-colours'; // Modify webpack pubilcPath at runtime based on location of WordPress Plugin. // eslint-disable-next-line no-undef,camelcase @@ -48,6 +49,8 @@ const embeddedRoot = document.getElementById( 'woocommerce-embedded-root' ); const settingsGroup = 'wc_admin'; const hydrateUser = getAdminSetting( 'currentUserData' ); +deriveWpAdminBackgroundColours(); + if ( appRoot ) { let HydratedPageLayout = withSettingsHydration( settingsGroup, diff --git a/plugins/woocommerce-admin/client/utils/derive-wp-admin-background-colours.ts b/plugins/woocommerce-admin/client/utils/derive-wp-admin-background-colours.ts new file mode 100644 index 00000000000..c039678f374 --- /dev/null +++ b/plugins/woocommerce-admin/client/utils/derive-wp-admin-background-colours.ts @@ -0,0 +1,48 @@ +/** + * Helper function that lightens a colour by blending it + * with some percentage of white + */ +function blendWithWhite( hex: string, alpha: number ) { + let r = parseInt( hex.slice( 1, 3 ), 16 ), + g = parseInt( hex.slice( 3, 5 ), 16 ), + b = parseInt( hex.slice( 5, 7 ), 16 ); + + // Blend with white + r = Math.floor( ( 1 - alpha ) * 255 + alpha * r ); + g = Math.floor( ( 1 - alpha ) * 255 + alpha * g ); + b = Math.floor( ( 1 - alpha ) * 255 + alpha * b ); + + // Convert to hex + const newHex = + '#' + + r.toString( 16 ).padStart( 2, '0' ) + + g.toString( 16 ).padStart( 2, '0' ) + + b.toString( 16 ).padStart( 2, '0' ); + + return newHex; +} + +/** + * wp-admin theme colour only include the main colour, + * but in some applications we want to derive a complementary + * background colour that's some percentage lighter than the + * wp-admin theme colour. This is not doable in CSS as it involves + * breaking down the hex colour code and then running calculations on it. + * As of writing, CSS calc can only operate on individual numbers + */ +export const deriveWpAdminBackgroundColours = () => { + const rootStyles = window.getComputedStyle( document.body ); + const wpAdminThemeColor = rootStyles + .getPropertyValue( '--wp-admin-theme-color' ) + .trim(); + + document.documentElement.style.setProperty( + '--wp-admin-theme-color-background-04', + blendWithWhite( wpAdminThemeColor, 0.04 ) + ); + + document.documentElement.style.setProperty( + '--wp-admin-theme-color-background-25', + blendWithWhite( wpAdminThemeColor, 0.25 ) + ); +}; diff --git a/plugins/woocommerce/changelog/fix-cys-visual-tweaks-12-sep b/plugins/woocommerce/changelog/fix-cys-visual-tweaks-12-sep new file mode 100644 index 00000000000..44d77d37c32 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-cys-visual-tweaks-12-sep @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Addressed visual tweaks for CYS in response to feedback from 12th Sept From cff7ee6ccc3a4064ec5edd4972447e7dbbf662b1 Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Wed, 20 Sep 2023 11:59:06 +0800 Subject: [PATCH 07/20] Add basic e2e setup and tests for the Assembler Hub (#40235) --- plugins/woocommerce/.wp-env.json | 74 ++++---- .../changelog/dev-add-assembler-e2e-tests | 4 + .../bin/enable-experimental-features.php | 13 -- .../tests/e2e-pw/bin/test-env-setup.sh | 4 +- .../tests/e2e-pw/bin/test-helper-apis.php | 106 +++++++++++ .../complete-onboarding-wizard.spec.js | 168 ++++++++---------- .../e2e-pw/tests/admin-tasks/payment.spec.js | 1 - .../customize-store/assembler-hub.spec.js | 65 +++++++ .../tests/e2e-pw/utils/features.js | 49 +++-- .../woocommerce/tests/e2e-pw/utils/options.js | 24 +++ .../tests/e2e-pw/utils/plugin-utils.js | 2 +- .../woocommerce/tests/e2e-pw/utils/themes.js | 16 ++ .../tests/e2e/docker/initialize.sh | 4 +- 13 files changed, 372 insertions(+), 158 deletions(-) create mode 100644 plugins/woocommerce/changelog/dev-add-assembler-e2e-tests delete mode 100644 plugins/woocommerce/tests/e2e-pw/bin/enable-experimental-features.php create mode 100644 plugins/woocommerce/tests/e2e-pw/bin/test-helper-apis.php create mode 100644 plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler-hub.spec.js create mode 100644 plugins/woocommerce/tests/e2e-pw/utils/options.js create mode 100644 plugins/woocommerce/tests/e2e-pw/utils/themes.js diff --git a/plugins/woocommerce/.wp-env.json b/plugins/woocommerce/.wp-env.json index c2d13bafcdc..e277dcb5211 100644 --- a/plugins/woocommerce/.wp-env.json +++ b/plugins/woocommerce/.wp-env.json @@ -1,40 +1,38 @@ { - "phpVersion": "7.4", - "plugins": [ - "." - ], - "config": { - "JETPACK_AUTOLOAD_DEV": true, - "WP_DEBUG_LOG": true, - "WP_DEBUG_DISPLAY": true, - "ALTERNATE_WP_CRON": true - }, - "mappings": { - "wp-cli.yml": "./tests/wp-cli.yml", - "wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php", - "wp-content/plugins/enable-experimental-features.php": "./tests/e2e-pw/bin/enable-experimental-features.php" - }, - "lifecycleScripts": { - "afterStart": "./tests/e2e-pw/bin/test-env-setup.sh", - "afterClean": "./tests/e2e-pw/bin/test-env-setup.sh" - }, - "env": { - "development": {}, - "tests": { - "port": 8086, - "plugins": [ - ".", - "https://downloads.wordpress.org/plugin/akismet.zip", - "https://github.com/WP-API/Basic-Auth/archive/master.zip", - "https://downloads.wordpress.org/plugin/wp-mail-logging.zip" - ], - "themes": [ - "https://downloads.wordpress.org/theme/twentynineteen.zip" - ], - "config": { - "WP_TESTS_DOMAIN": "localhost", - "ALTERNATE_WP_CRON": false - } - } - } + "phpVersion": "7.4", + "plugins": [ "." ], + "config": { + "JETPACK_AUTOLOAD_DEV": true, + "WP_DEBUG_LOG": true, + "WP_DEBUG_DISPLAY": true, + "ALTERNATE_WP_CRON": true + }, + "mappings": { + "wp-cli.yml": "./tests/wp-cli.yml", + "wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php", + "wp-content/plugins/test-helper-apis.php": "./tests/e2e-pw/bin/test-helper-apis.php" + }, + "lifecycleScripts": { + "afterStart": "./tests/e2e-pw/bin/test-env-setup.sh", + "afterClean": "./tests/e2e-pw/bin/test-env-setup.sh" + }, + "env": { + "development": {}, + "tests": { + "port": 8086, + "plugins": [ + ".", + "https://downloads.wordpress.org/plugin/akismet.zip", + "https://github.com/WP-API/Basic-Auth/archive/master.zip", + "https://downloads.wordpress.org/plugin/wp-mail-logging.zip" + ], + "themes": [ + "https://downloads.wordpress.org/theme/twentynineteen.zip" + ], + "config": { + "WP_TESTS_DOMAIN": "localhost", + "ALTERNATE_WP_CRON": false + } + } + } } diff --git a/plugins/woocommerce/changelog/dev-add-assembler-e2e-tests b/plugins/woocommerce/changelog/dev-add-assembler-e2e-tests new file mode 100644 index 00000000000..c8e94e72597 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-add-assembler-e2e-tests @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add some basic E2E tests for Assembler Hub diff --git a/plugins/woocommerce/tests/e2e-pw/bin/enable-experimental-features.php b/plugins/woocommerce/tests/e2e-pw/bin/enable-experimental-features.php deleted file mode 100644 index 327cb6994e5..00000000000 --- a/plugins/woocommerce/tests/e2e-pw/bin/enable-experimental-features.php +++ /dev/null @@ -1,13 +0,0 @@ - 'POST', + 'callback' => 'update_feature_flags', + 'permission_callback' => 'is_allowed', + ) + ); + + register_rest_route( + 'e2e-feature-flags', + '/reset', + array( + 'methods' => 'GET', + 'callback' => 'reset_feature_flags', + 'permission_callback' => 'is_allowed', + ) + ); + + register_rest_route( + 'e2e-options', + '/update', + array( + 'methods' => 'POST', + 'callback' => 'api_update_option', + 'permission_callback' => 'is_allowed', + ) + ); +} + +add_action( 'rest_api_init', 'register_helper_api' ); + +/** + * Update feature flags + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ +function update_feature_flags( WP_REST_Request $request ) { + $features = get_option( 'e2e_feature_flags', array() ); + $new_features = json_decode( $request->get_body(), true ); + + if ( is_array( $new_features ) ) { + $features = array_merge( $features, $new_features ); + update_option( 'e2e_feature_flags', $features ); + return new WP_REST_Response( 'Feature flags updated', 200 ); + } + + return new WP_REST_Response( 'Invalid request body', 400 ); +} + +/** + * Reset feature flags + * @return WP_REST_Response + */ +function reset_feature_flags() { + delete_option( 'e2e_feature_flags' ); + return new WP_REST_Response( 'Feature flags reset', 200 ); +} + +/** + * Enable experimental features + * @param array $features Array of features. + * @return array + */ +function enable_experimental_features( $features ) { + $stored_features = get_option( 'e2e_feature_flags', array() ); + + // We always enable this for tests at the moment. + $features['product-variation-management'] = true; + + return array_merge( $features, $stored_features ); +} + +add_filter( 'woocommerce_admin_get_feature_config', 'enable_experimental_features' ); + +/** + * Update a WordPress option. + * @param WP_REST_Request $request + * @return WP_REST_Response + */ +function api_update_option( WP_REST_Request $request ) { + $option_name = sanitize_text_field( $request['option_name'] ); + $option_value = sanitize_text_field( $request['option_value'] ); + + if ( update_option( $option_name, $option_value ) ) { + return new WP_REST_Response( 'Option updated', 200 ); + } + + return new WP_REST_Response( 'Invalid request body', 400 ); +} + +/** + * Check if user is admin + * @return bool + */ +function is_allowed() { + return current_user_can( 'manage_options' ); +} diff --git a/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/complete-onboarding-wizard.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/complete-onboarding-wizard.spec.js index 043a87ab940..9c7db55ce99 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/complete-onboarding-wizard.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/activate-and-setup/complete-onboarding-wizard.spec.js @@ -2,8 +2,6 @@ const { test, expect } = require( '@playwright/test' ); const { onboarding } = require( '../../utils' ); const { storeDetails } = require( '../../test-data/data' ); const { api } = require( '../../utils' ); -const { features } = require( '../../utils' ); -const { describe } = require('node:test'); test.skip( 'Store owner can complete onboarding wizard', () => { test.use( { storageState: process.env.ADMINSTATE } ); @@ -61,7 +59,6 @@ test.skip( 'Store owner can complete onboarding wizard', () => { test( 'can discard industry changes when navigating back to "Store Details"', async ( { page, } ) => { - // set up pre-condition to ensure Industries stored in // storeDetails.us.industries2 have been set await onboarding.completeIndustrySection( @@ -80,7 +77,9 @@ test.skip( 'Store owner can complete onboarding wizard', () => { if ( saveChangesModalVisible ) { // Save the changes to ensure the test is now in the correct state // independent of the previous test results - await onboarding.handleSaveChangesModal( page, { saveChanges: true } ); + await onboarding.handleSaveChangesModal( page, { + saveChanges: true, + } ); } // test proper begins @@ -156,101 +155,90 @@ test.skip( 'Store owner can complete onboarding wizard', () => { } ); } ); -// Skipping Onbaording tests as we're replacing StoreDetails with Core Profiler +// Skipping Onboarding tests as we're replacing StoreDetails with Core Profiler // !Changed from Japanese to Liberian store, as Japanese Yen does not use decimals -test.skip( - 'A Liberian store can complete the selective bundle install but does not include WCPay.', - () => { - test.use( { storageState: process.env.ADMINSTATE } ); +test.skip( 'A Liberian store can complete the selective bundle install but does not include WCPay.', () => { + test.use( { storageState: process.env.ADMINSTATE } ); - test.beforeEach( async () => { - // Complete "Store Details" step through the API to prevent flakiness when run on external sites. - await api.update.storeDetails( storeDetails.liberia.store ); - } ); + test.beforeEach( async () => { + // Complete "Store Details" step through the API to prevent flakiness when run on external sites. + await api.update.storeDetails( storeDetails.liberia.store ); + } ); - // eslint-disable-next-line jest/expect-expect - test( 'can choose the "Other" industry', async ( { page } ) => { - await onboarding.completeIndustrySection( - page, - storeDetails.liberia.industries, - storeDetails.liberia.expectedNumberOfIndustries - ); - await page.locator( 'button >> text=Continue' ).click(); - } ); - - // eslint-disable-next-line jest/expect-expect - test( 'can choose not to install any extensions', async ( { + // eslint-disable-next-line jest/expect-expect + test( 'can choose the "Other" industry', async ( { page } ) => { + await onboarding.completeIndustrySection( page, - } ) => { - const expect_wp_pay = false; + storeDetails.liberia.industries, + storeDetails.liberia.expectedNumberOfIndustries + ); + await page.locator( 'button >> text=Continue' ).click(); + } ); - await onboarding.completeIndustrySection( - page, - storeDetails.liberia.industries, - storeDetails.liberia.expectedNumberOfIndustries - ); - await page.locator( 'button >> text=Continue' ).click(); + // eslint-disable-next-line jest/expect-expect + test( 'can choose not to install any extensions', async ( { page } ) => { + const expect_wp_pay = false; - await onboarding.completeProductTypesSection( - page, - storeDetails.liberia.products - ); - // Make sure WC Payments is NOT present - await expect( - page.locator( - '.woocommerce-admin__business-details__selective-extensions-bundle__description a[href*=woocommerce-payments]' - ) - ).not.toBeVisible(); - - await page.locator( 'button >> text=Continue' ).click(); - - await onboarding.completeBusinessDetailsSection( page ); - await page.locator( 'button >> text=Continue' ).click(); - - await onboarding.unselectBusinessFeatures( page, expect_wp_pay ); - - await page.locator( 'button >> text=Continue' ).click(); - - await expect( page ).not.toHaveURL( /.*step=business-details/ ); - } ); - - // Skipping this test because it's very flaky. Onboarding checklist changed so that the text - // changes when a task is completed. - // eslint-disable-next-line jest/no-disabled-tests - test.skip( 'should display the choose payments task, and not the WC Pay task', async ( { + await onboarding.completeIndustrySection( page, - } ) => { - // If payment has previously been setup, the setup checklist will show something different - // This step resets it - await page.goto( - 'wp-admin/admin.php?page=wc-settings&tab=checkout' - ); - // Ensure that all payment methods are disabled - await expect( - page.locator( '.woocommerce-input-toggle--disabled' ) - ).toHaveCount( 3 ); - // Checklist shows when completing setup wizard - await onboarding.completeBusinessDetailsSection( page ); - await page.locator( 'button >> text=Continue' ).click(); + storeDetails.liberia.industries, + storeDetails.liberia.expectedNumberOfIndustries + ); + await page.locator( 'button >> text=Continue' ).click(); - await onboarding.unselectBusinessFeatures( page, expect_wp_pay ); - await page.locator( 'button >> text=Continue' ).click(); + await onboarding.completeProductTypesSection( + page, + storeDetails.liberia.products + ); + // Make sure WC Payments is NOT present + await expect( + page.locator( + '.woocommerce-admin__business-details__selective-extensions-bundle__description a[href*=woocommerce-payments]' + ) + ).not.toBeVisible(); - // Start test - await page.waitForLoadState( 'networkidle' ); - await expect( - page.locator( - ':nth-match(.woocommerce-task-list__item-title, 3)' - ) - ).toContainText( 'Set up payments' ); - await expect( - page.locator( - ':nth-match(.woocommerce-task-list__item-title, 3)' - ) - ).not.toContainText( 'Set up WooPayments' ); - } ); - } -); + await page.locator( 'button >> text=Continue' ).click(); + + await onboarding.completeBusinessDetailsSection( page ); + await page.locator( 'button >> text=Continue' ).click(); + + await onboarding.unselectBusinessFeatures( page, expect_wp_pay ); + + await page.locator( 'button >> text=Continue' ).click(); + + await expect( page ).not.toHaveURL( /.*step=business-details/ ); + } ); + + // Skipping this test because it's very flaky. Onboarding checklist changed so that the text + // changes when a task is completed. + // eslint-disable-next-line jest/no-disabled-tests + test.skip( 'should display the choose payments task, and not the WC Pay task', async ( { + page, + } ) => { + // If payment has previously been setup, the setup checklist will show something different + // This step resets it + await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=checkout' ); + // Ensure that all payment methods are disabled + await expect( + page.locator( '.woocommerce-input-toggle--disabled' ) + ).toHaveCount( 3 ); + // Checklist shows when completing setup wizard + await onboarding.completeBusinessDetailsSection( page ); + await page.locator( 'button >> text=Continue' ).click(); + + await onboarding.unselectBusinessFeatures( page, expect_wp_pay ); + await page.locator( 'button >> text=Continue' ).click(); + + // Start test + await page.waitForLoadState( 'networkidle' ); + await expect( + page.locator( ':nth-match(.woocommerce-task-list__item-title, 3)' ) + ).toContainText( 'Set up payments' ); + await expect( + page.locator( ':nth-match(.woocommerce-task-list__item-title, 3)' ) + ).not.toContainText( 'Set up WooPayments' ); + } ); +} ); // Skipping this test because it's very flaky. test.skip( 'Store owner can go through setup Task List', () => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js index 50b74fff794..452858bc320 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/admin-tasks/payment.spec.js @@ -1,6 +1,5 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -const { features } = require( '../../utils' ); test.describe( 'Payment setup task', () => { test.use( { storageState: process.env.ADMINSTATE } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler-hub.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler-hub.spec.js new file mode 100644 index 00000000000..d7b6a6a18c8 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler-hub.spec.js @@ -0,0 +1,65 @@ +const { test, expect, request } = require( '@playwright/test' ); +const { features } = require( '../../utils' ); +const { activateTheme } = require( '../../utils/themes' ); +const { setOption } = require( '../../utils/options' ); + +const ASSEMBLER_HUB_URL = + '/wp-admin/admin.php?page=wc-admin&path=%2Fcustomize-store%2Fassembler-hub'; + +test.describe( 'Store owner can view Assembler Hub for store customization', () => { + test.use( { storageState: process.env.ADMINSTATE } ); + + test.beforeAll( async ( { baseURL } ) => { + // In some environments the tour blocks clicking other elements. + await setOption( + request, + baseURL, + 'woocommerce_customize_store_onboarding_tour_hidden', + 'yes' + ); + + await features.setFeatureFlag( + request, + baseURL, + 'customize-store', + true + ); + + // Need a block enabled theme to test + await activateTheme( 'twentytwentythree' ); + } ); + + test.afterAll( async ( { baseURL } ) => { + await features.resetFeatureFlags( request, baseURL ); + + // Reset theme back to twentynineteen + await activateTheme( 'twentynineteen' ); + + // Reset tour to visible. + await setOption( + request, + baseURL, + 'woocommerce_customize_store_onboarding_tour_hidden', + 'no' + ); + } ); + + test( 'Can view the Assembler Hub page', async ( { page } ) => { + await page.goto( ASSEMBLER_HUB_URL ); + const locator = page.locator( 'h1:visible' ); + await expect( locator ).toHaveText( "Let's get creative" ); + } ); + + test( 'Visiting change header should show a list of block patterns to choose from', async ( { + page, + } ) => { + await page.goto( ASSEMBLER_HUB_URL ); + await page.click( 'text=Change your header' ); + + const locator = page.locator( + '.block-editor-block-patterns-list__list-item' + ); + + await expect( locator ).toHaveCount( 4 ); + } ); +} ); diff --git a/plugins/woocommerce/tests/e2e-pw/utils/features.js b/plugins/woocommerce/tests/e2e-pw/utils/features.js index 4aa3ecff2b9..db75f1b003b 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/features.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/features.js @@ -1,14 +1,41 @@ -function is_enabled( feature ) { - const phase = process.env.WC_ADMIN_PHASE; - let config = 'development.json'; - if ( ![ 'core','developer' ].includes( phase ) ) { - config = 'core.json'; - } +const { encodeCredentials } = require( './plugin-utils' ); - const features = require( `../../../client/admin/config/${config}` ).features; - return features[ feature ] && features[ feature ] === true; -} +const setFeatureFlag = async ( request, baseURL, flagName, enable ) => { + const apiContext = await request.newContext( { + baseURL, + extraHTTPHeaders: { + Authorization: `Basic ${ encodeCredentials( + 'admin', + 'password' + ) }`, + cookie: '', + }, + } ); + + await apiContext.post( '/wp-json/e2e-feature-flags/update', { + failOnStatusCode: true, + data: { [ flagName ]: enable }, + } ); +}; + +const resetFeatureFlags = async ( request, baseURL ) => { + const apiContext = await request.newContext( { + baseURL, + extraHTTPHeaders: { + Authorization: `Basic ${ encodeCredentials( + 'admin', + 'password' + ) }`, + cookie: '', + }, + } ); + + await apiContext.get( '/wp-json/e2e-feature-flags/reset', { + failOnStatusCode: true, + } ); +}; module.exports = { - is_enabled -} \ No newline at end of file + setFeatureFlag, + resetFeatureFlags, +}; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/options.js b/plugins/woocommerce/tests/e2e-pw/utils/options.js new file mode 100644 index 00000000000..1254c94ec47 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/options.js @@ -0,0 +1,24 @@ +import { encodeCredentials } from './plugin-utils'; + +export const setOption = async ( + request, + baseURL, + optionName, + optionValue +) => { + const apiContext = await request.newContext( { + baseURL, + extraHTTPHeaders: { + Authorization: `Basic ${ encodeCredentials( + 'admin', + 'password' + ) }`, + cookie: '', + }, + } ); + + await apiContext.post( '/wp-json/e2e-options/update', { + failOnStatusCode: true, + data: { option_name: optionName, option_value: optionValue }, + } ); +}; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js b/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js index a3be326a9a5..9b7e4ca97e3 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/plugin-utils.js @@ -12,7 +12,7 @@ const execAsync = promisify( require( 'child_process' ).exec ); * @param {string} password * @returns Base64-encoded string */ -const encodeCredentials = ( username, password ) => { +export const encodeCredentials = ( username, password ) => { return Buffer.from( `${ username }:${ password }` ).toString( 'base64' ); }; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/themes.js b/plugins/woocommerce/tests/e2e-pw/utils/themes.js new file mode 100644 index 00000000000..7a8b9a9a456 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/themes.js @@ -0,0 +1,16 @@ +const { exec } = require( 'node:child_process' ); + +export const activateTheme = ( themeName ) => { + return new Promise( ( resolve, reject ) => { + const command = `wp-env run tests-cli wp theme activate ${ themeName }`; + + exec( command, ( error, stdout, stderr ) => { + if ( error ) { + console.error( `Error executing command: ${ error }` ); + return reject( error ); + } + + resolve( stdout ); + } ); + } ); +}; diff --git a/plugins/woocommerce/tests/e2e/docker/initialize.sh b/plugins/woocommerce/tests/e2e/docker/initialize.sh index 4772fccbd17..7a3ce96b740 100755 --- a/plugins/woocommerce/tests/e2e/docker/initialize.sh +++ b/plugins/woocommerce/tests/e2e/docker/initialize.sh @@ -30,5 +30,5 @@ wp plugin activate filter-setter # initialize pretty permalinks wp rewrite structure /%postname%/ -# Activate our Enable Experimental Features utility. -wp plugin activate enable-experimental-features +# Activate our helper APIs plugin. +wp plugin activate test-helper-apis From b0ee77621fe0d097a0d91eaed072ff6125f440c2 Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:26:15 +1000 Subject: [PATCH 08/20] add: cys ai font pairing (#40240) * add: cys ai font pairing suggestion * Move cys ai tests to test folder * Update plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts Co-authored-by: Chi-Hsuan Huang --------- Co-authored-by: Chi-Hsuan Huang --- .../customize-store/design-with-ai/actions.ts | 20 +++ .../design-with-ai/prompts/fontPairings.ts | 120 ++++++++++++++++++ .../design-with-ai/prompts/index.ts | 1 + .../prompts/{ => test}/colorChoices.test.ts | 2 +- .../prompts/test/fontPairings.test.ts | 47 +++++++ .../prompts/{ => test}/lookAndTone.test.ts | 4 +- .../design-with-ai/state-machine.tsx | 23 +++- .../customize-store/design-with-ai/types.ts | 5 +- .../add-customize-store-font-pairing | 4 + 9 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts rename plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/{ => test}/colorChoices.test.ts (98%) create mode 100644 plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts rename plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/{ => test}/lookAndTone.test.ts (89%) create mode 100644 plugins/woocommerce/changelog/add-customize-store-font-pairing diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts index 661504275c1..ed7d6312f73 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts @@ -12,6 +12,7 @@ import { ColorPalette, designWithAiStateMachineContext, designWithAiStateMachineEvents, + FontPairing, LookAndToneCompletionResponse, } from './types'; import { aiWizardClosedBeforeCompletionEvent } from './events'; @@ -91,6 +92,24 @@ const assignDefaultColorPalette = assign< }, } ); +const assignFontPairing = assign< + designWithAiStateMachineContext, + designWithAiStateMachineEvents +>( { + aiSuggestions: ( context, event: unknown ) => { + return { + ...context.aiSuggestions, + fontPairing: ( + event as { + data: { + response: FontPairing; + }; + } + ).data.response.pair_name, + }; + }, +} ); + const logAIAPIRequestError = () => { // log AI API request error // eslint-disable-next-line no-console @@ -158,6 +177,7 @@ export const actions = { assignToneOfVoice, assignLookAndTone, assignDefaultColorPalette, + assignFontPairing, logAIAPIRequestError, updateQueryStep, recordTracksStepViewed, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts new file mode 100644 index 00000000000..e8af1cb7549 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts @@ -0,0 +1,120 @@ +/** + * External dependencies + */ +import { z } from 'zod'; + +/** This block below was generated by ChatGPT using GPT-4 on 2023-09-18 */ +/** Original source: plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/font-pairing-variations/constants.ts */ +const fontChoices = [ + { + pair_name: 'Bodoni Moda + Overpass', + fonts: { + 'Bodoni Moda': + 'A modern serif font with high contrast between thick and thin lines, commonly used for headings.', + Overpass: + 'A clean, modern sans-serif, originally inspired by Highway Gothic. Good for text and UI elements.', + }, + settings: + 'Overpass is used for buttons and general typography, while Bodoni Moda is specified for headings and some core blocks like site title and post navigation link.', + }, + { + pair_name: 'Commissioner + Crimson Pro', + fonts: { + Commissioner: + 'A low-contrast, geometric sans-serif, designed for legibility and readability in long texts.', + 'Crimson Pro': + 'A serif typeface designed for readability and long-form text.', + }, + settings: + 'Commissioner dominates elements like buttons, headings, and core blocks, while Crimson Pro is set for general typography.', + }, + { + pair_name: 'Libre Baskerville + DM Sans', + fonts: { + 'Libre Baskerville': + 'A serif typeface with a classic feel, good for long reading and often used for body text in books.', + 'DM Sans': + 'A clean, geometric sans-serif, often used for UI and short text.', + }, + settings: + 'Libre Baskerville is used for headings and core blocks, whereas DM Sans is used for buttons and general typography.', + }, + { + pair_name: 'Libre Franklin + EB Garamond', + fonts: { + 'Libre Franklin': + 'A sans-serif that offers readability, suitable for both text and display.', + 'EB Garamond': + "A revival of the classical 'Garamond' typefaces, suitable for long-form text.", + }, + settings: + 'Libre Franklin is predominantly used for elements like buttons, headings, and core blocks. EB Garamond is set for general typography.', + }, + { + pair_name: 'Montserrat + Arvo', + fonts: { + Montserrat: + 'A geometric sans-serif, popular for its modern clean lines.', + Arvo: 'A slab-serif font with a more traditional feel, suitable for print and screen.', + }, + settings: + 'Montserrat is used for buttons, headings, and core blocks. Arvo is used for general typography.', + }, + { + pair_name: 'Playfair Display + Fira Sans', + fonts: { + 'Playfair Display': + 'A high-contrast serif designed for headings and offers a modern take on older serif fonts.', + 'Fira Sans': + 'A sans-serif designed for readability at small sizes, making it suitable for both UI and text.', + }, + settings: + 'Playfair Display is used in italics for headings and core blocks, while Fira Sans is used for buttons and general typography.', + }, + { + pair_name: 'Rubik + Inter', + fonts: { + Rubik: 'A sans-serif with slightly rounded corners, designed for a softer, more modern look.', + Inter: 'A highly legible sans-serif, optimized for UI design.', + }, + settings: + 'Rubik is applied for headings and core blocks. Inter is used for buttons and general typography.', + }, + { + pair_name: 'Space Mono + Roboto', + fonts: { + 'Space Mono': 'A monospace typeface with a futuristic vibe.', + Roboto: 'A neo-grotesque sans-serif, known for its flexibility and modern design.', + }, + settings: + 'Space Mono is used for headings, while Roboto takes care of buttons and general typography.', + }, +]; + +const allowedFontChoices = fontChoices.map( ( config ) => config.pair_name ); + +export const fontChoiceValidator = z.object( { + pair_name: z + .string() + .refine( ( name ) => allowedFontChoices.includes( name ), { + message: 'Font choice not part of allowed list', + } ), +} ); + +export const fontPairings = { + queryId: 'font_pairings', + version: '2023-09-18', + prompt: ( businessDescription: string, look: string, tone: string ) => { + return ` + You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate font pairing. + Respond only with one font pairing and and in the format: '{"pair_name":"font 1 + font 2"}'. + + Chosen look and tone: ${ look } look, ${ tone } tone. + Business description: ${ businessDescription } + + Font pairings to choose from: + ${ JSON.stringify( fontChoices ) } + `; + }, + responseValidation: fontChoiceValidator.parse, +}; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts index ee134ed5941..b0533ec1dc8 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts @@ -1,2 +1,3 @@ export * from './colorChoices'; export * from './lookAndTone'; +export * from './fontPairings'; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts similarity index 98% rename from plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts rename to plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts index 91c7d0df06e..3903c3fce3e 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { defaultColorPalette } from '.'; +import { defaultColorPalette } from '..'; describe( 'colorPairing.responseValidation', () => { it( 'should validate a correct color palette', () => { diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts new file mode 100644 index 00000000000..dfb132a8e24 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import { fontChoiceValidator } from '..'; + +describe( 'fontChoiceValidator', () => { + it( 'should validate when font choice is part of the allowed list', () => { + const validFontChoice = { pair_name: 'Montserrat + Arvo' }; + expect( () => + fontChoiceValidator.parse( validFontChoice ) + ).not.toThrow(); + } ); + + it( 'should not validate when font choice is not part of the allowed list', () => { + const invalidFontChoice = { pair_name: 'Comic Sans' }; + expect( () => fontChoiceValidator.parse( invalidFontChoice ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"custom\\", + \\"message\\": \\"Font choice not part of allowed list\\", + \\"path\\": [ + \\"pair_name\\" + ] + } + ]" + ` ); + } ); + + it( 'should not validate when pair_name is not a string', () => { + const invalidType = { pair_name: 123 }; + expect( () => fontChoiceValidator.parse( invalidType ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"invalid_type\\", + \\"expected\\": \\"string\\", + \\"received\\": \\"number\\", + \\"path\\": [ + \\"pair_name\\" + ], + \\"message\\": \\"Expected string, received number\\" + } + ]" + ` ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts similarity index 89% rename from plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts rename to plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts index cea15cc91b2..a5957d25a3e 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts @@ -1,8 +1,8 @@ /** * Internal dependencies */ -import { LookAndToneCompletionResponse } from '../types'; -import { lookAndTone } from '.'; +import { LookAndToneCompletionResponse } from '../../types'; +import { lookAndTone } from '..'; describe( 'parseLookAndToneCompletionResponse', () => { it( 'should return a valid object when given valid JSON', () => { diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx index 58dd0367583..1f1ff61d994 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx @@ -11,6 +11,7 @@ import { designWithAiStateMachineContext, designWithAiStateMachineEvents, ColorPalette, + FontPairing, } from './types'; import { BusinessInfoDescription, @@ -20,7 +21,7 @@ import { } from './pages'; import { actions } from './actions'; import { services } from './services'; -import { defaultColorPalette } from './prompts'; +import { defaultColorPalette, fontPairings } from './prompts'; export const hasStepInUrl = ( _ctx: unknown, @@ -70,6 +71,7 @@ export const designWithAiStateMachineDefinition = createMachine( }, aiSuggestions: { defaultColorPalette: {} as ColorPalette, + fontPairing: '' as FontPairing[ 'pair_name' ], }, }, initial: 'navigate', @@ -291,6 +293,25 @@ export const designWithAiStateMachineDefinition = createMachine( }, }, }, + chooseFontPairing: { + invoke: { + src: 'queryAiEndpoint', + data: ( context ) => { + return { + ...fontPairings, + prompt: fontPairings.prompt( + context.businessInfoDescription + .descriptionText, + context.lookAndFeel.choice, + context.toneOfVoice.choice + ), + }; + }, + onDone: { + actions: [ 'assignFontPairing' ], + }, + }, + }, }, }, postApiCallLoader: {}, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts index 743d094d1c4..b44a81cd4be 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts @@ -5,7 +5,7 @@ import { z } from 'zod'; /** * Internal dependencies */ -import { colorPaletteValidator } from './prompts'; +import { colorPaletteValidator, fontChoiceValidator } from './prompts'; export type designWithAiStateMachineContext = { businessInfoDescription: { @@ -19,6 +19,7 @@ export type designWithAiStateMachineContext = { }; aiSuggestions: { defaultColorPalette: ColorPalette; + fontPairing: FontPairing[ 'pair_name' ]; }; // If we require more data from options, previously provided core profiler details, // we can retrieve them in preBusinessInfoDescription and then assign them here @@ -50,3 +51,5 @@ export interface LookAndToneCompletionResponse { } export type ColorPalette = z.infer< typeof colorPaletteValidator >; + +export type FontPairing = z.infer< typeof fontChoiceValidator >; diff --git a/plugins/woocommerce/changelog/add-customize-store-font-pairing b/plugins/woocommerce/changelog/add-customize-store-font-pairing new file mode 100644 index 00000000000..e5724ead763 --- /dev/null +++ b/plugins/woocommerce/changelog/add-customize-store-font-pairing @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add customize store AI wizard call for font pairing suggestion From 2313667d81bb7f53a1572f47210b5773a0a065fb Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Wed, 20 Sep 2023 12:52:22 +0800 Subject: [PATCH 09/20] Address CYS UI feedback 19 Sep (#40269) * Direct user to the assembler hub automatically after saving * Unset active state on color palette when user changes colors * Add changefile(s) from automation for the following project(s): woocommerce * Remove unneed styles * Fix color variation logic --------- Co-authored-by: github-actions --- .../sidebar/global-styles/color-panel.jsx | 20 ++++++++- .../global-styles/variation-container.jsx | 10 ++++- .../assembler-hub/sidebar/index.tsx | 2 +- .../assembler-hub/sidebar/save-hub.tsx | 11 +++-- .../customize-store/assembler-hub/style.scss | 43 ++----------------- .../changelog/update-address-cys-ui-feedback | 4 ++ 6 files changed, 42 insertions(+), 48 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-address-cys-ui-feedback diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/color-panel.jsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/color-panel.jsx index c6107b9d25c..c9199f3a039 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/color-panel.jsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/color-panel.jsx @@ -4,15 +4,19 @@ */ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; import { unlock } from '@wordpress/edit-site/build-module/lock-unlock'; +import { useContext } from '@wordpress/element'; +import { mergeBaseAndUserConfigs } from '@wordpress/edit-site/build-module/components/global-styles/global-styles-provider'; const { useGlobalStyle, useGlobalSetting, useSettingsForBlockElement, ColorPanel: StylesColorPanel, + GlobalStylesContext, } = unlock( blockEditorPrivateApis ); export const ColorPanel = () => { + const { setUserConfig } = useContext( GlobalStylesContext ); const [ style ] = useGlobalStyle( '', undefined, 'user', { shouldDecodeEncode: false, } ); @@ -22,11 +26,25 @@ export const ColorPanel = () => { const [ rawSettings ] = useGlobalSetting( '' ); const settings = useSettingsForBlockElement( rawSettings ); + const onChange = ( ...props ) => { + setStyle( ...props ); + setUserConfig( ( currentConfig ) => ( { + ...currentConfig, + settings: mergeBaseAndUserConfigs( currentConfig.settings, { + color: { + palette: { + hasCreatedOwnColors: true, + }, + }, + } ), + } ) ); + }; + return ( ); diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx index 71aabc0679a..50a751b84d1 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/variation-container.jsx @@ -28,6 +28,15 @@ export const VariationContainer = ( { variation, children } ) => { }, [ variation, base ] ); const selectVariation = () => { + // Remove the hasCreatedOwnColors flag if the user is switching to a color palette + if ( + variation.settings.color && + user.settings.color && + user.settings.color.hasCreatedOwnColors + ) { + delete user.settings.color.palette.hasCreatedOwnColors; + } + setUserConfig( () => { return { settings: mergeBaseAndUserConfigs( @@ -48,7 +57,6 @@ export const VariationContainer = ( { variation, children } ) => { selectVariation(); } }; - const isActive = useMemo( () => { if ( variation.settings.color ) { return isEqual( variation.settings.color, user.settings.color ); diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/index.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/index.tsx index 38e214133dc..c862af81453 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/index.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/index.tsx @@ -147,8 +147,8 @@ function Sidebar() { initialPath={ initialPath.current } > + - ); } diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/save-hub.tsx b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/save-hub.tsx index 882b1df989e..d28064da0cf 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/save-hub.tsx +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/save-hub.tsx @@ -10,6 +10,8 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { // @ts-ignore No types for this exist yet. __experimentalHStack as HStack, + // @ts-ignore No types for this exist yet. + __experimentalUseNavigator as useNavigator, Button, Spinner, } from '@wordpress/components'; @@ -41,13 +43,13 @@ export const SaveHub = () => { const urlParams = useQuery(); const { sendEvent } = useContext( CustomizeStoreContext ); const [ isResolving, setIsResolving ] = useState< boolean >( false ); + const navigator = useNavigator(); // @ts-ignore No types for this exist yet. const { __unstableMarkLastChangeAsPersistent } = useDispatch( blockEditorStore ); - const { createSuccessNotice, createErrorNotice, removeNotice } = - useDispatch( noticesStore ); + const { createErrorNotice, removeNotice } = useDispatch( noticesStore ); const { dirtyEntityRecords, @@ -176,10 +178,7 @@ export const SaveHub = () => { } } - createSuccessNotice( __( 'Site updated.', 'woocommerce' ), { - type: 'snackbar', - id: saveNoticeId, - } ); + navigator.goToParent(); } catch ( error ) { createErrorNotice( `${ __( 'Saving failed.', 'woocommerce' ) } ${ error }` diff --git a/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss b/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss index c10c0e30c8a..31061aa4d33 100644 --- a/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss +++ b/plugins/woocommerce-admin/client/customize-store/assembler-hub/style.scss @@ -119,10 +119,14 @@ .edit-site-layout__sidebar { .edit-site-sidebar__content { + display: flex; + flex-direction: column; + .components-navigator-screen { will-change: auto; padding: 0 16px; overflow-x: hidden; + flex: 1; } } @@ -550,42 +554,3 @@ margin-left: 12px; } } - -.woocommerce-customize-store_global-styles-variations_item { - border-radius: 2px; - padding: 2.5px; - - .woocommerce-customize-store_global-styles-variations_item-preview { - border: 1px solid #dcdcde; - background: #fff; - } - - &:hover, - &.is-active { - box-shadow: 0 0 0 1.5px var(--wp-admin-theme-color), 0 0 0 2.5px #fff; - } -} - -.edit-site-sidebar-navigation-screen-patterns__group-homepage { - .woocommerce-collapsible-content:last-child { - border-bottom: none; - } - .woocommerce-collapsible-content { - padding: 16px 0 16px 0; - border-bottom: 1px solid #ededed; - button { - width: 100%; - color: #1e1e1e; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: -0.15px; - } - svg { - margin-left: auto; - fill: #1e1e1e; - width: 20px; - height: 20px; - } - } -} diff --git a/plugins/woocommerce/changelog/update-address-cys-ui-feedback b/plugins/woocommerce/changelog/update-address-cys-ui-feedback new file mode 100644 index 00000000000..241ec85e6ef --- /dev/null +++ b/plugins/woocommerce/changelog/update-address-cys-ui-feedback @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix CYS UI issues From 77db736fc97a4b03f5e9fca3389df0644e432e80 Mon Sep 17 00:00:00 2001 From: Chris Runnells Date: Tue, 19 Sep 2023 20:36:20 -1000 Subject: [PATCH 10/20] Update Customize Your Store introduction page (#40293) * Add intro Banner components * Add intro banner svg * Add additional theme card data * Add theme slug * Add intro page layout and styling * Remove unused component files * Update button events * Add intro Banner components * Add intro banner svg * Add additional theme card data * Add theme slug * Add intro page layout and styling * Remove unused component files * Update button events * Add changelog * Fix lint issues * Fix lint issues * Fix lint issues * Fix lint issues --------- Co-authored-by: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> --- .../assets/images/banner-design-with-ai.svg | 26 +++ .../client/customize-store/intro/index.tsx | 130 ++++++++++++--- .../client/customize-store/intro/intro.scss | 155 ++++++++++++++++++ .../client/customize-store/intro/services.ts | 43 +++++ .../customize-store/intro/theme-cards.tsx | 3 + .../changelog/add-39528-intro-components | 4 + 6 files changed, 337 insertions(+), 24 deletions(-) create mode 100644 plugins/woocommerce-admin/client/customize-store/assets/images/banner-design-with-ai.svg create mode 100644 plugins/woocommerce-admin/client/customize-store/intro/intro.scss create mode 100644 plugins/woocommerce/changelog/add-39528-intro-components diff --git a/plugins/woocommerce-admin/client/customize-store/assets/images/banner-design-with-ai.svg b/plugins/woocommerce-admin/client/customize-store/assets/images/banner-design-with-ai.svg new file mode 100644 index 00000000000..3fc2ac8a365 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/assets/images/banner-design-with-ai.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/woocommerce-admin/client/customize-store/intro/index.tsx b/plugins/woocommerce-admin/client/customize-store/intro/index.tsx index 9e06b0e8c77..c498a3667bb 100644 --- a/plugins/woocommerce-admin/client/customize-store/intro/index.tsx +++ b/plugins/woocommerce-admin/client/customize-store/intro/index.tsx @@ -1,8 +1,17 @@ +/** + * External dependencies + */ + +import { __ } from '@wordpress/i18n'; +import { chevronLeft } from '@wordpress/icons'; + /** * Internal dependencies */ import { CustomizeStoreComponent } from '../types'; +import './intro.scss'; + export type events = | { type: 'DESIGN_WITH_AI' } | { type: 'CLICKED_ON_BREADCRUMB' } @@ -15,33 +24,106 @@ export * as services from './services'; export const Intro: CustomizeStoreComponent = ( { sendEvent, context } ) => { const { - intro: { themeCards, activeTheme }, + intro: { themeCards }, } = context; + return ( <> -

Intro

-
Active theme: { activeTheme }
- { themeCards?.map( ( themeCard ) => ( - - ) ) } - - +
+

{ 'Site title' }

+
+ +
+
+
+ + { __( 'Customize your store', 'woocommerce' ) } +
+

+ { __( + 'Create a store that reflects your brand and business. Select one of our professionally designed themes to customize, or create your own using AI.', + 'woocommerce' + ) } +

+
+ +
+
+
+

+ { __( + 'Use the power of AI to design your store', + 'woocommerce' + ) } +

+

+ { __( + 'Design the look of your store, create pages, and generate copy using our built-in AI tools.', + 'woocommerce' + ) } +

+ +
+
+ +

+ { __( + 'Or select a professionally designed theme to customize and make your own.', + 'woocommerce' + ) } +

+ +
+ { themeCards?.map( ( themeCard ) => ( +
+
+ { +
+

+ { themeCard.name } +

+
+ ) ) } +
+ +
+ +
+ + +
+
); }; diff --git a/plugins/woocommerce-admin/client/customize-store/intro/intro.scss b/plugins/woocommerce-admin/client/customize-store/intro/intro.scss new file mode 100644 index 00000000000..c579d9295a0 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/intro/intro.scss @@ -0,0 +1,155 @@ +.woocommerce-profile-wizard__container { + display: flex; + flex-direction: column; + + button { + cursor: pointer; + } +} + +.woocommerce-customize-store-header { + min-height: 64px; + padding: 1rem; + width: 100%; + + h1 { + font-size: 0.8125rem; + font-weight: 500; + margin: 0; + padding: 0; + line-height: 1.5rem; + } + +} + +.woocommerce-customize-store-container { + display: flex; + flex-direction: row; +} + +.woocommerce-customize-store-sidebar { + flex: 0 0 380px; + padding: 1rem; + + .woocommerce-customize-store-sidebar__title { + color: #1e1e1e; + font-size: 1rem; + font-weight: 600; + margin: 0; + padding: 0; + line-height: 2.5; + } + + button { + background-color: transparent; + border: none; + line-height: 1; + padding-right: 0; + vertical-align: middle; + } + + svg { + color: inherit; + height: 24px; + width: 24px; + margin: 0.25rem; + } + + p { + padding: 0 1rem; + color: #757575; + max-width: 20rem; + } +} + +.woocommerce-customize-store-main { + margin-right: 2.5rem; + + p { + color: #2f2f2f; + font-size: 1rem; + line-height: 1.5; + margin-bottom: 1.5rem; + } +} + +.woocommerce-customize-store-banner { + background: var(--woo-purple-woo-purple-0, #f2edff); + background-image: url(../assets/images/banner-design-with-ai.svg); + background-repeat: no-repeat; + background-position: bottom right; + background-size: contain; + border-radius: 4px; + display: flex; + margin: 1.25rem 0 3.375rem; + min-height: 343px; + padding: 70px 0; + width: 820px; + + .woocommerce-customize-store-banner-content { + width: 345px; + margin-left: 50px; + + h1 { + font-size: 1.5rem; + line-height: 1.33; + } + + p { + margin: 1rem 0 2rem 0; + } + + button { + background-color: #3858e9; + border: none; + border-radius: 2px; + color: #fff; + display: inline-block; + line-height: 1.25rem; + padding: 10px 15px; + + &:hover { + background-color: #2234e0; + } + } + + } +} + +.woocommerce-customize-store-theme-cards { + display: flex; + flex-wrap: wrap; + gap: 2rem; + max-width: 820px; + + .theme-card { + flex-basis: 45%; + + img { + border-radius: 4px; + border: 1px solid #e9e9e9; + width: 394px; + } + + .theme-card__title { + font-size: 1rem; + font-weight: 600; + margin: 1.5rem 0 0.5rem; + } + } +} + +.woocommerce-customize-store-browse-themes { + text-align: center; + + button { + background-color: #fff; + border: 1px solid #3858e9; + border-radius: 2px; + color: #3858e9; + display: inline-block; + font-size: 0.8125rem; + margin: 3.75rem 0; + padding: 0.5rem 0.75rem; + } +} diff --git a/plugins/woocommerce-admin/client/customize-store/intro/services.ts b/plugins/woocommerce-admin/client/customize-store/intro/services.ts index 37c282ab5a4..5fa170f3e9f 100644 --- a/plugins/woocommerce-admin/client/customize-store/intro/services.ts +++ b/plugins/woocommerce-admin/client/customize-store/intro/services.ts @@ -3,12 +3,55 @@ export const fetchThemeCards = async () => { return [ { + slug: 'twentytwentyone', name: 'Twenty Twenty One', description: 'The default theme for WordPress.', + image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/twentytwentyone/screenshot.png', + styleVariations: [], }, { + slug: 'twentytwenty', name: 'Twenty Twenty', description: 'The previous default theme for WordPress.', + image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/twentytwenty/screenshot.png', + styleVariations: [], + }, + { + slug: 'tsubaki', + name: 'Tsubaki', + description: + 'Tsubaki puts the spotlight on your products and your customers. This theme leverages WooCommerce to provide you with intuitive product navigation and the patterns you need to master digital merchandising.', + image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tsubaki/screenshot.png', + styleVariations: [], + }, + { + slug: 'winkel', + name: 'Winkel', + description: + 'Winkel is a minimal, product-focused theme featuring Payments block. Its clean, cool look combined with a simple layout makes it perfect for showcasing fashion items – clothes, shoes, and accessories.', + image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/winkel/screenshot.png', + styleVariations: [ + { + title: 'Default', + primary: '#ffffff', + secondary: '#676767', + }, + { + title: 'Charcoal', + primary: '#1f2527', + secondary: '#9fd3e8', + }, + { + title: 'Rainforest', + primary: '#eef4f7', + secondary: '#35845d', + }, + { + title: 'Ruby Wine', + primary: '#ffffff', + secondary: '#c8133e', + }, + ], }, ]; }; diff --git a/plugins/woocommerce-admin/client/customize-store/intro/theme-cards.tsx b/plugins/woocommerce-admin/client/customize-store/intro/theme-cards.tsx index a67b23534ae..7c8ca0f7ea3 100644 --- a/plugins/woocommerce-admin/client/customize-store/intro/theme-cards.tsx +++ b/plugins/woocommerce-admin/client/customize-store/intro/theme-cards.tsx @@ -1,6 +1,9 @@ export type ThemeCard = { // placeholder props, possibly take reference from https://github.com/Automattic/wp-calypso/blob/1f1b79210c49ef0d051f8966e24122229a334e29/packages/design-picker/src/components/theme-card/index.tsx#L32 + slug: string; name: string; description: string; image: string; + isActive: boolean; + styleVariations: string[]; }; diff --git a/plugins/woocommerce/changelog/add-39528-intro-components b/plugins/woocommerce/changelog/add-39528-intro-components new file mode 100644 index 00000000000..92163218af1 --- /dev/null +++ b/plugins/woocommerce/changelog/add-39528-intro-components @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update intro screen for the new Customize Your Store task From 2289fc70a9dd216c5e8ee604adeae4e8abcc538b Mon Sep 17 00:00:00 2001 From: Leif Singer Date: Wed, 20 Sep 2023 08:48:06 +0200 Subject: [PATCH 11/20] Tweak a few comments related to address type (#40110) * Tweak a few comments related to address type * tweak more address type comments * grammar nitpick * still more address type tweaks * appease the linter * Update plugins/woocommerce/includes/wc-template-functions.php Co-authored-by: Corey McKrill <916023+coreymckrill@users.noreply.github.com> --------- Co-authored-by: Corey McKrill <916023+coreymckrill@users.noreply.github.com> --- .../dev-update-address-type-comments | 5 ++ .../includes/class-wc-checkout.php | 2 +- .../includes/class-wc-customer.php | 37 +++++++++------ .../includes/class-wc-form-handler.php | 26 +++++++---- .../woocommerce/includes/class-wc-order.php | 46 +++++++++++++------ .../legacy/abstract-wc-legacy-order.php | 2 +- .../legacy/api/v2/class-wc-api-orders.php | 2 +- .../legacy/api/v3/class-wc-api-orders.php | 2 +- .../class-wc-rest-orders-v1-controller.php | 6 +-- .../class-wc-rest-orders-v2-controller.php | 2 +- .../class-wc-shortcode-my-account.php | 2 +- .../includes/wc-account-functions.php | 6 +-- .../includes/wc-template-functions.php | 2 +- .../PostToOrderAddressTableMigrator.php | 4 +- .../Orders/OrdersTableDataStore.php | 4 +- 15 files changed, 94 insertions(+), 54 deletions(-) create mode 100644 plugins/woocommerce/changelog/dev-update-address-type-comments diff --git a/plugins/woocommerce/changelog/dev-update-address-type-comments b/plugins/woocommerce/changelog/dev-update-address-type-comments new file mode 100644 index 00000000000..bb1a4fd5a34 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-update-address-type-comments @@ -0,0 +1,5 @@ +Significance: patch +Type: tweak +Comment: This change only tweaks a few comments. + + diff --git a/plugins/woocommerce/includes/class-wc-checkout.php b/plugins/woocommerce/includes/class-wc-checkout.php index cd39ca9ef6d..8dc5c20e121 100644 --- a/plugins/woocommerce/includes/class-wc-checkout.php +++ b/plugins/woocommerce/includes/class-wc-checkout.php @@ -1291,7 +1291,7 @@ class WC_Checkout { * Get a posted address field after sanitization and validation. * * @param string $key Field key. - * @param string $type Type of address. Available options: 'billing' or 'shipping'. + * @param string $type Type of address; 'billing' or 'shipping'. * @return string */ public function get_posted_address_data( $key, $type = 'billing' ) { diff --git a/plugins/woocommerce/includes/class-wc-customer.php b/plugins/woocommerce/includes/class-wc-customer.php index 06b262e5439..9074442a2f7 100644 --- a/plugins/woocommerce/includes/class-wc-customer.php +++ b/plugins/woocommerce/includes/class-wc-customer.php @@ -454,18 +454,27 @@ class WC_Customer extends WC_Legacy_Customer { * * @since 3.0.0 * @param string $prop Name of prop to get. - * @param string $address billing or shipping. - * @param string $context What the value is for. Valid values are 'view' and 'edit'. What the value is for. Valid values are view and edit. + * @param string $address_type Type of address; 'billing' or 'shipping'. + * @param string $context What the value is for. Valid values are 'view' and 'edit'. * @return mixed */ - protected function get_address_prop( $prop, $address = 'billing', $context = 'view' ) { + protected function get_address_prop( $prop, $address_type = 'billing', $context = 'view' ) { $value = null; - if ( array_key_exists( $prop, $this->data[ $address ] ) ) { - $value = isset( $this->changes[ $address ][ $prop ] ) ? $this->changes[ $address ][ $prop ] : $this->data[ $address ][ $prop ]; + if ( array_key_exists( $prop, $this->data[ $address_type ] ) ) { + $value = isset( $this->changes[ $address_type ][ $prop ] ) ? $this->changes[ $address_type ][ $prop ] : $this->data[ $address_type ][ $prop ]; if ( 'view' === $context ) { - $value = apply_filters( $this->get_hook_prefix() . $address . '_' . $prop, $value, $this ); + /** + * Filter: 'woocommerce_customer_get_[billing|shipping]_[prop]' + * + * Allow developers to change the returned value for any customer address property. + * + * @since 3.6.0 + * @param string $value The address property value. + * @param WC_Customer $customer The customer object being read. + */ + $value = apply_filters( $this->get_hook_prefix() . $address_type . '_' . $prop, $value, $this ); } } return $value; @@ -920,18 +929,18 @@ class WC_Customer extends WC_Legacy_Customer { * Sets a prop for a setter method. * * @since 3.0.0 - * @param string $prop Name of prop to set. - * @param string $address Name of address to set. billing or shipping. - * @param mixed $value Value of the prop. + * @param string $prop Name of prop to set. + * @param string $address_type Type of address; 'billing' or 'shipping'. + * @param mixed $value Value of the prop. */ - protected function set_address_prop( $prop, $address, $value ) { - if ( array_key_exists( $prop, $this->data[ $address ] ) ) { + protected function set_address_prop( $prop, $address_type, $value ) { + if ( array_key_exists( $prop, $this->data[ $address_type ] ) ) { if ( true === $this->object_read ) { - if ( $value !== $this->data[ $address ][ $prop ] || ( isset( $this->changes[ $address ] ) && array_key_exists( $prop, $this->changes[ $address ] ) ) ) { - $this->changes[ $address ][ $prop ] = $value; + if ( $value !== $this->data[ $address_type ][ $prop ] || ( isset( $this->changes[ $address_type ] ) && array_key_exists( $prop, $this->changes[ $address_type ] ) ) ) { + $this->changes[ $address_type ][ $prop ] = $value; } } else { - $this->data[ $address ][ $prop ] = $value; + $this->data[ $address_type ][ $prop ] = $value; } } } diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php index 21d5744ac11..972818b4cda 100644 --- a/plugins/woocommerce/includes/class-wc-form-handler.php +++ b/plugins/woocommerce/includes/class-wc-form-handler.php @@ -103,13 +103,13 @@ class WC_Form_Handler { return; } - $load_address = isset( $wp->query_vars['edit-address'] ) ? wc_edit_address_i18n( sanitize_title( $wp->query_vars['edit-address'] ), true ) : 'billing'; + $address_type = isset( $wp->query_vars['edit-address'] ) ? wc_edit_address_i18n( sanitize_title( $wp->query_vars['edit-address'] ), true ) : 'billing'; - if ( ! isset( $_POST[ $load_address . '_country' ] ) ) { + if ( ! isset( $_POST[ $address_type . '_country' ] ) ) { return; } - $address = WC()->countries->get_address_fields( wc_clean( wp_unslash( $_POST[ $load_address . '_country' ] ) ), $load_address . '_' ); + $address = WC()->countries->get_address_fields( wc_clean( wp_unslash( $_POST[ $address_type . '_country' ] ) ), $address_type . '_' ); foreach ( $address as $key => $field ) { if ( ! isset( $field['type'] ) ) { @@ -138,7 +138,7 @@ class WC_Form_Handler { foreach ( $field['validate'] as $rule ) { switch ( $rule ) { case 'postcode': - $country = wc_clean( wp_unslash( $_POST[ $load_address . '_country' ] ) ); + $country = wc_clean( wp_unslash( $_POST[ $address_type . '_country' ] ) ); $value = wc_format_postcode( $value, $country ); if ( '' !== $value && ! WC_Validation::is_postcode( $value, $country ) ) { @@ -191,12 +191,13 @@ class WC_Form_Handler { * * Allow developers to add custom validation logic and throw an error to prevent save. * + * @since 3.6.0 * @param int $user_id User ID being saved. - * @param string $load_address Type of address e.g. billing or shipping. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @param array $address The address fields. - * @param WC_Customer $customer The customer object being saved. @since 3.6.0 + * @param WC_Customer $customer The customer object being saved. */ - do_action( 'woocommerce_after_save_address_validation', $user_id, $load_address, $address, $customer ); + do_action( 'woocommerce_after_save_address_validation', $user_id, $address_type, $address, $customer ); if ( 0 < wc_notice_count( 'error' ) ) { return; @@ -206,7 +207,16 @@ class WC_Form_Handler { wc_add_notice( __( 'Address changed successfully.', 'woocommerce' ) ); - do_action( 'woocommerce_customer_save_address', $user_id, $load_address ); + /** + * Hook: woocommerce_customer_save_address. + * + * Fires after a customer address has been saved. + * + * @since 3.6.0 + * @param int $user_id User ID being saved. + * @param string $address_type Type of address; 'billing' or 'shipping'. + */ + do_action( 'woocommerce_customer_save_address', $user_id, $address_type ); wp_safe_redirect( wc_get_endpoint_url( 'edit-address', '', wc_get_page_permalink( 'myaccount' ) ) ); exit; diff --git a/plugins/woocommerce/includes/class-wc-order.php b/plugins/woocommerce/includes/class-wc-order.php index 6a1874bcee9..f3550ec7beb 100644 --- a/plugins/woocommerce/includes/class-wc-order.php +++ b/plugins/woocommerce/includes/class-wc-order.php @@ -557,18 +557,27 @@ class WC_Order extends WC_Abstract_Order { * * @since 3.0.0 * @param string $prop Name of prop to get. - * @param string $address billing or shipping. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @param string $context What the value is for. Valid values are view and edit. * @return mixed */ - protected function get_address_prop( $prop, $address = 'billing', $context = 'view' ) { + protected function get_address_prop( $prop, $address_type = 'billing', $context = 'view' ) { $value = null; - if ( array_key_exists( $prop, $this->data[ $address ] ) ) { - $value = isset( $this->changes[ $address ][ $prop ] ) ? $this->changes[ $address ][ $prop ] : $this->data[ $address ][ $prop ]; + if ( array_key_exists( $prop, $this->data[ $address_type ] ) ) { + $value = isset( $this->changes[ $address_type ][ $prop ] ) ? $this->changes[ $address_type ][ $prop ] : $this->data[ $address_type ][ $prop ]; if ( 'view' === $context ) { - $value = apply_filters( $this->get_hook_prefix() . $address . '_' . $prop, $value, $this ); + /** + * Filter: 'woocommerce_order_get_[billing|shipping]_[prop]' + * + * Allow developers to change the returned value for any order address property. + * + * @since 3.6.0 + * @param string $value The address property value. + * @param WC_Order $order The order object being read. + */ + $value = apply_filters( $this->get_hook_prefix() . $address_type . '_' . $prop, $value, $this ); } } return $value; @@ -896,11 +905,20 @@ class WC_Order extends WC_Abstract_Order { * Note: Merges raw data with get_prop data so changes are returned too. * * @since 2.4.0 - * @param string $type Billing or shipping. Anything else besides 'billing' will return shipping address. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @return array The stored address after filter. */ - public function get_address( $type = 'billing' ) { - return apply_filters( 'woocommerce_get_order_address', array_merge( $this->data[ $type ], $this->get_prop( $type, 'view' ) ), $type, $this ); + public function get_address( $address_type = 'billing' ) { + /** + * Filter: 'woocommerce_get_order_address' + * + * Allow developers to change the returned value for an order's billing or shipping address. + * + * @since 2.4.0 + * @param array $address_data The raw address data merged with the data from get_prop. + * @param string $address_type Type of address; 'billing' or 'shipping'. + */ + return apply_filters( 'woocommerce_get_order_address', array_merge( $this->data[ $address_type ], $this->get_prop( $address_type, 'view' ) ), $address_type, $this ); } /** @@ -1067,17 +1085,17 @@ class WC_Order extends WC_Abstract_Order { * * @since 3.0.0 * @param string $prop Name of prop to set. - * @param string $address Name of address to set. billing or shipping. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @param mixed $value Value of the prop. */ - protected function set_address_prop( $prop, $address, $value ) { - if ( array_key_exists( $prop, $this->data[ $address ] ) ) { + protected function set_address_prop( $prop, $address_type, $value ) { + if ( array_key_exists( $prop, $this->data[ $address_type ] ) ) { if ( true === $this->object_read ) { - if ( $value !== $this->data[ $address ][ $prop ] || ( isset( $this->changes[ $address ] ) && array_key_exists( $prop, $this->changes[ $address ] ) ) ) { - $this->changes[ $address ][ $prop ] = $value; + if ( $value !== $this->data[ $address_type ][ $prop ] || ( isset( $this->changes[ $address_type ] ) && array_key_exists( $prop, $this->changes[ $address_type ] ) ) ) { + $this->changes[ $address_type ][ $prop ] = $value; } } else { - $this->data[ $address ][ $prop ] = $value; + $this->data[ $address_type ][ $prop ] = $value; } } } diff --git a/plugins/woocommerce/includes/legacy/abstract-wc-legacy-order.php b/plugins/woocommerce/includes/legacy/abstract-wc-legacy-order.php index 8867096c739..3c2c8791b35 100644 --- a/plugins/woocommerce/includes/legacy/abstract-wc-legacy-order.php +++ b/plugins/woocommerce/includes/legacy/abstract-wc-legacy-order.php @@ -329,7 +329,7 @@ abstract class WC_Abstract_Legacy_Order extends WC_Data { /** * Set the customer address. * @param array $address Address data. - * @param string $type billing or shipping. + * @param string $type Type of address; 'billing' or 'shipping'. */ public function set_address( $address, $type = 'billing' ) { foreach ( $address as $key => $value ) { diff --git a/plugins/woocommerce/includes/legacy/api/v2/class-wc-api-orders.php b/plugins/woocommerce/includes/legacy/api/v2/class-wc-api-orders.php index 937de6e4d35..a8b65f6ef8a 100644 --- a/plugins/woocommerce/includes/legacy/api/v2/class-wc-api-orders.php +++ b/plugins/woocommerce/includes/legacy/api/v2/class-wc-api-orders.php @@ -761,7 +761,7 @@ class WC_API_Orders extends WC_API_Resource { * * @param WC_Order $order * @param array $posted - * @param string $type + * @param string $type Type of address; 'billing' or 'shipping'. */ protected function update_address( $order, $posted, $type = 'billing' ) { foreach ( $posted as $key => $value ) { diff --git a/plugins/woocommerce/includes/legacy/api/v3/class-wc-api-orders.php b/plugins/woocommerce/includes/legacy/api/v3/class-wc-api-orders.php index 4d602a94edb..7ed0870a1fb 100644 --- a/plugins/woocommerce/includes/legacy/api/v3/class-wc-api-orders.php +++ b/plugins/woocommerce/includes/legacy/api/v3/class-wc-api-orders.php @@ -805,7 +805,7 @@ class WC_API_Orders extends WC_API_Resource { * * @param WC_Order $order * @param array $posted - * @param string $type + * @param string $type Type of address; 'billing' or 'shipping'. */ protected function update_address( $order, $posted, $type = 'billing' ) { foreach ( $posted as $key => $value ) { diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php index 7cf477b7db9..7e65221c8f5 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php @@ -593,9 +593,9 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller { /** * Update address. * - * @param WC_Order $order - * @param array $posted - * @param string $type + * @param WC_Order $order Order object. + * @param array $posted Request data. + * @param string $type Type of address; 'billing' or 'shipping'. */ protected function update_address( $order, $posted, $type = 'billing' ) { foreach ( $posted as $key => $value ) { diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index d8a0a249ac0..8ee7a0edae1 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -804,7 +804,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { * * @param WC_Order $order Order data. * @param array $posted Posted data. - * @param string $type Address type. + * @param string $type Type of address; 'billing' or 'shipping'. */ protected function update_address( $order, $posted, $type = 'billing' ) { foreach ( $posted as $key => $value ) { diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php index 3a569201d82..4cfcc85e028 100644 --- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php +++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php @@ -165,7 +165,7 @@ class WC_Shortcode_My_Account { /** * Edit address page. * - * @param string $load_address Type of address to load. + * @param string $load_address Type of address; 'billing' or 'shipping'. */ public static function edit_address( $load_address = 'billing' ) { $current_user = wp_get_current_user(); diff --git a/plugins/woocommerce/includes/wc-account-functions.php b/plugins/woocommerce/includes/wc-account-functions.php index 7cefe49d9eb..61ee51d95ba 100644 --- a/plugins/woocommerce/includes/wc-account-functions.php +++ b/plugins/woocommerce/includes/wc-account-functions.php @@ -309,11 +309,9 @@ function wc_get_account_orders_actions( $order ) { * Get account formatted address. * * @since 3.2.0 - * @param string $address_type Address type. - * Accepts: 'billing' or 'shipping'. - * Default to 'billing'. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @param int $customer_id Customer ID. - * Default to 0. + * Defaults to 0. * @return string */ function wc_get_account_formatted_address( $address_type = 'billing', $customer_id = 0 ) { diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php index d82d8df4063..678cd1ac361 100644 --- a/plugins/woocommerce/includes/wc-template-functions.php +++ b/plugins/woocommerce/includes/wc-template-functions.php @@ -3295,7 +3295,7 @@ if ( ! function_exists( 'woocommerce_account_edit_address' ) ) { /** * My Account > Edit address template. * - * @param string $type Address type. + * @param string $type Type of address; 'billing' or 'shipping'. */ function woocommerce_account_edit_address( $type ) { $type = wc_edit_address_i18n( sanitize_title( $type ), true ); diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderAddressTableMigrator.php index 31cd9d09b35..699e132fb06 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderAddressTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderAddressTableMigrator.php @@ -16,7 +16,7 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; */ class PostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { /** - * Type of addresses being migrated, could be billing|shipping. + * Type of addresses being migrated; 'billing' or 'shipping'. * * @var $type */ @@ -25,7 +25,7 @@ class PostToOrderAddressTableMigrator extends MetaToCustomTableMigrator { /** * PostToOrderAddressTableMigrator constructor. * - * @param string $type Type of addresses being migrated, could be billing|shipping. + * @param string $type Type of address being migrated; 'billing' or 'shipping'. */ public function __construct( $type ) { $this->type = $type; diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index dc7d37f2bc0..71d66846787 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -537,7 +537,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements /** * Helper function to get alias for address table, this is used in select query. * - * @param string $type Address type. + * @param string $type Type of address; 'billing' or 'shipping'. * * @return string Alias. */ @@ -1665,7 +1665,7 @@ FROM $order_meta_table /** * Helper method to generate join and select query for address table. * - * @param string $address_type Type of address. Typically will be `billing` or `shipping`. + * @param string $address_type Type of address; 'billing' or 'shipping'. * @param string $order_table_alias Alias of order table to use. * @param string $address_table_alias Alias for address table to use. * From 1af971caf6a312c82b5a842f2449af981c833e99 Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:27:08 +1000 Subject: [PATCH 12/20] add: best colours AI suggestions (#40295) --- .../customize-store/design-with-ai/actions.ts | 4 +- .../design-with-ai/prompts/colorChoices.ts | 20 ++- .../prompts/test/colorChoices.test.ts | 114 ++++++++++++++++-- .../design-with-ai/state-machine.tsx | 4 +- .../customize-store/design-with-ai/types.ts | 11 +- .../add-customize-store-best-colours | 4 + 6 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-customize-store-best-colours diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts index ed7d6312f73..f51259c427a 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts @@ -9,7 +9,7 @@ import { recordEvent } from '@woocommerce/tracks'; * Internal dependencies */ import { - ColorPalette, + ColorPaletteResponse, designWithAiStateMachineContext, designWithAiStateMachineEvents, FontPairing, @@ -84,7 +84,7 @@ const assignDefaultColorPalette = assign< defaultColorPalette: ( event as { data: { - response: ColorPalette; + response: ColorPaletteResponse; }; } ).data.response, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.ts index 5fa682b0aee..2eaba5f279b 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.ts @@ -200,11 +200,14 @@ const colorChoices: ColorPalette[] = [ ]; const allowedNames: string[] = colorChoices.map( ( palette ) => palette.name ); const hexColorRegex = /^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/; +const colorPaletteNameValidator = z + .string() + .refine( ( name ) => allowedNames.includes( name ), { + message: 'Color palette not part of allowed list', + } ); export const colorPaletteValidator = z.object( { - name: z.string().refine( ( name ) => allowedNames.includes( name ), { - message: 'Color palette not part of allowed list', - } ), + name: colorPaletteNameValidator, primary: z .string() .regex( hexColorRegex, { message: 'Invalid primary color' } ), @@ -219,6 +222,11 @@ export const colorPaletteValidator = z.object( { .regex( hexColorRegex, { message: 'Invalid background color' } ), } ); +export const colorPaletteResponseValidator = z.object( { + default: colorPaletteNameValidator, + bestColors: z.array( colorPaletteNameValidator ).length( 8 ), +} ); + export const defaultColorPalette = { queryId: 'default_color_palette', @@ -226,8 +234,8 @@ export const defaultColorPalette = { version: '2023-09-18', prompt: ( businessDescription: string, look: string, tone: string ) => { return ` - You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate color scheme. - Respond only with one color scheme and only its JSON. + You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate color scheme, along with 8 best alternatives. + Respond in the form: "{ default: "palette name", bestColors: [ "palette name 1", "palette name 2", "palette name 3", "palette name 4", "palette name 5", "palette name 6", "palette name 7", "palette name 8" ] }" Chosen look and tone: ${ look } look, ${ tone } tone. Business description: ${ businessDescription } @@ -236,5 +244,5 @@ export const defaultColorPalette = { ${ JSON.stringify( colorChoices ) } `; }, - responseValidation: colorPaletteValidator.parse, + responseValidation: colorPaletteResponseValidator.parse, }; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts index 3903c3fce3e..d1aae496b45 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts @@ -1,9 +1,9 @@ /** * Internal dependencies */ -import { defaultColorPalette } from '..'; +import { colorPaletteValidator, defaultColorPalette } from '..'; -describe( 'colorPairing.responseValidation', () => { +describe( 'colorPaletteValidator', () => { it( 'should validate a correct color palette', () => { const validPalette = { name: 'Ancient Bronze', @@ -13,8 +13,7 @@ describe( 'colorPairing.responseValidation', () => { background: '#ffffff', }; - const parsedResult = - defaultColorPalette.responseValidation( validPalette ); + const parsedResult = colorPaletteValidator.parse( validPalette ); expect( parsedResult ).toEqual( validPalette ); } ); @@ -26,7 +25,7 @@ describe( 'colorPairing.responseValidation', () => { foreground: '#11163d', background: '#ffffff', }; - expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + expect( () => colorPaletteValidator.parse( invalidPalette ) ) .toThrowErrorMatchingInlineSnapshot( ` "[ { @@ -48,7 +47,7 @@ describe( 'colorPairing.responseValidation', () => { foreground: '#11163d', background: '#ffffff', }; - expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + expect( () => colorPaletteValidator.parse( invalidPalette ) ) .toThrowErrorMatchingInlineSnapshot( ` "[ { @@ -71,7 +70,7 @@ describe( 'colorPairing.responseValidation', () => { foreground: '#11163d', background: '#ffffff', }; - expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + expect( () => colorPaletteValidator.parse( invalidPalette ) ) .toThrowErrorMatchingInlineSnapshot( ` "[ { @@ -94,7 +93,7 @@ describe( 'colorPairing.responseValidation', () => { foreground: '#invalid_color', background: '#ffffff', }; - expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + expect( () => colorPaletteValidator.parse( invalidPalette ) ) .toThrowErrorMatchingInlineSnapshot( ` "[ { @@ -125,7 +124,7 @@ describe( 'colorPairing.responseValidation', () => { foreground: '#11163d', background: '#fffff', }; - expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + expect( () => colorPaletteValidator.parse( invalidPalette ) ) .toThrowErrorMatchingInlineSnapshot( ` "[ { @@ -140,3 +139,100 @@ describe( 'colorPairing.responseValidation', () => { ` ); } ); } ); + +describe( 'colorPaletteResponseValidator', () => { + it( 'should validate a correct color palette response', () => { + const validPalette = { + default: 'Ancient Bronze', + bestColors: Array( 8 ).fill( 'Ancient Bronze' ), + }; + + const parsedResult = + defaultColorPalette.responseValidation( validPalette ); + expect( parsedResult ).toEqual( validPalette ); + } ); + + it( 'should fail if array contains invalid color', () => { + const invalidPalette = { + default: 'Ancient Bronze', + bestColors: Array( 7 ) + .fill( 'Ancient Bronze' ) + .concat( [ 'Invalid Color' ] ), + }; + expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"custom\\", + \\"message\\": \\"Color palette not part of allowed list\\", + \\"path\\": [ + \\"bestColors\\", + 7 + ] + } + ]" + ` ); + } ); + + it( 'should fail if bestColors property is missing', () => { + const invalidPalette = { + default: 'Ancient Bronze', + }; + expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"invalid_type\\", + \\"expected\\": \\"array\\", + \\"received\\": \\"undefined\\", + \\"path\\": [ + \\"bestColors\\" + ], + \\"message\\": \\"Required\\" + } + ]" + ` ); + } ); + it( 'should fail if default property is missing', () => { + const invalidPalette = { + bestColors: Array( 8 ).fill( 'Ancient Bronze' ), + }; + expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"invalid_type\\", + \\"expected\\": \\"string\\", + \\"received\\": \\"undefined\\", + \\"path\\": [ + \\"default\\" + ], + \\"message\\": \\"Required\\" + } + ]" + ` ); + } ); + + it( 'should fail if bestColors array is not of length 8', () => { + const invalidPalette = { + default: 'Ancient Bronze', + bestColors: Array( 7 ).fill( 'Ancient Bronze' ), + }; + expect( () => defaultColorPalette.responseValidation( invalidPalette ) ) + .toThrowErrorMatchingInlineSnapshot( ` + "[ + { + \\"code\\": \\"too_small\\", + \\"minimum\\": 8, + \\"type\\": \\"array\\", + \\"inclusive\\": true, + \\"exact\\": true, + \\"message\\": \\"Array must contain exactly 8 element(s)\\", + \\"path\\": [ + \\"bestColors\\" + ] + } + ]" + ` ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx index 1f1ff61d994..50bc7fb8298 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx @@ -10,8 +10,8 @@ import { getQuery } from '@woocommerce/navigation'; import { designWithAiStateMachineContext, designWithAiStateMachineEvents, - ColorPalette, FontPairing, + ColorPaletteResponse, } from './types'; import { BusinessInfoDescription, @@ -70,7 +70,7 @@ export const designWithAiStateMachineDefinition = createMachine( choice: '', }, aiSuggestions: { - defaultColorPalette: {} as ColorPalette, + defaultColorPalette: {} as ColorPaletteResponse, fontPairing: '' as FontPairing[ 'pair_name' ], }, }, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts index b44a81cd4be..a639755078e 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts @@ -5,7 +5,11 @@ import { z } from 'zod'; /** * Internal dependencies */ -import { colorPaletteValidator, fontChoiceValidator } from './prompts'; +import { + colorPaletteValidator, + colorPaletteResponseValidator, + fontChoiceValidator, +} from './prompts'; export type designWithAiStateMachineContext = { businessInfoDescription: { @@ -18,7 +22,7 @@ export type designWithAiStateMachineContext = { choice: Tone | ''; }; aiSuggestions: { - defaultColorPalette: ColorPalette; + defaultColorPalette: ColorPaletteResponse; fontPairing: FontPairing[ 'pair_name' ]; }; // If we require more data from options, previously provided core profiler details, @@ -51,5 +55,8 @@ export interface LookAndToneCompletionResponse { } export type ColorPalette = z.infer< typeof colorPaletteValidator >; +export type ColorPaletteResponse = z.infer< + typeof colorPaletteResponseValidator +>; export type FontPairing = z.infer< typeof fontChoiceValidator >; diff --git a/plugins/woocommerce/changelog/add-customize-store-best-colours b/plugins/woocommerce/changelog/add-customize-store-best-colours new file mode 100644 index 00000000000..2486bf313fb --- /dev/null +++ b/plugins/woocommerce/changelog/add-customize-store-best-colours @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add customize store AI wizard call for best colour palette suggestions. From cb360ac2c2e314fb42fa62ccfd2f64ad9a24f225 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 20 Sep 2023 10:52:35 +0200 Subject: [PATCH 13/20] Move metadata processing code from WC_Meta_Box_Order_Data to CustomMetaBox --- .../class-wc-meta-box-order-data.php | 31 ------------ .../src/Internal/Admin/Orders/Edit.php | 5 +- .../Admin/Orders/MetaBoxes/CustomMetaBox.php | 50 ++++++++++++++++++- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php index 531c76175b1..b8b08aa5c92 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php @@ -709,37 +709,6 @@ class WC_Meta_Box_Order_Data { $props['customer_note'] = sanitize_textarea_field( wp_unslash( $_POST['customer_note'] ) ); } - // Metadata (only needed when HPOS table is in use). - if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) { - $order_meta = $order->get_meta_data(); - - $order_meta = - array_combine( - array_map( fn( $meta ) => $meta->id, $order_meta ), - array_map( fn( $meta ) => $meta, $order_meta ) - ); - - // phpcs:disable WordPress.Security.ValidatedSanitizedInput - - foreach ( ( $_POST['meta'] ?? array() ) as $request_meta_id => $request_meta_data ) { - $request_meta_id = wp_unslash( $request_meta_id ); - $request_meta_key = wp_unslash( $request_meta_data['key'] ); - $request_meta_value = wp_unslash( $request_meta_data['value'] ); - if ( array_key_exists( $request_meta_id, $order_meta ) && - ( $order_meta[ $request_meta_id ]->key !== $request_meta_key || $order_meta[ $request_meta_id ]->value !== $request_meta_value ) ) { - $order->update_meta_data( $request_meta_key, $request_meta_value, $request_meta_id ); - } - } - - $request_new_key = wp_unslash( $_POST['metakeyinput'] ?? '' ); - $request_new_value = wp_unslash( $_POST['metavalue'] ?? '' ); - if ( '' !== $request_new_key ) { - $order->add_meta_data( $request_new_key, $request_new_value ); - } - - // phpcs:enable WordPress.Security.ValidatedSanitizedInput - } - // Save order data. $order->set_props( $props ); $order->set_status( wc_clean( wp_unslash( $_POST['order_status'] ) ), '', true ); diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php index a956c681f27..16796b33299 100644 --- a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php +++ b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php @@ -208,7 +208,6 @@ class Edit { * @return void */ public function handle_order_update() { - global $theorder; if ( ! isset( $this->order ) ) { return; } @@ -233,6 +232,10 @@ class Edit { */ do_action( 'woocommerce_process_shop_order_meta', $this->order->get_id(), $this->order ); + if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) { + $this->custom_meta_box->handle_metadata_changes($this->order); + } + // Order updated message. $this->message = 1; diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomMetaBox.php b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomMetaBox.php index 5aae6fd949d..dd44808ca72 100644 --- a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomMetaBox.php +++ b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomMetaBox.php @@ -242,7 +242,7 @@ class CustomMetaBox { * @return void */ private function handle_add_meta( WC_Order $order, string $meta_key, string $meta_value ) { - $count = 0; + $count = 0; if ( is_protected_meta( $meta_key ) ) { wp_send_json_error( 'protected_meta' ); wp_die(); @@ -409,4 +409,52 @@ class CustomMetaBox { } wp_die( 0 ); } + + /** + * Handle the possible changes in order metadata coming from an order edit page in admin + * (labeled "custom fields" in the UI). + * + * This method expects the $_POST array to contain a 'meta' key that is an associative + * array of [meta item id => [ 'key' => meta item name, 'value' => meta item value ]; + * and also to contain (possibly empty) 'metakeyinput' and 'metavalue' keys. + * + * @param WC_Order $order The order to handle. + */ + public function handle_metadata_changes( $order ) { + $has_meta_changes = false; + + $order_meta = $order->get_meta_data(); + + $order_meta = + array_combine( + array_map( fn( $meta ) => $meta->id, $order_meta ), + $order_meta + ); + + // phpcs:disable WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing + + foreach ( ( $_POST['meta'] ?? array() ) as $request_meta_id => $request_meta_data ) { + $request_meta_id = wp_unslash( $request_meta_id ); + $request_meta_key = wp_unslash( $request_meta_data['key'] ); + $request_meta_value = wp_unslash( $request_meta_data['value'] ); + if ( array_key_exists( $request_meta_id, $order_meta ) && + ( $order_meta[ $request_meta_id ]->key !== $request_meta_key || $order_meta[ $request_meta_id ]->value !== $request_meta_value ) ) { + $order->update_meta_data( $request_meta_key, $request_meta_value, $request_meta_id ); + $has_meta_changes = true; + } + } + + $request_new_key = wp_unslash( $_POST['metakeyinput'] ?? '' ); + $request_new_value = wp_unslash( $_POST['metavalue'] ?? '' ); + if ( '' !== $request_new_key ) { + $order->add_meta_data( $request_new_key, $request_new_value ); + $has_meta_changes = true; + } + + // phpcs:enable WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing + + if ( $has_meta_changes ) { + $order->save(); + } + } } From 186474078eae6e011138393706be5fc91676e2af Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Wed, 20 Sep 2023 13:36:38 +0200 Subject: [PATCH 14/20] Update WooCommerce blocks package to 11.1.1 (#40300) bump Woo Blocks 11.1.1 --- .../changelog/update-woocommerce-blocks-11.1.1 | 4 ++++ plugins/woocommerce/composer.json | 2 +- plugins/woocommerce/composer.lock | 14 +++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-woocommerce-blocks-11.1.1 diff --git a/plugins/woocommerce/changelog/update-woocommerce-blocks-11.1.1 b/plugins/woocommerce/changelog/update-woocommerce-blocks-11.1.1 new file mode 100644 index 00000000000..44356b9d68e --- /dev/null +++ b/plugins/woocommerce/changelog/update-woocommerce-blocks-11.1.1 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Bump Woo Blocks 11.1.1 diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index 238800289bb..9351d5146e4 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -23,7 +23,7 @@ "maxmind-db/reader": "^1.11", "pelago/emogrifier": "^6.0", "woocommerce/action-scheduler": "3.6.3", - "woocommerce/woocommerce-blocks": "11.1.0" + "woocommerce/woocommerce-blocks": "11.1.1" }, "require-dev": { "automattic/jetpack-changelogger": "^3.3.0", diff --git a/plugins/woocommerce/composer.lock b/plugins/woocommerce/composer.lock index b9c75cc9229..ea1924d7ec6 100644 --- a/plugins/woocommerce/composer.lock +++ b/plugins/woocommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cc5c4172d1078ecf041bdf3240a8c4e1", + "content-hash": "e5800331e1c25cd08ad877b665da724f", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -1004,16 +1004,16 @@ }, { "name": "woocommerce/woocommerce-blocks", - "version": "11.1.0", + "version": "11.1.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-blocks.git", - "reference": "9a9749f72c16bbb9cd75e23061f6b19dec01c262" + "reference": "81bff865d7fdd7de40a4da8fc49539a124ac1863" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/9a9749f72c16bbb9cd75e23061f6b19dec01c262", - "reference": "9a9749f72c16bbb9cd75e23061f6b19dec01c262", + "url": "https://api.github.com/repos/woocommerce/woocommerce-blocks/zipball/81bff865d7fdd7de40a4da8fc49539a124ac1863", + "reference": "81bff865d7fdd7de40a4da8fc49539a124ac1863", "shasum": "" }, "require": { @@ -1062,9 +1062,9 @@ ], "support": { "issues": "https://github.com/woocommerce/woocommerce-blocks/issues", - "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v11.1.0" + "source": "https://github.com/woocommerce/woocommerce-blocks/tree/v11.1.1" }, - "time": "2023-09-12T13:56:36+00:00" + "time": "2023-09-20T10:14:34+00:00" } ], "packages-dev": [ From cff83dca10ed3b24d74c65d96d9d88093f04b3e4 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 20 Sep 2023 13:37:56 +0200 Subject: [PATCH 15/20] Remove unnecessary hpos check --- plugins/woocommerce/src/Internal/Admin/Orders/Edit.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php index 16796b33299..a1b77a59169 100644 --- a/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php +++ b/plugins/woocommerce/src/Internal/Admin/Orders/Edit.php @@ -232,9 +232,7 @@ class Edit { */ do_action( 'woocommerce_process_shop_order_meta', $this->order->get_id(), $this->order ); - if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) { - $this->custom_meta_box->handle_metadata_changes($this->order); - } + $this->custom_meta_box->handle_metadata_changes($this->order); // Order updated message. $this->message = 1; From 8c55e53772a20a84f7071089dbf1675bc7dceb02 Mon Sep 17 00:00:00 2001 From: Jorge Torres Date: Wed, 20 Sep 2023 13:02:51 +0100 Subject: [PATCH 16/20] Remove unnecessary import --- .../includes/admin/meta-boxes/class-wc-meta-box-order-data.php | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php index b8b08aa5c92..79bae985d1f 100644 --- a/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php +++ b/plugins/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php @@ -8,7 +8,6 @@ * @version 2.2.0 */ -use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Automattic\WooCommerce\Utilities\OrderUtil; if ( ! defined( 'ABSPATH' ) ) { From f058e04046d170510f4f8efdfa0c650f1fb0c34a Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 20 Sep 2023 09:05:51 -0300 Subject: [PATCH 17/20] Fix styling errors in variation actions in blocks product editor (#40220) * Fix actions style # Conflicts: # packages/js/product-editor/src/components/variations-table/styles.scss * Add changelog * Fix checkbox * Refactor fix --- .../changelog/fix-new_product_editor_variation_actions_style | 4 ++++ .../src/components/variations-table/styles.scss | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 packages/js/product-editor/changelog/fix-new_product_editor_variation_actions_style diff --git a/packages/js/product-editor/changelog/fix-new_product_editor_variation_actions_style b/packages/js/product-editor/changelog/fix-new_product_editor_variation_actions_style new file mode 100644 index 00000000000..9408bf3359a --- /dev/null +++ b/packages/js/product-editor/changelog/fix-new_product_editor_variation_actions_style @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix blocks product editor variation actions styles diff --git a/packages/js/product-editor/src/components/variations-table/styles.scss b/packages/js/product-editor/src/components/variations-table/styles.scss index 073d4f9a231..61c9c0ef026 100644 --- a/packages/js/product-editor/src/components/variations-table/styles.scss +++ b/packages/js/product-editor/src/components/variations-table/styles.scss @@ -41,6 +41,7 @@ $table-row-height: calc($grid-unit * 9); border-color: $gray-600; } } + margin-left: $gap-smallest; } &__filters { @@ -99,6 +100,7 @@ $table-row-height: calc($grid-unit * 9); align-items: center; justify-content: flex-end; gap: $gap-smaller; + margin-right: $gap-smallest; &--delete { &.components-button.components-menu-item__button.is-link { From 1c497f306695817a30d1279048422189c4149973 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 20 Sep 2023 09:12:48 -0300 Subject: [PATCH 18/20] Update copy in the add variation options modal (#40280) * Change add variation options modal copy * Add changelog * Clean component --- ...246_update_copy_in_variation_options_modal | 4 ++++ .../src/blocks/variation-options/edit.tsx | 17 +++---------- .../src/blocks/variations/edit.tsx | 24 ++++--------------- 3 files changed, 11 insertions(+), 34 deletions(-) create mode 100644 packages/js/product-editor/changelog/dev-40246_update_copy_in_variation_options_modal diff --git a/packages/js/product-editor/changelog/dev-40246_update_copy_in_variation_options_modal b/packages/js/product-editor/changelog/dev-40246_update_copy_in_variation_options_modal new file mode 100644 index 00000000000..b4b4f4edbdb --- /dev/null +++ b/packages/js/product-editor/changelog/dev-40246_update_copy_in_variation_options_modal @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Update copy in the add variation options modal diff --git a/packages/js/product-editor/src/blocks/variation-options/edit.tsx b/packages/js/product-editor/src/blocks/variation-options/edit.tsx index d6f9d625f24..fd36230821b 100644 --- a/packages/js/product-editor/src/blocks/variation-options/edit.tsx +++ b/packages/js/product-editor/src/blocks/variation-options/edit.tsx @@ -130,20 +130,9 @@ export function Edit() { 'Add variation options', 'woocommerce' ), - newAttributeModalDescription: createInterpolateElement( - __( - 'Select from existing global attributes or create options for buyers to choose on the product page. You can change the order later.', - 'woocommerce' - ), - { - globalAttributeLink: ( - - ), - } + newAttributeModalDescription: __( + 'Select from existing attributes or create new ones to add new variations for your product. You can change the order later.', + 'woocommerce' ), attributeRemoveLabel: __( 'Remove variation option', diff --git a/packages/js/product-editor/src/blocks/variations/edit.tsx b/packages/js/product-editor/src/blocks/variations/edit.tsx index f49b88bc962..9b4af8e98cb 100644 --- a/packages/js/product-editor/src/blocks/variations/edit.tsx +++ b/packages/js/product-editor/src/blocks/variations/edit.tsx @@ -4,14 +4,9 @@ import classNames from 'classnames'; import type { BlockEditProps } from '@wordpress/blocks'; import { Button } from '@wordpress/components'; -import { Link } from '@woocommerce/components'; import { Product, ProductAttribute } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; -import { - createElement, - useState, - createInterpolateElement, -} from '@wordpress/element'; +import { createElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useBlockProps, @@ -131,20 +126,9 @@ export function Edit( { { isNewModalVisible && ( global attributes or create options for buyers to choose on the product page. You can change the order later.', - 'woocommerce' - ), - { - globalAttributeLink: ( - - ), - } + description={ __( + 'Select from existing attributes or create new ones to add new variations for your product. You can change the order later.', + 'woocommerce' ) } createNewAttributesAsGlobal={ true } notice={ '' } From 6f4eb37b21af29618418e799bc98e8b2f195aed3 Mon Sep 17 00:00:00 2001 From: Vedanshu Jain Date: Wed, 20 Sep 2023 12:18:22 +0530 Subject: [PATCH 19/20] Enable HPOS by default for new installs. --- .../woocommerce/changelog/add-hpos_by_default | 4 ++ .../woocommerce/includes/class-wc-install.php | 51 ++++++++++++++++++- .../woocommerce/tests/legacy/bootstrap.php | 4 ++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce/changelog/add-hpos_by_default diff --git a/plugins/woocommerce/changelog/add-hpos_by_default b/plugins/woocommerce/changelog/add-hpos_by_default new file mode 100644 index 00000000000..983f44d074b --- /dev/null +++ b/plugins/woocommerce/changelog/add-hpos_by_default @@ -0,0 +1,4 @@ +Significance: major +Type: enhancement + +Enable HPOS by default for new installs. diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 8176e7bc93a..c451fee1313 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -18,6 +18,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Synchro use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; use Automattic\WooCommerce\Internal\WCCom\ConnectionHelper as WCConnectionHelper; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; +use Automattic\WooCommerce\Utilities\OrderUtil; defined( 'ABSPATH' ) || exit; @@ -256,6 +257,7 @@ class WC_Install { public static function init() { add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); add_action( 'init', array( __CLASS__, 'manual_database_update' ), 20 ); + add_action( 'woocommerce_newly_installed', array( __CLASS__, 'maybe_enable_hpos' ), 20 ); add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) ); add_action( 'admin_init', array( __CLASS__, 'add_admin_note_after_page_created' ) ); add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) ); @@ -878,6 +880,51 @@ class WC_Install { } } + /** + * Enable HPOS by default for new shops. + * + * @since 8.2.0 + */ + public static function maybe_enable_hpos() { + if ( self::should_enable_hpos_for_new_shop() ) { + $feature_controller = wc_get_container()->get( FeaturesController::class ); + $feature_controller->change_feature_enable( 'custom_order_tables', true ); + } + } + + /** + * Checks whether HPOS should be enabled for new shops. + * + * @return bool + */ + private static function should_enable_hpos_for_new_shop() { + if ( ! did_action( 'woocommerce_init' ) && ! doing_action( 'woocommerce_init' ) ) { + return false; + } + + $feature_controller = wc_get_container()->get( FeaturesController::class ); + + if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + return true; + } + + if ( ! empty( wc_get_orders( array( 'limit' => 1 ) ) ) ) { + return false; + } + + $plugin_compat_info = $feature_controller->get_compatible_plugins_for_feature( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, true ); + if ( ! empty( $plugin_compat_info['incompatible'] ) || ! empty( $plugin_compat_info['uncertain'] ) ) { + return false; + } + + /** + * Filter to enable HPOS by default for new shops. + * + * @since 8.2.0 + */ + return apply_filters( 'woocommerce_enable_hpos_by_default_for_new_shops', true ); + } + /** * Delete obsolete notes. */ @@ -1167,7 +1214,9 @@ class WC_Install { $feature_controller = wc_get_container()->get( FeaturesController::class ); $hpos_enabled = - $feature_controller->feature_is_enabled( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION ) || $feature_controller->feature_is_enabled( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ); + $feature_controller->feature_is_enabled( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION ) || $feature_controller->feature_is_enabled( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ) || + self::should_enable_hpos_for_new_shop() + ; $hpos_table_schema = $hpos_enabled ? wc_get_container()->get( OrdersTableDataStore::class )->get_database_schema() : ''; $tables = " diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php index 93ab1a62aba..2180f246105 100644 --- a/plugins/woocommerce/tests/legacy/bootstrap.php +++ b/plugins/woocommerce/tests/legacy/bootstrap.php @@ -226,6 +226,10 @@ class WC_Unit_Tests_Bootstrap { define( 'WC_REMOVE_ALL_DATA', true ); include $this->plugin_dir . '/uninstall.php'; + if ( ! getenv( 'HPOS' ) ) { + add_filter( 'woocommerce_enable_hpos_by_default_for_new_shops', '__return_false' ); + } + WC_Install::install(); // Reload capabilities after install, see https://core.trac.wordpress.org/ticket/28374. From 76b8042359aec0122f38f8fd6ecb8ca033c0a667 Mon Sep 17 00:00:00 2001 From: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:25:35 +0800 Subject: [PATCH 20/20] Replace "Personalize Your Store" Task with "Choose Your Theme" (#40239) * Update appearance task to choose a WP theme * Add changelog * Update redirect URL Redirects to the main theme screen. * Update plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php Co-authored-by: Ilyas Foo * Wrap URL in getAdminLink * Update position of task * Change title * Change redirect URL to be the site editor * Revert to original task name * Use inherited is_complete function * Use PHP-based action tracking * Fix lint issue --------- Co-authored-by: Ilyas Foo --- .../client/task-lists/fills/appearance.js | 443 +----------------- .../changelog/update-appearance-task | 4 + .../Features/OnboardingTasks/TaskLists.php | 2 +- .../OnboardingTasks/Tasks/Appearance.php | 87 +--- 4 files changed, 40 insertions(+), 496 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-appearance-task diff --git a/plugins/woocommerce-admin/client/task-lists/fills/appearance.js b/plugins/woocommerce-admin/client/task-lists/fills/appearance.js index bdc9a0f48d2..095f1c9b9b3 100644 --- a/plugins/woocommerce-admin/client/task-lists/fills/appearance.js +++ b/plugins/woocommerce-admin/client/task-lists/fills/appearance.js @@ -1,431 +1,36 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; -import { Button, Card, CardBody } from '@wordpress/components'; -import { Component, Fragment } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; -import { filter } from 'lodash'; -import { withDispatch, withSelect } from '@wordpress/data'; - -import { Stepper, TextControl, ImageUpload } from '@woocommerce/components'; -import { - OPTIONS_STORE_NAME, - ONBOARDING_STORE_NAME, - WC_ADMIN_NAMESPACE, -} from '@woocommerce/data'; -import { queueRecordEvent, recordEvent } from '@woocommerce/tracks'; +import React from 'react'; +import { WooOnboardingTaskListItem } from '@woocommerce/onboarding'; import { registerPlugin } from '@wordpress/plugins'; -import { WooOnboardingTask } from '@woocommerce/onboarding'; +import { getAdminLink } from '@woocommerce/settings'; -/** - * Internal dependencies - */ -class Appearance extends Component { - constructor( props ) { - super( props ); - const { hasHomepage, hasProducts, supportCustomLogo } = - props.task.additionalData; - - this.stepVisibility = { - homepage: ! hasHomepage, - import: ! hasProducts, - logo: supportCustomLogo, - }; - - this.state = { - isDirty: false, - isPending: false, - logo: null, - stepIndex: 0, - isUpdatingLogo: false, - isUpdatingNotice: false, - storeNoticeText: props.demoStoreNotice || '', - }; - - this.completeStep = this.completeStep.bind( this ); - this.createHomepage = this.createHomepage.bind( this ); - this.importProducts = this.importProducts.bind( this ); - this.updateLogo = this.updateLogo.bind( this ); - this.updateNotice = this.updateNotice.bind( this ); - } - - componentDidMount() { - const { themeMods } = this.props.task.additionalData; - - if ( themeMods && themeMods.custom_logo ) { - /* eslint-disable react/no-did-mount-set-state */ - this.setState( { logo: { id: themeMods.custom_logo } } ); - /* eslint-enable react/no-did-mount-set-state */ - } - } - - componentDidUpdate( prevProps ) { - const { isPending, logo } = this.state; - const { demoStoreNotice } = this.props; - - if ( logo && ! logo.url && ! isPending ) { - /* eslint-disable react/no-did-update-set-state */ - this.setState( { isPending: true } ); - wp.media - .attachment( logo.id ) - .fetch() - .then( () => { - const logoUrl = wp.media.attachment( logo.id ).get( 'url' ); - this.setState( { - isPending: false, - logo: { id: logo.id, url: logoUrl }, - } ); - } ); - /* eslint-enable react/no-did-update-set-state */ - } - - if ( - demoStoreNotice && - prevProps.demoStoreNotice !== demoStoreNotice - ) { - /* eslint-disable react/no-did-update-set-state */ - this.setState( { - storeNoticeText: demoStoreNotice, - } ); - /* eslint-enable react/no-did-update-set-state */ - } - } - - async completeStep() { - const { stepIndex } = this.state; - const { actionTask, onComplete } = this.props; - const nextStep = this.getSteps()[ stepIndex + 1 ]; - - if ( nextStep ) { - this.setState( { stepIndex: stepIndex + 1 } ); - } else { - this.setState( { isPending: true } ); - await actionTask( 'appearance' ); - onComplete(); - } - } - - importProducts() { - const { createNotice } = this.props; - this.setState( { isPending: true } ); - - recordEvent( 'tasklist_appearance_import_demo', {} ); - - apiFetch( { - path: `${ WC_ADMIN_NAMESPACE }/onboarding/tasks/import_sample_products`, - method: 'POST', - } ) - .then( ( result ) => { - if ( result.failed && result.failed.length ) { - createNotice( - 'error', - __( - 'There was an error importing some of the sample products', - 'woocommerce' - ) - ); - } else { - createNotice( - 'success', - __( - 'All sample products have been imported', - 'woocommerce' - ) - ); - } - - this.setState( { isPending: false } ); - this.completeStep(); - } ) - .catch( ( { message } ) => { - createNotice( - 'error', - message || - __( - 'There was an error importing the sample products', - 'woocommerce' - ), - { __unstableHTML: true } - ); - this.setState( { isPending: false } ); - } ); - } - - createHomepage() { - const { createNotice } = this.props; - this.setState( { isPending: true } ); - - recordEvent( 'tasklist_appearance_create_homepage', { - create_homepage: true, - } ); - - apiFetch( { - path: '/wc-admin/onboarding/tasks/create_homepage', - method: 'POST', - } ) - .then( ( response ) => { - createNotice( response.status, response.message, { - actions: response.edit_post_link - ? [ - { - label: __( 'Customize', 'woocommerce' ), - onClick: () => { - queueRecordEvent( - 'tasklist_appearance_customize_homepage', - {} - ); - window.location = `${ response.edit_post_link }&wc_onboarding_active_task=appearance`; - }, - }, - ] - : null, - } ); - - this.setState( { isPending: false } ); - this.completeStep(); - } ) - .catch( ( error ) => { - createNotice( 'error', error.message ); - this.setState( { isPending: false } ); - } ); - } - - async updateLogo() { - const { createNotice, task, updateOptions } = this.props; - const { stylesheet, themeMods } = task.additionalData; - const { logo } = this.state; - const updatedThemeMods = { - ...themeMods, - custom_logo: logo ? logo.id : null, - }; - - recordEvent( 'tasklist_appearance_upload_logo' ); - - this.setState( { isUpdatingLogo: true } ); - const update = await updateOptions( { - [ `theme_mods_${ stylesheet }` ]: updatedThemeMods, - } ); - - if ( update.success ) { - this.setState( { isUpdatingLogo: false } ); - createNotice( - 'success', - __( 'Store logo updated successfully', 'woocommerce' ) - ); - this.completeStep(); - } else { - createNotice( 'error', update.message ); - } - } - - async updateNotice() { - const { createNotice, updateOptions } = this.props; - const { storeNoticeText } = this.state; - - recordEvent( 'tasklist_appearance_set_store_notice', { - added_text: Boolean( storeNoticeText.length ), - } ); - - this.setState( { isUpdatingNotice: true } ); - const update = await updateOptions( { - woocommerce_demo_store: storeNoticeText.length ? 'yes' : 'no', - woocommerce_demo_store_notice: storeNoticeText, - } ); - - if ( update.success ) { - this.setState( { isUpdatingNotice: false } ); - createNotice( - 'success', - __( - "🎨 Your store is looking great! Don't forget to continue personalizing it", - 'woocommerce' - ) - ); - this.completeStep(); - } else { - createNotice( 'error', update.message ); - } - } - - getSteps() { - const { isDirty, isPending, logo, storeNoticeText, isUpdatingLogo } = - this.state; - - const steps = [ - { - key: 'import', - label: __( 'Import sample products', 'woocommerce' ), - description: __( - 'We’ll add some products that will make it easier to see what your store looks like', - 'woocommerce' - ), - content: ( - - - - - ), - visible: this.stepVisibility.import, - }, - { - key: 'homepage', - label: __( 'Create a custom homepage', 'woocommerce' ), - description: __( - 'Create a new homepage and customize it to suit your needs', - 'woocommerce' - ), - content: ( - - - - - ), - visible: this.stepVisibility.homepage, - }, - { - key: 'logo', - label: __( 'Upload a logo', 'woocommerce' ), - description: __( - 'Ensure your store is on-brand by adding your logo', - 'woocommerce' - ), - content: isPending ? null : ( - - - this.setState( { isDirty: true, logo: image } ) - } - /> - - - - ), - visible: this.stepVisibility.logo, - }, - { - key: 'notice', - label: __( 'Set a store notice', 'woocommerce' ), - description: __( - 'Optionally display a prominent notice across all pages of your store', - 'woocommerce' - ), - content: ( - - - this.setState( { storeNoticeText: value } ) - } - /> - - - ), - visible: true, - }, - ]; - - return filter( steps, ( step ) => step.visible ); - } - - render() { - const { isPending, stepIndex, isUpdatingLogo, isUpdatingNotice } = - this.state; - const currentStep = this.getSteps()[ stepIndex ].key; - - return ( -
- - - - - -
+const useAppearanceClick = () => { + const onClick = () => { + window.location = getAdminLink( + 'theme-install.php?browse=block-themes' ); - } -} + }; -const AppearanceWrapper = compose( - withSelect( ( select ) => { - const { getOption } = select( OPTIONS_STORE_NAME ); + return { onClick }; +}; - return { - demoStoreNotice: getOption( 'woocommerce_demo_store_notice' ), - }; - } ), - withDispatch( ( dispatch ) => { - const { createNotice } = dispatch( 'core/notices' ); - const { updateOptions } = dispatch( OPTIONS_STORE_NAME ); - const { actionTask } = dispatch( ONBOARDING_STORE_NAME ); - - return { - actionTask, - createNotice, - updateOptions, - }; - } ) -)( Appearance ); +const AppearanceFill = () => { + const { onClick } = useAppearanceClick(); + return ( + + { ( { defaultTaskItem: DefaultTaskItem } ) => ( + + ) } + + ); +}; registerPlugin( 'wc-admin-onboarding-task-appearance', { scope: 'woocommerce-tasks', - render: () => ( - - { ( { onComplete, task } ) => ( - - ) } - - ), + render: () => , } ); diff --git a/plugins/woocommerce/changelog/update-appearance-task b/plugins/woocommerce/changelog/update-appearance-task new file mode 100644 index 00000000000..dc069582853 --- /dev/null +++ b/plugins/woocommerce/changelog/update-appearance-task @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Replace Personalize Your Store task with Choose Your Theme diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php index 361d8dee1d7..d9215cdbb7b 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php @@ -112,12 +112,12 @@ class TaskLists { 'CustomizeStore', 'StoreDetails', 'Products', + 'Appearance', 'WooCommercePayments', 'Payments', 'Tax', 'Shipping', 'Marketing', - 'Appearance', ); if ( Features::is_enabled( 'core-profiler' ) ) { diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php index 2b2832bf635..721b3db43e3 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php @@ -14,14 +14,12 @@ use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; class Appearance extends Task { /** - * Constructor - * - * @param TaskList $task_list Parent task list. + * Constructor. */ - public function __construct( $task_list ) { - parent::__construct( $task_list ); - add_action( 'admin_enqueue_scripts', array( $this, 'add_media_scripts' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'possibly_add_return_notice_script' ) ); + public function __construct() { + if ( ! $this->is_complete() ) { + add_action( 'load-theme-install.php', array( $this, 'mark_actioned' ) ); + } } /** @@ -39,13 +37,7 @@ class Appearance extends Task { * @return string */ public function get_title() { - if ( $this->get_parent_option( 'use_completed_title' ) === true ) { - if ( $this->is_complete() ) { - return __( 'You personalized your store', 'woocommerce' ); - } - return __( 'Personalize your store', 'woocommerce' ); - } - return __( 'Personalize my store', 'woocommerce' ); + return __( 'Choose your theme', 'woocommerce' ); } /** @@ -55,7 +47,7 @@ class Appearance extends Task { */ public function get_content() { return __( - 'Add your logo, create a homepage, and start designing your store.', + "Choose a theme that best fits your brand's look and feel, then make it your own. Change the colors, add your logo, and create pages.", 'woocommerce' ); } @@ -70,68 +62,11 @@ class Appearance extends Task { } /** - * Addtional data. + * Action label. * - * @return array + * @return string */ - public function get_additional_data() { - return array( - 'has_homepage' => self::has_homepage(), - 'has_products' => Products::has_products(), - 'stylesheet' => get_option( 'stylesheet' ), - 'theme_mods' => get_theme_mods(), - 'support_custom_logo' => false !== get_theme_support( 'custom-logo' ), - ); - } - - /** - * Add media scripts for image uploader. - */ - public function add_media_scripts() { - if ( ! PageController::is_admin_page() || ! $this->can_view() ) { - return; - } - - wp_enqueue_media(); - } - - - /** - * Adds a return to task list notice when completing the task. - * - * @param string $hook Page hook. - */ - public function possibly_add_return_notice_script( $hook ) { - global $post; - - if ( $hook !== 'post.php' || $post->post_type !== 'page' ) { - return; - } - - if ( $this->is_complete() || ! $this->is_active() ) { - return; - } - - WCAdminAssets::register_script( 'wp-admin-scripts', 'onboarding-homepage-notice', true ); - } - - /** - * Check if the site has a homepage set up. - */ - public static function has_homepage() { - if ( get_option( 'classic-editor-replace' ) === 'classic' ) { - return true; - } - - $homepage_id = get_option( 'woocommerce_onboarding_homepage_post_id', false ); - - if ( ! $homepage_id ) { - return false; - } - - $post = get_post( $homepage_id ); - $completed = $post && $post->post_status === 'publish'; - - return $completed; + public function get_action_label() { + return __( 'Choose theme', 'woocommerce' ); } }