Merge branch 'trunk' into update/ai-product-endpoint-last-product-param

This commit is contained in:
Patricia Hillebrandt 2023-12-07 07:27:14 -03:00
commit 00b7b78ee8
288 changed files with 14100 additions and 17673 deletions

View File

@ -26,7 +26,7 @@ jobs:
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
with:
install: false
install: true
- uses: actions/github-script@v6
id: 'project-matrix'
name: 'Build Matrix'
@ -46,6 +46,7 @@ jobs:
runs-on: 'ubuntu-20.04'
needs: 'project-matrix'
if: ${{ needs.project-matrix.outputs.matrix != '[]' }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:

View File

@ -42,9 +42,6 @@
"dependencies": [
"@types/react"
],
"dependencyTypes": [
"devDependencies"
],
"pinVersion": "^17.0.2",
"packages": [
"**"
@ -61,7 +58,9 @@
},
{
"dependencies": [
"@types/react",
"react",
"@types/react-dom",
"react-dom"
],
"packages": [
@ -82,9 +81,6 @@
"dependencies": [
"eslint"
],
"dependencyTypes": [
"devDependencies"
],
"packages": [
"**"
],
@ -183,9 +179,6 @@
"dependencies": [
"postcss-loader"
],
"dependencyTypes": [
"devDependencies"
],
"packages": [
"**"
],
@ -209,6 +202,28 @@
],
"pinVersion": "^16.18.18"
},
{
"dependencies": [
"jest",
"jest-cli",
"jest-environment-jsdom",
"jest-environment-node",
"babel-jest"
],
"packages": [
"**"
],
"pinVersion": "~27.5.1"
},
{
"dependencies": [
"ts-jest"
],
"packages": [
"**"
],
"pinVersion": "~29.1.1"
},
{
"label": "Only manage versions for these dependencies",
"dependencies": [

View File

@ -47,7 +47,7 @@
"css-loader": "^6.7.3",
"glob": "^7.2.3",
"husky": "^7.0.4",
"jest": "^27.5.1",
"jest": "~27.5.1",
"lint-staged": "^12.5.0",
"mkdirp": "^1.0.4",
"moment": "^2.29.4",
@ -74,7 +74,8 @@
"pnpm": {
"overrides": {
"@types/react": "^17.0.2",
"react": "^17.0.2"
"react": "^17.0.2",
"react-resize-aware": "3.1.1"
}
}
}

View File

@ -45,11 +45,11 @@
"@woocommerce/api": "^0.2.0",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"jest-mock-extended": "^1.0.18",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"publishConfig": {

View File

@ -49,14 +49,14 @@
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss-loader": "^4.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -34,21 +34,21 @@
"debug": "^4.3.3",
"dompurify": "^2.3.6",
"prop-types": "^15.8.1",
"react-router-dom": "^6.3.0"
"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",
"@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/debug": "^4.1.7",
"@types/dompurify": "^2.3.3",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.2",
"@types/testing-library__jest-dom": "^5.14.3",
"@types/wordpress__core-data": "^2.4.5",
"@types/wordpress__core-data": "2.4.5",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
@ -57,8 +57,8 @@
"copy-webpack-plugin": "^9.1.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"react": "^17.0.2",
@ -66,7 +66,7 @@
"react-hooks^8.0.1": "link:@testing-library/react-hooks^8.0.1",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -28,7 +28,7 @@
"dependencies": {
"allure-commandline": "^2.17.2",
"dotenv": "^10.0.0",
"jest": "^27.5.1",
"jest": "~27.5.1",
"jest-allure": "^0.1.3",
"jest-runner-groups": "^2.1.0",
"postman-collection": "^4.1.0",

View File

@ -57,8 +57,8 @@
"@woocommerce/eslint-plugin": "workspace:*",
"axios-mock-adapter": "^1.20.0",
"eslint": "^8.32.0",
"jest": "^27",
"ts-jest": "^27",
"jest": "~27.5.1",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"publishConfig": {

View File

@ -37,13 +37,13 @@
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/runtime": "^7.17.2",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react-hooks": "8.0.1",
"@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__blocks": "^11.0.7",
"@types/wordpress__block-editor": "7.0.0",
"@types/wordpress__blocks": "11.0.7",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
@ -51,15 +51,15 @@
"copy-webpack-plugin": "^9.1.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: Remove unused @wordpress/scripts dependency

View File

@ -33,11 +33,11 @@
"@automattic/calypso-color-schemes": "^2.1.1",
"@automattic/interpolate-components": "^1.2.0",
"@automattic/tour-kit": "^1.1.1",
"@types/wordpress__block-editor": "^7.0.0",
"@types/wordpress__block-library": "^2.6.1",
"@types/wordpress__blocks": "^11.0.7",
"@types/wordpress__block-editor": "7.0.0",
"@types/wordpress__block-library": "2.6.1",
"@types/wordpress__blocks": "11.0.7",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__rich-text": "^3.4.6",
"@types/wordpress__rich-text": "3.4.6",
"@woocommerce/csv-export": "workspace:*",
"@woocommerce/currency": "workspace:*",
"@woocommerce/data": "workspace:*",
@ -112,36 +112,36 @@
"@storybook/core-events": "^6.5.17-alpha.0",
"@storybook/react": "^6.5.17-alpha.0",
"@storybook/theming": "^6.5.17-alpha.0",
"@testing-library/dom": "^8.11.3",
"@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",
"@testing-library/dom": "8.11.3",
"@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/lodash": "^4.14.184",
"@types/prop-types": "^15.7.4",
"@types/testing-library__jest-dom": "^5.14.3",
"@types/uuid": "^8.3.0",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__media-utils": "^3.0.0",
"@types/wordpress__viewport": "^2.5.4",
"@types/wordpress__data": "6.0.0",
"@types/wordpress__media-utils": "3.0.0",
"@types/wordpress__viewport": "2.5.4",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
"@wordpress/babel-preset-default": "^6.5.1",
"@wordpress/browserslist-config": "wp-6.0",
"@wordpress/scripts": "^12.6.1",
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"react": "^17.0.2",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^29.1.1",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"uuid": "^8.3.0",
"webpack": "^5.70.0",
@ -170,13 +170,5 @@
"pnpm lint:fix",
"pnpm test-staged"
]
},
"pnpm": {
"overrides": {
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
}

View File

@ -143,8 +143,7 @@ export const DateTimePickerControl = forwardRef(
const formatDateTimeForDisplay = useCallback(
( dateTime: Moment ) => {
return dateTime.isValid()
? // @ts-expect-error TODO - fix this type error with moment
formatDate( displayFormat, dateTime.local() )
? formatDate( displayFormat, dateTime.local() )
: dateTime.creationData().input?.toString() || '';
},
[ displayFormat ]

View File

@ -103,7 +103,6 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' );
expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default24HourDateTimeFormat, dateTime )
);
} );
@ -123,7 +122,6 @@ describe( 'DateTimePickerControl', () => {
expect( input?.value ).toBe(
formatDate(
default24HourDateTimeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment.utc( ambiguousISODateTimeString ).local()
)
);
@ -144,7 +142,6 @@ describe( 'DateTimePickerControl', () => {
expect( input?.value ).toBe(
formatDate(
default24HourDateTimeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment.utc( unambiguousISODateTimeString ).local()
)
);
@ -162,7 +159,6 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' );
expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default12HourDateTimeFormat, dateTime )
);
} );
@ -179,7 +175,6 @@ describe( 'DateTimePickerControl', () => {
);
const input = container.querySelector( 'input' );
// @ts-expect-error TODO - fix this type error with moment
expect( input?.value ).toBe( formatDate( dateTimeFormat, dateTime ) );
} );
@ -203,7 +198,6 @@ describe( 'DateTimePickerControl', () => {
const input = container.querySelector( 'input' );
expect( input?.value ).toBe(
// @ts-expect-error TODO - fix this type error with moment
formatDate( default24HourDateTimeFormat, updatedDateTime )
);
} );

View File

@ -184,7 +184,7 @@ function SelectControl< ItemType = DefaultItemType >( {
highlightedIndex,
getItemProps,
selectItem,
// @ts-expect-error - TODO fix this type.
// @ts-expect-error We're allowed to use the property.
selectedItem: comboboxSingleSelectedItem,
openMenu,
closeMenu,
@ -208,7 +208,7 @@ function SelectControl< ItemType = DefaultItemType >( {
onInputChange( value, changes );
}
},
// @ts-expect-error - TODO fix this type.
// @ts-expect-error We're allowed to use the property.
stateReducer: ( state, actionAndChanges ) => {
const { changes, type } = actionAndChanges;
let newChanges;

View File

@ -21,7 +21,7 @@ export default () => {
>
<mask
id={ maskId }
mask-type="alpha"
style={ { maskType: 'alpha' } }
maskUnits="userSpaceOnUse"
x="2"
y="3"

View File

@ -54,11 +54,11 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -57,11 +57,11 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -41,12 +41,12 @@
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@testing-library/react": "^12.1.3",
"@testing-library/react": "12.1.3",
"@types/jest": "^27.4.1",
"@types/prop-types": "^15.7.4",
"@types/testing-library__jest-dom": "^5.14.3",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__data": "6.0.0",
"@woocommerce/data": "workspace:*",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
@ -57,13 +57,13 @@
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -50,27 +50,27 @@
"@automattic/data-stores": "^2.0.1",
"@babel/core": "^7.17.5",
"@babel/runtime": "^7.17.2",
"@testing-library/react": "^12.1.3",
"@testing-library/react-hooks": "^7.0.2",
"@testing-library/react": "12.1.3",
"@testing-library/react-hooks": "7.0.2",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.182",
"@types/md5": "^2.3.2",
"@types/qs": "^6.9.7",
"@types/react": "^17.0.2",
"@types/node": "^16.18.18",
"@types/wordpress__compose": "^4.0.1",
"@types/wordpress__core-data": "^2.4.5",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__data-controls": "^2.2.0",
"@types/wordpress__compose": "4.0.1",
"@types/wordpress__core-data": "2.4.5",
"@types/wordpress__data": "6.0.0",
"@types/wordpress__data-controls": "~2.2.0",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"redux": "^4.1.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"peerDependencies": {

View File

@ -43,11 +43,11 @@
"@woocommerce/internal-js-tests": "workspace:*",
"d3-time-format": "^2.3.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"peerDependencies": {

View File

@ -31,10 +31,10 @@
"@babel/core": "^7.17.5",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -34,7 +34,7 @@
"app-root-path": "^3.0.0",
"commander": "4.1.1",
"config": "3.3.7",
"jest": "^27.5.1",
"jest": "~27.5.1",
"jest-circus": "27.5.1",
"jest-each": "27.5.1",
"jest-puppeteer": "^5.0.4",

View File

@ -49,10 +49,10 @@
"devDependencies": {
"@babel/core": "^7.17.5",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -52,9 +52,9 @@
"@storybook/addon-actions": "^6.5.17-alpha.0",
"@storybook/addon-console": "^1.2.3",
"@storybook/react": "^6.5.17-alpha.0",
"@testing-library/dom": "^8.11.3",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"@testing-library/dom": "8.11.3",
"@testing-library/react": "12.1.3",
"@testing-library/user-event": "13.5.0",
"@types/dompurify": "^2.3.3",
"@types/jest": "^27.4.1",
"@types/react-transition-group": "^4.4.4",
@ -67,13 +67,13 @@
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -47,11 +47,11 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"scripts": {

View File

@ -54,10 +54,10 @@
"@woocommerce/internal-js-tests": "workspace:*",
"concurrently": "^7.0.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -42,8 +42,8 @@
"devDependencies": {
"@babel/core": "^7.21.3",
"@babel/runtime": "^7.17.2",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "^27.4.1",
"@types/node": "^16.18.18",
"@types/testing-library__jest-dom": "^5.14.3",
@ -53,10 +53,10 @@
"copy-webpack-plugin": "^9.1.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -34,6 +34,8 @@ module.exports = {
__dirname,
'build/mocks/style-mock.js'
),
// Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
"uuid": require.resolve('uuid'),
},
restoreMocks: true,
setupFiles: [

View File

@ -30,8 +30,8 @@
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3",
"@wordpress/data": "wp-6.0",
"@wordpress/i18n": "wp-6.0",
"@wordpress/jest-console": "^5.0.1",
@ -41,12 +41,12 @@
"@babel/core": "^7.17.5",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"resize-observer-polyfill": "1.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"babel-jest": "^27.5.1",
"ts-jest": "~29.1.1",
"babel-jest": "~27.5.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -41,10 +41,10 @@
"@babel/core": "^7.17.5",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0"
},

View File

@ -36,7 +36,7 @@
"@wordpress/url": "wp-6.0",
"history": "^5.3.0",
"qs": "^6.10.3",
"react-router-dom": "^6.3.0"
"react-router-dom": "~6.3.0"
},
"peerDependencies": {
"lodash": "^4.17.0"
@ -67,11 +67,11 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -168,7 +168,6 @@ export const addHistoryListener = ( listener ) => {
if ( ! window.wcNavigation.historyPatched ) {
( ( history ) => {
/* global CustomEvent */
const pushState = history.pushState;
const replaceState = history.replaceState;
history.pushState = function ( state ) {

View File

@ -53,16 +53,16 @@
"@automattic/data-stores": "^2.0.1",
"@babel/core": "^7.17.5",
"@types/lodash": "^4.14.182",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__notices": "^3.5.0",
"@types/wordpress__data": "6.0.0",
"@types/wordpress__notices": "3.5.0",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"redux": "^4.2.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -53,11 +53,11 @@
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -44,12 +44,12 @@
"devDependencies": {
"@babel/core": "^7.17.5",
"@storybook/addon-knobs": "^7.0.2",
"@testing-library/react": "^12.1.3",
"@testing-library/react": "12.1.3",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.2",
"@types/string-similarity": "4.0.0",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__data": "6.0.0",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
@ -57,13 +57,13 @@
"concurrently": "^7.0.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add max-width to tooltip #41797

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
[Product Block Editor]: introduce UI state

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
[Product Block Editor]: move Modal edittor out of the description block edit component

View File

@ -30,7 +30,7 @@
"dependencies": {
"@types/lodash": "^4.14.179",
"@types/prop-types": "^15.7.4",
"@types/wordpress__blocks": "^11.0.7",
"@types/wordpress__blocks": "11.0.7",
"@woocommerce/admin-layout": "workspace:*",
"@woocommerce/block-templates": "workspace:*",
"@woocommerce/components": "workspace:*",
@ -68,32 +68,32 @@
"lodash": "^4.17.21",
"moment": "^2.29.4",
"prop-types": "^15.8.1",
"react-router-dom": "^6.3.0"
"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",
"@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/dompurify": "^2.3.3",
"@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",
"@types/wordpress__block-editor": "7.0.0",
"@types/wordpress__block-library": "2.6.1",
"@types/wordpress__blocks": "11.0.7",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__core-data": "^2.4.5",
"@types/wordpress__data": "^6.0.2",
"@types/wordpress__date": "^3.3.2",
"@types/wordpress__editor": "^13.0.0",
"@types/wordpress__edit-post": "^7.5.4",
"@types/wordpress__keycodes": "^2.3.1",
"@types/wordpress__media-utils": "^3.0.0",
"@types/wordpress__plugins": "^3.0.0",
"@types/wordpress__rich-text": "^3.4.6",
"@types/wordpress__core-data": "2.4.5",
"@types/wordpress__data": "6.0.2",
"@types/wordpress__date": "3.3.2",
"@types/wordpress__editor": "13.0.0",
"@types/wordpress__edit-post": "7.5.4",
"@types/wordpress__keycodes": "2.3.1",
"@types/wordpress__media-utils": "3.0.0",
"@types/wordpress__plugins": "3.0.0",
"@types/wordpress__rich-text": "3.4.6",
"@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/internal-js-tests": "workspace:*",
"@woocommerce/internal-style-build": "workspace:*",
@ -103,15 +103,15 @@
"copy-webpack-plugin": "^9.1.0",
"css-loader": "^3.6.0",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"postcss": "^8.4.7",
"postcss-loader": "^4.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"rimraf": "^3.0.2",
"sass-loader": "^10.2.1",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"webpack": "^5.70.0",
"webpack-cli": "^3.3.12"

View File

@ -107,6 +107,8 @@ export function Edit( {
<ToggleControl
label={ label }
checked={ isChecked() }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
disabled={ disabled }
onChange={ handleChange }
help={ help }

View File

@ -2,13 +2,14 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createElement, useState } from '@wordpress/element';
import { createElement, useEffect } from '@wordpress/element';
import {
BlockAttributes,
BlockInstance,
parse,
serialize,
} from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';
import { Button } from '@wordpress/components';
import { useWooBlockProps } from '@woocommerce/block-templates';
import { recordEvent } from '@woocommerce/tracks';
@ -18,9 +19,9 @@ import { useEntityProp } from '@wordpress/core-data';
* Internal dependencies
*/
import { ContentPreview } from '../../../components/content-preview';
import { ModalEditor } from '../../../components/modal-editor';
import { ProductEditorBlockEditProps } from '../../../types';
import ModalEditorWelcomeGuide from '../../../components/modal-editor-welcome-guide';
import { store } from '../../../store/product-editor-ui';
/**
* Internal dependencies
@ -50,19 +51,50 @@ export function DescriptionBlockEdit( {
attributes,
}: ProductEditorBlockEditProps< BlockAttributes > ) {
const blockProps = useWooBlockProps( attributes );
const [ isModalOpen, setIsModalOpen ] = useState( false );
const [ description, setDescription ] = useEntityProp< string >(
'postType',
'product',
'description'
);
// Pick Modal editor data from the store.
const { isModalEditorOpen, modalEditorBlocks, hasChanged } = useSelect(
( select ) => {
return {
isModalEditorOpen: select( store ).isModalEditorOpen(),
modalEditorBlocks: select( store ).getModalEditorBlocks(),
hasChanged: select( store ).getModalEditorContentHasChanged(),
};
},
[]
);
const { openModalEditor, setModalEditorBlocks } = useDispatch( store );
// Update the description when the blocks change.
useEffect( () => {
if ( ! hasChanged ) {
return;
}
if ( ! modalEditorBlocks?.length ) {
setDescription( '' );
}
const html = serialize( clearDescriptionIfEmpty( modalEditorBlocks ) );
setDescription( html );
}, [ modalEditorBlocks, setDescription, hasChanged ] );
return (
<div { ...blockProps }>
<Button
variant="secondary"
onClick={ () => {
setIsModalOpen( true );
if ( description ) {
setModalEditorBlocks( parse( description ) );
}
openModalEditor();
recordEvent( 'product_add_description_click' );
} }
>
@ -70,23 +102,12 @@ export function DescriptionBlockEdit( {
? __( 'Edit description', 'woocommerce' )
: __( 'Add description', 'woocommerce' ) }
</Button>
{ isModalOpen && (
<ModalEditor
initialBlocks={ parse( description ) }
onChange={ ( blocks ) => {
const html = serialize(
clearDescriptionIfEmpty( blocks )
);
setDescription( html );
} }
onClose={ () => setIsModalOpen( false ) }
title={ __( 'Edit description', 'woocommerce' ) }
/>
) }
{ !! description.length && (
<ContentPreview content={ description } />
) }
{ isModalOpen && <ModalEditorWelcomeGuide /> }
{ isModalEditorOpen && <ModalEditorWelcomeGuide /> }
</div>
);
}

View File

@ -143,6 +143,8 @@ export function Edit( {
label={ __( 'Schedule sale', 'woocommerce' ) }
checked={ showScheduleSale }
onChange={ handleToggleChange }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
disabled={ ! isSalePriceGreaterThanZero }
/>

View File

@ -6,6 +6,7 @@ import { createElement, useMemo, useLayoutEffect } from '@wordpress/element';
import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data';
import { uploadMedia } from '@wordpress/media-utils';
import { PluginArea } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
@ -34,6 +35,8 @@ import {
import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes';
import { ProductEditorContext } from '../../types';
import { PostTypeContext } from '../../contexts/post-type-context';
import { ModalEditor } from '../modal-editor';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type BlockEditorSettings = Partial<
EditorSettings & EditorBlockListSettings
@ -111,10 +114,26 @@ export function BlockEditor( {
updateEditorSettings( settings ?? {} );
}, [ productType, productId ] );
// Check if the Modal editor is open from the store.
const isModalEditorOpen = useSelect( ( select ) => {
return select( productEditorUiStore ).isModalEditorOpen();
}, [] );
const { closeModalEditor } = useDispatch( productEditorUiStore );
if ( ! blocks ) {
return null;
}
if ( isModalEditorOpen ) {
return (
<ModalEditor
onClose={ closeModalEditor }
title={ __( 'Edit description', 'woocommerce' ) }
/>
);
}
return (
<div className="woocommerce-product-block-editor">
<BlockContextProvider value={ context }>
@ -123,6 +142,7 @@ export function BlockEditor( {
onInput={ onInput }
onChange={ onChange }
settings={ settings }
useSubRegistry={ false }
>
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore No types for this exist yet. */ }

View File

@ -37,3 +37,7 @@
white-space: normal !important;
}
}
.components-tooltip {
max-width: 300px;
}

View File

@ -31,31 +31,41 @@ import { HeaderToolbar } from './header-toolbar/header-toolbar';
import { ResizableEditor } from './resizable-editor';
import { SecondarySidebar } from './secondary-sidebar/secondary-sidebar';
import { useEditorHistory } from './hooks/use-editor-history';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type IframeEditorProps = {
closeModal?: () => void;
initialBlocks?: BlockInstance[];
onChange?: ( blocks: BlockInstance[] ) => void;
onClose?: () => void;
onInput?: ( blocks: BlockInstance[] ) => void;
settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined;
showBackButton?: boolean;
};
export function IframeEditor( {
closeModal = () => {},
initialBlocks = [],
onChange = () => {},
onClose,
onInput = () => {},
settings: __settings,
showBackButton = false,
}: IframeEditorProps ) {
const [ resizeObserver ] = useResizeObserver();
const [ blocks, setBlocks ] = useState< BlockInstance[] >( initialBlocks );
const [ temporalBlocks, setTemporalBlocks ] =
useState< BlockInstance[] >( initialBlocks );
// Pick the blocks from the store.
const blocks: BlockInstance[] = useSelect( ( select ) => {
return select( productEditorUiStore ).getModalEditorBlocks();
}, [] );
const { setModalEditorBlocks: setBlocks, setModalEditorContentHasChanged } =
useDispatch( productEditorUiStore );
const { appendEdit } = useEditorHistory( {
setBlocks,
} );
const {
appendEdit: tempAppendEdit,
hasRedo,
@ -127,15 +137,16 @@ export function IframeEditor( {
onSave={ () => {
appendEdit( temporalBlocks );
setBlocks( temporalBlocks );
setModalEditorContentHasChanged( true );
onChange( temporalBlocks );
closeModal();
onClose?.();
} }
onCancel={ () => {
appendEdit( blocks );
setBlocks( blocks );
onChange( blocks );
setTemporalBlocks( blocks );
closeModal();
onClose?.();
} }
/>
<div className="woocommerce-iframe-editor__main">
@ -157,7 +168,7 @@ export function IframeEditor( {
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
{ /* @ts-ignore */ }
<BlockEditorKeyboardShortcuts.Register />
{ onClose && (
{ showBackButton && onClose && (
<BackButton
onClick={ () => {
setTimeout( onClose, 550 );

View File

@ -3,6 +3,7 @@
*/
import { BlockInstance } from '@wordpress/blocks';
import { createElement } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import {
EditorSettings,
EditorBlockListSettings,
@ -14,11 +15,12 @@ import { useDebounce } from '@wordpress/compose';
* Internal dependencies
*/
import { IframeEditor } from '../iframe-editor';
import { store as productEditorUiStore } from '../../store/product-editor-ui';
type ModalEditorProps = {
initialBlocks?: BlockInstance[];
onChange: ( blocks: BlockInstance[] ) => void;
onClose: () => void;
onChange?: ( blocks: BlockInstance[] ) => void;
onClose?: () => void;
settings?: Partial< EditorSettings & EditorBlockListSettings > | undefined;
title: string;
};
@ -29,16 +31,19 @@ export function ModalEditor( {
onClose,
title,
}: ModalEditorProps ) {
const { closeModalEditor } = useDispatch( productEditorUiStore );
const debouncedOnChange = useDebounce( ( blocks: BlockInstance[] ) => {
onChange( blocks );
onChange?.( blocks );
}, 250 );
function handleClose() {
const blocks = debouncedOnChange.flush();
if ( blocks ) {
onChange( blocks );
onChange?.( blocks );
}
onClose();
closeModalEditor();
onClose?.();
}
return (
@ -52,7 +57,7 @@ export function ModalEditor( {
initialBlocks={ initialBlocks }
onInput={ debouncedOnChange }
onChange={ debouncedOnChange }
closeModal={ handleClose }
onClose={ handleClose }
/>
</Modal>
);

View File

@ -1,3 +1,8 @@
/**
* Internal dependencies
*/
import registerProductEditorUiStore from './store/product-editor-ui';
export * from './components';
export {
DETAILS_SECTION_ID,
@ -16,6 +21,11 @@ export * from './types';
*/
export * from './utils';
/*
* Store
*/
export * from './store/product-editor-ui';
/**
* Hooks
*/
@ -23,3 +33,6 @@ export * from './hooks';
export { PostTypeContext } from './contexts/post-type-context';
export { useValidation, useValidations } from './contexts/validation-context';
export * from './contexts/validation-context/types';
// Init the store
registerProductEditorUiStore();

View File

@ -0,0 +1,29 @@
# WooCommerce Product Editor UI Store
This module provides a @wordpress/data store for managing the UI state of a WooCommerce Product Editor.
## Structure
Defines action types for the UI state:
- `ACTION_MODAL_EDITOR_OPEN`
- `ACTION_MODAL_EDITOR_CLOSE`
### Actions
- `openModalEditor`
- `closeModalEditor`
### Selectors
Selector function:
- `isModalEditorOpen`
### Store
Registers the WooCommerce Product Editor UI store with the following:
- Store Name: `woo/product-editor-ui`
## Usage

View File

@ -0,0 +1,38 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import {
ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
} from './constants';
const modalEditorActions = {
openModalEditor: () => ( {
type: ACTION_MODAL_EDITOR_OPEN,
} ),
closeModalEditor: () => ( {
type: ACTION_MODAL_EDITOR_CLOSE,
} ),
setModalEditorBlocks: ( blocks: BlockInstance ) => ( {
type: ACTION_MODAL_EDITOR_SET_BLOCKS,
blocks,
} ),
setModalEditorContentHasChanged: ( hasChanged: boolean ) => ( {
type: ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
hasChanged,
} ),
};
export default {
...modalEditorActions,
};

View File

@ -0,0 +1,8 @@
/**
* Full editor actions
*/
export const ACTION_MODAL_EDITOR_OPEN = 'MODAL_EDITOR_OPEN';
export const ACTION_MODAL_EDITOR_CLOSE = 'MODAL_EDITOR_CLOSE';
export const ACTION_MODAL_EDITOR_SET_BLOCKS = 'MODAL_EDITOR_SET_BLOCKS';
export const ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED =
'MODAL_EDITOR_CONTENT_HAS_CHANGED';

View File

@ -0,0 +1,26 @@
/**
* External dependencies
*/
import { createReduxStore, register } from '@wordpress/data';
/**
* Internal dependencies
*/
import actions from './actions';
import selectors from './selectors';
import reducer from './reducer';
/**
* Types
*/
export const store = 'woo/product-editor-ui';
const wooProductEditorUiStore = createReduxStore( store, {
actions,
selectors,
reducer,
} );
export default function registerProductEditorUiStore() {
register( wooProductEditorUiStore );
}

View File

@ -0,0 +1,69 @@
/**
* Internal dependencies
*/
import {
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
} from './constants';
import type {
ProductEditorModalEditorAction,
ProductEditorUIStateProps,
} from './types';
/**
* Types & Constants
*/
const INITIAL_STATE: ProductEditorUIStateProps = {
modalEditor: {
isOpen: false,
blocks: [],
hasChanged: false,
},
};
export default function reducer(
state = INITIAL_STATE,
action: ProductEditorModalEditorAction
) {
switch ( action.type ) {
case ACTION_MODAL_EDITOR_OPEN:
return {
...state,
modalEditor: {
...state.modalEditor,
isOpen: true,
},
};
case ACTION_MODAL_EDITOR_CLOSE:
return {
...state,
modalEditor: {
...state.modalEditor,
isOpen: false,
},
};
case ACTION_MODAL_EDITOR_SET_BLOCKS:
return {
...state,
modalEditor: {
...state.modalEditor,
blocks: action.blocks || [],
},
};
case ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED:
return {
...state,
modalEditor: {
...state.modalEditor,
hasChanged: action?.hasChanged || false,
},
};
}
return state;
}

View File

@ -0,0 +1,29 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import type { ProductEditorUIStateProps } from './types';
export default {
isModalEditorOpen: function isModalEditorOpen(
state: ProductEditorUIStateProps
) {
return state.modalEditor.isOpen;
},
getModalEditorBlocks: function getModalEditorBlocks(
state: ProductEditorUIStateProps
): BlockInstance[] {
return state.modalEditor.blocks;
},
getModalEditorContentHasChanged: function getModalEditorContentHasChanged(
state: ProductEditorUIStateProps
): boolean {
return !! state.modalEditor.hasChanged;
},
};

View File

@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { BlockInstance } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import {
ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED,
ACTION_MODAL_EDITOR_CLOSE,
ACTION_MODAL_EDITOR_OPEN,
ACTION_MODAL_EDITOR_SET_BLOCKS,
} from './constants';
export type ProductEditorUIStateProps = {
modalEditor: {
isOpen: boolean;
blocks: BlockInstance[];
hasChanged?: boolean;
};
};
export type ProductEditorModalEditorAction = {
type:
| typeof ACTION_MODAL_EDITOR_OPEN
| typeof ACTION_MODAL_EDITOR_CLOSE
| typeof ACTION_MODAL_EDITOR_SET_BLOCKS
| typeof ACTION_MODAL_EDITOR_CONTENT_HAS_CHANGED;
blocks?: BlockInstance[];
hasChanged?: boolean;
};

View File

@ -47,11 +47,11 @@
"@types/debug": "^4.1.7",
"@woocommerce/eslint-plugin": "workspace:*",
"eslint": "^8.32.0",
"jest": "^27.5.1",
"jest-cli": "^27.5.1",
"jest": "~27.5.1",
"jest-cli": "~27.5.1",
"concurrently": "^7.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6"
},
"lint-staged": {

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Woo AI - Fix z-index issue with Background Removal Spotlight.

View File

@ -1,3 +1,3 @@
body.post-type-product .woocommerce-tour-kit {
z-index: 160001; /* value chosen to appear above media library modal */
z-index: auto;
}

View File

@ -247,6 +247,8 @@ function ScaledBlockPreview( {
>
<CustomIframeComponent
aria-hidden
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
scrolling={ isScrollable ? 'yes' : 'no' }
tabIndex={ -1 }
readonly={ ! isNavigable }

View File

@ -154,6 +154,13 @@ export const AssemblerHub: CustomizeStoreComponent = ( props ) => {
useEffect( () => {
onBackButtonClicked( () => setShowExitModal( true ) );
}, [] );
// @ts-expect-error temp fix
// Since we load the assember hub in an iframe, we don't have access to
// xstate's context values.
// This is the best workaround I can think of for now.
// Set the aiOnline value from the parent window so that any child components
// can access it.
props.context.aiOnline = window.parent?.window.cys_aiOnline;
return (
<>

View File

@ -2,9 +2,15 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { useContext, useState } from '@wordpress/element';
import { TourKit, TourKitTypes } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
import { Button, Modal } from '@wordpress/components';
/**
* Internal dependencies
*/
import { CustomizeStoreContext } from '..';
export * from './use-onboarding-tour';
type OnboardingTourProps = {
@ -23,7 +29,47 @@ export const OnboardingTour = ( {
const [ placement, setPlacement ] =
useState< TourKitTypes.WooConfig[ 'placement' ] >( 'left' );
const { context } = useContext( CustomizeStoreContext );
const aiOnline = context.aiOnline;
if ( showWelcomeTour ) {
const takeTour = () => {
// Click on "Take a tour" button
recordEvent( 'customize_your_store_assembler_hub_tour_start' );
setShowWelcomeTour( false );
};
const skipTour = () => {
recordEvent( 'customize_your_store_assembler_hub_tour_skip' );
onClose();
};
if ( ! aiOnline ) {
return (
<Modal
className="woocommerce-customize-store__onboarding-welcome-modal"
title={ __( 'Welcome to your store!', 'woocommerce' ) }
onRequestClose={ skipTour }
shouldCloseOnClickOutside={ false }
>
<p>
{ __(
"We encountered some issues while generating content with AI. But don't worry — you can still customize the look and feel of your store, including adding your logo, and changing colors and layouts. Take a quick tour to discover what's possible.",
'woocommerce'
) }
</p>
<div className="woocommerce-customize-store__design-change-warning-modal-footer">
<Button onClick={ skipTour } variant="link">
{ __( 'Skip', 'woocommerce' ) }
</Button>
<Button onClick={ takeTour } variant="primary">
{ __( 'Take a tour', 'woocommerce' ) }
</Button>
</div>
</Modal>
);
}
return (
<TourKit
config={ {
@ -87,15 +133,9 @@ export const OnboardingTour = ( {
closeHandler: ( _steps, _currentStepIndex, source ) => {
if ( source === 'done-btn' ) {
// Click on "Take a tour" button
recordEvent(
'customize_your_store_assembler_hub_tour_start'
);
setShowWelcomeTour( false );
takeTour();
} else {
recordEvent(
'customize_your_store_assembler_hub_tour_skip'
);
onClose();
skipTour();
}
},
} }

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { createContext } from '@wordpress/element';
import { render, screen } from '@testing-library/react';
import { recordEvent } from '@woocommerce/tracks';
@ -10,6 +11,13 @@ import { recordEvent } from '@woocommerce/tracks';
import { OnboardingTour } from '../index';
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
jest.mock( '../../', () => ( {
CustomizeStoreContext: createContext( {
context: {
aiOnline: true,
},
} ),
} ) );
describe( 'OnboardingTour', () => {
let props: {

View File

@ -11,7 +11,7 @@ export const SYSTEM_FONT_SLUG = 'system-font';
// Generated from /wpcom/v2/sites/{site_id}/global-styles-variation/font-pairings
// TODO: Consider creating an API endpoint for this data
export const FONT_PAIRINGS = [
export const FONT_PAIRINGS_WHEN_AI_IS_OFFLINE = [
{
title: 'Inter + Inter',
version: 2,
@ -69,45 +69,6 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Albert Sans + Lora',
version: 2,
lookAndFeel: [ 'Contemporary', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Albert Sans',
slug: 'albert-sans',
},
{
fontFamily: 'Lora',
slug: 'lora',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--albert-sans)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--lora)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.67',
},
},
},
{
title: 'Bodoni Moda + Overpass',
version: 2,
@ -169,6 +130,277 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Albert Sans + Lora',
version: 2,
lookAndFeel: [ 'Contemporary', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Albert Sans',
slug: 'albert-sans',
},
{
fontFamily: 'Lora',
slug: 'lora',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--albert-sans)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--lora)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.67',
},
},
},
{
title: 'Montserrat + Arvo',
version: 2,
lookAndFeel: [ 'Contemporary', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Montserrat',
slug: 'montserrat',
},
{
fontFamily: 'Arvo',
slug: 'arvo',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontStyle: 'normal',
fontWeight: '500',
},
},
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontStyle: 'normal',
fontWeight: '700',
lineHeight: '1.4',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontWeight: '700',
},
},
'core/post-navigation-link': {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--arvo)',
fontSize: 'var(--wp--preset--font-size--small)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Rubik + Inter',
version: 2,
lookAndFeel: [ 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Rubik',
slug: 'rubik',
},
{
fontFamily: 'Inter',
slug: 'inter',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily: 'var(--wp--preset--font-family--inter)',
fontWeight: '400',
lineHeight: '1',
},
},
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
fontStyle: 'normal',
fontWeight: '800',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
fontWeight: '800',
},
},
'core/post-navigation-link': {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--inter)',
fontSize: 'var(--wp--preset--font-size--medium)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Newsreader + Newsreader',
version: 2,
lookAndFeel: [ 'Classic' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Newsreader',
slug: 'newsreader',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--newsreader)',
fontStyle: 'normal',
fontWeight: '400',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--newsreader)',
fontSize: 'var(--wp--preset--font-size--medium)',
lineHeight: '1.67',
},
},
},
{
title: 'Cormorant + Work Sans',
version: 2,
lookAndFeel: [] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Cormorant',
slug: 'cormorant',
},
{
fontFamily: 'Work Sans',
slug: 'work-sans',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--cormorant)',
fontStyle: 'normal',
fontWeight: '500',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--work-sans)',
},
},
},
{
title: 'Raleway + Cormorant',
version: 2,
lookAndFeel: [ 'Classic', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Raleway',
slug: 'raleway',
},
{
fontFamily: 'Cormorant',
slug: 'cormorant',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--raleway)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--cormorant)',
fontSize: 'var(--wp--preset--font-size--medium)',
lineHeight: '1.67',
},
},
},
];
export const FONT_PAIRINGS = [
{
title: 'Commissioner + Crimson Pro',
version: 2,
@ -232,41 +464,6 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Cormorant + Work Sans',
version: 2,
lookAndFeel: [] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Cormorant',
slug: 'cormorant',
},
{
fontFamily: 'Work Sans',
slug: 'work-sans',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--cormorant)',
fontStyle: 'normal',
fontWeight: '500',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--work-sans)',
},
},
},
{
title: 'DM Sans + IBM Plex Mono',
version: 2,
@ -403,168 +600,6 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Libre Franklin + EB Garamond',
version: 2,
lookAndFeel: [ 'Classic' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Libre Franklin',
slug: 'libre-franklin',
},
{
fontFamily: 'EB Garamond',
slug: 'eb-garamond',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontSize: 'var(--wp--preset--font-size--small)',
fontWeight: '400',
lineHeight: '1',
},
},
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontWeight: '500',
},
},
'core/post-navigation-link': {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--eb-garamond)',
fontSize: 'var(--wp--preset--font-size--medium)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Montserrat + Arvo',
version: 2,
lookAndFeel: [ 'Contemporary', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Montserrat',
slug: 'montserrat',
},
{
fontFamily: 'Arvo',
slug: 'arvo',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontStyle: 'normal',
fontWeight: '500',
},
},
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontStyle: 'normal',
fontWeight: '700',
lineHeight: '1.4',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
fontWeight: '700',
},
},
'core/post-navigation-link': {
typography: {
fontFamily:
'var(--wp--preset--font-family--montserrat)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--arvo)',
fontSize: 'var(--wp--preset--font-size--small)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Newsreader + Newsreader',
version: 2,
lookAndFeel: [ 'Classic' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Newsreader',
slug: 'newsreader',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--newsreader)',
fontStyle: 'normal',
fontWeight: '400',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--newsreader)',
fontSize: 'var(--wp--preset--font-size--medium)',
lineHeight: '1.67',
},
},
},
{
title: 'Playfair Display + Fira Sans',
version: 2,
@ -630,6 +665,70 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Libre Franklin + EB Garamond',
version: 2,
lookAndFeel: [ 'Classic' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Libre Franklin',
slug: 'libre-franklin',
},
{
fontFamily: 'EB Garamond',
slug: 'eb-garamond',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontSize: 'var(--wp--preset--font-size--small)',
fontWeight: '400',
lineHeight: '1',
},
},
heading: {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
fontWeight: '500',
},
},
'core/post-navigation-link': {
typography: {
fontFamily:
'var(--wp--preset--font-family--libre-franklin)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--eb-garamond)',
fontSize: 'var(--wp--preset--font-size--medium)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Plus Jakarta Sans + Plus Jakarta Sans',
version: 2,
@ -663,102 +762,6 @@ export const FONT_PAIRINGS = [
},
},
},
{
title: 'Raleway + Cormorant',
version: 2,
lookAndFeel: [ 'Classic', 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Raleway',
slug: 'raleway',
},
{
fontFamily: 'Cormorant',
slug: 'cormorant',
},
],
},
},
},
styles: {
elements: {
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--raleway)',
fontStyle: 'normal',
fontWeight: '700',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--cormorant)',
fontSize: 'var(--wp--preset--font-size--medium)',
lineHeight: '1.67',
},
},
},
{
title: 'Rubik + Inter',
version: 2,
lookAndFeel: [ 'Bold' ] as Look[],
settings: {
typography: {
fontFamilies: {
theme: [
{
fontFamily: 'Rubik',
slug: 'rubik',
},
{
fontFamily: 'Inter',
slug: 'inter',
},
],
},
},
},
styles: {
elements: {
button: {
typography: {
fontFamily: 'var(--wp--preset--font-family--inter)',
fontWeight: '400',
lineHeight: '1',
},
},
heading: {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
fontStyle: 'normal',
fontWeight: '800',
},
},
},
blocks: {
'core/site-title': {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
fontWeight: '800',
},
},
'core/post-navigation-link': {
typography: {
fontFamily: 'var(--wp--preset--font-family--rubik)',
},
},
},
typography: {
fontFamily: 'var(--wp--preset--font-family--inter)',
fontSize: 'var(--wp--preset--font-size--medium)',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '1.6',
},
},
},
{
title: 'Rubik + Rubik',
version: 2,
@ -854,4 +857,5 @@ export const FONT_PAIRINGS = [
},
},
},
...FONT_PAIRINGS_WHEN_AI_IS_OFFLINE,
];

View File

@ -7,15 +7,16 @@
import { __experimentalGrid as Grid, Spinner } from '@wordpress/components';
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
import { useContext, useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import { FONT_PAIRINGS } from './constants';
import { FONT_PAIRINGS, FONT_PAIRINGS_WHEN_AI_IS_OFFLINE } from './constants';
import { VariationContainer } from '../variation-container';
import { FontPairingVariationPreview } from './preview';
import { Look } from '~/customize-store/design-with-ai/types';
import { CustomizeStoreContext } from '~/customize-store/assembler-hub';
export const FontPairing = () => {
const { aiSuggestions, isLoading } = useSelect( ( select ) => {
@ -31,14 +32,17 @@ export const FontPairing = () => {
};
} );
const { context } = useContext( CustomizeStoreContext );
const aiOnline = context.aiOnline;
const fontPairings = useMemo(
() =>
aiSuggestions?.lookAndFeel
aiOnline && aiSuggestions?.lookAndFeel
? FONT_PAIRINGS.filter( ( font ) =>
font.lookAndFeel.includes( aiSuggestions?.lookAndFeel )
)
: FONT_PAIRINGS,
[ aiSuggestions ]
: FONT_PAIRINGS_WHEN_AI_IS_OFFLINE,
[ aiOnline, aiSuggestions?.lookAndFeel ]
);
if ( isLoading ) {

View File

@ -4,7 +4,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import { createInterpolateElement, useContext } from '@wordpress/element';
import { Link } from '@woocommerce/components';
import { recordEvent } from '@woocommerce/tracks';
@ -14,55 +14,63 @@ import { recordEvent } from '@woocommerce/tracks';
import { SidebarNavigationScreen } from './sidebar-navigation-screen';
import { ADMIN_URL } from '~/utils/admin-settings';
import { FontPairing } from './global-styles';
import { CustomizeStoreContext } from '..';
export const SidebarNavigationScreenTypography = () => {
const { context } = useContext( CustomizeStoreContext );
const aiOnline = context.aiOnline;
const label = aiOnline
? __(
"AI has selected a font pairing that's the best fit for your business. If you'd like to change them, select a new option below now, or later in <EditorLink>Editor</EditorLink> | <StyleLink>Styles</StyleLink>.",
'woocommerce'
)
: __(
'Select the pair of fonts that best suits your brand. The larger font will be used for headings, and the smaller for supporting content. You can change your font at any time in <EditorLink>Editor</EditorLink> | <StyleLink>Styles</StyleLink>.',
'woocommerce'
);
return (
<SidebarNavigationScreen
title={ __( 'Change your font', 'woocommerce' ) }
description={ createInterpolateElement(
__(
"AI has selected a font pairing that's the best fit for your business. If you'd like to change them, select a new option below now, or later in <EditorLink>Editor</EditorLink> | <StyleLink>Styles</StyleLink>.",
'woocommerce'
description={ createInterpolateElement( label, {
EditorLink: (
<Link
onClick={ () => {
recordEvent(
'customize_your_store_assembler_hub_editor_link_click',
{
source: 'typography',
}
);
window.open(
`${ ADMIN_URL }site-editor.php`,
'_blank'
);
return false;
} }
href=""
/>
),
{
EditorLink: (
<Link
onClick={ () => {
recordEvent(
'customize_your_store_assembler_hub_editor_link_click',
{
source: 'typography',
}
);
window.open(
`${ ADMIN_URL }site-editor.php`,
'_blank'
);
return false;
} }
href=""
/>
),
StyleLink: (
<Link
onClick={ () => {
recordEvent(
'customize_your_store_assembler_hub_style_link_click',
{
source: 'typography',
}
);
window.open(
`${ ADMIN_URL }site-editor.php?path=%2Fwp_global_styles&canvas=edit`,
'_blank'
);
return false;
} }
href=""
/>
),
}
) }
StyleLink: (
<Link
onClick={ () => {
recordEvent(
'customize_your_store_assembler_hub_style_link_click',
{
source: 'typography',
}
);
window.open(
`${ ADMIN_URL }site-editor.php?path=%2Fwp_global_styles`,
'_blank'
);
return false;
} }
href=""
/>
),
} ) }
content={
<div className="woocommerce-customize-store_sidebar-typography-content">
<FontPairing />

View File

@ -664,6 +664,21 @@
box-shadow: none;
}
&.ai-offline {
.woocommerce-tour-kit-step {
width: 430px;
.components-card__header {
background-image: none;
height: auto;
margin: 0;
}
}
.tour-kit-frame__container {
bottom: 0;
}
}
.woocommerce-tour-kit-step {
width: 335px;
border-radius: 8px;

View File

@ -8,7 +8,10 @@ import { AnyInterpreter, Sender } from 'xstate';
/**
* Internal dependencies
*/
import { CustomizeStoreComponent } from '../types';
import {
CustomizeStoreComponent,
customizeStoreStateMachineContext,
} from '../types';
import { designWithAiStateMachineDefinition } from './state-machine';
import { findComponentMeta } from '~/utils/xstate/find-component';
import {
@ -33,10 +36,15 @@ export type DesignWithAiComponentMeta = {
export const DesignWithAiController = ( {
parentMachine,
parentContext,
}: {
parentMachine?: AnyInterpreter;
sendEventToParent?: Sender< customizeStoreStateMachineEvents >;
parentContext?: customizeStoreStateMachineContext;
} ) => {
// Assign aiOnline value from the parent context if it exists. Otherwise, ai is online by default.
designWithAiStateMachineDefinition.context.aiOnline =
parentContext?.aiOnline ?? true;
const [ state, send, service ] = useMachine(
designWithAiStateMachineDefinition,
{
@ -84,10 +92,16 @@ export const DesignWithAiController = ( {
};
//loader should send event 'THEME_SUGGESTED' when it's done
export const DesignWithAi: CustomizeStoreComponent = ( { parentMachine } ) => {
export const DesignWithAi: CustomizeStoreComponent = ( {
parentMachine,
context,
} ) => {
return (
<>
<DesignWithAiController parentMachine={ parentMachine } />
<DesignWithAiController
parentMachine={ parentMachine }
parentContext={ context }
/>
</>
);
};

View File

@ -99,7 +99,10 @@ const loaderSteps = [
];
// Make the loader last longer and provide a smoother progress by duplicating the steps.
const createAugmentedSteps = ( steps: typeof loaderSteps ) => {
const createAugmentedSteps = (
steps: typeof loaderSteps,
numOfDupes: number
) => {
// Duplicate each step, so we can animate each one
// (e.g. each step will be duplicated 3 times, and each duplicate will
// have different progress)
@ -112,8 +115,7 @@ const createAugmentedSteps = ( steps: typeof loaderSteps ) => {
if ( ! nextItem ) return [ item ];
// If there is a next item, we're not at the end of the array
// so return the current item, plus two duplicates
const numOfDupes = 2;
// so return the current item, plus duplicates
const duplicates = [ item ];
const progressIncreaseBy =
( nextItem.progress - item.progress ) / numOfDupes;
@ -149,7 +151,10 @@ export const ApiCallLoader = () => {
preload( openingTheDoors );
}, [] );
const augmentedSteps = createAugmentedSteps( loaderSteps.slice( 0, -1 ) );
const augmentedSteps = createAugmentedSteps(
loaderSteps.slice( 0, -1 ),
10
);
return (
<Loader>
@ -182,7 +187,7 @@ export const ApiCallLoader = () => {
export const AssembleHubLoader = () => {
// Show the last two steps of the loader so that the last frame is the shortest time possible
const augmentedSteps = createAugmentedSteps( loaderSteps.slice( -2 ) );
const augmentedSteps = createAugmentedSteps( loaderSteps.slice( -2 ), 10 );
const [ progress, setProgress ] = useState( augmentedSteps[ 0 ].progress );

View File

@ -205,7 +205,7 @@ export const updateStorePatterns = async (
try {
// TODO: Probably move this to a more appropriate place with a check. We should set this when the user granted permissions during the onboarding phase.
await dispatch( OPTIONS_STORE_NAME ).updateOptions( {
woocommerce_blocks_allow_ai_connection: true,
woocommerce_blocks_allow_ai_connection: 'yes',
} );
const { images } = await apiFetch< {
@ -440,7 +440,7 @@ export const assembleSite = async (
};
const installAndActivateTheme = async () => {
const themeSlug = 'twentytwentythree';
const themeSlug = 'twentytwentyfour';
try {
await apiFetch( {
@ -473,6 +473,23 @@ const saveAiResponseToOption = ( context: designWithAiStateMachineContext ) => {
} );
};
const resetPatternsAndProducts = () => async () => {
await dispatch( OPTIONS_STORE_NAME ).updateOptions( {
woocommerce_blocks_allow_ai_connection: 'yes',
} );
return Promise.all( [
apiFetch( {
path: '/wc/private/ai/patterns',
method: 'DELETE',
} ),
apiFetch( {
path: '/wc/private/ai/products',
method: 'DELETE',
} ),
] );
};
export const services = {
browserPopstateHandler,
queryAiEndpoint,
@ -480,4 +497,5 @@ export const services = {
updateStorePatterns,
saveAiResponseToOption,
installAndActivateTheme,
resetPatternsAndProducts,
};

View File

@ -38,6 +38,10 @@ export const hasStepInUrl = (
);
};
export const isAiOnline = ( _ctx: designWithAiStateMachineContext ) => {
return _ctx.aiOnline;
};
export const designWithAiStateMachineDefinition = createMachine(
{
id: 'designWithAi',
@ -84,6 +88,7 @@ export const designWithAiStateMachineDefinition = createMachine(
apiCallLoader: {
hasErrors: false,
},
aiOnline: true,
},
initial: 'navigate',
states: {
@ -272,8 +277,19 @@ export const designWithAiStateMachineDefinition = createMachine(
type: 'parallel',
states: {
chooseColorPairing: {
initial: 'pending',
initial: 'executeOrSkip',
states: {
executeOrSkip: {
always: [
{
target: 'pending',
cond: 'isAiOnline',
},
{
target: 'success',
},
],
},
pending: {
invoke: {
src: 'queryAiEndpoint',
@ -307,8 +323,19 @@ export const designWithAiStateMachineDefinition = createMachine(
},
},
chooseFontPairing: {
initial: 'pending',
initial: 'executeOrSkip',
states: {
executeOrSkip: {
always: [
{
target: 'pending',
cond: 'isAiOnline',
},
{
target: 'success',
},
],
},
pending: {
entry: [ 'assignFontPairing' ],
always: {
@ -319,8 +346,19 @@ export const designWithAiStateMachineDefinition = createMachine(
},
},
updateStorePatterns: {
initial: 'pending',
initial: 'executeOrSkip',
states: {
executeOrSkip: {
always: [
{
target: 'pending',
cond: 'isAiOnline',
},
{
target: 'resetPatternsAndProducts',
},
],
},
pending: {
invoke: {
src: 'updateStorePatterns',
@ -335,6 +373,20 @@ export const designWithAiStateMachineDefinition = createMachine(
},
},
},
resetPatternsAndProducts: {
invoke: {
src: 'resetPatternsAndProducts',
onDone: {
target: 'success',
},
onError: {
actions: [
'assignAPICallLoaderError',
],
target: '#toneOfVoice',
},
},
},
success: { type: 'final' },
},
},
@ -429,6 +481,7 @@ export const designWithAiStateMachineDefinition = createMachine(
services,
guards: {
hasStepInUrl,
isAiOnline,
},
}
);

View File

@ -12,6 +12,6 @@
.progress-bar.smooth-transition {
.woocommerce-onboarding-progress-bar__filler {
transition: width linear 1s;
transition: width linear 2s;
}
}

View File

@ -38,6 +38,6 @@
.progress-bar.smooth-transition {
.woocommerce-onboarding-progress-bar__filler {
transition: width linear 1s;
transition: width linear 2s;
}
}

View File

@ -39,6 +39,7 @@ export type designWithAiStateMachineContext = {
// If we require more data from options, previously provided core profiler details,
// we can retrieve them in preBusinessInfoDescription and then assign them here
spawnSaveDescriptionToOptionRef?: ReturnType< typeof spawn >;
aiOnline: boolean;
};
export type designWithAiStateMachineEvents =
| { type: 'AI_WIZARD_CLOSED_BEFORE_COMPLETION'; payload: { step: string } }

View File

@ -160,6 +160,7 @@ export const customizeStoreStateMachineDefinition = createMachine( {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} as customizeStoreStateMachineContext,
invoke: {
src: 'browserPopstateHandler',
@ -214,22 +215,54 @@ export const customizeStoreStateMachineDefinition = createMachine( {
initial: 'preIntro',
states: {
preIntro: {
invoke: {
src: 'fetchIntroData',
onError: {
actions: 'assignFetchIntroDataError',
target: 'intro',
type: 'parallel',
states: {
checkAiStatus: {
initial: 'pending',
states: {
pending: {
invoke: {
src: 'fetchAiStatus',
onDone: {
actions: 'assignAiStatus',
target: 'success',
},
onError: {
actions: 'assignAiOffline',
target: 'success',
},
},
},
success: { type: 'final' },
},
},
onDone: {
target: 'intro',
actions: [
'assignThemeData',
'assignActiveThemeHasMods',
'assignCustomizeStoreCompleted',
'assignCurrentThemeIsAiGenerated',
],
fetchIntroData: {
initial: 'pending',
states: {
pending: {
invoke: {
src: 'fetchIntroData',
onError: {
actions:
'assignFetchIntroDataError',
target: 'success',
},
onDone: {
target: 'success',
actions: [
'assignThemeData',
'assignActiveThemeHasMods',
'assignCustomizeStoreCompleted',
'assignCurrentThemeIsAiGenerated',
],
},
},
},
success: { type: 'final' },
},
},
},
onDone: 'intro',
},
intro: {
meta: {
@ -285,8 +318,21 @@ export const customizeStoreStateMachineDefinition = createMachine( {
},
},
assemblerHub: {
initial: 'assemblerHub',
initial: 'checkAiStatus',
states: {
checkAiStatus: {
invoke: {
src: 'fetchAiStatus',
onDone: {
actions: 'assignAiStatus',
target: 'assemblerHub',
},
onError: {
actions: 'assignAiOffline',
target: 'assemblerHub',
},
},
},
assemblerHub: {
entry: [
{ type: 'updateQueryStep', step: 'assembler-hub' },
@ -388,6 +434,12 @@ export const CustomizeStoreController = ( {
( cond as { step: string | undefined } ).step
);
},
isAiOnline: ( _ctx ) => {
return _ctx.aiOnline;
},
isAiOffline: ( _ctx ) => {
return ! _ctx.aiOnline;
},
},
} );
}, [ actionOverrides, servicesOverrides ] );

View File

@ -9,6 +9,7 @@ import { recordEvent } from '@woocommerce/tracks';
*/
import { customizeStoreStateMachineEvents } from '..';
import {
aiStatusResponse,
customizeStoreStateMachineContext,
RecommendThemesAPIResponse,
} from '../types';
@ -103,3 +104,37 @@ export const assignCurrentThemeIsAiGenerated = assign<
return { ...context.intro, currentThemeIsAiGenerated };
},
} );
export const assignAiStatus = assign<
customizeStoreStateMachineContext,
customizeStoreStateMachineEvents // this is actually the wrong type for the event but I still don't know how to type this properly
>( {
aiOnline: ( _context, _event ) => {
const indicator = ( _event as DoneInvokeEvent< aiStatusResponse > ).data
.status.indicator;
const status = indicator !== 'critical' && indicator !== 'major';
// @ts-expect-error temp workaround;
window.cys_aiOnline = status;
recordEvent( 'customize_your_store_ai_status', {
online: status ? 'yes' : 'no',
} );
return status;
},
} );
export const assignAiOffline = assign<
customizeStoreStateMachineContext,
customizeStoreStateMachineEvents // this is actually the wrong type for the event but I still don't know how to type this properly
>( {
aiOnline: () => {
// @ts-expect-error temp workaround;
window.cys_aiOnline = false;
recordEvent( 'customize_your_store_ai_status', {
online: 'no',
} );
return false;
},
} );

View File

@ -307,38 +307,3 @@
padding: 0.5rem 0.75rem;
}
}
.woocommerce-customize-store__design-change-warning-modal {
width: 480px;
max-width: 480px;
.components-modal__header {
padding-top: 32px;
}
p {
padding: 16px 0 16px 0;
margin: 0;
}
a,
button {
text-decoration: none !important;
}
.components-button {
padding: 8px 16px;
}
h1 {
line-height: 28px;
font-size: 20px;
color: #1e1e1e;
}
.woocommerce-customize-store__design-change-warning-modal-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
}
}

View File

@ -8,6 +8,19 @@ import { resolveSelect } from '@wordpress/data';
import { ONBOARDING_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { aiStatusResponse } from '../types';
export const fetchAiStatus = () => async (): Promise< aiStatusResponse > => {
const response = await fetch(
'https://status.openai.com/api/v2/status.json'
);
const data = await response.json();
return data;
};
export const fetchThemeCards = async () => {
const themes = await apiFetch( {
path: '/wc-admin/onboarding/themes/recommended',

View File

@ -44,6 +44,7 @@ describe( 'Intro Banners', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }
@ -81,6 +82,7 @@ describe( 'Intro Banners', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }
@ -124,6 +126,7 @@ describe( 'Intro Banners', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }

View File

@ -42,6 +42,7 @@ describe( 'Intro Modals', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }
@ -96,6 +97,7 @@ describe( 'Intro Modals', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }
@ -148,6 +150,7 @@ describe( 'Intro Modals', () => {
transitionalScreen: {
hasCompleteSurvey: false,
},
aiOnline: true,
} }
currentState={ 'intro' }
parentMachine={ null as unknown as AnyInterpreter }

View File

@ -180,3 +180,39 @@ body.woocommerce-customize-store.js.is-fullscreen-mode {
transition: opacity 1.2s linear;
opacity: 0;
}
.woocommerce-customize-store__onboarding-welcome-modal,
.woocommerce-customize-store__design-change-warning-modal {
width: 480px;
max-width: 480px;
.components-modal__header {
padding-top: 32px;
}
p {
padding: 16px 0 16px 0;
margin: 0;
}
a,
button {
text-decoration: none !important;
}
.components-button {
padding: 8px 16px;
}
h1 {
line-height: 28px;
font-size: 20px;
color: #1e1e1e;
}
.woocommerce-customize-store__design-change-warning-modal-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
}
}

View File

@ -29,6 +29,12 @@ export type RecommendThemesAPIResponse = {
};
};
export type aiStatusResponse = {
status: {
indicator: 'major' | 'critical' | 'ok';
};
};
export type customizeStoreStateMachineContext = {
themeConfiguration: Record< string, unknown >; // placeholder for theme configuration until we know what it looks like
intro: {
@ -42,4 +48,5 @@ export type customizeStoreStateMachineContext = {
transitionalScreen: {
hasCompleteSurvey: boolean;
};
aiOnline: boolean;
};

View File

@ -8,7 +8,7 @@ export default () => (
>
<mask
id="mask0"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="1"
y="1"

View File

@ -76,7 +76,7 @@
}
.is-alert-update {
$alert-color: var(--wp-admin-theme-color);
$alert-color: var(--wp-admin-theme-color, #11a0d2);
&::before {
background-color: $alert-color;

View File

@ -10,6 +10,13 @@ import { recordPageView } from '@woocommerce/tracks';
import { updateLinkHref } from '../controller';
import { EmbedLayout } from '../index';
jest.mock( '@woocommerce/admin-layout', () => {
return {
LayoutContextProvider: () => null,
getLayoutContextValue: () => null,
};
} );
describe( 'updateLinkHref', () => {
const timeExcludedScreens = [ 'stock', 'settings', 'customers' ];

View File

@ -21,9 +21,10 @@ const initialState = {
error: undefined,
},
campaigns: {
perPage: undefined,
pages: undefined,
total: undefined,
pages: {},
meta: {
total: undefined,
},
},
campaignTypes: {
data: undefined,
@ -66,13 +67,15 @@ export const reducer: Reducer< State, Action > = (
};
case TYPES.RECEIVE_CAMPAIGNS:
const { meta } = action;
const key = `${ meta.page }-${ meta.perPage }`;
return {
...state,
campaigns: {
perPage: action.meta.perPage,
pages: {
...state.campaigns.pages,
[ action.meta.page ]: action.error
[ key ]: action.error
? {
error: action.payload,
}
@ -80,7 +83,9 @@ export const reducer: Reducer< State, Action > = (
data: action.payload,
},
},
total: action.meta.total,
meta: {
total: meta.total,
},
},
};

View File

@ -13,9 +13,17 @@ export const getRecommendedChannels = ( state: State ) => {
/**
* Get campaigns from state.
*
* @param state State passed in from the data store.
* @param page Page number. First page is `1`.
* @param perPage Page size, i.e. number of records in one page.
*/
export const getCampaigns = ( state: State ) => {
return state.campaigns;
export const getCampaigns = ( state: State, page: number, perPage: number ) => {
const key = `${ page }-${ perPage }`;
return {
campaignsPage: state.campaigns.pages[ key ] || null,
meta: state.campaigns.meta,
};
};
export const getCampaignTypes = ( state: State ) => {

View File

@ -66,12 +66,20 @@ export type CampaignsPage = {
error?: ApiFetchError;
};
export type CampaignsState = {
perPage?: number;
pages?: Record< number, CampaignsPage >;
export type CampaignsMeta = {
total?: number;
};
export type CampaignsState = {
pages: Record< string, CampaignsPage >;
meta: CampaignsMeta;
};
export type CampaignsPagination = {
campaignsPage: CampaignsPage | null;
meta: CampaignsMeta;
};
export type CampaignType = {
id: string;
name: string;

View File

@ -0,0 +1,230 @@
/**
* External dependencies
*/
import { renderHook } from '@testing-library/react-hooks/dom';
import { waitFor } from '@testing-library/react';
import { dispatch } from '@wordpress/data';
import 'whatwg-fetch'; /* eslint-disable-line import/no-unresolved */ /* To make sure Response is available */
/**
* Internal dependencies
*/
import { useCampaigns } from '../useCampaigns';
import { useRegisteredChannels } from '../useRegisteredChannels';
import { STORE_KEY } from '../../data-multichannel/constants';
import { RegisteredChannel } from '../../types';
import { Campaign as APICampaign } from '../../data-multichannel/types';
import '../../data-multichannel'; // To ensure the store is registered
type Channel = Pick< RegisteredChannel, 'slug' | 'title' | 'icon' >;
jest.mock( '@wordpress/api-fetch', () =>
jest.fn( ( { path } ) => {
const total = 9;
const params = new URLSearchParams( path.replace( /^[^?]*/, '' ) );
const page = Number( params.get( 'page' ) );
const perPage = Number( params.get( 'per_page' ) );
if ( ! Number.isInteger( page ) || ! Number.isInteger( perPage ) ) {
return Promise.reject(
new Response( '{"message": "invalid query"}', { status: 400 } )
);
}
const length = Math.min( perPage, total - ( page - 1 ) * perPage );
const campaigns: Array< APICampaign > = Array.from( { length } ).map(
( _, index ) => {
const id = `${ page }_${ index + 1 }`;
return {
id,
channel: 'extension-foo',
title: `Campaign ${ id }`,
manage_url: `https://test/extension-foo?path=setup&id=${ id }`,
cost: {
value: ( ( page * perPage + index ) * 0.25 ).toString(),
currency: 'USD',
},
};
}
);
// For testing fallbacks when data fields are not available
if ( campaigns[ 2 ] ) {
campaigns[ 2 ].channel = 'intentional-mismatch-channel';
campaigns[ 2 ].cost = null;
}
return Promise.resolve(
new Response( JSON.stringify( campaigns ), {
headers: new Headers( { 'x-wp-total': total.toString() } ),
} )
);
} )
);
jest.mock( '../useRegisteredChannels', () => ( {
useRegisteredChannels: jest.fn(),
} ) );
function mockRegisteredChannels( ...channels: Array< Channel > ) {
(
useRegisteredChannels as jest.MockedFunction<
typeof useRegisteredChannels
>
).mockReturnValue( {
loading: false,
data: channels.map( ( channel ) => ( {
...channel,
// The following is not relevant to this test scope
description: '',
isSetupCompleted: true,
setupUrl: '',
manageUrl: '',
syncStatus: 'synced',
issueType: 'none',
issueText: '',
} ) ),
refetch: () => {},
} );
}
describe( 'useCampaigns', () => {
beforeEach( () => {
dispatch( STORE_KEY ).invalidateResolutionForStoreSelector(
'getCampaigns'
);
mockRegisteredChannels( {
slug: 'extension-foo',
title: 'Extension Foo',
icon: 'https://test/foo.png',
} );
} );
it( 'should return correct data', async () => {
const { result } = renderHook( () => useCampaigns() );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.error ).toBeUndefined();
// Campaign matched with a channel
expect( result.current.data?.[ 0 ] ).toEqual( {
id: 'extension-foo|1_1',
title: 'Campaign 1_1',
description: '',
cost: 'USD 1.25',
manageUrl: 'https://test/extension-foo?path=setup&id=1_1',
icon: 'https://test/foo.png',
channelName: 'Extension Foo',
channelSlug: 'extension-foo',
} );
// Campaign didn't match any channel
expect( result.current.data?.[ 2 ] ).toEqual( {
id: 'intentional-mismatch-channel|1_3',
title: 'Campaign 1_3',
description: '',
cost: '',
manageUrl: 'https://test/extension-foo?path=setup&id=1_3',
icon: '',
channelName: '',
channelSlug: 'intentional-mismatch-channel',
} );
} );
it( 'should handle error', async () => {
const { result } = renderHook( () => useCampaigns( 1.5 ) );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.data ).toBeUndefined();
expect( result.current.error ).toEqual( { message: 'invalid query' } );
} );
it( 'should handle pagination according to the page and perPage arguments', async () => {
// Initial page
const { result, rerender } = renderHook<
{ page: number; perPage: number },
ReturnType< typeof useCampaigns >
>( ( { page, perPage } ) => useCampaigns( page, perPage ), {
initialProps: { page: 1, perPage: 5 },
} );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.meta ).toEqual( { total: 9 } );
expect( result.current.data ).toHaveLength( 5 );
expect( result.current.data?.[ 0 ].id ).toEqual( 'extension-foo|1_1' );
expect( result.current.data?.[ 4 ].id ).toEqual( 'extension-foo|1_5' );
// Change page
rerender( { page: 2, perPage: 5 } );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.data ).toHaveLength( 4 );
expect( result.current.data?.[ 0 ].id ).toEqual( 'extension-foo|2_1' );
expect( result.current.data?.[ 1 ].id ).toEqual( 'extension-foo|2_2' );
// Change page to a page that doesn't exist
rerender( { page: 3, perPage: 5 } );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.data ).toEqual( [] );
// Change perPage
rerender( { page: 3, perPage: 4 } );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.meta ).toEqual( { total: 9 } );
expect( result.current.data ).toHaveLength( 1 );
expect( result.current.data?.[ 0 ].id ).toEqual( 'extension-foo|3_1' );
} );
it( 'should update data accordingly once the registered channels are updated', async () => {
const { result, rerender } = renderHook( () => useCampaigns() );
await waitFor( () => expect( result.current.loading ).toBe( false ) );
expect( result.current.data?.[ 0 ] ).toMatchObject( {
id: 'extension-foo|1_1',
channelName: 'Extension Foo',
icon: 'https://test/foo.png',
} );
// Update registered channels
mockRegisteredChannels( {
slug: 'extension-foo',
title: 'Extension Bar',
icon: 'https://test/bar.png',
} );
rerender();
expect( result.current.data?.[ 0 ] ).toMatchObject( {
id: 'extension-foo|1_1',
channelName: 'Extension Bar',
icon: 'https://test/bar.png',
} );
} );
it( 'should be able to use different arguments for different instances at the same time', async () => {
const { result: resultA } = renderHook( () => useCampaigns( 1, 2 ) );
const { result: resultB } = renderHook( () => useCampaigns( 2, 2 ) );
const { result: resultC } = renderHook( () => useCampaigns( 2, 4 ) );
await waitFor( () => expect( resultA.current.loading ).toBe( false ) );
await waitFor( () => expect( resultB.current.loading ).toBe( false ) );
await waitFor( () => expect( resultC.current.loading ).toBe( false ) );
expect( resultA.current.data ).toHaveLength( 2 );
expect( resultA.current.data?.[ 0 ].id ).toEqual( 'extension-foo|1_1' );
expect( resultA.current.data?.[ 1 ].id ).toEqual( 'extension-foo|1_2' );
expect( resultB.current.data ).toHaveLength( 2 );
expect( resultB.current.data?.[ 0 ].id ).toEqual( 'extension-foo|2_1' );
expect( resultB.current.data?.[ 1 ].id ).toEqual( 'extension-foo|2_2' );
expect( resultC.current.data ).toHaveLength( 4 );
expect( resultC.current.data?.[ 0 ].id ).toEqual( 'extension-foo|2_1' );
expect( resultC.current.data?.[ 3 ].id ).toEqual( 'extension-foo|2_4' );
} );
} );

View File

@ -9,7 +9,7 @@ import { useSelect } from '@wordpress/data';
import { Campaign } from '~/marketing/types';
import { STORE_KEY } from '~/marketing/data-multichannel/constants';
import {
CampaignsState,
CampaignsPagination,
Campaign as APICampaign,
ApiFetchError,
} from '~/marketing/data-multichannel/types';
@ -36,7 +36,7 @@ export const useCampaigns = ( page = 1, perPage = 5 ): UseCampaignsType => {
return useSelect(
( select ) => {
const { hasFinishedResolution, getCampaigns } = select( STORE_KEY );
const campaignsState = getCampaigns< CampaignsState >(
const { campaignsPage, meta } = getCampaigns< CampaignsPagination >(
page,
perPage
);
@ -62,25 +62,16 @@ export const useCampaigns = ( page = 1, perPage = 5 ): UseCampaignsType => {
};
};
const error =
campaignsState.pages && campaignsState.pages[ page ]?.error;
const data =
campaignsState.pages &&
campaignsState.pages[ page ]?.data?.map( convert );
return {
loading: ! hasFinishedResolution( 'getCampaigns', [
page,
perPage,
] ),
data,
error,
meta: {
total: campaignsState.total,
},
data: campaignsPage?.data?.map( convert ),
error: campaignsPage?.error,
meta,
};
},
[ page, perPage ]
[ page, perPage, channels ]
);
};

View File

@ -140,7 +140,6 @@ export const PricingSaleField: React.FC< PricingListFieldProps > = ( {
<span>
{ formatDate(
timeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment().startOf( 'day' )
) }
</span>
@ -149,7 +148,6 @@ export const PricingSaleField: React.FC< PricingListFieldProps > = ( {
<span>
{ formatDate(
timeFormat,
// @ts-expect-error TODO - fix this type error with moment
moment().endOf( 'day' )
) }
</span>

View File

@ -33,7 +33,7 @@ const CustomizeStoreHeader = ( {
<h1>{ __( 'Start customizing your store', 'woocommerce' ) }</h1>
<p>
{ __(
'Use our built-in AI tools to effortlessly design your store and generate content, or choose a pre-built theme and customize it to fit your brand.',
'Quickly create a beautiful looking store using our built-in store designer, or select a pre-built theme and customize it to fit your brand.',
'woocommerce'
) }
</p>

View File

@ -8,7 +8,6 @@ export const RegionPicker = ( { options, initialValues } ) => {
const [ selected, setSelected ] = useState( initialValues );
const onChange = ( value ) => {
document.body.dispatchEvent(
/* global CustomEvent */
new CustomEvent( 'wc_region_picker_update', { detail: value } )
);
setSelected( value );

View File

@ -55,7 +55,7 @@
"@automattic/interpolate-components": "^1.2.0",
"@automattic/components": "^2.0.1",
"@react-spring/web": "^9.4.3",
"@types/wordpress__blocks": "^11.0.7",
"@types/wordpress__blocks": "11.0.7",
"@woocommerce/api": "^0.2.0",
"@wordpress/a11y": "wp-6.0",
"@wordpress/api-fetch": "wp-6.0",
@ -100,7 +100,7 @@
"qs": "^6.10.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^6.3.0",
"react-router-dom": "~6.3.0",
"react-transition-group": "^4.4.2",
"react-visibility-sensor": "^5.1.1",
"redux": "^4.1.2",
@ -121,11 +121,11 @@
"@babel/runtime": "^7.17.2",
"@octokit/core": "^3.5.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@testing-library/dom": "^8.11.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/react-hooks": "^7.0.2",
"@testing-library/user-event": "^13.5.0",
"@testing-library/dom": "8.11.3",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3",
"@testing-library/react-hooks": "7.0.2",
"@testing-library/user-event": "13.5.0",
"@types/cookie": "^0.4.1",
"@types/dompurify": "^2.3.3",
"@types/expect-puppeteer": "^4.4.7",
@ -139,12 +139,12 @@
"@types/testing-library__jest-dom": "^5.14.3",
"@types/tinymce": "^4.6.5",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__compose": "^4.0.1",
"@types/wordpress__data": "^6.0.0",
"@types/wordpress__data-controls": "^2.2.0",
"@types/wordpress__media-utils": "^3.0.0",
"@types/wordpress__notices": "^3.3.0",
"@types/wordpress__plugins": "^3.0.0",
"@types/wordpress__compose": "4.0.1",
"@types/wordpress__data": "6.0.0",
"@types/wordpress__data-controls": "~2.2.0",
"@types/wordpress__media-utils": "3.0.0",
"@types/wordpress__notices": "3.3.0",
"@types/wordpress__plugins": "3.0.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@woocommerce/admin-e2e-tests": "workspace:*",
@ -182,7 +182,7 @@
"@xstate/test": "0.5.1",
"autoprefixer": "^10.4.2",
"await-exec": "^0.1.2",
"babel-jest": "^27.5.1",
"babel-jest": "~27.5.1",
"babel-loader": "^8.2.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
@ -201,9 +201,9 @@
"eslint-plugin-xstate": "^1.1.0",
"expose-loader": "^3.1.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"jest": "^27.5.1",
"jest-environment-jsdom": "~27.5.0",
"jest-environment-node": "^27.5.1",
"jest": "~27.5.1",
"jest-environment-jsdom": "~27.5.1",
"jest-environment-node": "~27.5.1",
"md5": "^2.3.0",
"merge-config": "^2.0.0",
"mini-css-extract-plugin": "^2.6.0",
@ -228,7 +228,7 @@
"sass-loader": "^10.2.1",
"style-loader": "^0.23.1",
"stylelint": "^14.5.3",
"ts-jest": "^27.1.3",
"ts-jest": "~29.1.1",
"typescript": "^5.1.6",
"url-loader": "^1.1.2",
"webpack": "^5.70.0",

View File

@ -1 +1 @@
jQuery(function(i){const e={init(){i("#wp-admin-bar-show-version-info").on("click",this.showModal)},showModal(e){e.preventDefault(),0<i(".wc-backbone-modal-beta-tester-version-info").length||i(this).WCBackboneModal({template:"wc-beta-tester-version-info",variable:{version:wc_beta_tester_version_info_params.version,description:wc_beta_tester_version_info_params.description}})}};e.init()});
jQuery(function(i){({init(){i("#wp-admin-bar-show-version-info").on("click",this.showModal)},showModal(e){e.preventDefault(),0<i(".wc-backbone-modal-beta-tester-version-info").length||i(this).WCBackboneModal({template:"wc-beta-tester-version-info",variable:{version:wc_beta_tester_version_info_params.version,description:wc_beta_tester_version_info_params.description}})}}).init()});

View File

@ -1 +1 @@
jQuery(function(n){const i={init(){instance=this,instance.new_version=void 0,n("#wcbt-modal-version-switch-confirm").on("click",this.showConfirmVersionSwitchModal),n("input[type=radio][name=wcbt_switch_to_version]").change(function(){n(this).is(":checked")&&(instance.new_version=n(this).val())}).trigger("change")},showConfirmVersionSwitchModal(i){i.preventDefault(),instance.new_version?(n(this).WCBackboneModal({template:"wcbt-version-switch-confirm",variable:{new_version:instance.new_version}}),n("#wcbt-submit-version-switch").on("click",instance.submitSwitchVersionForm)):alert(wc_beta_tester_version_picker_params.i18n_pick_version)},submitSwitchVersionForm(i){i.preventDefault(),n("form[name=wcbt-select-version]").get(0).submit()}};i.init()});
jQuery(function(e){({init(){(instance=this).new_version=void 0,e("#wcbt-modal-version-switch-confirm").on("click",this.showConfirmVersionSwitchModal),e("input[type=radio][name=wcbt_switch_to_version]").change(function(){e(this).is(":checked")&&(instance.new_version=e(this).val())}).trigger("change")},showConfirmVersionSwitchModal(i){i.preventDefault(),instance.new_version?(e(this).WCBackboneModal({template:"wcbt-version-switch-confirm",variable:{new_version:instance.new_version}}),e("#wcbt-submit-version-switch").on("click",instance.submitSwitchVersionForm)):alert(wc_beta_tester_version_picker_params.i18n_pick_version)},submitSwitchVersionForm(i){i.preventDefault(),e("form[name=wcbt-select-version]").get(0).submit()}}).init()});

View File

@ -13,7 +13,7 @@
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@types/wordpress__components": "^19.10.3",
"@types/wordpress__plugins": "^3.0.0",
"@types/wordpress__plugins": "3.0.0",
"@woocommerce/dependency-extraction-webpack-plugin": "workspace:*",
"@woocommerce/eslint-plugin": "workspace:*",
"@wordpress/env": "^8.2.0",

View File

@ -8,7 +8,7 @@
<rect width="32" height="32" rx="16" fill="#8E9196" />
<mask
id="bacs0"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="8"
y="8"
@ -27,7 +27,7 @@
</g>
<mask
id="bacs1"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="39"
y="10"
@ -52,7 +52,7 @@
<rect x="64" width="32" height="32" rx="16" fill="#8E9196" />
<mask
id="bacs2"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="72"
y="8"

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -8,7 +8,7 @@
<rect width="32" height="32" rx="16" fill="#8E9196" />
<mask
id="cod-mask-0"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="7"
y="10"
@ -27,7 +27,7 @@
</g>
<mask
id="cod-mask-1"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="39"
y="10"
@ -52,7 +52,7 @@
<rect x="64" width="32" height="32" rx="16" fill="#8E9196" />
<mask
id="cod-mask-2"
mask-type="alpha"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="76"
y="9"

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -268,16 +268,16 @@
},
{
"name": "symfony/console",
"version": "v5.4.31",
"version": "v5.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "11ac5f154e0e5c4c77af83ad11ead9165280b92a"
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/11ac5f154e0e5c4c77af83ad11ead9165280b92a",
"reference": "11ac5f154e0e5c4c77af83ad11ead9165280b92a",
"url": "https://api.github.com/repos/symfony/console/zipball/c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
"reference": "c70df1ffaf23a8d340bded3cfab1b86752ad6ed7",
"shasum": ""
},
"require": {
@ -347,7 +347,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.31"
"source": "https://github.com/symfony/console/tree/v5.4.32"
},
"funding": [
{
@ -363,7 +363,7 @@
"type": "tidelift"
}
],
"time": "2023-10-31T07:58:33+00:00"
"time": "2023-11-18T18:23:04+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -1072,16 +1072,16 @@
},
{
"name": "symfony/string",
"version": "v5.4.31",
"version": "v5.4.32",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "2765096c03f39ddf54f6af532166e42aaa05b24b"
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/2765096c03f39ddf54f6af532166e42aaa05b24b",
"reference": "2765096c03f39ddf54f6af532166e42aaa05b24b",
"url": "https://api.github.com/repos/symfony/string/zipball/91bf4453d65d8231688a04376c3a40efe0770f04",
"reference": "91bf4453d65d8231688a04376c3a40efe0770f04",
"shasum": ""
},
"require": {
@ -1138,7 +1138,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.4.31"
"source": "https://github.com/symfony/string/tree/v5.4.32"
},
"funding": [
{
@ -1154,7 +1154,7 @@
"type": "tidelift"
}
],
"time": "2023-11-09T08:19:44+00:00"
"time": "2023-11-26T13:43:46+00:00"
}
],
"aliases": [],

View File

@ -1,4 +0,0 @@
Significance: patch
Type: tweak
Update pattern assembler thumbnail shadow

View File

@ -1,4 +0,0 @@
Significance: patch
Type: fix
Fix save button is still disabled after updating logo settings

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