diff --git a/plugins/woocommerce-admin/client/marketplace/components/discover/discover.tsx b/plugins/woocommerce-admin/client/marketplace/components/discover/discover.tsx index b16ecb68098..52842d3625f 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/discover/discover.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/discover/discover.tsx @@ -8,21 +8,29 @@ import { useEffect, useState } from '@wordpress/element'; */ import ProductList from '../product-list/product-list'; import { fetchDiscoverPageData, ProductGroup } from '../../utils/functions'; +import ProductLoader from '../product-loader/product-loader'; import './discover.scss'; export default function Discover(): JSX.Element | null { const [ productGroups, setProductGroups ] = useState< Array< ProductGroup > >( [] ); + const [ isLoading, setIsLoading ] = useState( false ); useEffect( () => { - fetchDiscoverPageData().then( ( products: Array< ProductGroup > ) => { - setProductGroups( products ); - } ); + setIsLoading( true ); + + fetchDiscoverPageData() + .then( ( products: Array< ProductGroup > ) => { + setProductGroups( products ); + } ) + .finally( () => { + setIsLoading( false ); + } ); }, [] ); - if ( ! productGroups.length ) { - return null; + if ( isLoading ) { + return ; } const groupsList = productGroups.flatMap( ( group ) => group ); diff --git a/plugins/woocommerce-admin/client/marketplace/components/product-list-content/no-results.tsx b/plugins/woocommerce-admin/client/marketplace/components/product-list-content/no-results.tsx index 10285383534..63a69d81407 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/product-list-content/no-results.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/product-list-content/no-results.tsx @@ -25,6 +25,8 @@ export default function NoResults(): JSX.Element { useEffect( () => { if ( query.term ) { setNoResultsTerm( query.term ); + + return; } if ( query.category ) { @@ -42,7 +44,7 @@ export default function NoResults(): JSX.Element { setisLoadingProductGroup( true ); fetchDiscoverPageData() - .then( ( products: Array< ProductGroup > ) => { + .then( ( products: ProductGroup[] ) => { const mostPopularGroup = products.find( ( group ) => group.id === 'most-popular' ); diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/product-list-context.tsx b/plugins/woocommerce-admin/client/marketplace/contexts/product-list-context.tsx index bdbdb14da56..9104602391d 100644 --- a/plugins/woocommerce-admin/client/marketplace/contexts/product-list-context.tsx +++ b/plugins/woocommerce-admin/client/marketplace/contexts/product-list-context.tsx @@ -23,15 +23,9 @@ export const ProductListContext = createContext< ProductListContextType >( { isLoading: false, } ); -type ProductListContextProviderProps = { +export function ProductListContextProvider( props: { children: JSX.Element; - country?: string; - locale?: string; -}; - -export function ProductListContextProvider( - props: ProductListContextProviderProps -): JSX.Element { +} ): JSX.Element { const [ isLoading, setIsLoading ] = useState( false ); const [ productList, setProductList ] = useState< Product[] >( [] ); @@ -47,9 +41,9 @@ export function ProductListContextProvider( const params = new URLSearchParams(); - params.append( 'term', query.term ?? '' ); - params.append( 'country', props.country ?? '' ); - params.append( 'locale', props.locale ?? '' ); + if ( query.term ) { + params.append( 'term', query.term ); + } if ( query.category ) { params.append( 'category', query.category ); @@ -94,7 +88,7 @@ export function ProductListContextProvider( .finally( () => { setIsLoading( false ); } ); - }, [ query, props.country, props.locale ] ); + }, [ query ] ); return ( diff --git a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx index 8695d3e9403..39b9d39d828 100644 --- a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx +++ b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx @@ -1,9 +1,15 @@ +/** + * External dependencies + */ +import apiFetch from '@wordpress/api-fetch'; + /** * Internal dependencies */ import { Product } from '../components/product-list/types'; import { MARKETPLACE_URL } from '../components/constants'; import { CategoryAPIItem } from '../components/category-selector/types'; +import { LOCALE } from '../../utils/admin-settings'; interface ProductGroup { id: string; @@ -13,26 +19,28 @@ interface ProductGroup { } // Fetch data for the discover page from the WooCommerce.com API -const fetchDiscoverPageData = async (): Promise< Array< ProductGroup > > => { - const fetchUrl = MARKETPLACE_URL + '/wp-json/wccom-extensions/2.0/featured'; +async function fetchDiscoverPageData(): Promise< ProductGroup[] > { + let url = '/wc/v3/marketplace/featured'; - return fetch( fetchUrl ) - .then( ( response ) => { - if ( ! response.ok ) { - throw new Error( response.statusText ); - } - return response.json(); - } ) - .then( ( json ) => { - return json; - } ) - .catch( () => { - return []; - } ); -}; + if ( LOCALE.userLocale ) { + url = `${ url }?locale=${ LOCALE.userLocale }`; + } + + try { + return await apiFetch( { path: url.toString() } ); + } catch ( error ) { + return []; + } +} function fetchCategories(): Promise< CategoryAPIItem[] > { - return fetch( MARKETPLACE_URL + '/wp-json/wccom-extensions/1.0/categories' ) + let url = MARKETPLACE_URL + '/wp-json/wccom-extensions/1.0/categories'; + + if ( LOCALE.userLocale ) { + url = `${ url }?locale=${ LOCALE.userLocale }`; + } + + return fetch( url.toString() ) .then( ( response ) => { if ( ! response.ok ) { throw new Error( response.statusText ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php index 41b0accf525..6f00b9dbfa0 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-addons.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-addons.php @@ -64,8 +64,24 @@ class WC_Admin_Addons { * @return void */ public static function render_featured() { + $featured = self::fetch_featured(); + + if ( is_wp_error( $featured ) ) { + self::output_empty( $featured->get_error_message() ); + } + + self::output_featured( $featured ); + } + + /** + * Fetch featured products from WCCOM's the Featured 2.0 Endpoint and cache the data for a day. + * + * @return array|WP_Error + */ + public static function fetch_featured() { $locale = get_user_locale(); $featured = self::get_locale_data_from_transient( 'wc_addons_featured', $locale ); + if ( false === $featured ) { $headers = array(); $auth = WC_Helper_Options::get( 'auth' ); @@ -96,9 +112,7 @@ class WC_Admin_Addons { ? __( 'We encountered an SSL error. Please ensure your site supports TLS version 1.2 or above.', 'woocommerce' ) : $raw_featured->get_error_message(); - self::output_empty( $message ); - - return; + return new WP_Error( 'wc-addons-connection-error', $message ); } $response_code = (int) wp_remote_retrieve_response_code( $raw_featured ); @@ -117,18 +131,15 @@ class WC_Admin_Addons { $response_code ); - self::output_empty( $message ); - - return; + return new WP_Error( 'wc-addons-connection-error', $message ); } $featured = json_decode( wp_remote_retrieve_body( $raw_featured ) ); if ( empty( $featured ) || ! is_array( $featured ) ) { do_action( 'woocommerce_page_wc-addons_connection_error', 'Empty or malformed response' ); $message = __( 'Our request to the featured API got a malformed response.', 'woocommerce' ); - self::output_empty( $message ); - return; + return new WP_Error( 'wc-addons-connection-error', $message ); } if ( $featured ) { @@ -136,7 +147,7 @@ class WC_Admin_Addons { } } - self::output_featured( $featured ); + return $featured; } /** diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php index f45fa4c370c..0d1484be925 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php @@ -25,6 +25,7 @@ class WC_Helper_Admin { */ public static function load() { add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) ); + add_filter( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) ); } /** @@ -82,6 +83,46 @@ class WC_Helper_Admin { return $connect_url; } + + /** + * Registers the REST routes for the featured products endpoint. + * This endpoint is used by the WooCommerce > Extensions > Discover + * page. + */ + public static function register_rest_routes() { + register_rest_route( + 'wc/v3', + '/marketplace/featured', + array( + 'methods' => 'GET', + 'callback' => array( __CLASS__, 'get_featured' ), + 'permission_callback' => array( __CLASS__, 'get_permission' ), + ) + ); + } + + /** + * The Extensions page can only be accessed by users with the manage_woocommerce + * capability. So the API mimics that behavior. + */ + public static function get_permission() { + return current_user_can( 'manage_woocommerce' ); + } + + /** + * Fetch featured procucts from WooCommerce.com and serve them + * as JSON. + */ + public static function get_featured() { + $featured = WC_Admin_Addons::fetch_featured(); + + if ( is_wp_error( $featured ) ) { + wp_send_json_error( array( 'message' => $featured->get_error_message() ) ); + } + + wp_send_json( $featured ); + } + } WC_Helper_Admin::load();