diff --git a/packages/js/components/changelog/add-29_product_link_slug b/packages/js/components/changelog/add-29_product_link_slug
new file mode 100644
index 00000000000..a0e4f41a088
--- /dev/null
+++ b/packages/js/components/changelog/add-29_product_link_slug
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Update resetForm arguments, adding changed fields, touched fields and errors.
diff --git a/packages/js/components/src/form/form-context.ts b/packages/js/components/src/form/form-context.ts
index e1d281a358f..ef00f36586b 100644
--- a/packages/js/components/src/form/form-context.ts
+++ b/packages/js/components/src/form/form-context.ts
@@ -31,7 +31,12 @@ export type FormContext< Values extends Record< string, any > > = {
help: string | null | undefined;
};
isValidForm: boolean;
- resetForm: ( initialValues: Values ) => void;
+ resetForm: (
+ initialValues: Values,
+ changedFields?: { [ P in keyof Values ]?: boolean | undefined },
+ touchedFields?: { [ P in keyof Values ]?: boolean | undefined },
+ errors?: { [ P in keyof Values ]?: string }
+ ) => void;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/packages/js/components/src/form/form.tsx b/packages/js/components/src/form/form.tsx
index f22e6ad60bb..f76132841e9 100644
--- a/packages/js/components/src/form/form.tsx
+++ b/packages/js/components/src/form/form.tsx
@@ -110,11 +110,16 @@ function FormComponent< Values extends Record< string, any > >(
validate( values );
}, [] );
- const resetForm = ( newInitialValues: Values ) => {
+ const resetForm = (
+ newInitialValues: Values,
+ newChangedFields = {},
+ newTouchedFields = {},
+ newErrors = {}
+ ) => {
setValues( newInitialValues || {} );
- setChangedFields( {} );
- setTouched( {} );
- setErrors( {} );
+ setChangedFields( newChangedFields );
+ setTouched( newTouchedFields );
+ setErrors( newErrors );
};
useImperativeHandle( ref, () => ( {
diff --git a/packages/js/data/changelog/add-29_product_link_slug b/packages/js/data/changelog/add-29_product_link_slug
new file mode 100644
index 00000000000..260a9ca1f67
--- /dev/null
+++ b/packages/js/data/changelog/add-29_product_link_slug
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Update types for update and create product.
diff --git a/packages/js/data/src/products/actions.ts b/packages/js/data/src/products/actions.ts
index 399e6da44a9..d340bbcae64 100644
--- a/packages/js/data/src/products/actions.ts
+++ b/packages/js/data/src/products/actions.ts
@@ -127,7 +127,7 @@ export function getProductsTotalCountError(
}
export function* createProduct(
- data: Omit< Product, ReadOnlyProperties >
+ data: Partial< Omit< Product, ReadOnlyProperties > >
): Generator< unknown, Product, Product > {
yield createProductStart();
try {
@@ -147,7 +147,7 @@ export function* createProduct(
export function* updateProduct(
id: number,
- data: Omit< Product, ReadOnlyProperties >
+ data: Partial< Omit< Product, ReadOnlyProperties > >
): Generator< unknown, Product, Product > {
yield updateProductStart( id );
try {
diff --git a/packages/js/data/src/products/constants.ts b/packages/js/data/src/products/constants.ts
index a6cccbcaf06..251ff06f535 100644
--- a/packages/js/data/src/products/constants.ts
+++ b/packages/js/data/src/products/constants.ts
@@ -1,3 +1,4 @@
export const STORE_NAME = 'wc/admin/products';
export const WC_PRODUCT_NAMESPACE = '/wc/v3/products';
+export const PERMALINK_PRODUCT_REGEX = /%(?:postname|pagename)%/;
diff --git a/packages/js/data/src/products/index.ts b/packages/js/data/src/products/index.ts
index 0d1cd5a38ec..0d3087d3ff5 100644
--- a/packages/js/data/src/products/index.ts
+++ b/packages/js/data/src/products/index.ts
@@ -19,6 +19,7 @@ registerStore< State >( STORE_NAME, {
reducer: reducer as Reducer< ProductState >,
actions,
controls,
+ // @ts-expect-error as the registerStore type is not allowing the createRegistrySelector selector.
selectors,
resolvers,
} );
diff --git a/packages/js/data/src/products/selectors.ts b/packages/js/data/src/products/selectors.ts
index 251c69181db..ef1d3cc1bd5 100644
--- a/packages/js/data/src/products/selectors.ts
+++ b/packages/js/data/src/products/selectors.ts
@@ -2,6 +2,7 @@
* External dependencies
*/
import createSelector from 'rememo';
+import { createRegistrySelector } from '@wordpress/data';
/**
* Internal dependencies
@@ -14,6 +15,7 @@ import { WPDataSelector, WPDataSelectors } from '../types';
import { ProductState } from './reducer';
import { PartialProduct, ProductQuery } from './types';
import { ActionDispatchers } from './actions';
+import { PERMALINK_PRODUCT_REGEX } from './constants';
export const getProduct = (
state: ProductState,
@@ -120,6 +122,39 @@ export const isPending = (
return false;
};
+export const getPermalinkParts = createRegistrySelector(
+ ( select ) => ( state: ProductState, productId: number ) => {
+ const product = select( 'core' ).getEntityRecord(
+ 'postType',
+ 'product',
+ productId,
+ // @ts-expect-error query object is not part of the @wordpress/core-data types yet.
+ {
+ _fields: [
+ 'id',
+ 'permalink_template',
+ 'slug',
+ 'generated_slug',
+ ],
+ }
+ );
+ if ( product && product.permalink_template ) {
+ const postName = product.slug || product.generated_slug;
+
+ const [ prefix, suffix ] = product.permalink_template.split(
+ PERMALINK_PRODUCT_REGEX
+ );
+
+ return {
+ prefix,
+ postName,
+ suffix,
+ };
+ }
+ return null;
+ }
+);
+
export type ProductsSelectors = {
getCreateProductError: WPDataSelector< typeof getCreateProductError >;
getProduct: WPDataSelector< typeof getProduct >;
@@ -127,4 +162,7 @@ export type ProductsSelectors = {
getProductsTotalCount: WPDataSelector< typeof getProductsTotalCount >;
getProductsError: WPDataSelector< typeof getProductsError >;
isPending: WPDataSelector< typeof isPending >;
+ getPermalinkParts: (
+ productId: number
+ ) => { prefix: string; postName: string; suffix: string } | null;
} & WPDataSelectors;
diff --git a/plugins/woocommerce-admin/client/products/add-product-page.tsx b/plugins/woocommerce-admin/client/products/add-product-page.tsx
index 6fdcfe3e908..2ac487d0548 100644
--- a/plugins/woocommerce-admin/client/products/add-product-page.tsx
+++ b/plugins/woocommerce-admin/client/products/add-product-page.tsx
@@ -12,7 +12,6 @@ import { Product } from '@woocommerce/data';
import { ProductFormLayout } from './layout/product-form-layout';
import { ProductFormActions } from './product-form-actions';
import { ProductDetailsSection } from './sections/product-details-section';
-import { ProductImagesSection } from './sections/product-images-section';
import './product-page.scss';
const AddProductPage: React.FC = () => {
diff --git a/plugins/woocommerce-admin/client/products/edit-product-page.tsx b/plugins/woocommerce-admin/client/products/edit-product-page.tsx
index 2046cdf4dc5..a1dfab56a17 100644
--- a/plugins/woocommerce-admin/client/products/edit-product-page.tsx
+++ b/plugins/woocommerce-admin/client/products/edit-product-page.tsx
@@ -28,14 +28,32 @@ const EditProductPage: React.FC = () => {
const formRef = useRef< FormRef< Partial< Product > > >( null );
const { product, isLoading, isPendingAction } = useSelect(
( select: WCDataSelector ) => {
- const { getProduct, hasFinishedResolution, isPending } =
- select( PRODUCTS_STORE_NAME );
+ const {
+ getProduct,
+ hasFinishedResolution,
+ isPending,
+ getPermalinkParts,
+ } = select( PRODUCTS_STORE_NAME );
if ( productId ) {
+ const retrievedProduct = getProduct(
+ parseInt( productId, 10 ),
+ undefined
+ );
+ const permalinkParts = getPermalinkParts(
+ parseInt( productId, 10 )
+ );
return {
- product: getProduct( parseInt( productId, 10 ), undefined ),
- isLoading: ! hasFinishedResolution( 'getProduct', [
- parseInt( productId, 10 ),
- ] ),
+ product:
+ permalinkParts && retrievedProduct
+ ? retrievedProduct
+ : undefined,
+ isLoading:
+ ! hasFinishedResolution( 'getProduct', [
+ parseInt( productId, 10 ),
+ ] ) ||
+ ! hasFinishedResolution( 'getPermalinkParts', [
+ parseInt( productId, 10 ),
+ ] ),
isPendingAction:
isPending( 'createProduct' ) ||
isPending(
diff --git a/plugins/woocommerce-admin/client/products/layout/product-section-layout.scss b/plugins/woocommerce-admin/client/products/layout/product-section-layout.scss
index b7ace9dd586..9ff2510049c 100644
--- a/plugins/woocommerce-admin/client/products/layout/product-section-layout.scss
+++ b/plugins/woocommerce-admin/client/products/layout/product-section-layout.scss
@@ -8,7 +8,6 @@
flex-direction: column;
align-items: flex-start;
padding: $gap-large;
- gap: $gap-smaller;
background: $white;
border: 1px solid $gray-400;
@@ -18,4 +17,8 @@
width: 100%;
}
}
+
+ .product-field-layout:not(:first-child) {
+ margin-top: $gap;
+ }
}
diff --git a/plugins/woocommerce-admin/client/products/layout/product-section-layout.tsx b/plugins/woocommerce-admin/client/products/layout/product-section-layout.tsx
index 05feace6bcf..26fef357c8f 100644
--- a/plugins/woocommerce-admin/client/products/layout/product-section-layout.tsx
+++ b/plugins/woocommerce-admin/client/products/layout/product-section-layout.tsx
@@ -29,7 +29,7 @@ export const ProductSectionLayout: React.FC< ProductSectionLayoutProps > = ( {
{ Children.map( children, ( child ) => {
- if ( isValidElement( child ) && child.props.name ) {
+ if ( isValidElement( child ) && child.props.onChange ) {
return (
{
};
} );
-const SampleInputField: React.FC< { name: string } > = ( { name } ) => {
+const SampleInputField: React.FC< { name: string; onChange: () => void } > = ( {
+ name,
+} ) => {
return smaple-input-field-{ name }
;
};
@@ -46,14 +48,14 @@ describe( 'ProductSectionLayout', () => {
expect( queryByText( 'This is a description' ) ).toBeInTheDocument();
} );
- it( 'should wrap children in ProductFieldLayout if prop contains name', () => {
+ it( 'should wrap children in ProductFieldLayout if prop contains onChange', () => {
const { queryByText, queryAllByText } = render(
-
-
+ {} } />
+ {} } />
);
@@ -66,7 +68,7 @@ describe( 'ProductSectionLayout', () => {
).toBeInTheDocument();
} );
- it( 'should not wrap children in ProductFieldLayout if prop does not contain name', () => {
+ it( 'should not wrap children in ProductFieldLayout if prop does not contain onChange', () => {
const { queryByText, queryAllByText } = render(
{
if ( ! values.id ) {
createProductWithStatus( values, 'draft' );
} else {
- const product = await updateProductWithStatus( values, 'draft' );
+ const product = await updateProductWithStatus(
+ values.id,
+ values,
+ 'draft'
+ );
if ( product && product.id ) {
resetForm( product );
}
@@ -65,7 +69,11 @@ export const ProductFormActions: React.FC = () => {
if ( ! values.id ) {
createProductWithStatus( values, 'publish' );
} else {
- const product = await updateProductWithStatus( values, 'publish' );
+ const product = await updateProductWithStatus(
+ values.id,
+ values,
+ 'publish'
+ );
if ( product && product.id ) {
resetForm( product );
}
@@ -78,7 +86,7 @@ export const ProductFormActions: React.FC = () => {
...getProductDataForTracks(),
} );
if ( values.id ) {
- await updateProductWithStatus( values, 'publish' );
+ await updateProductWithStatus( values.id, values, 'publish' );
} else {
await createProductWithStatus( values, 'publish', false, true );
}
@@ -91,7 +99,11 @@ export const ProductFormActions: React.FC = () => {
...getProductDataForTracks(),
} );
if ( values.id ) {
- await updateProductWithStatus( values, values.status || 'draft' );
+ await updateProductWithStatus(
+ values.id,
+ values,
+ values.status || 'draft'
+ );
}
await copyProductWithStatus( values );
};
diff --git a/plugins/woocommerce-admin/client/products/sections/product-details-section.scss b/plugins/woocommerce-admin/client/products/sections/product-details-section.scss
new file mode 100644
index 00000000000..2eda224f181
--- /dev/null
+++ b/plugins/woocommerce-admin/client/products/sections/product-details-section.scss
@@ -0,0 +1,18 @@
+.product-details-section {
+ &__product-link {
+ color: $gray-700;
+ font-size: 12px;
+
+ > a {
+ color: inherit;
+ text-decoration: none;
+ font-weight: 600;
+ }
+
+ .components-button.is-link {
+ font-size: 12px;
+ text-decoration: none;
+ margin-left: $gap-smaller;
+ }
+ }
+}
diff --git a/plugins/woocommerce-admin/client/products/sections/product-details-section.tsx b/plugins/woocommerce-admin/client/products/sections/product-details-section.tsx
index be5ac39e09b..b549105a8d9 100644
--- a/plugins/woocommerce-admin/client/products/sections/product-details-section.tsx
+++ b/plugins/woocommerce-admin/client/products/sections/product-details-section.tsx
@@ -1,22 +1,47 @@
/**
* External dependencies
*/
-import { CheckboxControl, TextControl } from '@wordpress/components';
+import { CheckboxControl, Button, TextControl } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+import { cleanForSlug } from '@wordpress/url';
import { EnrichedLabel, useFormContext } from '@woocommerce/components';
-import { Product } from '@woocommerce/data';
+import {
+ Product,
+ PRODUCTS_STORE_NAME,
+ WCDataSelector,
+} from '@woocommerce/data';
import classnames from 'classnames';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
+import './product-details-section.scss';
import { ProductSectionLayout } from '../layout/product-section-layout';
+import { EditProductLinkModal } from '../shared/edit-product-link-modal';
const PRODUCT_DETAILS_SLUG = 'product-details';
export const ProductDetailsSection: React.FC = () => {
- const { getInputProps } = useFormContext< Product >();
+ const { getInputProps, values } = useFormContext< Product >();
+ const [ showProductLinkEditModal, setShowProductLinkEditModal ] =
+ useState( false );
+ const { permalinkPrefix, permalinkSuffix } = useSelect(
+ ( select: WCDataSelector ) => {
+ const { getPermalinkParts } = select( PRODUCTS_STORE_NAME );
+ if ( values.id ) {
+ const parts = getPermalinkParts( values.id );
+ return {
+ permalinkPrefix: parts?.prefix,
+ permalinkSuffix: parts?.suffix,
+ };
+ }
+ return {};
+ }
+ );
+
const getCheckboxProps = ( item: string ) => {
const { checked, className, onChange, onBlur } =
getInputProps< boolean >( item );
@@ -67,6 +92,26 @@ export const ProductDetailsSection: React.FC = () => {
placeholder={ __( 'e.g. 12 oz Coffee Mug', 'woocommerce' ) }
{ ...getTextControlProps( 'name' ) }
/>
+ { values.id && permalinkPrefix && (
+
+ ) }
{
}
{ ...getCheckboxProps( 'featured' ) }
/>
+ { showProductLinkEditModal && (
+ setShowProductLinkEditModal( false ) }
+ onSaved={ () => setShowProductLinkEditModal( false ) }
+ />
+ ) }
);
};
diff --git a/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.scss b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.scss
new file mode 100644
index 00000000000..38ed78ef5dc
--- /dev/null
+++ b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.scss
@@ -0,0 +1,11 @@
+.woocommerce-product-link-edit-modal {
+ min-width: 650px;
+
+ &__buttons {
+ margin-top: $gap-larger;
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ justify-content: flex-end;
+ }
+}
diff --git a/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.tsx b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.tsx
new file mode 100644
index 00000000000..4574ba5ab43
--- /dev/null
+++ b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/edit-product-link-modal.tsx
@@ -0,0 +1,134 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Button, Modal, TextControl } from '@wordpress/components';
+import { useState } from '@wordpress/element';
+import { useDispatch } from '@wordpress/data';
+import { cleanForSlug } from '@wordpress/url';
+import { Product } from '@woocommerce/data';
+import { Text } from '@woocommerce/experimental';
+import { useFormContext } from '@woocommerce/components';
+import { recordEvent } from '@woocommerce/tracks';
+
+/**
+ * Internal dependencies
+ */
+import './edit-product-link-modal.scss';
+import { useProductHelper } from '../../use-product-helper';
+
+type EditProductLinkModalProps = {
+ product: Product;
+ permalinkPrefix: string;
+ permalinkSuffix: string;
+ onCancel: () => void;
+ onSaved: () => void;
+};
+
+export const EditProductLinkModal: React.FC< EditProductLinkModalProps > = ( {
+ product,
+ permalinkPrefix,
+ permalinkSuffix,
+ onCancel,
+ onSaved,
+} ) => {
+ const { createNotice } = useDispatch( 'core/notices' );
+ const { updateProductWithStatus, isUpdatingDraft, isUpdatingPublished } =
+ useProductHelper();
+ const [ slug, setSlug ] = useState(
+ product.slug || cleanForSlug( product.name )
+ );
+ const { resetForm, changedFields, touched, errors } =
+ useFormContext< Product >();
+
+ const onSave = async () => {
+ recordEvent( 'product_update_slug', {
+ new_product_page: true,
+ product_id: product.id,
+ product_type: product.type,
+ } );
+ const updatedProduct = await updateProductWithStatus(
+ product.id,
+ {
+ slug,
+ },
+ product.status,
+ true
+ );
+ if ( updatedProduct && updatedProduct.id ) {
+ // only reset the updated slug and permalink fields.
+ resetForm(
+ {
+ ...product,
+ slug: updatedProduct.slug,
+ permalink: updatedProduct.permalink,
+ },
+ changedFields,
+ touched,
+ errors
+ );
+ createNotice(
+ updatedProduct.slug === cleanForSlug( slug )
+ ? 'success'
+ : 'info',
+ updatedProduct.slug === cleanForSlug( slug )
+ ? __( 'Product link successfully updated.', 'woocommerce' )
+ : __(
+ 'Product link already existed, updated to ',
+ 'woocommerce'
+ ) + updatedProduct.permalink
+ );
+ } else {
+ createNotice(
+ 'error',
+ __( 'Failed to update product link.', 'woocommerce' )
+ );
+ }
+ onSaved();
+ };
+
+ const newProductLinkLabel =
+ permalinkPrefix + cleanForSlug( slug ) + permalinkSuffix;
+
+ return (
+ onCancel() }
+ className="woocommerce-product-link-edit-modal"
+ >
+
+
+
+ { __(
+ "Use simple, descriptive words and numbers. We'll replace spaces with hyphens (-).",
+ 'woocommerce'
+ ) }
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/index.ts b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/index.ts
new file mode 100644
index 00000000000..fdc1873765c
--- /dev/null
+++ b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/index.ts
@@ -0,0 +1 @@
+export * from './edit-product-link-modal';
diff --git a/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/test/edit-product-link-modal.test.tsx b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/test/edit-product-link-modal.test.tsx
new file mode 100644
index 00000000000..ac78d8da369
--- /dev/null
+++ b/plugins/woocommerce-admin/client/products/shared/edit-product-link-modal/test/edit-product-link-modal.test.tsx
@@ -0,0 +1,87 @@
+/**
+ * External dependencies
+ */
+import { render } from '@testing-library/react';
+import { Product } from '@woocommerce/data';
+import userEvent from '@testing-library/user-event';
+
+/**
+ * Internal dependencies
+ */
+import { EditProductLinkModal } from '../';
+
+describe( 'EditProductLinkModal', () => {
+ beforeEach( () => {
+ jest.clearAllMocks();
+ } );
+
+ it( 'should show a field with the permalink as label', () => {
+ const { queryByText } = render(
+ {} }
+ onSaved={ () => {} }
+ />
+ );
+ expect(
+ queryByText( 'wootesting.com/product/test' )
+ ).toBeInTheDocument();
+ } );
+
+ it( 'should update the permalink label as the slug is being updated', () => {
+ const { queryByText, getByLabelText } = render(
+ {} }
+ onSaved={ () => {} }
+ />
+ );
+ userEvent.type(
+ getByLabelText( 'wootesting.com/product/test' ),
+ '{esc}{space}update',
+ {}
+ );
+ expect(
+ queryByText( 'wootesting.com/product/test-update' )
+ ).toBeInTheDocument();
+ } );
+
+ it( 'should only update the end of the permalink incase the slug matches other parts of the url', () => {
+ const { queryByText, getByLabelText } = render(
+ {} }
+ onSaved={ () => {} }
+ />
+ );
+ userEvent.type(
+ getByLabelText( 'wootesting.com/product/product' ),
+ '{esc}{space}update',
+ {}
+ );
+ expect(
+ queryByText( 'wootesting.com/product/product-update' )
+ ).toBeInTheDocument();
+ } );
+} );
diff --git a/plugins/woocommerce-admin/client/products/test/product-form-actions.spec.tsx b/plugins/woocommerce-admin/client/products/test/product-form-actions.spec.tsx
index 159b205b21c..e6f335770c8 100644
--- a/plugins/woocommerce-admin/client/products/test/product-form-actions.spec.tsx
+++ b/plugins/woocommerce-admin/client/products/test/product-form-actions.spec.tsx
@@ -177,6 +177,7 @@ describe( 'ProductFormActions', () => {
);
queryByText( 'Save draft' )?.click();
expect( updateProductWithStatus ).toHaveBeenCalledWith(
+ product.id,
{ ...product, name: 'Name Update' },
'draft'
);
@@ -219,6 +220,7 @@ describe( 'ProductFormActions', () => {
manage_stock: true,
} );
expect( updateProductWithStatus ).toHaveBeenCalledWith(
+ product.id,
product,
'publish'
);
@@ -367,6 +369,7 @@ describe( 'ProductFormActions', () => {
);
updateProductWithStatus.mockReturnValue( Promise.resolve() );
expect( updateProductWithStatus ).toHaveBeenCalledWith(
+ product.id,
product,
'publish'
);
@@ -404,6 +407,7 @@ describe( 'ProductFormActions', () => {
} );
updateProductWithStatus.mockReturnValue( Promise.resolve() );
expect( updateProductWithStatus ).toHaveBeenCalledWith(
+ product.id,
product,
'publish'
);
diff --git a/plugins/woocommerce-admin/client/products/use-product-helper.ts b/plugins/woocommerce-admin/client/products/use-product-helper.ts
index a98aec38224..bd39b2e1a7f 100644
--- a/plugins/woocommerce-admin/client/products/use-product-helper.ts
+++ b/plugins/woocommerce-admin/client/products/use-product-helper.ts
@@ -107,12 +107,20 @@ export function useProductHelper() {
}
},
() => {
- createNotice(
- 'error',
- status === 'publish'
- ? __( 'Failed to publish product.', 'woocommerce' )
- : __( 'Failed to create product.', 'woocommerce' )
- );
+ if ( ! skipNotice ) {
+ createNotice(
+ 'error',
+ status === 'publish'
+ ? __(
+ 'Failed to publish product.',
+ 'woocommerce'
+ )
+ : __(
+ 'Failed to create product.',
+ 'woocommerce'
+ )
+ );
+ }
setUpdating( {
...updating,
[ status ]: false,
@@ -126,14 +134,16 @@ export function useProductHelper() {
/**
* Update product with status.
*
- * @param {Product} product the product to be updated (should contain product id).
+ * @param {number} productId the product id to be updated.
+ * @param {Product} product the product to be updated.
* @param {string} status the product status.
* @param {boolean} skipNotice if the notice should be skipped (default: false).
* @return {Promise} Returns a promise with the updated product.
*/
const updateProductWithStatus = useCallback(
async (
- product: Product,
+ productId: number,
+ product: Partial< Product >,
status: ProductStatus,
skipNotice = false
): Promise< Product > => {
@@ -141,7 +151,7 @@ export function useProductHelper() {
...updating,
[ status ]: true,
} );
- return updateProduct( product.id, {
+ return updateProduct( productId, {
...product,
status,
} ).then(
@@ -174,10 +184,12 @@ export function useProductHelper() {
return updatedProduct;
},
( error ) => {
- createNotice(
- 'error',
- __( 'Failed to update product.', 'woocommerce' )
- );
+ if ( ! skipNotice ) {
+ createNotice(
+ 'error',
+ __( 'Failed to update product.', 'woocommerce' )
+ );
+ }
setUpdating( {
...updating,
[ status ]: false,
diff --git a/plugins/woocommerce/changelog/add-29_product_link_slug b/plugins/woocommerce/changelog/add-29_product_link_slug
new file mode 100644
index 00000000000..a3fd87da242
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-29_product_link_slug
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add product link field to the new edit product form.