From 87d79f15a3144132354edec3cc84636d863302b1 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 23 Feb 2023 09:48:49 -0800 Subject: [PATCH] Move product editor utils to product editor package (#36730) * Move product editor utils to product editor package * Add changelog entries * Move remaining utils * Move util import/exports to separate index file --- .../js/product-editor/changelog/update-36719 | 4 + packages/js/product-editor/package.json | 12 ++- packages/js/product-editor/src/index.ts | 5 ++ .../js/product-editor/src/utils/constants.ts | 9 ++ .../utils/format-currency-display-value.ts | 39 ++++++++ .../src/utils/get-checkbox-tracks.ts | 24 +++++ .../src/utils/get-currency-symbol-props.ts | 26 ++++++ .../src}/utils/get-derived-product-type.ts | 0 .../src}/utils/get-product-status.ts | 0 .../src}/utils/get-product-stock-status.ts | 0 .../src}/utils/get-product-title.ts | 0 .../src}/utils/get-product-variation-title.ts | 2 +- packages/js/product-editor/src/utils/index.ts | 34 +++++++ .../utils/prevent-leaving-product-form.ts | 0 .../utils/test/get-derived-product-type.ts | 0 .../src}/utils/test/get-product-status.ts | 0 .../test/get-product-stock-status.test.ts | 0 .../src}/utils/test/get-product-title.test.ts | 0 .../utils/test/get-product-variation-title.ts | 0 .../test/prevent-leaving-product-form.test.ts | 0 .../js/product-editor/typings/global.d.ts | 9 ++ .../products/fields/variations/variations.tsx | 8 +- .../details-section/details-field-feature.tsx | 2 +- .../inventory-field-stock-limit.tsx | 6 +- .../inventory-field-track-quantity.tsx | 2 +- .../pricing-section/pricing-field-list.tsx | 2 +- .../pricing-section/pricing-field-sale.tsx | 2 +- .../hooks/use-product-variations-helper.ts | 6 +- .../client/products/product-form-actions.tsx | 2 +- .../product-settings/product-settings.tsx | 2 +- .../client/products/product-status-badge.tsx | 8 +- .../client/products/product-title.tsx | 10 +-- .../product-variation-form-actions.tsx | 2 +- .../product-variation-details-section.tsx | 2 +- .../client/products/sections/utils.ts | 81 ----------------- .../client/products/use-product-helper.ts | 6 +- plugins/woocommerce/changelog/update-36719 | 4 + pnpm-lock.yaml | 89 +++++++++++++------ 38 files changed, 253 insertions(+), 145 deletions(-) create mode 100644 packages/js/product-editor/changelog/update-36719 create mode 100644 packages/js/product-editor/src/utils/constants.ts create mode 100644 packages/js/product-editor/src/utils/format-currency-display-value.ts create mode 100644 packages/js/product-editor/src/utils/get-checkbox-tracks.ts create mode 100644 packages/js/product-editor/src/utils/get-currency-symbol-props.ts rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/get-derived-product-type.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/get-product-status.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/get-product-stock-status.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/get-product-title.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/get-product-variation-title.ts (94%) create mode 100644 packages/js/product-editor/src/utils/index.ts rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/prevent-leaving-product-form.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/get-derived-product-type.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/get-product-status.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/get-product-stock-status.test.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/get-product-title.test.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/get-product-variation-title.ts (100%) rename {plugins/woocommerce-admin/client/products => packages/js/product-editor/src}/utils/test/prevent-leaving-product-form.test.ts (100%) create mode 100644 packages/js/product-editor/typings/global.d.ts delete mode 100644 plugins/woocommerce-admin/client/products/sections/utils.ts create mode 100644 plugins/woocommerce/changelog/update-36719 diff --git a/packages/js/product-editor/changelog/update-36719 b/packages/js/product-editor/changelog/update-36719 new file mode 100644 index 0000000000000..07d610d51f85e --- /dev/null +++ b/packages/js/product-editor/changelog/update-36719 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product editor utils diff --git a/packages/js/product-editor/package.json b/packages/js/product-editor/package.json index abb3c6d7361ef..d75fb9e56f1f0 100644 --- a/packages/js/product-editor/package.json +++ b/packages/js/product-editor/package.json @@ -29,10 +29,15 @@ }, "dependencies": { "@woocommerce/components": "workspace:*", + "@woocommerce/data": "workspace:^4.1.0", + "@woocommerce/tracks": "workspace:^1.3.0", "@wordpress/components": "^19.5.0", - "@wordpress/element": "^4.1.1" + "@wordpress/element": "^4.1.1", + "@wordpress/i18n": "^4.26.0", + "react-router-dom": "^6.3.0" }, "devDependencies": { + "@types/react": "^17.0.2", "@types/wordpress__components": "^19.10.1", "@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/internal-style-build": "workspace:*", @@ -42,6 +47,7 @@ "jest": "^27.5.1", "jest-cli": "^27.5.1", "postcss-loader": "^3.0.0", + "react": "^17.0.2", "sass-loader": "^10.2.1", "ts-jest": "^27.1.3", "typescript": "^4.8.3", @@ -60,5 +66,9 @@ "start": "concurrently \"tsc --build --watch\" \"webpack --watch\"", "prepack": "pnpm run clean && pnpm run build", "lint:fix": "eslint src --fix" + }, + "peerDependencies": { + "@types/react": "^17.0.2", + "react": "^17.0.2" } } diff --git a/packages/js/product-editor/src/index.ts b/packages/js/product-editor/src/index.ts index e38787183bf7c..8734d1e1479f4 100644 --- a/packages/js/product-editor/src/index.ts +++ b/packages/js/product-editor/src/index.ts @@ -2,3 +2,8 @@ export { ProductSectionLayout as __experimentalProductSectionLayout, ProductFieldSection as __experimentalProductFieldSection, } from './product-section-layout'; + +/** + * Utils + */ +export * from './utils'; diff --git a/packages/js/product-editor/src/utils/constants.ts b/packages/js/product-editor/src/utils/constants.ts new file mode 100644 index 0000000000000..0aa72fd516ad2 --- /dev/null +++ b/packages/js/product-editor/src/utils/constants.ts @@ -0,0 +1,9 @@ +export const NUMBERS_AND_ALLOWED_CHARS = '[^-0-9%s1%s2]'; +export const NUMBERS_AND_DECIMAL_SEPARATOR = '[^-\\d\\%s]+'; +export const ONLY_ONE_DECIMAL_SEPARATOR = '[%s](?=%s*[%s])'; +// This should never be a real slug value of any existing shipping class +export const ADD_NEW_SHIPPING_CLASS_OPTION_VALUE = + '__ADD_NEW_SHIPPING_CLASS_OPTION__'; +export const UNCATEGORIZED_CATEGORY_SLUG = 'uncategorized'; +export const PRODUCT_VARIATION_TITLE_LIMIT = 32; +export const STANDARD_RATE_TAX_CLASS_SLUG = 'standard'; diff --git a/packages/js/product-editor/src/utils/format-currency-display-value.ts b/packages/js/product-editor/src/utils/format-currency-display-value.ts new file mode 100644 index 0000000000000..8950006929be0 --- /dev/null +++ b/packages/js/product-editor/src/utils/format-currency-display-value.ts @@ -0,0 +1,39 @@ +/** + * Internal dependencies + */ +import { NUMBERS_AND_ALLOWED_CHARS } from './constants'; + +type CurrencyConfig = { + code: string; + symbol: string; + symbolPosition: string; + decimalSeparator: string; + priceFormat: string; + thousandSeparator: string; + precision: number; +}; + +/** + * Cleans and formats the currency value shown to the user. + * + * @param {string} value Form value. + * @param {Object} currencyConfig Currency context. + * @return {string} Display value. + */ +export const formatCurrencyDisplayValue = ( + value: string, + currencyConfig: CurrencyConfig, + format: ( number: number | string ) => string +) => { + const { decimalSeparator, thousandSeparator } = currencyConfig; + + const regex = new RegExp( + NUMBERS_AND_ALLOWED_CHARS.replace( '%s1', decimalSeparator ).replace( + '%s2', + thousandSeparator + ), + 'g' + ); + + return value === undefined ? value : format( value ).replace( regex, '' ); +}; diff --git a/packages/js/product-editor/src/utils/get-checkbox-tracks.ts b/packages/js/product-editor/src/utils/get-checkbox-tracks.ts new file mode 100644 index 0000000000000..8d219c91681bc --- /dev/null +++ b/packages/js/product-editor/src/utils/get-checkbox-tracks.ts @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { ChangeEvent } from 'react'; +import { Product } from '@woocommerce/data'; +import { recordEvent } from '@woocommerce/tracks'; + +/** + * Get additional props to be passed to all checkbox inputs. + * + * @param name Name of the checkbox. + * @return Props. + */ +export function getCheckboxTracks< T = Product >( name: string ) { + return { + onChange: ( + isChecked: ChangeEvent< HTMLInputElement > | T[ keyof T ] + ) => { + recordEvent( `product_checkbox_${ name }`, { + checked: isChecked, + } ); + }, + }; +} diff --git a/packages/js/product-editor/src/utils/get-currency-symbol-props.ts b/packages/js/product-editor/src/utils/get-currency-symbol-props.ts new file mode 100644 index 0000000000000..6064b28133d4d --- /dev/null +++ b/packages/js/product-editor/src/utils/get-currency-symbol-props.ts @@ -0,0 +1,26 @@ +type CurrencyConfig = { + code: string; + symbol: string; + symbolPosition: string; + decimalSeparator: string; + priceFormat: string; + thousandSeparator: string; + precision: number; +}; + +/** + * Get input props for currency related values and symbol positions. + * + * @param {Object} currencyConfig - Currency context + * @return {Object} Props. + */ +export const getCurrencySymbolProps = ( currencyConfig: CurrencyConfig ) => { + const { symbol, symbolPosition } = currencyConfig; + const currencyPosition = symbolPosition.includes( 'left' ) + ? 'prefix' + : 'suffix'; + + return { + [ currencyPosition ]: symbol, + }; +}; diff --git a/plugins/woocommerce-admin/client/products/utils/get-derived-product-type.ts b/packages/js/product-editor/src/utils/get-derived-product-type.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/get-derived-product-type.ts rename to packages/js/product-editor/src/utils/get-derived-product-type.ts diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-status.ts b/packages/js/product-editor/src/utils/get-product-status.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/get-product-status.ts rename to packages/js/product-editor/src/utils/get-product-status.ts diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts b/packages/js/product-editor/src/utils/get-product-stock-status.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/get-product-stock-status.ts rename to packages/js/product-editor/src/utils/get-product-stock-status.ts diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-title.ts b/packages/js/product-editor/src/utils/get-product-title.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/get-product-title.ts rename to packages/js/product-editor/src/utils/get-product-title.ts diff --git a/plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts b/packages/js/product-editor/src/utils/get-product-variation-title.ts similarity index 94% rename from plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts rename to packages/js/product-editor/src/utils/get-product-variation-title.ts index a15a79436cd2a..0480311c1bdf9 100644 --- a/plugins/woocommerce-admin/client/products/utils/get-product-variation-title.ts +++ b/packages/js/product-editor/src/utils/get-product-variation-title.ts @@ -6,7 +6,7 @@ import { ProductVariation } from '@woocommerce/data'; /** * Internal dependencies */ -import { PRODUCT_VARIATION_TITLE_LIMIT } from '../constants'; +import { PRODUCT_VARIATION_TITLE_LIMIT } from './constants'; /** * Get the product variation title for use in the header. diff --git a/packages/js/product-editor/src/utils/index.ts b/packages/js/product-editor/src/utils/index.ts new file mode 100644 index 0000000000000..58624bb402e42 --- /dev/null +++ b/packages/js/product-editor/src/utils/index.ts @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import { formatCurrencyDisplayValue } from './format-currency-display-value'; +import { getCheckboxTracks } from './get-checkbox-tracks'; +import { getCurrencySymbolProps } from './get-currency-symbol-props'; +import { getDerivedProductType } from './get-derived-product-type'; +import { getProductStatus, PRODUCT_STATUS_LABELS } from './get-product-status'; +import { + getProductStockStatus, + getProductStockStatusClass, +} from './get-product-stock-status'; +import { getProductTitle, AUTO_DRAFT_NAME } from './get-product-title'; +import { + getProductVariationTitle, + getTruncatedProductVariationTitle, +} from './get-product-variation-title'; +import { preventLeavingProductForm } from './prevent-leaving-product-form'; + +export { + AUTO_DRAFT_NAME, + formatCurrencyDisplayValue, + getCheckboxTracks, + getCurrencySymbolProps, + getDerivedProductType, + getProductStatus, + getProductStockStatus, + getProductStockStatusClass, + getProductTitle, + getProductVariationTitle, + getTruncatedProductVariationTitle, + preventLeavingProductForm, + PRODUCT_STATUS_LABELS, +}; diff --git a/plugins/woocommerce-admin/client/products/utils/prevent-leaving-product-form.ts b/packages/js/product-editor/src/utils/prevent-leaving-product-form.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/prevent-leaving-product-form.ts rename to packages/js/product-editor/src/utils/prevent-leaving-product-form.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-derived-product-type.ts b/packages/js/product-editor/src/utils/test/get-derived-product-type.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/get-derived-product-type.ts rename to packages/js/product-editor/src/utils/test/get-derived-product-type.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-status.ts b/packages/js/product-editor/src/utils/test/get-product-status.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/get-product-status.ts rename to packages/js/product-editor/src/utils/test/get-product-status.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts b/packages/js/product-editor/src/utils/test/get-product-stock-status.test.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/get-product-stock-status.test.ts rename to packages/js/product-editor/src/utils/test/get-product-stock-status.test.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-title.test.ts b/packages/js/product-editor/src/utils/test/get-product-title.test.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/get-product-title.test.ts rename to packages/js/product-editor/src/utils/test/get-product-title.test.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts b/packages/js/product-editor/src/utils/test/get-product-variation-title.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/get-product-variation-title.ts rename to packages/js/product-editor/src/utils/test/get-product-variation-title.ts diff --git a/plugins/woocommerce-admin/client/products/utils/test/prevent-leaving-product-form.test.ts b/packages/js/product-editor/src/utils/test/prevent-leaving-product-form.test.ts similarity index 100% rename from plugins/woocommerce-admin/client/products/utils/test/prevent-leaving-product-form.test.ts rename to packages/js/product-editor/src/utils/test/prevent-leaving-product-form.test.ts diff --git a/packages/js/product-editor/typings/global.d.ts b/packages/js/product-editor/typings/global.d.ts new file mode 100644 index 0000000000000..69abbdeff5eb0 --- /dev/null +++ b/packages/js/product-editor/typings/global.d.ts @@ -0,0 +1,9 @@ +declare global { + interface Window { + wcAdminFeatures: Record< string, boolean >; + } +} + +/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */ +export {}; + diff --git a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx index 5076f08090d81..16298715f4706 100644 --- a/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx +++ b/plugins/woocommerce-admin/client/products/fields/variations/variations.tsx @@ -8,6 +8,10 @@ import { Product, ProductVariation, } from '@woocommerce/data'; +import { + getProductStockStatus, + getProductStockStatusClass, +} from '@woocommerce/product-editor'; import { Link, ListItem, @@ -31,10 +35,6 @@ import useVariationsOrder from '~/products/hooks/use-variations-order'; import HiddenIcon from '~/products/images/hidden-icon'; import VisibleIcon from '~/products/images/visible-icon'; import { CurrencyContext } from '../../../lib/currency-context'; -import { - getProductStockStatus, - getProductStockStatusClass, -} from '../../utils/get-product-stock-status'; import './variations.scss'; /** diff --git a/plugins/woocommerce-admin/client/products/fills/details-section/details-field-feature.tsx b/plugins/woocommerce-admin/client/products/fills/details-section/details-field-feature.tsx index afbae52275dae..4f1feb8e91052 100644 --- a/plugins/woocommerce-admin/client/products/fills/details-section/details-field-feature.tsx +++ b/plugins/woocommerce-admin/client/products/fills/details-section/details-field-feature.tsx @@ -8,6 +8,7 @@ import { Link, __experimentalTooltip as Tooltip, } from '@woocommerce/components'; +import { getCheckboxTracks } from '@woocommerce/product-editor'; import interpolateComponents from '@automattic/interpolate-components'; import { Product } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; @@ -15,7 +16,6 @@ import { recordEvent } from '@woocommerce/tracks'; /** * Internal dependencies */ -import { getCheckboxTracks } from '../../sections/utils'; import { PRODUCT_DETAILS_SLUG } from '../constants'; export const DetailsFeatureField = () => { diff --git a/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-stock-limit.tsx b/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-stock-limit.tsx index 9eaa49752467e..d373ca9d2f3ac 100644 --- a/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-stock-limit.tsx +++ b/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-stock-limit.tsx @@ -4,13 +4,9 @@ import { __ } from '@wordpress/i18n'; import { useFormContext } from '@woocommerce/components'; import { CheckboxControl } from '@wordpress/components'; +import { getCheckboxTracks } from '@woocommerce/product-editor'; import { Product } from '@woocommerce/data'; -/** - * Internal dependencies - */ -import { getCheckboxTracks } from '../../sections/utils'; - export const InventoryStockLimitField = () => { const { getCheckboxControlProps } = useFormContext< Product >(); diff --git a/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-track-quantity.tsx b/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-track-quantity.tsx index a2c42f8e6938f..ee1ba6789b636 100644 --- a/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-track-quantity.tsx +++ b/plugins/woocommerce-admin/client/products/fills/inventory-section/inventory-field-track-quantity.tsx @@ -6,6 +6,7 @@ import { useFormContext, __experimentalConditionalWrapper as ConditionalWrapper, } from '@woocommerce/components'; +import { getCheckboxTracks } from '@woocommerce/product-editor'; import { Tooltip, ToggleControl } from '@wordpress/components'; import { Product } from '@woocommerce/data'; @@ -13,7 +14,6 @@ import { Product } from '@woocommerce/data'; * Internal dependencies */ import { getAdminSetting } from '~/utils/admin-settings'; -import { getCheckboxTracks } from '../../sections/utils'; export const InventoryTrackQuantityField = () => { const { getCheckboxControlProps } = useFormContext< Product >(); diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx index 67f5c971df6b5..fb089980cabab 100644 --- a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx @@ -2,6 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; +import { formatCurrencyDisplayValue } from '@woocommerce/product-editor'; import { useFormContext, Link } from '@woocommerce/components'; import { recordEvent } from '@woocommerce/tracks'; import { useContext } from '@wordpress/element'; @@ -18,7 +19,6 @@ import { * Internal dependencies */ import { CurrencyInputProps } from './pricing-section-fills'; -import { formatCurrencyDisplayValue } from '../../sections/utils'; import { CurrencyContext } from '../../../lib/currency-context'; import { ADMIN_URL } from '~/utils/admin-settings'; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx index 4cd274c274333..b46361f5fbf2f 100644 --- a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx @@ -14,6 +14,7 @@ import { Product, OPTIONS_STORE_NAME } from '@woocommerce/data'; import { useSelect } from '@wordpress/data'; import interpolateComponents from '@automattic/interpolate-components'; import { format as formatDate } from '@wordpress/date'; +import { formatCurrencyDisplayValue } from '@woocommerce/product-editor'; import moment from 'moment'; import { BaseControl, @@ -26,7 +27,6 @@ import { * Internal dependencies */ import { CurrencyInputProps } from './pricing-section-fills'; -import { formatCurrencyDisplayValue } from '../../sections/utils'; import { CurrencyContext } from '../../../lib/currency-context'; type PricingListFieldProps = { diff --git a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts b/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts index 0b40861f5ce7c..f999a67be3e29 100644 --- a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts +++ b/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts @@ -1,6 +1,7 @@ /** * External dependencies */ +import { AUTO_DRAFT_NAME } from '@woocommerce/product-editor'; import { useDispatch } from '@wordpress/data'; import { useCallback, useState } from '@wordpress/element'; import { @@ -10,11 +11,6 @@ import { } from '@woocommerce/data'; import { useFormContext } from '@woocommerce/components'; -/** - * Internal dependencies - */ -import { AUTO_DRAFT_NAME } from '../utils/get-product-title'; - export function useProductVariationsHelper() { const { generateProductVariations: _generateProductVariations, diff --git a/plugins/woocommerce-admin/client/products/product-form-actions.tsx b/plugins/woocommerce-admin/client/products/product-form-actions.tsx index 63274767afb6a..19fb65153dbe8 100644 --- a/plugins/woocommerce-admin/client/products/product-form-actions.tsx +++ b/plugins/woocommerce-admin/client/products/product-form-actions.tsx @@ -12,6 +12,7 @@ import { import { chevronDown, check, Icon } from '@wordpress/icons'; import { registerPlugin } from '@wordpress/plugins'; import { useFormContext } from '@woocommerce/components'; +import { preventLeavingProductForm } from '@woocommerce/product-editor'; import { Product } from '@woocommerce/data'; import { recordEvent } from '@woocommerce/tracks'; import { navigateTo } from '@woocommerce/navigation'; @@ -24,7 +25,6 @@ import { store } from '@wordpress/viewport'; /** * Internal dependencies */ -import { preventLeavingProductForm } from './utils/prevent-leaving-product-form'; import usePreventLeavingPage from '~/hooks/usePreventLeavingPage'; import { WooHeaderItem } from '~/header/utils'; import { useProductHelper } from './use-product-helper'; diff --git a/plugins/woocommerce-admin/client/products/product-settings/product-settings.tsx b/plugins/woocommerce-admin/client/products/product-settings/product-settings.tsx index f33d1b9d427ff..26f0543731085 100644 --- a/plugins/woocommerce-admin/client/products/product-settings/product-settings.tsx +++ b/plugins/woocommerce-admin/client/products/product-settings/product-settings.tsx @@ -11,6 +11,7 @@ import { TextControl, } from '@wordpress/components'; import { closeSmall, cog } from '@wordpress/icons'; +import { getCheckboxTracks } from '@woocommerce/product-editor'; import { Product } from '@woocommerce/data'; import { useFormContext } from '@woocommerce/components'; import { useState } from '@wordpress/element'; @@ -18,7 +19,6 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { getCheckboxTracks } from '../sections/utils'; import { WooHeaderItem } from '~/header/utils'; import './product-settings.scss'; diff --git a/plugins/woocommerce-admin/client/products/product-status-badge.tsx b/plugins/woocommerce-admin/client/products/product-status-badge.tsx index c031b2e93fe50..aea5368b6e4a8 100644 --- a/plugins/woocommerce-admin/client/products/product-status-badge.tsx +++ b/plugins/woocommerce-admin/client/products/product-status-badge.tsx @@ -7,14 +7,14 @@ import { Pill } from '@woocommerce/components'; import { PRODUCTS_STORE_NAME, WCDataSelector } from '@woocommerce/data'; import { useParams } from 'react-router-dom'; import { useSelect } from '@wordpress/data'; +import { + getProductStatus, + PRODUCT_STATUS_LABELS, +} from '@woocommerce/product-editor'; /** * Internal dependencies */ -import { - getProductStatus, - PRODUCT_STATUS_LABELS, -} from './utils/get-product-status'; import './product-status-badge.scss'; export const ProductStatusBadge: React.FC = () => { diff --git a/plugins/woocommerce-admin/client/products/product-title.tsx b/plugins/woocommerce-admin/client/products/product-title.tsx index 7a377c657c375..b05c6a7e51556 100644 --- a/plugins/woocommerce-admin/client/products/product-title.tsx +++ b/plugins/woocommerce-admin/client/products/product-title.tsx @@ -10,6 +10,11 @@ import { } from '@woocommerce/data'; import { getAdminLink } from '@woocommerce/settings'; import { getNewPath } from '@woocommerce/navigation'; +import { + getProductTitle, + getProductVariationTitle, + getTruncatedProductVariationTitle, +} from '@woocommerce/product-editor'; import { useFormContext } from '@woocommerce/components'; import { useParams } from 'react-router-dom'; import { useSelect } from '@wordpress/data'; @@ -17,11 +22,6 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { getProductTitle } from './utils/get-product-title'; -import { - getProductVariationTitle, - getTruncatedProductVariationTitle, -} from './utils/get-product-variation-title'; import { ProductBreadcrumbs } from './product-breadcrumbs'; import { ProductStatusBadge } from './product-status-badge'; import { WooHeaderPageTitle } from '~/header/utils'; diff --git a/plugins/woocommerce-admin/client/products/product-variation-form-actions.tsx b/plugins/woocommerce-admin/client/products/product-variation-form-actions.tsx index f059f1bf4aaef..d4c0298374657 100644 --- a/plugins/woocommerce-admin/client/products/product-variation-form-actions.tsx +++ b/plugins/woocommerce-admin/client/products/product-variation-form-actions.tsx @@ -7,6 +7,7 @@ import { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, ProductVariation, } from '@woocommerce/data'; +import { preventLeavingProductForm } from '@woocommerce/product-editor'; import { registerPlugin } from '@wordpress/plugins'; import { useDispatch } from '@wordpress/data'; import { useFormContext } from '@woocommerce/components'; @@ -16,7 +17,6 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { preventLeavingProductForm } from './utils/prevent-leaving-product-form'; import usePreventLeavingPage from '~/hooks/usePreventLeavingPage'; import { WooHeaderItem } from '~/header/utils'; import './product-form-actions.scss'; diff --git a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx index 7886d844bb16f..6b0687d8c6d3d 100644 --- a/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx +++ b/plugins/woocommerce-admin/client/products/sections/product-variation-details-section.tsx @@ -9,6 +9,7 @@ import { CardBody, BaseControl, } from '@wordpress/components'; +import { getCheckboxTracks } from '@woocommerce/product-editor'; import { MediaItem } from '@wordpress/media-utils'; import { useFormContext, @@ -21,7 +22,6 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import { getCheckboxTracks } from './utils'; import { ProductSectionLayout } from '../layout/product-section-layout'; import { SingleImageField } from '../fields/single-image-field'; diff --git a/plugins/woocommerce-admin/client/products/sections/utils.ts b/plugins/woocommerce-admin/client/products/sections/utils.ts deleted file mode 100644 index ab56552bcf6dd..0000000000000 --- a/plugins/woocommerce-admin/client/products/sections/utils.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * External dependencies - */ -import { ChangeEvent } from 'react'; -import { Product } from '@woocommerce/data'; -import { recordEvent } from '@woocommerce/tracks'; - -/** - * Internal dependencies - */ -import { NUMBERS_AND_ALLOWED_CHARS } from '../constants'; - -type CurrencyConfig = { - code: string; - symbol: string; - symbolPosition: string; - decimalSeparator: string; - priceFormat: string; - thousandSeparator: string; - precision: number; -}; - -/** - * Get additional props to be passed to all checkbox inputs. - * - * @param name Name of the checkbox. - * @return Props. - */ -export function getCheckboxTracks< T = Product >( name: string ) { - return { - onChange: ( - isChecked: ChangeEvent< HTMLInputElement > | T[ keyof T ] - ) => { - recordEvent( `product_checkbox_${ name }`, { - checked: isChecked, - } ); - }, - }; -} - -/** - * Get input props for currency related values and symbol positions. - * - * @param {Object} currencyConfig - Currency context - * @return {Object} Props. - */ -export const getCurrencySymbolProps = ( currencyConfig: CurrencyConfig ) => { - const { symbol, symbolPosition } = currencyConfig; - const currencyPosition = symbolPosition.includes( 'left' ) - ? 'prefix' - : 'suffix'; - - return { - [ currencyPosition ]: symbol, - }; -}; - -/** - * Cleans and formats the currency value shown to the user. - * - * @param {string} value Form value. - * @param {Object} currencyConfig Currency context. - * @return {string} Display value. - */ -export const formatCurrencyDisplayValue = ( - value: string, - currencyConfig: CurrencyConfig, - format: ( number: number | string ) => string -) => { - const { decimalSeparator, thousandSeparator } = currencyConfig; - - const regex = new RegExp( - NUMBERS_AND_ALLOWED_CHARS.replace( '%s1', decimalSeparator ).replace( - '%s2', - thousandSeparator - ), - 'g' - ); - - return value === undefined ? value : format( value ).replace( regex, '' ); -}; diff --git a/plugins/woocommerce-admin/client/products/use-product-helper.ts b/plugins/woocommerce-admin/client/products/use-product-helper.ts index d40129be55b79..57d02daf71125 100644 --- a/plugins/woocommerce-admin/client/products/use-product-helper.ts +++ b/plugins/woocommerce-admin/client/products/use-product-helper.ts @@ -2,6 +2,10 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; +import { + AUTO_DRAFT_NAME, + getDerivedProductType, +} from '@woocommerce/product-editor'; import { useDispatch } from '@wordpress/data'; import { useCallback, useContext, useState } from '@wordpress/element'; import * as WooNumber from '@woocommerce/number'; @@ -20,9 +24,7 @@ import { recordEvent } from '@woocommerce/tracks'; /** * Internal dependencies */ -import { AUTO_DRAFT_NAME } from './utils/get-product-title'; import { CurrencyContext } from '../lib/currency-context'; -import { getDerivedProductType } from './utils/get-derived-product-type'; import { NUMBERS_AND_DECIMAL_SEPARATOR, ONLY_ONE_DECIMAL_SEPARATOR, diff --git a/plugins/woocommerce/changelog/update-36719 b/plugins/woocommerce/changelog/update-36719 new file mode 100644 index 0000000000000..896dc558a69e4 --- /dev/null +++ b/plugins/woocommerce/changelog/update-36719 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Move product utils into product editor package diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f3b91b64a379..bf50de3b1ef84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1291,18 +1291,24 @@ importers: packages/js/product-editor: specifiers: + '@types/react': ^17.0.2 '@types/wordpress__components': ^19.10.1 '@woocommerce/components': workspace:* + '@woocommerce/data': workspace:^4.1.0 '@woocommerce/eslint-plugin': workspace:* '@woocommerce/internal-style-build': workspace:* + '@woocommerce/tracks': workspace:^1.3.0 '@wordpress/browserslist-config': ^4.1.1 '@wordpress/components': ^19.5.0 '@wordpress/element': ^4.1.1 + '@wordpress/i18n': ^4.26.0 css-loader: ^3.6.0 eslint: ^8.32.0 jest: ^27.5.1 jest-cli: ^27.5.1 postcss-loader: ^3.0.0 + react: ^17.0.2 + react-router-dom: ^6.3.0 sass-loader: ^10.2.1 ts-jest: ^27.1.3 typescript: ^4.8.3 @@ -1310,9 +1316,14 @@ importers: webpack-cli: ^3.3.12 dependencies: '@woocommerce/components': link:../components - '@wordpress/components': 19.12.0_rysvg2ttzfworbkpz2ftlx73d4 + '@woocommerce/data': link:../data + '@woocommerce/tracks': link:../tracks + '@wordpress/components': 19.12.0_y6y2satmlys6ddgpljj6f5uzae '@wordpress/element': 4.20.0 + '@wordpress/i18n': 4.26.0 + react-router-dom: 6.3.0_prpqlkd37azqwypxturxi7uyci devDependencies: + '@types/react': 17.0.50 '@types/wordpress__components': 19.10.1_prpqlkd37azqwypxturxi7uyci '@woocommerce/eslint-plugin': link:../eslint-plugin '@woocommerce/internal-style-build': link:../internal-style-build @@ -1322,6 +1333,7 @@ importers: jest: 27.5.1 jest-cli: 27.5.1 postcss-loader: 3.0.0 + react: 17.0.2 sass-loader: 10.2.1_webpack@5.70.0 ts-jest: 27.1.3_77oryishcckaigojnzbhxsiona typescript: 4.8.4 @@ -7749,7 +7761,7 @@ packages: dependencies: '@floating-ui/core': 1.0.1 - /@floating-ui/react-dom/0.6.3_hiunvzosbwliizyirxfy6hjyim: + /@floating-ui/react-dom/0.6.3_eh3i7lxcgsnewksy5lf2odhkg4: resolution: {integrity: sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==} peerDependencies: react: '>=16.8.0' @@ -7757,13 +7769,13 @@ packages: dependencies: '@floating-ui/dom': 0.4.5 react: 17.0.2 - react-dom: 17.0.2_react@17.0.2 + react-dom: 16.14.0_react@17.0.2 use-isomorphic-layout-effect: 1.1.1_pxzommwrsowkd4kgag6q3sluym transitivePeerDependencies: - '@types/react' dev: false - /@floating-ui/react-dom/0.6.3_prpqlkd37azqwypxturxi7uyci: + /@floating-ui/react-dom/0.6.3_hiunvzosbwliizyirxfy6hjyim: resolution: {integrity: sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==} peerDependencies: react: '>=16.8.0' @@ -7771,8 +7783,8 @@ packages: dependencies: '@floating-ui/dom': 0.4.5 react: 17.0.2 - react-dom: 16.14.0_react@17.0.2 - use-isomorphic-layout-effect: 1.1.1_react@17.0.2 + react-dom: 17.0.2_react@17.0.2 + use-isomorphic-layout-effect: 1.1.1_pxzommwrsowkd4kgag6q3sluym transitivePeerDependencies: - '@types/react' dev: false @@ -14362,7 +14374,7 @@ packages: dependencies: '@babel/runtime': 7.19.0 '@wordpress/dom-ready': 3.19.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 /@wordpress/a11y/3.4.1: resolution: {integrity: sha512-SjeLO8x/Y/QAcKBrvyJiu8KVAPckRLNwuFfgX7zCGM8vBfg+Depj94Hp55ARLjq0oXHg7EWKxSdzNkvmTz8AIA==} @@ -14946,7 +14958,7 @@ packages: - react-with-direction dev: false - /@wordpress/components/19.12.0_rysvg2ttzfworbkpz2ftlx73d4: + /@wordpress/components/19.12.0_tufdcic6wklrwyy3rhbsbktylu: resolution: {integrity: sha512-Ac1+aIMM7NDgN3G7i5kcaETSvZfeqB4U6PubApPmM6FdBF5VfkYUZeqNcC7cuJdveyokRrqHg11/l+DcJGA7/g==} engines: {node: '>=12'} peerDependencies: @@ -14956,11 +14968,11 @@ packages: '@babel/runtime': 7.19.0 '@emotion/cache': 11.10.5 '@emotion/css': 11.7.1_@babel+core@7.17.8 - '@emotion/react': 11.10.5_mcptgafjogap2nfvnfqvfwh6uu + '@emotion/react': 11.10.5_lvgioobbs7lf3pr6y4xfpughau '@emotion/serialize': 1.1.1 - '@emotion/styled': 11.8.1_c2qm47vaialpqni522adyu6za4 + '@emotion/styled': 11.8.1_hhesyqfwklnojgamcachhyxace '@emotion/utils': 1.0.0 - '@floating-ui/react-dom': 0.6.3_prpqlkd37azqwypxturxi7uyci + '@floating-ui/react-dom': 0.6.3_hiunvzosbwliizyirxfy6hjyim '@use-gesture/react': 10.2.10_react@17.0.2 '@wordpress/a11y': 3.19.0 '@wordpress/compose': 5.17.0_react@17.0.2 @@ -14981,18 +14993,18 @@ packages: colord: 2.9.2 dom-scroll-into-view: 1.2.1 downshift: 6.1.12_react@17.0.2 - framer-motion: 6.2.8_prpqlkd37azqwypxturxi7uyci + framer-motion: 6.2.8_sfoxds7t5ydpegc3knd667wn6m gradient-parser: 0.1.5 highlight-words-core: 1.2.2 lodash: 4.17.21 memize: 1.1.0 moment: 2.29.1 - re-resizable: 6.9.5_prpqlkd37azqwypxturxi7uyci + re-resizable: 6.9.5_sfoxds7t5ydpegc3knd667wn6m react: 17.0.2 - react-colorful: 5.5.1_prpqlkd37azqwypxturxi7uyci - react-dates: 21.8.0_itvjtpkzdblwka3tnmrs5t5e6y - react-dom: 16.14.0_react@17.0.2 - reakit: 1.3.11_prpqlkd37azqwypxturxi7uyci + react-colorful: 5.5.1_sfoxds7t5ydpegc3knd667wn6m + react-dates: 21.8.0_ruqm5uqcpfavftrrblmrkdsf44 + react-dom: 17.0.2_react@17.0.2 + reakit: 1.3.11_sfoxds7t5ydpegc3knd667wn6m uuid: 8.3.2 transitivePeerDependencies: - '@babel/core' @@ -15000,7 +15012,7 @@ packages: - react-with-direction dev: false - /@wordpress/components/19.12.0_tufdcic6wklrwyy3rhbsbktylu: + /@wordpress/components/19.12.0_y6y2satmlys6ddgpljj6f5uzae: resolution: {integrity: sha512-Ac1+aIMM7NDgN3G7i5kcaETSvZfeqB4U6PubApPmM6FdBF5VfkYUZeqNcC7cuJdveyokRrqHg11/l+DcJGA7/g==} engines: {node: '>=12'} peerDependencies: @@ -15014,7 +15026,7 @@ packages: '@emotion/serialize': 1.1.1 '@emotion/styled': 11.8.1_hhesyqfwklnojgamcachhyxace '@emotion/utils': 1.0.0 - '@floating-ui/react-dom': 0.6.3_hiunvzosbwliizyirxfy6hjyim + '@floating-ui/react-dom': 0.6.3_eh3i7lxcgsnewksy5lf2odhkg4 '@use-gesture/react': 10.2.10_react@17.0.2 '@wordpress/a11y': 3.19.0 '@wordpress/compose': 5.17.0_react@17.0.2 @@ -15024,7 +15036,7 @@ packages: '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 '@wordpress/hooks': 3.19.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 '@wordpress/icons': 9.10.0 '@wordpress/is-shallow-equal': 4.19.0 '@wordpress/keycodes': 3.19.0 @@ -15035,18 +15047,18 @@ packages: colord: 2.9.2 dom-scroll-into-view: 1.2.1 downshift: 6.1.12_react@17.0.2 - framer-motion: 6.2.8_sfoxds7t5ydpegc3knd667wn6m + framer-motion: 6.2.8_prpqlkd37azqwypxturxi7uyci gradient-parser: 0.1.5 highlight-words-core: 1.2.2 lodash: 4.17.21 memize: 1.1.0 moment: 2.29.1 - re-resizable: 6.9.5_sfoxds7t5ydpegc3knd667wn6m + re-resizable: 6.9.5_prpqlkd37azqwypxturxi7uyci react: 17.0.2 - react-colorful: 5.5.1_sfoxds7t5ydpegc3knd667wn6m - react-dates: 21.8.0_ruqm5uqcpfavftrrblmrkdsf44 - react-dom: 17.0.2_react@17.0.2 - reakit: 1.3.11_sfoxds7t5ydpegc3knd667wn6m + react-colorful: 5.5.1_prpqlkd37azqwypxturxi7uyci + react-dates: 21.8.0_itvjtpkzdblwka3tnmrs5t5e6y + react-dom: 16.14.0_react@17.0.2 + reakit: 1.3.11_prpqlkd37azqwypxturxi7uyci uuid: 8.3.2 transitivePeerDependencies: - '@babel/core' @@ -16067,6 +16079,12 @@ packages: '@babel/runtime': 7.19.0 dev: false + /@wordpress/hooks/3.26.0: + resolution: {integrity: sha512-NYFnKttKLdkr7OZMqqRgsuQy1LMHjK7tkrGO9NWgrGkvwsaWEIn0hI65za9/TJnUccEBMwl9knsiGA4Fwe7dAA==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.19.0 + /@wordpress/hooks/3.4.1: resolution: {integrity: sha512-+RODEvxNsx3KW5nvk4KbnYMNFYvWA4Fduf784Ht7PJoL2L3z63Wm2blRh7a21hDEFHGPgWkne0f3Fgr/dqalHA==} engines: {node: '>=12'} @@ -16146,6 +16164,18 @@ packages: sprintf-js: 1.1.2 tannin: 1.2.0 + /@wordpress/i18n/4.26.0: + resolution: {integrity: sha512-W94aIByO+3YraI7fJbk+3STnz3e0hhrtBPPjKK1XvT4+3RZiKPaVN2Y8mvCCknbaAILCT+CixUBJOgq6m6bwjQ==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@babel/runtime': 7.19.0 + '@wordpress/hooks': 3.26.0 + gettext-parser: 1.4.0 + memize: 1.1.0 + sprintf-js: 1.1.2 + tannin: 1.2.0 + /@wordpress/i18n/4.4.1: resolution: {integrity: sha512-cDD3dxynq0P+HwB1dwUD2xpmjTr55oyuB0Mybcr8N4sTrbl/bDj5VRrh8bdps7KSqIdM4OuMtxJKLbg3y5CktA==} engines: {node: '>=12'} @@ -16393,7 +16423,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.19.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 change-case: 4.1.2 lodash: 4.17.21 @@ -16730,7 +16760,7 @@ packages: '@wordpress/deprecated': 3.19.0 '@wordpress/element': 4.20.0 '@wordpress/escape-html': 2.22.0 - '@wordpress/i18n': 4.19.0 + '@wordpress/i18n': 4.26.0 '@wordpress/keycodes': 3.19.0 memize: 1.1.0 react: 17.0.2 @@ -25625,7 +25655,7 @@ packages: /history/5.3.0: resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} dependencies: - '@babel/runtime': 7.17.7 + '@babel/runtime': 7.19.0 /hjson/1.8.4: resolution: {integrity: sha1-C8j/sCGY0hjEm1iZGxH2BIUBHTY=} @@ -39168,6 +39198,7 @@ packages: optional: true dependencies: react: 17.0.2 + dev: true /use-latest/1.2.0_pxzommwrsowkd4kgag6q3sluym: resolution: {integrity: sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==}