Merge branch 'trunk' into e2e/release-plugins-0

This commit is contained in:
rodelgc 2023-03-22 17:29:08 +08:00
commit 5f8aa3671c
347 changed files with 10788 additions and 4018 deletions

View File

@ -65,3 +65,12 @@
- plugins/woocommerce/src/Admin/**/*
- plugins/woocommerce/src/Internal/Admin/**/*
- plugins/woocommerce-admin/**/*
'focus: performance tests [team:Solaris]':
- plugins/woocommerce/tests/performance/**/*
'focus: api tests [team:Solaris]':
- plugins/woocommerce/tests/api-core-tests/**/*
'focus: e2e tests [team:Solaris]':
- plugins/woocommerce/tests/e2e-pw/**/*

View File

@ -15,12 +15,14 @@ permissions: {}
jobs:
test:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.user.login != 'github-actions[bot]' }}
name: PHP ${{ matrix.php }} WP ${{ matrix.wp }}
name: PHP ${{ matrix.php }} WP ${{ matrix.wp }} ${{ matrix.hpos && 'HPOS' || '' }}
timeout-minutes: 30
runs-on: ubuntu-20.04
permissions:
contents: read
continue-on-error: ${{ matrix.wp == 'nightly' }}
env:
HPOS: ${{ matrix.hpos }}
strategy:
fail-fast: false
matrix:
@ -33,6 +35,9 @@ jobs:
php: 7.4
- wp: '5.9'
php: 7.4
- wp: 'latest'
php: '7.4'
hpos: true
services:
database:
image: mysql:5.6

View File

@ -1,5 +1,78 @@
== Changelog ==
= 7.5.1 2023-03-21 =
**WooCommerce**
* Fix - Fix no enforcing of min/max limits in quantity selector of variable products. [#36871](https://github.com/woocommerce/woocommerce/pull/36871)
* Dev - Update column definitions with synonymous types to prevent dbDelta from trying to ALTER them on each install. [#37277](https://github.com/woocommerce/woocommerce/pull/37277)
* Update - Update WooCommerce Blocks to 9.6.6. [#37298](https://github.com/woocommerce/woocommerce/pull/37298)
= 7.5.0 2023-03-14 =
**WooCommerce**
* Fix - Add HPOS support to the reserved stock query [#36535](https://github.com/woocommerce/woocommerce/pull/36535)
* Fix - Comment: Fix inconsistencies on Analytics > Orders table when using date_paid or date_completed [#36876](https://github.com/woocommerce/woocommerce/pull/36876)
* Fix - Define a public `api` property in the WooCommerce class to prevent a PHP deprecation warning [#36545](https://github.com/woocommerce/woocommerce/pull/36545)
* Fix - Don't delete order from posts table when deleted from orders table if the later is authoritative and sync is off [#36617](https://github.com/woocommerce/woocommerce/pull/36617)
* Fix - Eliminate data store internal meta keys duplicates [#36611](https://github.com/woocommerce/woocommerce/pull/36611)
* Fix - Ensure changes made via the `woocommerce_order_list_table_prepare_items_query_args` are observed. [#36649](https://github.com/woocommerce/woocommerce/pull/36649)
* Fix - Ensuring that we know if allowTracking is true before adding exit page. [#36656](https://github.com/woocommerce/woocommerce/pull/36656)
* Fix - Fix Ampersand changed to &amp on product attribute export [#36525](https://github.com/woocommerce/woocommerce/pull/36525)
* Fix - Fix decimal points for NOK currency [#36780](https://github.com/woocommerce/woocommerce/pull/36780)
* Fix - Fix inconsitent product task icon colors [#36889](https://github.com/woocommerce/woocommerce/pull/36889)
* Fix - Fix WordPress unit tests libraries being installed in a symlinked folder structure [#36641](https://github.com/woocommerce/woocommerce/pull/36641)
* Fix - Make states optional for Hungary and Bulgaria. [#36701](https://github.com/woocommerce/woocommerce/pull/36701)
* Fix - Screen ID matching switched to untranslated 'woocommerce' strings. [#36854](https://github.com/woocommerce/woocommerce/pull/36854)
* Fix - Translate the labels for units of measure. [#36708](https://github.com/woocommerce/woocommerce/pull/36708)
* Fix - Update `config@3.3.7` (from `3.3.3`). Fix `node_env_var_name is not defined` error. [#33828](https://github.com/woocommerce/woocommerce/pull/33828)
* Add - Add 'add_tab' method in FormFactory to allow plugins to extend the WooCommerce admin product form [#36583](https://github.com/woocommerce/woocommerce/pull/36583)
* Add - Add @woocommerce/product-editor dependency and change dependency of ProductSectionLayout component. [#36600](https://github.com/woocommerce/woocommerce/pull/36600)
* Add - Add additional global attributes and local attributes information when saving product attributes [#36858](https://github.com/woocommerce/woocommerce/pull/36858)
* Add - Add a new Channels card in multichannel marketing page. [#36541](https://github.com/woocommerce/woocommerce/pull/36541)
* Add - Add an experimental slot for marketing overview extensibility [#36828](https://github.com/woocommerce/woocommerce/pull/36828)
* Add - Add slot fill support for tabs for the new product management MVP. [#36551](https://github.com/woocommerce/woocommerce/pull/36551)
* Add - Add survey after disabling new experience [#36544](https://github.com/woocommerce/woocommerce/pull/36544)
* Add - Add unique sku option to error data when setting product sku [#36612](https://github.com/woocommerce/woocommerce/pull/36612)
* Add - Add WC-specific criteria to the Site Health test for persistent object caches [#35202](https://github.com/woocommerce/woocommerce/pull/35202)
* Add - Enable new experience when new user selects "Physical product". [#36406](https://github.com/woocommerce/woocommerce/pull/36406)
* Update - Update WooCommerce Blocks to 9.6.5 [#37051](https://github.com/woocommerce/woocommerce/pull/37051)
* Update - Update WooCommerce Blocks to 9.6.3 [#36992](https://github.com/woocommerce/woocommerce/pull/36992)
* Update - Update WooCommerce Blocks to 9.6.2 [#36919](https://github.com/woocommerce/woocommerce/pull/36919)
* Update - Add date_paid and date_completed date sorting options for Revenue and Order reports [#36492](https://github.com/woocommerce/woocommerce/pull/36492)
* Update - Add default value for backorders [#36607](https://github.com/woocommerce/woocommerce/pull/36607)
* Update - Add Skydropx, Envia, Sendcloud, Packlink to shipping task [#36873](https://github.com/woocommerce/woocommerce/pull/36873)
* Update - Always show comments for product feedback form [#36484](https://github.com/woocommerce/woocommerce/pull/36484)
* Update - Delete FlexSlider code for legacy browsers. [#36690](https://github.com/woocommerce/woocommerce/pull/36690)
* Update - Disable the new product editor, pending design updates. [#36894](https://github.com/woocommerce/woocommerce/pull/36894)
* Update - Have "Grow your store" appear first in marketing task by default [#36826](https://github.com/woocommerce/woocommerce/pull/36826)
* Update - Migrating product editor pricing section to slot fills. [#36500](https://github.com/woocommerce/woocommerce/pull/36500)
* Update - Refactor slot fills to ensure variant fills have distinct slots. [#36646](https://github.com/woocommerce/woocommerce/pull/36646)
* Update - Removed I.D column from product import samples [#36857](https://github.com/woocommerce/woocommerce/pull/36857)
* Update - Remove Meta from grow your store list [#36886](https://github.com/woocommerce/woocommerce/pull/36886)
* Update - Remove opinionated styles from buttons in block themes so they inherit theme styles more accurately [#36651](https://github.com/woocommerce/woocommerce/pull/36651)
* Update - Replace $.ajax() calls with browser-native window.fetch() calls. [#36275](https://github.com/woocommerce/woocommerce/pull/36275)
* Update - Update payment gateway list ordering priority and remove Klarna from North America [#36550](https://github.com/woocommerce/woocommerce/pull/36550)
* Update - Update Playwright version from 1.28.0 -> 1.30.0 [#36789](https://github.com/woocommerce/woocommerce/pull/36789)
* Update - Updates to product editor fill to support new prop API. [#36592](https://github.com/woocommerce/woocommerce/pull/36592)
* Update - Update WooCommerce Blocks 9.6.0 & 9.6.1 [#36852](https://github.com/woocommerce/woocommerce/pull/36852)
* Dev - Add attribute creation form when there are no attributes [#36606](https://github.com/woocommerce/woocommerce/pull/36606)
* Dev - Add a unit test for woocommerce_admin_experimental_onboarding_tasklists filter [#36827](https://github.com/woocommerce/woocommerce/pull/36827)
* Dev - Code refactor on marketing components. [#36540](https://github.com/woocommerce/woocommerce/pull/36540)
* Dev - Made e2e selectors more robust [#36499](https://github.com/woocommerce/woocommerce/pull/36499)
* Dev - Remove attribute type logic from attribute component [#36563](https://github.com/woocommerce/woocommerce/pull/36563)
* Dev - Update eslint to 8.32.0 across the monorepo. [#36700](https://github.com/woocommerce/woocommerce/pull/36700)
* Dev - Update pnpm command to run e2e tests for consistency. Also update docs with new command. [#35287](https://github.com/woocommerce/woocommerce/pull/35287)
* Tweak - Add IR and fields priorities to list of get_country_locale() method to follow conventional way of addressing in Iran. [#36491](https://github.com/woocommerce/woocommerce/pull/36491)
* Tweak - Add missing deprecation notice for filter hook woocommerce_my_account_my_orders_columns. [#36356](https://github.com/woocommerce/woocommerce/pull/36356)
* Tweak - Adjust default sizes for the quantity and coupon input fields within the cart page. [#29122](https://github.com/woocommerce/woocommerce/pull/29122)
* Tweak - Do not display low/out-of-stock information in the dashboard status widget when stock management is disabled. [#36703](https://github.com/woocommerce/woocommerce/pull/36703)
* Tweak - Remove free trial terms from Avalara tax task [#36888](https://github.com/woocommerce/woocommerce/pull/36888)
* Tweak - Tweak product link description and display in the new product management experience [#36591](https://github.com/woocommerce/woocommerce/pull/36591)
* Enhancement - Change the sass variable names to more predictable ones. [#28908](https://github.com/woocommerce/woocommerce/pull/28908)
= 7.4.1 2023-03-01 =
**WooCommerce**

View File

@ -51,7 +51,7 @@
"sass": "^1.49.9",
"sass-loader": "^10.2.1",
"syncpack": "^9.8.4",
"turbo": "^1.7.0",
"turbo": "^1.8.3",
"typescript": "^4.8.3",
"url-loader": "^1.1.2",
"webpack": "^5.70.0"

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Update showOtherPaymentMethods() to test latest payment task properly

View File

@ -31,12 +31,6 @@ export class PaymentsSetup extends BasePage {
}
async showOtherPaymentMethods(): Promise< void > {
const selector = '.woocommerce-task-payments button.toggle-button';
await this.page.waitForSelector( selector );
const toggleButton = await this.page.$(
`${ selector }[aria-expanded=false]`
);
await toggleButton?.click();
await waitForElementByText( 'h2', 'Offline payment methods' );
}

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Adding simple DisplayState wrapper and modifying Collapsible component to allow rendering hidden content.

View File

@ -7,10 +7,12 @@ import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
/**
* Internal dependencies
*/
import { DisplayState } from '../display-state';
export type CollapsedProps = {
initialCollapsed?: boolean;
toggleText: string;
persistRender?: boolean;
children: React.ReactNode;
} & React.HTMLAttributes< HTMLDivElement >;
@ -18,9 +20,19 @@ export const CollapsibleContent: React.FC< CollapsedProps > = ( {
initialCollapsed = true,
toggleText,
children,
persistRender = false,
...props
}: CollapsedProps ) => {
const [ collapsed, setCollapsed ] = useState( initialCollapsed );
const getState = () => {
if ( ! collapsed ) {
return 'visible';
}
return persistRender ? 'visually-hidden' : 'hidden';
};
return (
<div
aria-expanded={ collapsed ? 'false' : 'true' }
@ -38,14 +50,14 @@ export const CollapsibleContent: React.FC< CollapsedProps > = ( {
/>
</div>
</button>
{ ! collapsed && (
<DisplayState state={ getState() }>
<div
{ ...props }
className="woocommerce-collapsible-content__content"
>
{ children }
</div>
) }
</DisplayState>
</div>
);
};

View File

@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { createElement, Fragment } from '@wordpress/element';
/**
* Internal dependencies
*/
export type DisplayStateProps = {
state?: 'visible' | 'visually-hidden' | 'hidden';
children: React.ReactNode;
} & React.HTMLAttributes< HTMLDivElement >;
export const DisplayState: React.FC< DisplayStateProps > = ( {
state = 'visible',
children,
} ) => {
if ( state === 'visible' ) {
return <>{ children }</>;
}
if ( state === 'visually-hidden' ) {
return <div style={ { display: 'none' } }>{ children }</div>;
}
return null;
};

View File

@ -0,0 +1 @@
export * from './display-state';

View File

@ -103,3 +103,4 @@ export {
ProductSectionLayout as __experimentalProductSectionLayout,
ProductFieldSection as __experimentalProductFieldSection,
} from './product-section-layout';
export { DisplayState } from './display-state';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add CES data store to @woocommerce/customer-effort-score

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Move additional components to @woocommerce/customer-effort-score.

View File

@ -51,6 +51,7 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
"@woocommerce/navigation": "workspace:*",
"@woocommerce/tracks": "workspace:*",
"@wordpress/browserslist-config": "wp-6.0",
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",

View File

@ -3,16 +3,17 @@
*/
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { CustomerFeedbackModal } from '@woocommerce/customer-effort-score';
import { recordEvent } from '@woocommerce/tracks';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { createElement } from '@wordpress/element';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { getStoreAgeInWeeks } from './utils';
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from './constants';
import { STORE_KEY } from './data/constants';
import { CustomerFeedbackModal } from '../';
import { getStoreAgeInWeeks } from '../../utils';
import { STORE_KEY } from '../../store';
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from '../../constants';
export const PRODUCT_MVP_CES_ACTION_OPTION_NAME =
'woocommerce_ces_product_mvp_ces_action';

View File

@ -4,15 +4,15 @@
import { useEffect } from 'react';
import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import { createElement, Fragment } from '@wordpress/element';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import CustomerEffortScoreTracks from './customer-effort-score-tracks';
import { STORE_KEY, QUEUE_OPTION_NAME } from './data/constants';
import './data';
import { CustomerEffortScoreTracks } from '../';
import { QUEUE_OPTION_NAME, STORE_KEY } from '../../store';
/**
* Maps the queue of CES tracks surveys to CustomerEffortScoreTracks
@ -25,7 +25,7 @@ import './data';
* @param {boolean} props.resolving Whether the queue is resolving.
* @param {Function} props.clearQueue Sets up clearing of the queue on the next page load.
*/
function CustomerEffortScoreTracksContainer( {
function _CustomerEffortScoreTracksContainer( {
queue,
resolving,
clearQueue,
@ -65,7 +65,7 @@ function CustomerEffortScoreTracksContainer( {
);
}
CustomerEffortScoreTracksContainer.propTypes = {
_CustomerEffortScoreTracksContainer.propTypes = {
/**
* The queue of CES tracks surveys to display.
*/
@ -80,7 +80,7 @@ CustomerEffortScoreTracksContainer.propTypes = {
clearQueue: PropTypes.func,
};
export default compose(
export const CustomerEffortScoreTracksContainer = compose(
withSelect( ( select ) => {
const { getCesSurveyQueue, isResolving } = select( STORE_KEY );
const queue = getCesSurveyQueue();
@ -107,4 +107,4 @@ export default compose(
},
};
} )
)( CustomerEffortScoreTracksContainer );
)( _CustomerEffortScoreTracksContainer );

View File

@ -1,26 +1,24 @@
/**
* External dependencies
*/
import { useState } from '@wordpress/element';
import PropTypes from 'prop-types';
import { recordEvent } from '@woocommerce/tracks';
import {
ALLOW_TRACKING_OPTION_NAME,
CustomerEffortScore,
} from '@woocommerce/customer-effort-score';
import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { createElement, useState } from '@wordpress/element';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { CustomerEffortScore } from '../';
import {
SHOWN_FOR_ACTIONS_OPTION_NAME,
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
} from './constants';
import { getStoreAgeInWeeks } from './utils';
ALLOW_TRACKING_OPTION_NAME,
SHOWN_FOR_ACTIONS_OPTION_NAME,
} from '../../constants';
import { getStoreAgeInWeeks } from '../../utils';
/**
* A CustomerEffortScore wrapper that uses tracks to track the selected
@ -43,7 +41,7 @@ import { getStoreAgeInWeeks } from './utils';
* @param {Function} props.updateOptions Function to update options.
* @param {Function} props.createNotice Function to create a snackbar.
*/
function CustomerEffortScoreTracks( {
function _CustomerEffortScoreTracks( {
action,
trackProps,
title,
@ -176,7 +174,7 @@ function CustomerEffortScoreTracks( {
);
}
CustomerEffortScoreTracks.propTypes = {
_CustomerEffortScoreTracks.propTypes = {
/**
* The action name sent to Tracks.
*/
@ -219,7 +217,7 @@ CustomerEffortScoreTracks.propTypes = {
createNotice: PropTypes.func,
};
export default compose(
export const CustomerEffortScoreTracks = compose(
withSelect( ( select ) => {
const { getOption, hasFinishedResolution } =
select( OPTIONS_STORE_NAME );
@ -262,4 +260,4 @@ export default compose(
createNotice,
};
} )
)( CustomerEffortScoreTracks );
)( _CustomerEffortScoreTracks );

View File

@ -0,0 +1,8 @@
export * from './customer-effort-score';
export * from './customer-effort-score-modal-container';
export * from './customer-effort-score-tracks';
export * from './customer-effort-score-tracks-container';
export * from './customer-feedback-simple';
export * from './customer-feedback-modal';
export * from './product-mvp-feedback-modal';
export * from './feedback-modal';

View File

@ -1 +1,7 @@
export const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
'woocommerce_admin_install_timestamp';
export const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
export const SHOWN_FOR_ACTIONS_OPTION_NAME =
'woocommerce_ces_shown_for_actions';

View File

@ -0,0 +1 @@
export * from './use-customer-effort-score-exit-page-tracker';

View File

@ -1,8 +1,5 @@
export * from './components/customer-effort-score';
export * from './components/customer-feedback-simple';
export * from './components/customer-feedback-modal';
export * from './components/product-mvp-feedback-modal';
export * from './components/feedback-modal';
export * from './hooks/use-customer-effort-score-exit-page-tracker';
export * from './utils/customer-effort-score-exit-page';
export * from './components';
export * from './constants';
export * from './hooks';
export * from './store';
export * from './utils';

View File

@ -11,7 +11,9 @@ import * as actions from './actions';
import * as resolvers from './resolvers';
import * as selectors from './selectors';
import reducer from './reducer';
import { STORE_KEY } from './constants';
import { QUEUE_OPTION_NAME, STORE_KEY } from './constants';
export { QUEUE_OPTION_NAME, STORE_KEY };
export default registerStore( STORE_KEY, {
actions,

View File

@ -0,0 +1,2 @@
export * from './customer-effort-score-exit-page';
export * from './get-store-age-in-weeks';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: fix
Fix linter errors

View File

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Add tracks for plugin actions and handle plugin error properly

View File

@ -28,6 +28,7 @@
"dependencies": {
"@woocommerce/date": "workspace:*",
"@woocommerce/navigation": "workspace:*",
"@woocommerce/tracks": "workspace:*",
"@wordpress/api-fetch": "wp-6.0",
"@wordpress/compose": "wp-6.0",
"@wordpress/core-data": "wp-6.0",

View File

@ -9,6 +9,7 @@ import {
import { _n, sprintf } from '@wordpress/i18n';
import { DispatchFromMap } from '@automattic/data-stores';
import { controls } from '@wordpress/data';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
@ -49,21 +50,22 @@ const isPluginResponseError = (
typeof error === 'object' && error !== null && plugins[ 0 ] in error;
const formatErrorMessage = (
pluginErrors: PluginResponseErrors,
actionType = 'install'
actionType: 'install' | 'activate' = 'install',
plugins: Partial< PluginNames >[],
rawErrorMessage: string
) => {
return sprintf(
/* translators: %(actionType): install or activate (the plugin). %(pluginName): a plugin slug (e.g. woocommerce-services). %(error): a single error message or in plural a comma separated error message list.*/
_n(
'Could not %(actionType)s %(pluginName)s plugin, %(error)s',
'Could not %(actionType)s the following plugins: %(pluginName)s with these Errors: %(error)s',
Object.keys( pluginErrors ).length || 1,
Object.keys( plugins ).length || 1,
'woocommerce'
),
{
actionType,
pluginName: Object.keys( pluginErrors ).join( ', ' ),
error: Object.values( pluginErrors ).join( ', \n' ),
pluginName: plugins.join( ', ' ),
error: rawErrorMessage,
}
);
};
@ -174,35 +176,42 @@ export function setRecommendedPlugins(
}
function* handlePluginAPIError(
actionType: string,
actionType: 'install' | 'activate',
plugins: Partial< PluginNames >[],
error: unknown
) {
yield setError( 'installPlugins', error );
let rawErrorMessage;
let pluginResponseError = error;
if (
( error instanceof Error || isRestApiError( error ) ) &&
plugins[ 0 ]
) {
pluginResponseError = {
[ plugins[ 0 ] ]: [ error.message ],
};
}
if ( isPluginResponseError( plugins, pluginResponseError ) ) {
throw new PluginError(
formatErrorMessage( pluginResponseError, actionType ),
pluginResponseError
);
if ( isPluginResponseError( plugins, error ) ) {
// Backend error messages are in the form of { plugin-slug: [ error messages ] }.
rawErrorMessage = Object.values( error ).join( ', \n' );
} else {
throw new PluginError(
`Unexpected Plugin Error: ${ JSON.stringify(
pluginResponseError
) }`,
pluginResponseError
);
// Other error such as API connection errors.
rawErrorMessage =
isRestApiError( error ) || error instanceof Error
? error.message
: JSON.stringify( error );
}
// Track the error.
switch ( actionType ) {
case 'install':
recordEvent( 'install_plugins_error', {
plugins: plugins.join( ', ' ),
message: rawErrorMessage,
} );
break;
case 'activate':
recordEvent( 'activate_plugins_error', {
plugins: plugins.join( ', ' ),
message: rawErrorMessage,
} );
}
throw new PluginError(
formatErrorMessage( actionType, plugins, rawErrorMessage ),
error
);
}
// Action Creator Generators
@ -225,6 +234,7 @@ export function* installPlugins( plugins: Partial< PluginNames >[] ) {
return results;
} catch ( error ) {
yield setError( 'installPlugins', error );
yield handlePluginAPIError( 'install', plugins, error );
} finally {
yield setIsRequesting( 'installPlugins', false );
@ -251,6 +261,7 @@ export function* activatePlugins( plugins: Partial< PluginNames >[] ) {
return results;
} catch ( error ) {
yield setError( 'activatePlugins', error );
yield handlePluginAPIError( 'activate', plugins, error );
} finally {
yield setIsRequesting( 'activatePlugins', false );
@ -305,7 +316,7 @@ export function* connectToJetpack(
}
export function* installJetpackAndConnect(
errorAction: ( errorMesage: string ) => void,
errorAction: ( errorMessage: string ) => void,
getAdminLink: ( endpoint: string ) => string
) {
try {
@ -329,7 +340,7 @@ export function* installJetpackAndConnect(
export function* connectToJetpackWithFailureRedirect(
failureRedirect: string,
errorAction: ( errorMesage: string ) => void,
errorAction: ( errorMessage: string ) => void,
getAdminLink: ( endpoint: string ) => string
) {
try {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add hook to check unsaved form changes before page navigation

View File

@ -31,6 +31,7 @@
"@wordpress/compose": "wp-6.0",
"@wordpress/element": "wp-6.0",
"@wordpress/hooks": "wp-6.0",
"@wordpress/i18n": "wp-6.0",
"@wordpress/notices": "wp-6.0",
"@wordpress/url": "wp-6.0",
"history": "^5.3.0",

View File

@ -1,16 +1,17 @@
/**
* External dependencies
*/
import { useContext, useEffect, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { parseAdminUrl } from '@woocommerce/navigation';
import {
Location,
UNSAFE_NavigationContext as NavigationContext,
useLocation,
} from 'react-router-dom';
import { Location } from 'react-router-dom';
import { useEffect, useMemo } from '@wordpress/element';
export default function usePreventLeavingPage(
/**
* Internal dependencies
*/
import { getHistory } from '../history';
import { parseAdminUrl } from '../';
export const useConfirmUnsavedChanges = (
hasUnsavedChanges: boolean,
shouldConfirm?: ( path: URL, fromUrl: Location ) => boolean,
/**
@ -19,24 +20,24 @@ export default function usePreventLeavingPage(
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#compatibility_notes
*/
message?: string
) {
) => {
const confirmMessage = useMemo(
() =>
message ??
__( 'Changes you made may not be saved.', 'woocommerce' ),
[ message ]
);
const { navigator } = useContext( NavigationContext );
const fromUrl = useLocation();
const history = getHistory();
// This effect prevent react router from navigate and show
// a confirmation message. It's a work around to beforeunload
// because react router does not triggers that event.
useEffect( () => {
if ( hasUnsavedChanges ) {
const push = navigator.push;
const push = history.push;
navigator.push = ( ...args: Parameters< typeof push > ) => {
history.push = ( ...args: Parameters< typeof push > ) => {
const fromUrl = history.location;
const toUrl = parseAdminUrl( args[ 0 ] ) as URL;
if (
typeof shouldConfirm === 'function' &&
@ -54,10 +55,10 @@ export default function usePreventLeavingPage(
};
return () => {
navigator.push = push;
history.push = push;
};
}
}, [ navigator, hasUnsavedChanges, confirmMessage ] );
}, [ history, hasUnsavedChanges, confirmMessage ] );
// This effect listen to the native beforeunload event to show
// a confirmation message
@ -79,4 +80,4 @@ export default function usePreventLeavingPage(
};
}
}, [ hasUnsavedChanges, confirmMessage ] );
}
};

View File

@ -18,9 +18,6 @@ import { getAdminLink } from '@woocommerce/settings';
* Internal dependencies
*/
import { getHistory } from './history';
import * as navUtils from './index';
// For the above, import the module into itself. Functions consumed from this import can be mocked in tests.
// Expose history so all uses get the same history object.
export { getHistory };
@ -28,6 +25,9 @@ export { getHistory };
// Export all filter utilities
export * from './filters';
// Export all hooks
export { useConfirmUnsavedChanges } from './hooks/use-confirm-unsaved-changes';
const TIME_EXCLUDED_SCREENS_FILTER = 'woocommerce_admin_time_excluded_screens';
/**
@ -79,7 +79,7 @@ export function getNewPath(
* @param {Object} query Query containing the parameters.
* @return {Object} Object containing the persisted queries.
*/
export const getPersistedQuery = ( query = navUtils.getQuery() ) => {
export const getPersistedQuery = ( query = getQuery() ) => {
/**
* Filter persisted queries. These query parameters remain in the url when other parameters are updated.
*
@ -226,7 +226,7 @@ export function getIdsFromQuery( queryString = '' ) {
* @param {Object} query Query object.
* @return {Array} List of search words.
*/
export function getSearchWords( query = navUtils.getQuery() ) {
export function getSearchWords( query = getQuery() ) {
if ( typeof query !== 'object' ) {
throw new Error(
'Invalid parameter passed to getSearchWords, it expects an object or no parameters.'

View File

@ -0,0 +1,3 @@
module.exports = {
extends: '../internal-js-tests/babel.config.js',
};

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add custom validation hook

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add a product header component to the blocks interface

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add tests around product block editor tabs

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add new pricing block to the product editor package.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add summary block

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Adding Collapsible block with support for flexible rendering.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Remove the product block breadcrumbs and sidebar inspector

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Fix issue were template was not re-synced when switching between products.

View File

@ -0,0 +1,4 @@
{
"rootDir": "./src",
"preset": "../../internal-js-tests/jest.config.js"
}

View File

@ -36,6 +36,7 @@
"@woocommerce/data": "workspace:^4.1.0",
"@woocommerce/navigation": "workspace:^8.1.0",
"@woocommerce/number": "workspace:*",
"@woocommerce/settings": "^1.0.0",
"@woocommerce/tracks": "workspace:^1.3.0",
"@wordpress/block-editor": "^9.8.0",
"@wordpress/blocks": "^12.3.0",
@ -57,8 +58,15 @@
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/runtime": "^7.17.2",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.2",
"@types/testing-library__jest-dom": "^5.14.3",
"@types/wordpress__block-editor": "^7.0.0",
"@types/wordpress__block-library": "^2.6.1",
"@types/wordpress__blocks": "^11.0.7",
@ -90,10 +98,12 @@
},
"scripts": {
"turbo:build": "pnpm run build:js && pnpm run build:css",
"turbo:test": "jest --config ./jest.config.json",
"prepare": "composer install",
"changelog": "composer exec -- changelogger",
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
"build": "pnpm -w exec turbo run turbo:build --filter=$npm_package_name",
"test": "pnpm -w exec turbo run turbo:test --filter=$npm_package_name",
"lint": "eslint src",
"build:js": "tsc --build ./tsconfig.json ./tsconfig-cjs.json",
"build:css": "webpack",

View File

@ -12,9 +12,6 @@ import { Product } from '@woocommerce/data';
import { useSelect, select as WPSelect } from '@wordpress/data';
import { uploadMedia } from '@wordpress/media-utils';
import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
BlockBreadcrumb,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
BlockContextProvider,
@ -24,7 +21,6 @@ import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
BlockTools,
BlockInspector,
EditorSettings,
EditorBlockListSettings,
WritingFlow,
@ -41,7 +37,6 @@ import {
/**
* Internal dependencies
*/
import { Sidebar } from '../sidebar';
import { Tabs } from '../tabs';
type BlockEditorProps = {
@ -99,7 +94,7 @@ export function BlockEditor( {
synchronizeBlocksWithTemplate( [], _settings?.template ),
{}
);
}, [] );
}, [ product.id ] );
if ( ! blocks ) {
return null;
@ -115,10 +110,6 @@ export function BlockEditor( {
settings={ settings }
>
<Tabs onChange={ setSelectedTab } />
<BlockBreadcrumb />
<Sidebar.InspectorFill>
<BlockInspector />
</Sidebar.InspectorFill>
<div className="editor-styles-wrapper">
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore No types for this exist yet. */ }

View File

@ -0,0 +1,28 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/collapsible",
"title": "Collapsible",
"category": "widgets",
"description": "Container with collapsible inner blocks.",
"textdomain": "default",
"attributes": {
"toggleText": {
"type": "string"
},
"initialCollapsed": {
"type": "boolean"
},
"persistRender": {
"type": "boolean"
}
},
"supports": {
"align": false,
"html": false,
"multiple": true,
"reusable": false,
"inserter": false,
"lock": false
}
}

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { CollapsibleContent } from '@woocommerce/components';
import type { BlockAttributes } from '@wordpress/blocks';
import { createElement } from '@wordpress/element';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
export function Edit( { attributes }: { attributes: BlockAttributes } ) {
const blockProps = useBlockProps();
const { toggleText, initialCollapsed, persistRender = true } = attributes;
return (
<div { ...blockProps }>
<CollapsibleContent
toggleText={ toggleText }
initialCollapsed={ initialCollapsed }
persistRender={ persistRender }
>
<InnerBlocks templateLock="all" />
</CollapsibleContent>
</div>
);
}

View File

@ -0,0 +1,18 @@
/**
* Internal dependencies
*/
import { initBlock } from '../../utils';
import metadata from './block.json';
import { Edit } from './edit';
const { name } = metadata;
export { metadata, name };
export const settings = {
example: {},
edit: Edit,
};
export const init = () =>
initBlock( { name, metadata: metadata as never, settings } );

View File

@ -14,4 +14,9 @@ export const settings = {
edit: Edit,
};
export const init = () => initBlock( { name, metadata, settings } );
export const init = () =>
initBlock( {
name,
metadata: metadata as never,
settings,
} );

View File

@ -0,0 +1,30 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-summary",
"title": "Product summary",
"category": "widgets",
"description": "The product summary.",
"keywords": [ "products", "summary", "excerpt" ],
"textdomain": "default",
"attributes": {
"align": {
"type": "string"
},
"direction": {
"type": "string",
"enum": [ "ltr", "rtl" ]
},
"label": {
"type": "string"
}
},
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false,
"lock": false
}
}

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
alignCenter,
alignJustify,
alignLeft,
alignRight,
} from '@wordpress/icons';
export const ALIGNMENT_CONTROLS = [
{
icon: alignLeft,
title: __( 'Align text left', 'woocommerce' ),
align: 'left',
},
{
icon: alignCenter,
title: __( 'Align text center', 'woocommerce' ),
align: 'center',
},
{
icon: alignRight,
title: __( 'Align text right', 'woocommerce' ),
align: 'right',
},
{
icon: alignJustify,
title: __( 'Align text justify', 'woocommerce' ),
align: 'justify',
},
];

View File

@ -0,0 +1,90 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { BlockEditProps } from '@wordpress/blocks';
import { BaseControl } from '@wordpress/components';
import { useEntityProp } from '@wordpress/core-data';
import uniqueId from 'lodash/uniqueId';
import classNames from 'classnames';
import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
AlignmentControl,
BlockControls,
RichText,
useBlockProps,
} from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { ParagraphRTLControl } from './paragraph-rtl-control';
import { SummaryAttributes } from './types';
import { ALIGNMENT_CONTROLS } from './constants';
export function Edit( {
attributes,
setAttributes,
}: BlockEditProps< SummaryAttributes > ) {
const { align, direction, label } = attributes;
const blockProps = useBlockProps( {
style: { direction },
} );
const id = uniqueId();
const [ summary, setSummary ] = useEntityProp< string >(
'postType',
'product',
'short_description'
);
function handleAlignmentChange( value: SummaryAttributes[ 'align' ] ) {
setAttributes( { align: value } );
}
function handleDirectionChange( value: SummaryAttributes[ 'direction' ] ) {
setAttributes( { direction: value } );
}
return (
<div { ...blockProps }>
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore No types for this exist yet. */ }
<BlockControls group="block">
<AlignmentControl
alignmentControls={ ALIGNMENT_CONTROLS }
value={ align }
onChange={ handleAlignmentChange }
/>
<ParagraphRTLControl
direction={ direction }
onChange={ handleDirectionChange }
/>
</BlockControls>
<BaseControl
id={ id }
label={ label || __( 'Summary', 'woocommerce' ) }
>
<RichText
id={ id }
identifier="content"
tagName="p"
value={ summary }
onChange={ setSummary }
placeholder={ __(
"Summarize this product in 1-2 short sentences. We'll show it at the top of the page.",
'woocommerce'
) }
data-empty={ Boolean( summary ) }
className={ classNames( 'components-summary-control', {
[ `has-text-align-${ align }` ]: align,
} ) }
dir={ direction }
/>
</BaseControl>
</div>
);
}

View File

@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { initBlock } from '../../utils';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { SummaryAttributes } from './types';
const { name, ...metadata } =
blockConfiguration as BlockConfiguration< SummaryAttributes >;
export { name, metadata };
export const settings = {
example: {},
edit: Edit,
};
export function init() {
return initBlock< SummaryAttributes >( {
name,
metadata,
settings,
} );
}

View File

@ -0,0 +1,2 @@
export * from './paragraph-rtl-control';
export * from './types';

View File

@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { createElement, Fragment } from '@wordpress/element';
import { ToolbarButton } from '@wordpress/components';
import { _x, isRTL } from '@wordpress/i18n';
import { formatLtr } from '@wordpress/icons';
/**
* Internal dependencies
*/
import { ParagraphRTLControlProps } from './types';
export function ParagraphRTLControl( {
direction,
onChange,
}: ParagraphRTLControlProps ) {
function handleClick() {
if ( typeof onChange === 'function' ) {
onChange( direction === 'ltr' ? undefined : 'ltr' );
}
}
return (
<>
{ isRTL() && (
<ToolbarButton
icon={ formatLtr }
title={ _x(
'Left to right',
'editor button',
'woocommerce'
) }
isActive={ direction === 'ltr' }
onClick={ handleClick }
/>
) }
</>
);
}

View File

@ -0,0 +1,11 @@
/**
* Internal dependencies
*/
import { SummaryAttributes } from '../types';
export type ParagraphRTLControlProps = Pick<
SummaryAttributes,
'direction'
> & {
onChange( direction?: SummaryAttributes[ 'direction' ] ): void;
};

View File

@ -0,0 +1,6 @@
// This alignment class does not exists in
// https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/common.scss
.has-text-align-justify {
/*rtl:ignore*/
text-align: justify;
}

View File

@ -0,0 +1,5 @@
export type SummaryAttributes = {
align: 'left' | 'center' | 'right' | 'justify';
direction: 'ltr' | 'rtl';
label: string;
};

View File

@ -11,8 +11,8 @@ import { createElement } from '@wordpress/element';
*/
import { EditProductLinkModal } from '../';
jest.mock( '@woocommerce/product-editor', () => ( {
__experimentalUseProductHelper: jest.fn().mockReturnValue( {
jest.mock( '../../../hooks/use-product-helper', () => ( {
useProductHelper: jest.fn().mockReturnValue( {
updateProductWithStatus: jest.fn(),
isUpdatingDraft: jest.fn(),
isUpdatingPublished: jest.fn(),

View File

@ -6,13 +6,12 @@ import {
EditorSettings,
EditorBlockListSettings,
} from '@wordpress/block-editor';
import { SlotFillProvider } from '@wordpress/components';
import { Popover, SlotFillProvider } from '@wordpress/components';
import { Product } from '@woocommerce/data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
import { EntityProvider } from '@wordpress/core-data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
@ -26,14 +25,15 @@ import { FullscreenMode, InterfaceSkeleton } from '@wordpress/interface';
* Internal dependencies
*/
import { Header } from '../header';
import { Sidebar } from '../sidebar';
import { BlockEditor } from '../block-editor';
import { initBlocks } from './init-blocks';
initBlocks();
export type ProductEditorSettings = Partial<
EditorSettings & EditorBlockListSettings
>;
type EditorProps = {
product: Product;
settings: ProductEditorSettings | undefined;
@ -47,8 +47,12 @@ export function Editor( { product, settings }: EditorProps ) {
<FullscreenMode isActive={ false } />
<SlotFillProvider>
<InterfaceSkeleton
header={ <Header title={ product.name } /> }
sidebar={ <Sidebar /> }
header={
<Header
productId={ product.id }
productName={ product.name }
/>
}
content={
<BlockEditor
settings={ settings }
@ -56,6 +60,8 @@ export function Editor( { product, settings }: EditorProps ) {
/>
}
/>
<Popover.Slot />
</SlotFillProvider>
</ShortcutProvider>
</EntityProvider>

View File

@ -1,12 +1,24 @@
/**
* External dependencies
*/
import { registerCoreBlocks } from '@wordpress/block-library';
/**
* Internal dependencies
*/
import { init as initName } from '../details-name-block';
import { init as initSummary } from '../details-summary-block';
import { init as initSection } from '../section';
import { init as initTab } from '../tab';
import { init as initPricing } from '../pricing-block';
import { init as initCollapsible } from '../collapsible-block';
export const initBlocks = () => {
registerCoreBlocks();
initName();
initSummary();
initSection();
initTab();
initPricing();
initCollapsible();
};

View File

@ -1,14 +1,68 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Product } from '@woocommerce/data';
import { Button } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { createElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { navigateTo, getNewPath } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import { AUTO_DRAFT_NAME, getHeaderTitle } from '../../utils';
export type HeaderProps = {
title: string;
productId: number;
productName: string;
};
export function Header( { title }: HeaderProps ) {
export function Header( { productId, productName }: HeaderProps ) {
const { isProductLocked, isSaving, editedProductName } = useSelect(
( select ) => {
const { isSavingEntityRecord, getEditedEntityRecord } =
select( 'core' );
const { isPostSavingLocked } = select( 'core/editor' );
const product: Product = getEditedEntityRecord(
'postType',
'product',
productId
);
return {
isProductLocked: isPostSavingLocked(),
isSaving: isSavingEntityRecord(
'postType',
'product',
productId
),
editedProductName: product?.name,
};
},
[ productId ]
);
const isDisabled = isProductLocked || isSaving;
const isCreating = productName === AUTO_DRAFT_NAME;
const { saveEditedEntityRecord } = useDispatch( 'core' );
function handleSave() {
saveEditedEntityRecord< Product >(
'postType',
'product',
productId
).then( ( response ) => {
if ( isCreating ) {
navigateTo( {
url: getNewPath( {}, `/product/${ response.id }` ),
} );
}
} );
}
return (
<div
className="woocommerce-product-header"
@ -16,7 +70,22 @@ export function Header( { title }: HeaderProps ) {
aria-label={ __( 'Product Editor top bar.', 'woocommerce' ) }
tabIndex={ -1 }
>
<h1 className="woocommerce-product-header__title">{ title }</h1>
<h1 className="woocommerce-product-header__title">
{ getHeaderTitle( editedProductName, productName ) }
</h1>
<div className="woocommerce-product-header__actions">
<Button
onClick={ handleSave }
variant="primary"
isBusy={ isSaving }
disabled={ isDisabled }
>
{ isCreating
? __( 'Add', 'woocommerce' )
: __( 'Save', 'woocommerce' ) }
</Button>
</div>
</div>
);
}

View File

@ -3,4 +3,8 @@
display: flex;
align-items: center;
padding: 0 $gap;
&__actions {
margin-left: auto;
}
}

View File

@ -0,0 +1,29 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-pricing",
"description": "A product price block with currency display.",
"title": "Product pricing",
"category": "widgets",
"keywords": [ "products", "price" ],
"textdomain": "default",
"attributes": {
"name": {
"type": "string"
},
"label": {
"type": "string"
},
"showPricingSection": {
"type": "boolean"
}
},
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false,
"lock": false
}
}

View File

@ -0,0 +1,89 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement, useContext, Fragment } from '@wordpress/element';
import interpolateComponents from '@automattic/interpolate-components';
import { Link } from '@woocommerce/components';
import { useBlockProps } from '@wordpress/block-editor';
import { useEntityProp } from '@wordpress/core-data';
import { BlockAttributes } from '@wordpress/blocks';
import { CurrencyContext } from '@woocommerce/currency';
import { getSetting } from '@woocommerce/settings';
import { recordEvent } from '@woocommerce/tracks';
import {
BaseControl,
// @ts-expect-error `__experimentalInputControl` does exist.
__experimentalInputControl as InputControl,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import { formatCurrencyDisplayValue } from '../../utils';
import { useCurrencyInputProps } from '../../hooks/use-currency-input-props';
export function Edit( { attributes }: { attributes: BlockAttributes } ) {
const blockProps = useBlockProps();
const { name, label, showPricingSection = false } = attributes;
const [ regularPrice, setRegularPrice ] = useEntityProp< string >(
'postType',
'product',
name
);
const context = useContext( CurrencyContext );
const { getCurrencyConfig, formatAmount } = context;
const currencyConfig = getCurrencyConfig();
const inputProps = useCurrencyInputProps( {
value: regularPrice,
setValue: setRegularPrice,
} );
const taxSettingsElement = showPricingSection
? interpolateComponents( {
mixedString: __(
'Manage more settings in {{link}}Pricing.{{/link}}',
'woocommerce'
),
components: {
link: (
<Link
href={ `${ getSetting(
'adminUrl'
) }admin.php?page=wc-settings&tab=tax` }
target="_blank"
type="external"
onClick={ () => {
recordEvent(
'product_pricing_list_price_help_tax_settings_click'
);
} }
>
<></>
</Link>
),
},
} )
: null;
return (
<div { ...blockProps }>
<BaseControl
id={ 'product_pricing_' + name }
help={ taxSettingsElement ? taxSettingsElement : '' }
>
<InputControl
name={ name }
onChange={ setRegularPrice }
label={ label || __( 'Price', 'woocommerce' ) }
value={ formatCurrencyDisplayValue(
String( regularPrice ),
currencyConfig,
formatAmount
) }
{ ...inputProps }
/>
</BaseControl>
</div>
);
}

View File

@ -0,0 +1,22 @@
/**
* Internal dependencies
*/
import { initBlock } from '../../utils';
import metadata from './block.json';
import { Edit } from './edit';
const { name } = metadata;
export { metadata, name };
export const settings = {
example: {},
edit: Edit,
};
export const init = () =>
initBlock( {
name,
metadata: metadata as never,
settings,
} );

View File

@ -1 +0,0 @@
export * from './sidebar';

View File

@ -1,30 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement } from '@wordpress/element';
import { createSlotFill, Panel } from '@wordpress/components';
const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill(
'ProductBlockEditorSidebarInspector'
);
export function Sidebar() {
return (
<div
className="woocommerce-product-sidebar"
role="region"
aria-label={ __(
'Product Block Editor advanced settings.',
'woocommerce'
) }
tabIndex={ -1 }
>
<Panel header={ __( 'Inspector', 'woocommerce' ) }>
<InspectorSlot bubblesVirtually />
</Panel>
</div>
);
}
Sidebar.InspectorFill = InspectorFill;

View File

@ -17,7 +17,7 @@ export function Edit( {
}: {
attributes: BlockAttributes;
context?: {
selectedTab?: string;
selectedTab?: string | null;
};
} ) {
const blockProps = useBlockProps();

View File

@ -0,0 +1,160 @@
/**
* External dependencies
*/
import { render, fireEvent } from '@testing-library/react';
import { getQuery, navigateTo } from '@woocommerce/navigation';
import React, { createElement } from 'react';
import { SlotFillProvider } from '@wordpress/components';
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { Tabs } from '../';
import { Edit as Tab } from '../../tab/edit';
jest.mock( '@wordpress/block-editor', () => ( {
...jest.requireActual( '@wordpress/block-editor' ),
useBlockProps: jest.fn(),
} ) );
jest.mock( '@woocommerce/navigation', () => ( {
...jest.requireActual( '@woocommerce/navigation' ),
navigateTo: jest.fn(),
getQuery: jest.fn().mockReturnValue( {} ),
} ) );
function MockTabs( { onChange = jest.fn() } ) {
const [ selected, setSelected ] = useState< string | null >( null );
const mockContext = {
selectedTab: selected,
};
return (
<SlotFillProvider>
<Tabs
onChange={ ( tabId ) => {
setSelected( tabId );
onChange( tabId );
} }
/>
<Tab
attributes={ { id: 'test1', title: 'Test button 1' } }
context={ mockContext }
/>
<Tab
attributes={ { id: 'test2', title: 'Test button 2' } }
context={ mockContext }
/>
<Tab
attributes={ { id: 'test3', title: 'Test button 3' } }
context={ mockContext }
/>
</SlotFillProvider>
);
}
describe( 'Tabs', () => {
beforeEach( () => {
( getQuery as jest.Mock ).mockReturnValue( {
tab: null,
} );
} );
it( 'should render tab buttons added to the slot', () => {
const { queryByText } = render( <MockTabs /> );
expect( queryByText( 'Test button 1' ) ).toBeInTheDocument();
expect( queryByText( 'Test button 2' ) ).toBeInTheDocument();
} );
it( 'should set the first tab as active initially', () => {
const { queryByText } = render( <MockTabs /> );
expect( queryByText( 'Test button 1' ) ).toHaveAttribute(
'aria-selected',
'true'
);
expect( queryByText( 'Test button 2' ) ).toHaveAttribute(
'aria-selected',
'false'
);
} );
it( 'should navigate to a new URL when a tab is clicked', () => {
const { getByText } = render( <MockTabs /> );
const button = getByText( 'Test button 2' );
fireEvent.click( button );
expect( navigateTo ).toHaveBeenLastCalledWith( {
url: 'admin.php?page=wc-admin&tab=test2',
} );
} );
it( 'should select the tab provided in the URL initially', () => {
( getQuery as jest.Mock ).mockReturnValue( {
tab: 'test2',
} );
const { getByText } = render( <MockTabs /> );
expect( getByText( 'Test button 2' ) ).toHaveAttribute(
'aria-selected',
'true'
);
} );
it( 'should select the tab provided on URL change', () => {
const { getByText, rerender } = render( <MockTabs /> );
( getQuery as jest.Mock ).mockReturnValue( {
tab: 'test3',
} );
rerender( <MockTabs /> );
expect( getByText( 'Test button 3' ) ).toHaveAttribute(
'aria-selected',
'true'
);
} );
it( 'should call the onChange props when changing', async () => {
const mockOnChange = jest.fn();
const { rerender } = render( <MockTabs onChange={ mockOnChange } /> );
expect( mockOnChange ).toHaveBeenCalledWith( 'test1' );
( getQuery as jest.Mock ).mockReturnValue( {
tab: 'test2',
} );
rerender( <MockTabs onChange={ mockOnChange } /> );
expect( mockOnChange ).toHaveBeenCalledWith( 'test2' );
} );
it( 'should add a class to the initially selected tab panel', async () => {
const { getByRole } = render( <MockTabs /> );
const panel1 = getByRole( 'tabpanel', { name: 'Test button 1' } );
const panel2 = getByRole( 'tabpanel', { name: 'Test button 2' } );
expect( panel1.classList ).toContain( 'is-selected' );
expect( panel2.classList ).not.toContain( 'is-selected' );
} );
it( 'should add a class to the newly selected tab panel', async () => {
const { getByText, getByRole, rerender } = render( <MockTabs /> );
const button = getByText( 'Test button 2' );
fireEvent.click( button );
const panel1 = getByRole( 'tabpanel', { name: 'Test button 1' } );
const panel2 = getByRole( 'tabpanel', { name: 'Test button 2' } );
( getQuery as jest.Mock ).mockReturnValue( {
tab: 'test2',
} );
rerender( <MockTabs /> );
expect( panel1.classList ).not.toContain( 'is-selected' );
expect( panel2.classList ).toContain( 'is-selected' );
} );
} );

View File

@ -1,2 +1,3 @@
export { useProductHelper as __experimentalUseProductHelper } from './use-product-helper';
export { useVariationsOrder as __experimentalUseVariationsOrder } from './use-variations-order';
export { useCurrencyInputProps as __experimentalUseCurrencyInputProps } from './use-currency-input-props';

View File

@ -0,0 +1,77 @@
/**
* External dependencies
*/
import { CurrencyContext } from '@woocommerce/currency';
import { useContext } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useProductHelper } from './use-product-helper';
export type CurrencyInputProps = {
prefix: string;
className: string;
sanitize: ( value: string | number ) => string;
onFocus: ( event: React.FocusEvent< HTMLInputElement > ) => void;
onKeyUp: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
};
type Props = {
value: string;
setValue: ( value: string ) => void;
onFocus?: ( event: React.FocusEvent< HTMLInputElement > ) => void;
onKeyUp?: ( event: React.KeyboardEvent< HTMLInputElement > ) => void;
};
export const useCurrencyInputProps = ( {
value,
setValue,
onFocus,
onKeyUp,
}: Props ) => {
const { sanitizePrice } = useProductHelper();
const context = useContext( CurrencyContext );
const { getCurrencyConfig } = context;
const currencyConfig = getCurrencyConfig();
const currencyInputProps: CurrencyInputProps = {
prefix: currencyConfig.symbol,
className: 'half-width-field components-currency-control',
sanitize: ( val: string | number ) => {
return sanitizePrice( String( val ) );
},
onFocus( event: React.FocusEvent< HTMLInputElement > ) {
// In some browsers like safari .select() function inside
// the onFocus event doesn't work as expected because it
// conflicts with onClick the first time user click the
// input. Using setTimeout defers the text selection and
// avoid the unexpected behaviour.
setTimeout(
function deferSelection( element: HTMLInputElement ) {
element.select();
},
0,
event.currentTarget
);
if ( onFocus ) {
onFocus( event );
}
},
onKeyUp( event: React.KeyboardEvent< HTMLInputElement > ) {
const amount = Number.parseFloat( sanitizePrice( value || '0' ) );
const step = Number( event.currentTarget.step || '1' );
if ( event.code === 'ArrowUp' ) {
setValue( String( amount + step ) );
}
if ( event.code === 'ArrowDown' ) {
setValue( String( amount - step ) );
}
if ( onKeyUp ) {
onKeyUp( event );
}
},
};
return currencyInputProps;
};

View File

@ -0,0 +1,41 @@
# useValidation
This custom hook uses the helper functions `const { lockPostSaving, unlockPostSaving } = useDispatch( 'core/editor' );` to lock/unlock the current editing product before saving it.
## Usage
Syncronous validation
```typescript
import { useCallback } from '@wordpress/element';
import { useValidation } from '@woocommerce/product-editor';
const product = ...;
const validateTitle = useCallback( (): boolean => {
if ( product.title.length < 2 ) {
return false;
}
return true;
}, [ product.title ] );
const isTitleValid = useValidation( 'product/title', validateTitle );
```
Asyncronous validation
```typescript
import { useCallback } from '@wordpress/element';
import { useValidation } from '@woocommerce/product-editor';
const product = ...;
const validateSlug = useCallback( async (): Promise< boolean > => {
return fetch( `.../validate-slug?slug=${ product.slug }` )
.then( ( response ) => response.json() )
.then( ( { isValid } ) => isValid )
.catch( () => false );
}, [ product.slug ] );
const isSlugValid = useValidation( 'product/slug', validateSlug );
```

View File

@ -0,0 +1 @@
export * from './use-validation';

View File

@ -0,0 +1,95 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks';
import { useDispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import { useValidation } from '../use-validation';
jest.mock( '@wordpress/data', () => ( {
useDispatch: jest.fn(),
} ) );
describe( 'useValidation', () => {
const useDispatchMock = useDispatch as jest.Mock;
const lockPostSaving = jest.fn();
const unlockPostSaving = jest.fn();
beforeEach( () => {
useDispatchMock.mockReturnValue( {
lockPostSaving,
unlockPostSaving,
} );
} );
afterEach( () => {
jest.clearAllMocks();
} );
describe( 'sync', () => {
it( 'should lock the editor if validate returns false', async () => {
const { result, waitForNextUpdate } = renderHook( () =>
useValidation( 'product/name', () => false )
);
await waitForNextUpdate();
expect( result.current ).toBeFalsy();
expect( lockPostSaving ).toHaveBeenCalled();
expect( unlockPostSaving ).not.toHaveBeenCalled();
} );
it( 'should unlock the editor if validate returns true', async () => {
const { result, waitForNextUpdate } = renderHook( () =>
useValidation( 'product/name', () => true )
);
await waitForNextUpdate();
expect( result.current ).toBeTruthy();
expect( lockPostSaving ).not.toHaveBeenCalled();
expect( unlockPostSaving ).toHaveBeenCalled();
} );
} );
describe( 'async', () => {
it( 'should lock the editor if validate resolves false', async () => {
const { result, waitForNextUpdate } = renderHook( () =>
useValidation( 'product/name', () => Promise.resolve( false ) )
);
await waitForNextUpdate();
expect( result.current ).toBeFalsy();
expect( lockPostSaving ).toHaveBeenCalled();
expect( unlockPostSaving ).not.toHaveBeenCalled();
} );
it( 'should lock the editor if validate rejects', async () => {
const { result, waitForNextUpdate } = renderHook( () =>
useValidation( 'product/name', () => Promise.reject() )
);
await waitForNextUpdate();
expect( result.current ).toBeFalsy();
expect( lockPostSaving ).toHaveBeenCalled();
expect( unlockPostSaving ).not.toHaveBeenCalled();
} );
it( 'should unlock the editor if validate resolves true', async () => {
const { result, waitForNextUpdate } = renderHook( () =>
useValidation( 'product/name', () => Promise.resolve( true ) )
);
await waitForNextUpdate();
expect( result.current ).toBeTruthy();
expect( lockPostSaving ).not.toHaveBeenCalled();
expect( unlockPostSaving ).toHaveBeenCalled();
} );
} );
} );

View File

@ -0,0 +1,43 @@
/**
* External dependencies
*/
import { useDispatch } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
/**
* Signals that product saving is locked.
*
* @param lockName The namespace used to lock the product saving if validation fails.
* @param validate The validator function.
*/
export function useValidation(
lockName: string,
validate: () => boolean | Promise< boolean >
): boolean | undefined {
const [ isValid, setIsValid ] = useState< boolean | undefined >();
const { lockPostSaving, unlockPostSaving } = useDispatch( 'core/editor' );
useEffect( () => {
let validationResponse = validate();
if ( typeof validationResponse === 'boolean' ) {
validationResponse = Promise.resolve( validationResponse );
}
validationResponse
.then( ( isValidationValid ) => {
if ( isValidationValid ) {
unlockPostSaving( lockName );
} else {
lockPostSaving( lockName );
}
setIsValid( isValidationValid );
} )
.catch( () => {
lockPostSaving( lockName );
setIsValid( false );
} );
}, [ lockName, validate, lockPostSaving, unlockPostSaving ] );
return isValid;
}

View File

@ -8,3 +8,4 @@
@import 'components/section/style.scss';
@import 'components/tab/style.scss';
@import 'components/tabs/style.scss';
@import 'components/details-summary-block/style.scss';

View File

@ -7,3 +7,4 @@ export const ADD_NEW_SHIPPING_CLASS_OPTION_VALUE =
export const UNCATEGORIZED_CATEGORY_SLUG = 'uncategorized';
export const PRODUCT_VARIATION_TITLE_LIMIT = 32;
export const STANDARD_RATE_TAX_CLASS_SLUG = 'standard';
export const AUTO_DRAFT_NAME = 'AUTO-DRAFT';

View File

@ -0,0 +1,35 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { AUTO_DRAFT_NAME } from './constants';
/**
* Get the header title using the product name.
*
* @param editedProductName Name value entered for the product.
* @param initialProductName Name already persisted to the database.
* @return The new title
*/
export const getHeaderTitle = (
editedProductName: string,
initialProductName: string
): string => {
const isProductNameNotEmpty = Boolean( editedProductName );
const isProductNameDirty = editedProductName !== initialProductName;
const isCreating = initialProductName === AUTO_DRAFT_NAME;
if ( isProductNameNotEmpty && isProductNameDirty ) {
return editedProductName;
}
if ( isCreating ) {
return __( 'Add new product', 'woocommerce' );
}
return initialProductName;
};

View File

@ -51,7 +51,9 @@ export const getProductStockStatus = (
}
if ( product.stock_status ) {
return PRODUCT_STOCK_STATUS_LABELS[ product.stock_status ];
return PRODUCT_STOCK_STATUS_LABELS[
product.stock_status as PRODUCT_STOCK_STATUS_KEYS
];
}
return PRODUCT_STOCK_STATUS_LABELS.instock;
@ -77,6 +79,8 @@ export const getProductStockStatusClass = (
return PRODUCT_STOCK_STATUS_CLASSES.outofstock;
}
return product.stock_status
? PRODUCT_STOCK_STATUS_CLASSES[ product.stock_status ]
? PRODUCT_STOCK_STATUS_CLASSES[
product.stock_status as PRODUCT_STOCK_STATUS_KEYS
]
: '';
};

View File

@ -3,7 +3,10 @@
*/
import { __ } from '@wordpress/i18n';
export const AUTO_DRAFT_NAME = 'AUTO-DRAFT';
/**
* Internal dependencies
*/
import { AUTO_DRAFT_NAME } from './constants';
/**
* Get the product title for use in the header.

View File

@ -1,16 +1,18 @@
/**
* Internal dependencies
*/
import { AUTO_DRAFT_NAME } from './constants';
import { formatCurrencyDisplayValue } from './format-currency-display-value';
import { getCheckboxTracks } from './get-checkbox-tracks';
import { getCurrencySymbolProps } from './get-currency-symbol-props';
import { getDerivedProductType } from './get-derived-product-type';
import { getHeaderTitle } from './get-header-title';
import { getProductStatus, PRODUCT_STATUS_LABELS } from './get-product-status';
import {
getProductStockStatus,
getProductStockStatusClass,
} from './get-product-stock-status';
import { getProductTitle, AUTO_DRAFT_NAME } from './get-product-title';
import { getProductTitle } from './get-product-title';
import {
getProductVariationTitle,
getTruncatedProductVariationTitle,
@ -27,6 +29,7 @@ export {
getCheckboxTracks,
getCurrencySymbolProps,
getDerivedProductType,
getHeaderTitle,
getProductStatus,
getProductStockStatus,
getProductStockStatusClass,

View File

@ -1,26 +1,31 @@
/**
* External dependencies
*/
import { BlockConfiguration, registerBlockType } from '@wordpress/blocks';
import {
Block,
BlockConfiguration,
registerBlockType,
} from '@wordpress/blocks';
interface BlockRepresentation {
name: string;
metadata: BlockConfiguration;
settings: Partial< BlockConfiguration >;
interface BlockRepresentation< T extends Record< string, object > > {
name?: string;
metadata: BlockConfiguration< T >;
settings: Partial< BlockConfiguration< T > >;
}
/**
* Function to register an individual block.
*
* @param {Object} block The block to be registered.
*
* @return {?WPBlockType} The block, if it has been successfully registered;
* otherwise `undefined`.
* @param block The block to be registered.
* @return The block, if it has been successfully registered; otherwise `undefined`.
*/
export const initBlock = ( block: BlockRepresentation ) => {
export function initBlock<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Record< string, any > = Record< string, any >
>( block: BlockRepresentation< T > ): Block< T > | undefined {
if ( ! block ) {
return;
}
const { metadata, settings, name } = block;
return registerBlockType( { name, ...metadata }, settings );
};
return registerBlockType< T >( { name, ...metadata }, settings );
}

View File

@ -6,4 +6,3 @@ declare global {
/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */
export {};

View File

@ -0,0 +1,18 @@
declare module '@woocommerce/settings' {
export declare function getAdminLink( path: string ): string;
export declare function getSetting< T >(
name: string,
fallback?: unknown,
filter = ( val: unknown, fb: unknown ) =>
typeof val !== 'undefined' ? val : fb
): T;
}
declare module '@wordpress/core-data' {
function useEntityProp< T = unknown >(
kind: string,
name: string,
prop: string,
id?: string
): [ T, ( value: T ) => void, T ];
}

View File

@ -12,6 +12,7 @@ import {
import { useSelect, useDispatch } from '@wordpress/data';
import { uniqueId, find } from 'lodash';
import { Icon, help as helpIcon, external } from '@wordpress/icons';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
import { H, Section } from '@woocommerce/components';
import {
ONBOARDING_STORE_NAME,
@ -48,7 +49,6 @@ import { useActiveSetupTasklist } from '~/tasks';
import { LayoutContext } from '~/layout';
import { getSegmentsFromPath } from '~/utils/url-helpers';
import { FeedbackIcon } from '~/products/images/feedback-icon';
import { STORE_KEY as CES_STORE_KEY } from '~/customer-effort-score-tracks/data/constants';
import { ProductFeedbackTour } from '~/guided-tours/add-product-feedback-tour';
const HelpPanel = lazy( () =>

View File

@ -6,6 +6,7 @@ import { compose } from '@wordpress/compose';
import PropTypes from 'prop-types';
import { omitBy, isUndefined, snakeCase } from 'lodash';
import { withSelect, withDispatch } from '@wordpress/data';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
import { ReportFilters as Filters } from '@woocommerce/components';
import { SETTINGS_STORE_NAME } from '@woocommerce/data';
import {
@ -19,7 +20,6 @@ import { CurrencyContext } from '@woocommerce/currency';
/**
* Internal dependencies
*/
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
import { LOCALE } from '~/utils/admin-settings';
class ReportFilters extends Component {

View File

@ -10,6 +10,7 @@ import { withDispatch, withSelect } from '@wordpress/data';
import { get, noop, partial, uniq } from 'lodash';
import { __, sprintf } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
import { CompareButton, Search, TableCard } from '@woocommerce/components';
import {
getIdsFromQuery,
@ -39,7 +40,6 @@ import { recordEvent } from '@woocommerce/tracks';
import DownloadIcon from './download-icon';
import ReportError from '../report-error';
import { extendTableData } from './utils';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
import './style.scss';
const TABLE_FILTER = 'woocommerce_admin_report_table';

View File

@ -4,12 +4,12 @@
import { __, _x } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { dispatch } from '@wordpress/data';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
/**
* Internal dependencies
*/
import { getCategoryLabels } from '../../../lib/async-requests';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const CATEGORY_REPORT_CHARTS_FILTER =
'woocommerce_admin_categories_report_charts';

View File

@ -4,12 +4,12 @@
import { __, _x } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { dispatch } from '@wordpress/data';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
/**
* Internal dependencies
*/
import { getCouponLabels } from '../../../lib/async-requests';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const COUPON_REPORT_CHARTS_FILTER = 'woocommerce_admin_coupons_report_charts';
const COUPON_REPORT_FILTERS_FILTER = 'woocommerce_admin_coupons_report_filters';

View File

@ -4,6 +4,7 @@
import { __, _x } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { dispatch } from '@wordpress/data';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
/**
* Internal dependencies
@ -12,7 +13,6 @@ import {
getProductLabels,
getVariationLabels,
} from '../../../lib/async-requests';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const PRODUCTS_REPORT_CHARTS_FILTER =
'woocommerce_admin_products_report_charts';

View File

@ -3,6 +3,7 @@
*/
import { __, _x } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
import { NAMESPACE } from '@woocommerce/data';
import { dispatch } from '@wordpress/data';
@ -11,7 +12,6 @@ import { dispatch } from '@wordpress/data';
*/
import { getRequestByIdString } from '../../../lib/async-requests';
import { getTaxCode } from './utils';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const TAXES_REPORT_CHARTS_FILTER = 'woocommerce_admin_taxes_report_charts';
const TAXES_REPORT_FILTERS_FILTER = 'woocommerce_admin_taxes_report_filters';

View File

@ -4,6 +4,7 @@
import { __, _x } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { dispatch } from '@wordpress/data';
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
/**
* Internal dependencies
@ -13,7 +14,6 @@ import {
getProductLabels,
getVariationLabels,
} from '../../../lib/async-requests';
import { STORE_KEY as CES_STORE_KEY } from '../../../customer-effort-score-tracks/data/constants';
const VARIATIONS_REPORT_CHARTS_FILTER =
'woocommerce_admin_variations_report_charts';

View File

@ -1,4 +0,0 @@
export const SHOWN_FOR_ACTIONS_OPTION_NAME =
'woocommerce_ces_shown_for_actions';
export const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
'woocommerce_admin_install_timestamp';

View File

@ -1,3 +0,0 @@
export { default as CustomerEffortScoreTracks } from './customer-effort-score-tracks';
export { default as CustomerEffortScoreTracksContainer } from './customer-effort-score-tracks-container';
export * from './customer-effort-score-modal-container.tsx';

Some files were not shown because too many files have changed in this diff Show More