Add search results count to the in-app marketplace (#51266)
* Add searchResults to context * Use setSearchResults in Content * Add ribbons to the tabs * Changelog * Use setState as the function name * Only show ribbon counts when there's an active search * Refactor how 'setSearchResultsCount' is used (h/t @mcliwanow) * Don't populate initial search results * Unify css styling
This commit is contained in:
parent
b728f53f29
commit
ab0e76c943
|
@ -17,6 +17,8 @@ import MySubscriptions from '../my-subscriptions/my-subscriptions';
|
|||
import { MarketplaceContext } from '../../contexts/marketplace-context';
|
||||
import { fetchSearchResults } from '../../utils/functions';
|
||||
import { SubscriptionsContextProvider } from '../../contexts/subscriptions-context';
|
||||
import { SearchResultsCountType } from '../../contexts/types';
|
||||
|
||||
import {
|
||||
recordMarketplaceView,
|
||||
recordLegacyTabView,
|
||||
|
@ -30,40 +32,51 @@ import SubscriptionsExpiredExpiringNotice from '~/marketplace/components/my-subs
|
|||
export default function Content(): JSX.Element {
|
||||
const marketplaceContextValue = useContext( MarketplaceContext );
|
||||
const [ products, setProducts ] = useState< Product[] >( [] );
|
||||
const { setIsLoading, selectedTab, setHasBusinessServices } =
|
||||
marketplaceContextValue;
|
||||
const {
|
||||
setIsLoading,
|
||||
selectedTab,
|
||||
setHasBusinessServices,
|
||||
setSearchResultsCount,
|
||||
} = marketplaceContextValue;
|
||||
const query = useQuery();
|
||||
|
||||
// On initial load of the in-app marketplace, fetch extensions, themes and business services
|
||||
// and check if there are any business services available on WCCOM
|
||||
useEffect( () => {
|
||||
const categories = [ '', 'themes', 'business-services' ];
|
||||
const categories: Array< keyof SearchResultsCountType > = [
|
||||
'extensions',
|
||||
'themes',
|
||||
'business-services',
|
||||
];
|
||||
const abortControllers = categories.map( () => new AbortController() );
|
||||
|
||||
categories.forEach( ( category: string, index ) => {
|
||||
const params = new URLSearchParams();
|
||||
if ( category !== '' ) {
|
||||
params.append( 'category', category );
|
||||
}
|
||||
categories.forEach(
|
||||
( category: keyof SearchResultsCountType, index ) => {
|
||||
const params = new URLSearchParams();
|
||||
if ( category !== 'extensions' ) {
|
||||
params.append( 'category', category );
|
||||
}
|
||||
|
||||
const wccomSettings = getAdminSetting( 'wccomHelper', false );
|
||||
if ( wccomSettings.storeCountry ) {
|
||||
params.append( 'country', wccomSettings.storeCountry );
|
||||
}
|
||||
const wccomSettings = getAdminSetting( 'wccomHelper', false );
|
||||
if ( wccomSettings.storeCountry ) {
|
||||
params.append( 'country', wccomSettings.storeCountry );
|
||||
}
|
||||
|
||||
fetchSearchResults( params, abortControllers[ index ].signal ).then(
|
||||
( productList ) => {
|
||||
fetchSearchResults(
|
||||
params,
|
||||
abortControllers[ index ].signal
|
||||
).then( ( productList ) => {
|
||||
if ( category === 'business-services' ) {
|
||||
setHasBusinessServices( productList.length > 0 );
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
abortControllers.forEach( ( controller ) => {
|
||||
controller.abort();
|
||||
} );
|
||||
};
|
||||
} );
|
||||
return () => {
|
||||
abortControllers.forEach( ( controller ) => {
|
||||
controller.abort();
|
||||
} );
|
||||
};
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [] );
|
||||
|
||||
|
@ -108,6 +121,20 @@ export default function Content(): JSX.Element {
|
|||
fetchSearchResults( params, abortController.signal )
|
||||
.then( ( productList ) => {
|
||||
setProducts( productList );
|
||||
|
||||
if ( query.term ) {
|
||||
setSearchResultsCount( {
|
||||
extensions: productList.filter(
|
||||
( p ) => p.type === 'extension'
|
||||
).length,
|
||||
themes: productList.filter(
|
||||
( p ) => p.type === 'theme'
|
||||
).length,
|
||||
'business-services': productList.filter(
|
||||
( p ) => p.type === 'business-service'
|
||||
).length,
|
||||
} );
|
||||
}
|
||||
} )
|
||||
.catch( () => {
|
||||
setProducts( [] );
|
||||
|
@ -142,7 +169,9 @@ export default function Content(): JSX.Element {
|
|||
case 'extensions':
|
||||
return (
|
||||
<Products
|
||||
products={ products }
|
||||
products={ products.filter(
|
||||
( p ) => p.type === 'extension'
|
||||
) }
|
||||
categorySelector={ true }
|
||||
type={ ProductType.extension }
|
||||
/>
|
||||
|
@ -150,7 +179,9 @@ export default function Content(): JSX.Element {
|
|||
case 'themes':
|
||||
return (
|
||||
<Products
|
||||
products={ products }
|
||||
products={ products.filter(
|
||||
( p ) => p.type === 'theme'
|
||||
) }
|
||||
categorySelector={ true }
|
||||
type={ ProductType.theme }
|
||||
/>
|
||||
|
@ -158,7 +189,9 @@ export default function Content(): JSX.Element {
|
|||
case 'business-services':
|
||||
return (
|
||||
<Products
|
||||
products={ products }
|
||||
products={ products.filter(
|
||||
( p ) => p.type === 'business-service'
|
||||
) }
|
||||
categorySelector={ true }
|
||||
type={ ProductType.businessService }
|
||||
/>
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
text-align: center;
|
||||
z-index: 26;
|
||||
}
|
||||
|
||||
&__update-count {
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= $breakpoint-medium) {
|
||||
|
|
|
@ -35,45 +35,6 @@ interface Tabs {
|
|||
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
|
||||
const wooUpdateCount = wccomSettings?.wooUpdateCount ?? 0;
|
||||
|
||||
const tabs: Tabs = {
|
||||
search: {
|
||||
name: 'search',
|
||||
title: __( 'Search results', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
discover: {
|
||||
name: 'discover',
|
||||
title: __( 'Discover', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
extensions: {
|
||||
name: 'extensions',
|
||||
title: __( 'Extensions', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
themes: {
|
||||
name: 'themes',
|
||||
title: __( 'Themes', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
'business-services': {
|
||||
name: 'business-services',
|
||||
title: __( 'Business services', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
'my-subscriptions': {
|
||||
name: 'my-subscriptions',
|
||||
title: __( 'My subscriptions', 'woocommerce' ),
|
||||
showUpdateCount: true,
|
||||
updateCount: wooUpdateCount,
|
||||
},
|
||||
};
|
||||
|
||||
const setUrlTabParam = ( tabKey: string ) => {
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
|
@ -84,7 +45,11 @@ const setUrlTabParam = ( tabKey: string ) => {
|
|||
} );
|
||||
};
|
||||
|
||||
const getVisibleTabs = ( selectedTab: string, hasBusinessServices = false ) => {
|
||||
const getVisibleTabs = (
|
||||
selectedTab: string,
|
||||
hasBusinessServices = false,
|
||||
tabs: Tabs
|
||||
) => {
|
||||
if ( selectedTab === '' ) {
|
||||
return tabs;
|
||||
}
|
||||
|
@ -101,7 +66,8 @@ const getVisibleTabs = ( selectedTab: string, hasBusinessServices = false ) => {
|
|||
|
||||
const renderTabs = (
|
||||
marketplaceContextValue: MarketplaceContextType,
|
||||
visibleTabs: Tabs
|
||||
visibleTabs: Tabs,
|
||||
tabs: Tabs
|
||||
) => {
|
||||
const { selectedTab, setSelectedTab } = marketplaceContextValue;
|
||||
|
||||
|
@ -141,12 +107,13 @@ const renderTabs = (
|
|||
key={ tabKey }
|
||||
>
|
||||
{ tabs[ tabKey ]?.title }
|
||||
{ tabs[ tabKey ]?.showUpdateCount &&
|
||||
tabs[ tabKey ]?.updateCount > 0 && (
|
||||
<span className="woocommerce-marketplace__update-count">
|
||||
<span> { tabs[ tabKey ]?.updateCount } </span>
|
||||
</span>
|
||||
) }
|
||||
{ tabs[ tabKey ]?.showUpdateCount && (
|
||||
<span
|
||||
className={ `woocommerce-marketplace__update-count woocommerce-marketplace__update-count-${ tabKey }` }
|
||||
>
|
||||
<span> { tabs[ tabKey ]?.updateCount } </span>
|
||||
</span>
|
||||
) }
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
@ -159,10 +126,53 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
|
|||
const marketplaceContextValue = useContext( MarketplaceContext );
|
||||
const { selectedTab, setSelectedTab, hasBusinessServices } =
|
||||
marketplaceContextValue;
|
||||
const [ visibleTabs, setVisibleTabs ] = useState( getVisibleTabs( '' ) );
|
||||
const { searchResultsCount } = marketplaceContextValue;
|
||||
|
||||
const query: Record< string, string > = useQuery();
|
||||
|
||||
const tabs: Tabs = {
|
||||
search: {
|
||||
name: 'search',
|
||||
title: __( 'Search results', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
discover: {
|
||||
name: 'discover',
|
||||
title: __( 'Discover', 'woocommerce' ),
|
||||
showUpdateCount: false,
|
||||
updateCount: 0,
|
||||
},
|
||||
extensions: {
|
||||
name: 'extensions',
|
||||
title: __( 'Extensions', 'woocommerce' ),
|
||||
showUpdateCount: !! query.term,
|
||||
updateCount: searchResultsCount.extensions,
|
||||
},
|
||||
themes: {
|
||||
name: 'themes',
|
||||
title: __( 'Themes', 'woocommerce' ),
|
||||
showUpdateCount: !! query.term,
|
||||
updateCount: searchResultsCount.themes,
|
||||
},
|
||||
'business-services': {
|
||||
name: 'business-services',
|
||||
title: __( 'Business services', 'woocommerce' ),
|
||||
showUpdateCount: !! query.term,
|
||||
updateCount: searchResultsCount[ 'business-services' ],
|
||||
},
|
||||
'my-subscriptions': {
|
||||
name: 'my-subscriptions',
|
||||
title: __( 'My subscriptions', 'woocommerce' ),
|
||||
showUpdateCount: true,
|
||||
updateCount: wooUpdateCount,
|
||||
},
|
||||
};
|
||||
|
||||
const [ visibleTabs, setVisibleTabs ] = useState(
|
||||
getVisibleTabs( '', false, tabs )
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
if ( query?.tab && tabs[ query.tab ] ) {
|
||||
setSelectedTab( query.tab );
|
||||
|
@ -172,8 +182,11 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
|
|||
}, [ query, setSelectedTab ] );
|
||||
|
||||
useEffect( () => {
|
||||
setVisibleTabs( getVisibleTabs( selectedTab, hasBusinessServices ) );
|
||||
setVisibleTabs(
|
||||
getVisibleTabs( selectedTab, hasBusinessServices, tabs )
|
||||
);
|
||||
}, [ selectedTab, hasBusinessServices ] );
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={ clsx(
|
||||
|
@ -181,7 +194,7 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
|
|||
additionalClassNames || []
|
||||
) }
|
||||
>
|
||||
{ renderTabs( marketplaceContextValue, visibleTabs ) }
|
||||
{ renderTabs( marketplaceContextValue, visibleTabs, tabs ) }
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useEffect, createContext } from '@wordpress/element';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
createContext,
|
||||
} from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { MarketplaceContextType } from './types';
|
||||
import { SearchResultsCountType, MarketplaceContextType } from './types';
|
||||
import { getAdminSetting } from '../../utils/admin-settings';
|
||||
|
||||
export const MarketplaceContext = createContext< MarketplaceContextType >( {
|
||||
|
@ -18,6 +23,12 @@ export const MarketplaceContext = createContext< MarketplaceContextType >( {
|
|||
addInstalledProduct: () => {},
|
||||
hasBusinessServices: false,
|
||||
setHasBusinessServices: () => {},
|
||||
searchResultsCount: {
|
||||
extensions: 0,
|
||||
themes: 0,
|
||||
'business-services': 0,
|
||||
},
|
||||
setSearchResultsCount: () => {},
|
||||
} );
|
||||
|
||||
export function MarketplaceContextProvider( props: {
|
||||
|
@ -29,6 +40,22 @@ export function MarketplaceContextProvider( props: {
|
|||
[]
|
||||
);
|
||||
const [ hasBusinessServices, setHasBusinessServices ] = useState( false );
|
||||
const [ searchResultsCount, setSearchResultsCountState ] =
|
||||
useState< SearchResultsCountType >( {
|
||||
extensions: 0,
|
||||
themes: 0,
|
||||
'business-services': 0,
|
||||
} );
|
||||
|
||||
const setSearchResultsCount = useCallback(
|
||||
( updatedCounts: Partial< SearchResultsCountType > ) => {
|
||||
setSearchResultsCountState( ( prev ) => ( {
|
||||
...prev,
|
||||
...updatedCounts,
|
||||
} ) );
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Knowing installed products will help us to determine which products
|
||||
|
@ -59,6 +86,8 @@ export function MarketplaceContextProvider( props: {
|
|||
addInstalledProduct,
|
||||
hasBusinessServices,
|
||||
setHasBusinessServices,
|
||||
searchResultsCount,
|
||||
setSearchResultsCount,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,6 +8,12 @@ import { Options } from '@wordpress/notices';
|
|||
*/
|
||||
import { Subscription } from '../components/my-subscriptions/types';
|
||||
|
||||
export interface SearchResultsCountType {
|
||||
extensions: number;
|
||||
themes: number;
|
||||
'business-services': number;
|
||||
}
|
||||
|
||||
export type MarketplaceContextType = {
|
||||
isLoading: boolean;
|
||||
setIsLoading: ( isLoading: boolean ) => void;
|
||||
|
@ -17,6 +23,10 @@ export type MarketplaceContextType = {
|
|||
addInstalledProduct: ( slug: string ) => void;
|
||||
hasBusinessServices: boolean;
|
||||
setHasBusinessServices: ( hasBusinessServices: boolean ) => void;
|
||||
searchResultsCount: SearchResultsCountType;
|
||||
setSearchResultsCount: (
|
||||
updatedCounts: Partial< SearchResultsCountType >
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type SubscriptionsContextType = {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add search result counts to the in-app marketplace header tabs (Extensions area)
|
Loading…
Reference in New Issue