Commit b352e18e authored by Max Alekseenko's avatar Max Alekseenko

new category tabs

parent 5e61c159
......@@ -20,6 +20,6 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & {
}
export enum MarketplaceCategory {
ALL = 'All apps',
ALL = 'All',
FAVORITES = 'Favorites',
}
......@@ -2,9 +2,12 @@ import { Grid } from '@chakra-ui/react';
import React from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import { MarketplaceCategory } from 'types/client/marketplace';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import IconSvg from 'ui/shared/IconSvg';
import MarketplaceAppCard from './MarketplaceAppCard';
......@@ -15,9 +18,12 @@ type Props = {
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
showDisclaimer: (id: string) => void;
selectedCategoryId?: string;
}
const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading, showDisclaimer }: Props) => {
const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading, showDisclaimer, selectedCategoryId }: Props) => {
const { value: isExperiment } = useFeatureValue('marketplace_exp', false);
return apps.length > 0 ? (
<Grid
templateColumns={{
......@@ -48,7 +54,18 @@ const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLo
)) }
</Grid>
) : (
<EmptySearchResult text={ `Couldn${ apos }t find an app that matches your filter query.` }/>
<EmptySearchResult
text={
(selectedCategoryId === MarketplaceCategory.FAVORITES && !favoriteApps.length && isExperiment) ? (
<>
You don{ apos }t have any favorite apps.
Click on the <IconSvg name="star_outline" w={ 4 } h={ 4 } mb={ -0.5 }/> icon on the app{ apos }s card to add it to Favorites.
</>
) : (
`Couldn${ apos }t find an app that matches your filter query.`
)
}
/>
);
};
......
import _groudBy from 'lodash/groupBy';
import _pickBy from 'lodash/pickBy';
import _unique from 'lodash/uniq';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -74,7 +74,12 @@ export default function useMarketplace() {
const { isPlaceholderData, isError, error, data, displayedApps } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps);
const categories = React.useMemo(() => {
return _unique(data?.map(app => app.categories).flat()) || [];
const grouped = _groudBy(data, app => app.categories);
return Object.keys(grouped).map(category => ({
name: category,
count: grouped[category].length,
}));
// return _unique(data?.map(app => app.categories).flat()) || [];
}, [ data ]);
React.useEffect(() => {
......@@ -83,7 +88,7 @@ export default function useMarketplace() {
React.useEffect(() => {
if (!isPlaceholderData && !isError) {
const isValidDefaultCategory = categories.includes(defaultCategoryId);
const isValidDefaultCategory = categories.map(c => c.name).includes(defaultCategoryId);
isValidDefaultCategory && setSelectedCategoryId(defaultCategoryId);
}
// run only when data is loaded
......@@ -128,6 +133,7 @@ export default function useMarketplace() {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
appsTotal: data?.length || 0,
}), [
selectedCategoryId,
categories,
......@@ -145,5 +151,6 @@ export default function useMarketplace() {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
data?.length,
]);
}
import { Box } from '@chakra-ui/react';
import React from 'react';
import { MarketplaceCategory } from 'types/client/marketplace';
import type { TabItem } from 'ui/shared/Tabs/types';
import config from 'configs/app';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal';
import MarketplaceCategoriesMenu from 'ui/marketplace/MarketplaceCategoriesMenu';
import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal';
import MarketplaceList from 'ui/marketplace/MarketplaceList';
import FilterInput from 'ui/shared/filters/FilterInput';
import IconSvg from 'ui/shared/IconSvg';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll';
import useMarketplace from '../marketplace/useMarketplace';
const feature = config.features.marketplace;
......@@ -30,8 +37,45 @@ const Marketplace = () => {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
appsTotal,
} = useMarketplace();
const { value: isExperiment } = useFeatureValue('marketplace_exp', false);
const categoryTabs = React.useMemo(() => {
const tabs: Array<TabItem> = categories.map(category => ({
id: category.name,
title: category.name,
count: category.count,
component: null,
}));
tabs.unshift({
id: MarketplaceCategory.ALL,
title: MarketplaceCategory.ALL,
count: appsTotal,
component: null,
});
tabs.unshift({
id: MarketplaceCategory.FAVORITES,
title: () => <IconSvg name="star_outline" w={ 4 } h={ 4 }/>,
count: null,
component: null,
});
return tabs;
}, [ categories, appsTotal ]);
const selectedCategoryIndex = React.useMemo(() => {
const index = categoryTabs.findIndex(c => c.id === selectedCategoryId);
return index === -1 ? 0 : index;
}, [ categoryTabs, selectedCategoryId ]);
const handleCategoryChange = React.useCallback((index: number) => {
onCategoryChange(categoryTabs[index].id);
}, [ categoryTabs, onCategoryChange ]);
if (isError) {
throw new Error('Unable to get apps list', { cause: error });
}
......@@ -44,16 +88,32 @@ const Marketplace = () => {
return (
<>
{ isExperiment && (
<Box marginTop={{ base: 0, lg: 8 }}>
{ isPlaceholderData ? (
<TabsSkeleton tabs={ categoryTabs }/>
) : (
<TabsWithScroll
tabs={ categoryTabs }
onTabChange={ handleCategoryChange }
defaultTabIndex={ selectedCategoryIndex }
marginBottom={{ base: 0, lg: -2 }}
/>
) }
</Box>
) }
<Box
display="flex"
flexDirection={{ base: 'column', sm: 'row' }}
>
<MarketplaceCategoriesMenu
categories={ categories }
selectedCategoryId={ selectedCategoryId }
onSelect={ onCategoryChange }
isLoading={ isPlaceholderData }
/>
{ !isExperiment && (
<MarketplaceCategoriesMenu
categories={ categories.map(c => c.name) }
selectedCategoryId={ selectedCategoryId }
onSelect={ onCategoryChange }
isLoading={ isPlaceholderData }
/>
) }
<FilterInput
initialValue={ filterQuery }
......@@ -72,6 +132,7 @@ const Marketplace = () => {
onFavoriteClick={ onFavoriteClick }
isLoading={ isPlaceholderData }
showDisclaimer={ showDisclaimer }
selectedCategoryId={ selectedCategoryId }
/>
{ (selectedApp && isAppInfoModalOpen) && (
......
......@@ -4,7 +4,7 @@ import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
text: string;
text: string | JSX.Element;
}
const EmptySearchResult = ({ text }: Props) => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment