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:
Boro Sitnikovski 2024-09-11 13:00:49 +02:00 committed by GitHub
parent b728f53f29
commit ab0e76c943
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 169 additions and 76 deletions

View File

@ -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 }
/>

View File

@ -45,6 +45,10 @@
text-align: center;
z-index: 26;
}
&__update-count {
background-color: #000;
}
}
@media (width <= $breakpoint-medium) {

View File

@ -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>
);
};

View File

@ -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 (

View File

@ -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 = {

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add search result counts to the in-app marketplace header tabs (Extensions area)