Commit 446c9cde354 for woocommerce
commit 446c9cde35456d8ac393c6fcbcf4fe761e822d8c
Author: Luigi Teschio <gigitux@gmail.com>
Date: Mon Apr 27 16:34:26 2026 +0200
[DataViews - All Products] Refactor Product Actions (#64419)
* Refactor product actions and improve layout handling in the experimental products app
* lint code
* Update packages/js/experimental-products-app/typings/index.d.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix hook
* lint code
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
diff --git a/packages/js/experimental-products-app/src/dataviews-actions/index.tsx b/packages/js/experimental-products-app/src/dataviews-actions/index.tsx
index 5dd17ccc0e2..0eb95004f04 100644
--- a/packages/js/experimental-products-app/src/dataviews-actions/index.tsx
+++ b/packages/js/experimental-products-app/src/dataviews-actions/index.tsx
@@ -1,45 +1,64 @@
/**
* External dependencies
*/
+import { edit, external } from '@wordpress/icons';
+import { __, _x } from '@wordpress/i18n';
+import { addQueryArgs } from '@wordpress/url';
+import { getAdminLink } from '@woocommerce/settings';
+import type { Action } from '@wordpress/dataviews';
import { useMemo } from '@wordpress/element';
-import { edit } from '@wordpress/icons';
-import { privateApis as routerPrivateApis } from '@wordpress/router';
-import { __ } from '@wordpress/i18n';
-import { Product } from '@woocommerce/data';
/**
* Internal dependencies
*/
-import { unlock } from '../lock-unlock';
-
-const { useHistory, useLocation } = unlock( routerPrivateApis );
-
-export const useEditProductAction = ( { postType }: { postType: string } ) => {
- const history = useHistory();
- const location = useLocation();
- return useMemo(
- () => ( {
- id: 'edit-product',
- label: __( 'Edit', 'woocommerce' ),
- isPrimary: true,
- icon: edit,
- supportsBulk: true,
- isEligible( product: Product ) {
- if ( product.status === 'trash' ) {
- return false;
- }
- return true;
- },
- callback( items: Product[] ) {
- const product = items[ 0 ];
- history.push( {
- ...location.params,
- postId: product.id,
- postType,
- quickEdit: true,
- } );
- },
- } ),
- [ history, location.params ]
- );
-};
+import type { ProductEntityRecord } from '../fields/types';
+
+export const editAction = (): Action< ProductEntityRecord > => ( {
+ id: 'edit-product',
+ label: __( 'Edit', 'woocommerce' ),
+ isPrimary: true,
+ icon: edit,
+ isEligible( product ) {
+ return product.status !== 'trash';
+ },
+ callback( items, { onActionPerformed } ) {
+ const product = items[ 0 ];
+
+ if ( product ) {
+ window.location.href = getAdminLink(
+ addQueryArgs( 'post.php', {
+ post: product.id,
+ action: 'edit',
+ } )
+ );
+ }
+
+ if ( onActionPerformed ) {
+ onActionPerformed( items );
+ }
+ },
+} );
+
+export const viewAction = (): Action< ProductEntityRecord > => ( {
+ id: 'view-product',
+ label: _x( 'View', 'verb', 'woocommerce' ),
+ isPrimary: true,
+ icon: external,
+ isEligible( product ) {
+ return product.status !== 'trash' && !! product.permalink;
+ },
+ callback( items, { onActionPerformed } ) {
+ const product = items[ 0 ];
+
+ if ( product?.permalink ) {
+ window.open( product.permalink, '_blank' );
+ }
+
+ if ( onActionPerformed ) {
+ onActionPerformed( items );
+ }
+ },
+} );
+
+export const useProductActions = () =>
+ useMemo( () => [ editAction(), viewAction() ], [] );
diff --git a/packages/js/experimental-products-app/src/fields/utils/currency.ts b/packages/js/experimental-products-app/src/fields/utils/currency.ts
index 612ad8991b8..97438f35890 100644
--- a/packages/js/experimental-products-app/src/fields/utils/currency.ts
+++ b/packages/js/experimental-products-app/src/fields/utils/currency.ts
@@ -1,10 +1,7 @@
/**
* External dependencies
*/
-import {
- CURRENCY,
- // @ts-expect-error - The CURRENCY object doesn't have types yet.
-} from '@woocommerce/settings';
+import { CURRENCY } from '@woocommerce/settings';
type CurrencyObject = {
code: string;
diff --git a/packages/js/experimental-products-app/src/product-list/index.tsx b/packages/js/experimental-products-app/src/product-list/index.tsx
index 35bcc12e916..ee6e2690944 100644
--- a/packages/js/experimental-products-app/src/product-list/index.tsx
+++ b/packages/js/experimental-products-app/src/product-list/index.tsx
@@ -2,22 +2,18 @@
* External dependencies
*/
import { DataViews, View } from '@wordpress/dataviews';
-import {
- useState,
- useMemo,
- useCallback,
- useEffect,
- Fragment,
-} from '@wordpress/element';
-import { ProductQuery, productsStore } from '@woocommerce/data';
+import { useState, useMemo, useCallback, useEffect } from '@wordpress/element';
+import { ProductQuery } from '@woocommerce/data';
import { privateApis as routerPrivateApis } from '@wordpress/router';
-import { store as coreStore } from '@wordpress/core-data';
+import { store as coreStore, useEntityRecords } from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import clsx from 'clsx';
-import { Button } from '@wordpress/components';
+import { Button, Stack } from '@wordpress/ui';
import { privateApis as editorPrivateApis } from '@wordpress/editor';
import { Page } from '@wordpress/admin-ui';
+import { addQueryArgs } from '@wordpress/url';
+import { getAdminLink } from '@woocommerce/settings';
/**
* Internal dependencies
@@ -29,7 +25,7 @@ import {
DEFAULT_PRODUCT_TABLE_LAYOUT,
DEFAULT_PRODUCT_TABLE_VIEW,
} from './layouts';
-import { useEditProductAction } from '../dataviews-actions';
+import { useProductActions } from '../dataviews-actions';
const { usePostActions } = unlock( editorPrivateApis );
const { useHistory, useLocation } = unlock( routerPrivateApis );
@@ -79,11 +75,7 @@ function getItemId( item: ProductEntityRecord ) {
return item.id.toString();
}
-export default function ProductList( {
- subTitle,
- className,
- hideTitleFromUI = false,
-}: ProductListProps ) {
+export default function ProductList( { className }: ProductListProps ) {
const history = useHistory();
const location = useLocation();
const {
@@ -133,19 +125,14 @@ export default function ProductList( {
[ history, location.params ]
);
- // TODO: Use the Woo data store to get all the products, as this doesn't contain all the product data.
- const { records, totalCount, isLoading } = useSelect(
- ( select ) => {
- // @ts-expect-error - The productsStore doesn't have types yet.
- const { getProducts, getProductsTotalCount, isResolving } =
- select( productsStore );
- return {
- records: getProducts( queryParams ) as ProductEntityRecord[],
- totalCount: getProductsTotalCount( queryParams ),
- isLoading: isResolving( 'getProducts', [ queryParams ] ),
- };
- },
- [ queryParams ]
+ const {
+ records,
+ totalItems: totalCount,
+ isResolving: isLoading,
+ } = useEntityRecords< ProductEntityRecord >(
+ 'root',
+ 'product',
+ queryParams
);
const paginationInfo = useMemo(
@@ -158,14 +145,10 @@ export default function ProductList( {
[ totalCount, view.perPage ]
);
- const { labels, canCreateRecord } = useSelect(
+ const { canCreateRecord } = useSelect(
( select ) => {
- const { getPostType, canUser } = select( coreStore );
- const postTypeData:
- | { labels: Record< string, string > }
- | undefined = getPostType( postType );
+ const { canUser } = select( coreStore );
return {
- labels: postTypeData?.labels,
canCreateRecord: canUser( 'create', {
kind: 'postType',
name: postType,
@@ -179,36 +162,74 @@ export default function ProductList( {
postType,
context: 'list',
} );
- const editAction = useEditProductAction( { postType } );
+ const productActions = useProductActions();
const actions = useMemo(
- () => [ editAction, ...postTypeActions ],
- [ postTypeActions, editAction ]
+ () => [
+ ...productActions,
+ ...postTypeActions.filter(
+ ( { id }: { id: string } ) => id !== 'view-post'
+ ),
+ ],
+ [ postTypeActions, productActions ]
);
const classes = clsx( 'edit-site-page', className );
- const pageActions = ! hideTitleFromUI && (
- <Fragment>
- { labels?.add_new_item && canCreateRecord && (
- <Button
- variant="primary"
- disabled={ true }
- __next40pxDefaultSize
- >
- { labels.add_new_item }
- </Button>
- ) }
- </Fragment>
+ const pageActions = (
+ <Stack gap="lg">
+ <Button
+ size="compact"
+ variant="outline"
+ onClick={ () =>
+ ( window.location.href = getAdminLink(
+ addQueryArgs( 'edit.php', {
+ post_type: 'product',
+ page: 'product_exporter',
+ } )
+ ) )
+ }
+ >
+ { __( 'Export', 'woocommerce' ) }
+ </Button>
+ <Button
+ size="compact"
+ onClick={ () =>
+ ( window.location.href = getAdminLink(
+ addQueryArgs( 'edit.php', {
+ post_type: 'product',
+ page: 'product_importer',
+ } )
+ ) )
+ }
+ variant="outline"
+ >
+ { __( 'Import', 'woocommerce' ) }
+ </Button>
+ <Button
+ size="compact"
+ disabled={ canCreateRecord === false }
+ onClick={ () =>
+ ( window.location.href = getAdminLink(
+ addQueryArgs( 'post-new.php', {
+ post_type: 'product',
+ } )
+ ) )
+ }
+ >
+ { __( 'Add new product', 'woocommerce' ) }
+ </Button>
+ </Stack>
);
return (
<Page
className={ classes }
ariaLabel={ __( 'Products', 'woocommerce' ) }
- title={
- hideTitleFromUI ? undefined : __( 'Products', 'woocommerce' )
- }
- subTitle={ hideTitleFromUI ? undefined : subTitle }
+ subTitle={ __(
+ 'Add, edit, and manage the products you sell in your store',
+ 'woocommerce'
+ ) }
+ title={ __( 'Products', 'woocommerce' ) }
actions={ pageActions }
>
<DataViews
diff --git a/packages/js/experimental-products-app/src/style.scss b/packages/js/experimental-products-app/src/style.scss
index bafa651beb2..7b59e23fc22 100644
--- a/packages/js/experimental-products-app/src/style.scss
+++ b/packages/js/experimental-products-app/src/style.scss
@@ -1,5 +1,7 @@
+@import "../node_modules/@wordpress/theme/src/prebuilt/css/design-tokens.css";
@import "@wordpress/components/build-style/style.css";
@import "@wordpress/dataviews/build-style/style.css";
+@import "@wordpress/admin-ui/build-style/style.css";
.product_page_woocommerce-products-dashboard {
background-color: unset;
diff --git a/packages/js/experimental-products-app/tsconfig.json b/packages/js/experimental-products-app/tsconfig.json
index 376e1607afd..7611087fbe7 100644
--- a/packages/js/experimental-products-app/tsconfig.json
+++ b/packages/js/experimental-products-app/tsconfig.json
@@ -10,9 +10,16 @@
"resolveJsonModule": true,
"jsx": "react-jsx",
"jsxFactory": null,
- "jsxFragmentFactory": null
+ "jsxFragmentFactory": null,
+ "typeRoots": [
+ "./typings",
+ "./node_modules/@types"
+ ]
},
- "include": [ "src/**/*" ],
+ "include": [
+ "typings/**/*",
+ "src/**/*"
+ ],
"exclude": [
"**/test/**",
"**/stories/**",
diff --git a/packages/js/experimental-products-app/typings/index.d.ts b/packages/js/experimental-products-app/typings/index.d.ts
new file mode 100644
index 00000000000..6c9a0270236
--- /dev/null
+++ b/packages/js/experimental-products-app/typings/index.d.ts
@@ -0,0 +1,16 @@
+declare module '@woocommerce/settings' {
+ export declare const CURRENCY: {
+ code: string;
+ precision: number;
+ symbol: string;
+ symbolPosition: string;
+ decimalSeparator?: string;
+ thousandSeparator?: string;
+ };
+ export declare function getAdminLink( path: string ): string;
+ export function getSetting< T >(
+ name: string,
+ fallback?: unknown,
+ filter?: ( val: unknown, fb: unknown ) => unknown
+ ): T;
+}