Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New category tabs #1543

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions icons/ABI_slim.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion types/client/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & {
}

export enum MarketplaceCategory {
ALL = 'All apps',
ALL = 'All',
FAVORITES = 'Favorites',
}
21 changes: 19 additions & 2 deletions ui/marketplace/MarketplaceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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={{
Expand Down Expand Up @@ -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.`
)
}
/>
);
};

Expand Down
13 changes: 10 additions & 3 deletions ui/marketplace/useMarketplace.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _groudBy from 'lodash/groupBy';
import _pickBy from 'lodash/pickBy';
import _unique from 'lodash/uniq';
import { useRouter } from 'next/router';
import React from 'react';

Expand Down Expand Up @@ -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()) || [];
maxaleks marked this conversation as resolved.
Show resolved Hide resolved
}, [ data ]);

React.useEffect(() => {
Expand All @@ -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
Expand Down Expand Up @@ -128,6 +133,7 @@ export default function useMarketplace() {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
appsTotal: data?.length || 0,
}), [
selectedCategoryId,
categories,
Expand All @@ -145,5 +151,6 @@ export default function useMarketplace() {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
data?.length,
]);
}
73 changes: 67 additions & 6 deletions ui/pages/Marketplace.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
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 throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
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;
Expand All @@ -31,8 +38,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 ]);

throwOnResourceLoadError(isError && error ? { isError, error } : { isError: false, error: null });

if (!feature.isEnabled) {
Expand All @@ -43,16 +87,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 }
Expand All @@ -71,6 +131,7 @@ const Marketplace = () => {
onFavoriteClick={ onFavoriteClick }
isLoading={ isPlaceholderData }
showDisclaimer={ showDisclaimer }
selectedCategoryId={ selectedCategoryId }
/>

{ (selectedApp && isAppInfoModalOpen) && (
Expand Down
2 changes: 1 addition & 1 deletion ui/shared/EmptySearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading