Commit f4f434d7 authored by Max Alekseenko's avatar Max Alekseenko

add new view with scores for marketplace

parent c4f9e899
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.167 4.167c0-.92.746-1.667 1.667-1.667h3.333c.92 0 1.667.746 1.667 1.667V7.5c0 .92-.747 1.667-1.667 1.667H3.834c-.92 0-1.667-.747-1.667-1.667V4.167Zm1.667.5a.5.5 0 0 1 .5-.5h2.333a.5.5 0 0 1 .5.5V7a.5.5 0 0 1-.5.5H4.334a.5.5 0 0 1-.5-.5V4.667ZM10.5 4.27c0 .519.42.938.938.938h4.792a.938.938 0 0 0 0-1.875h-4.792a.937.937 0 0 0-.938.938Zm0 3.334c0 .518.42.938.938.938h4.792a.938.938 0 0 0 0-1.875h-4.792a.937.937 0 0 0-.938.937ZM2.167 12.5c0-.92.746-1.667 1.667-1.667h3.333c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.747 1.667-1.667 1.667H3.834c-.92 0-1.667-.746-1.667-1.667V12.5Zm1.667.5a.5.5 0 0 1 .5-.5h2.333a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H4.334a.5.5 0 0 1-.5-.5V13Zm6.666-.396c0 .518.42.938.938.938h4.792a.938.938 0 0 0 0-1.875h-4.792a.937.937 0 0 0-.938.937Zm0 3.334c0 .517.42.937.938.937h4.792a.938.938 0 0 0 0-1.875h-4.792a.937.937 0 0 0-.938.938Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 4.167c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667V7.5c0 .92-.747 1.667-1.667 1.667H4.667C3.747 9.167 3 8.42 3 7.5V4.167Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5V7a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V4.667ZM3 12.5c0-.92.746-1.667 1.667-1.667H8c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.747 1.667-1.667 1.667H4.667C3.747 17.5 3 16.754 3 15.833V12.5Zm1.667.5a.5.5 0 0 1 .5-.5H7.5a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H5.167a.5.5 0 0 1-.5-.5V13ZM13 2.5c-.92 0-1.667.746-1.667 1.667V7.5c0 .92.746 1.667 1.667 1.667h3.333C17.253 9.167 18 8.42 18 7.5V4.167c0-.92-.746-1.667-1.667-1.667H13Zm3.333 2.167a.5.5 0 0 0-.5-.5H13.5a.5.5 0 0 0-.5.5V7a.5.5 0 0 0 .5.5h2.333a.5.5 0 0 0 .5-.5V4.667ZM11.333 12.5c0-.92.746-1.667 1.667-1.667h3.333c.92 0 1.667.746 1.667 1.667v3.333c0 .92-.746 1.667-1.667 1.667H13c-.92 0-1.667-.746-1.667-1.667V12.5ZM13 13a.5.5 0 0 1 .5-.5h2.333a.5.5 0 0 1 .5.5v2.333a.5.5 0 0 1-.5.5H13.5a.5.5 0 0 1-.5-.5V13Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.4 2a1.8 1.8 0 0 0-1.8 1.8v14.8A1.4 1.4 0 0 0 3 20h1.6v-1.326H3V3.516h1.6V2H3.4Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087-.252.289-.593.451-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm.948 1.088h6.87v4.497c0 .388.315.702.702.702h4.485v11.725H4.94V1.538Zm8.274.59 2.791 3.205h-2.79V2.128Z" fill="currentColor"/>
<rect x="7.2" y="14.3" width="7.8" height="1.2" rx=".6" fill="currentColor"/>
<rect x="7.2" y="12" width="7.8" height="1.2" rx=".6" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.4 2a1.8 1.8 0 0 0-1.8 1.8v14.8A1.4 1.4 0 0 0 3 20h1.6v-1.326H3V3.516h1.6V2H3.4Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.992.45C4.244.163 4.585 0 4.94 0h8.038a.63.63 0 0 1 .474.225L18.14 5.61a.83.83 0 0 1 .196.544v12.308c0 .408-.141.799-.392 1.087-.252.289-.593.451-.948.451H4.94c-.356 0-.696-.162-.948-.45a1.661 1.661 0 0 1-.392-1.088V1.538c0-.408.141-.799.392-1.087Zm8.709 1.088H4.94v16.924h12.057V6.472l-4.296-4.934Z" fill="currentColor"/>
<path d="m7.9 13.357 2.2 2.357L14.5 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.512.42c.388 0 .702.315.702.703v4.21h4.21a.702.702 0 1 1 0 1.404h-4.912a.702.702 0 0 1-.702-.702V1.123c0-.388.315-.702.702-.702Z" fill="currentColor"/>
</svg>
......@@ -15,6 +15,7 @@ export enum NAMES {
MIXPANEL_DEBUG='_mixpanel_debug',
ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type',
UUID='uuid',
MARKETPLACE_DISPLAY_TYPE='marketplace_display_type',
}
export function get(name?: NAMES | undefined | null, serverCookie?: string) {
......
......@@ -4,6 +4,8 @@
| "ABI_slim"
| "ABI"
| "API"
| "apps_list"
| "apps_xs"
| "apps"
| "arrows/down-right"
| "arrows/east-mini"
......@@ -22,6 +24,8 @@
| "collection"
| "contract_verified"
| "contract"
| "contracts_verified"
| "contracts"
| "copy"
| "cross"
| "delete"
......
import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, Tooltip } from '@chakra-ui/react';
import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue } from '@chakra-ui/react';
import type { MouseEvent } from 'react';
import React, { useCallback } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import * as mixpanel from 'lib/mixpanel/index';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
interface Props extends MarketplaceAppPreview {
onInfoClick: (id: string) => void;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
showDisclaimer: (id: string) => void;
onAppClick: (event: MouseEvent, id: string) => void;
}
const MarketplaceAppCard = ({
......@@ -31,19 +31,11 @@ const MarketplaceAppCard = ({
isFavorite,
onFavoriteClick,
isLoading,
showDisclaimer,
internalWallet,
onAppClick,
}: Props) => {
const categoriesLabel = categories.join(', ');
const handleClick = useCallback((event: MouseEvent) => {
const isShown = window.localStorage.getItem('marketplace-disclaimer-shown');
if (!isShown) {
event.preventDefault();
showDisclaimer(id);
}
}, [ showDisclaimer, id ]);
const handleInfoClick = useCallback((event: MouseEvent) => {
event.preventDefault();
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id });
......@@ -56,23 +48,6 @@ const MarketplaceAppCard = ({
const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
const [ integrationIcon, integrationIconColor, integrationText ] = React.useMemo(() => {
let icon: IconName = 'integration/partial';
let color = 'gray.400';
let text = 'This app opens in Blockscout without Blockscout wallet functionality. Use your external web3 wallet to connect directly to this application';
if (external) {
icon = 'arrows/north-east';
text = 'This app opens in a separate tab';
} else if (internalWallet) {
icon = 'integration/full';
color = 'green.500';
text = 'This app opens in Blockscout and your Blockscout wallet connects automatically';
}
return [ icon, color, text ];
}, [ external, internalWallet ]);
return (
<LinkBox
_hover={{
......@@ -130,25 +105,9 @@ const MarketplaceAppCard = ({
url={ url }
external={ external }
title={ title }
onClick={ handleClick }
onClick={ onAppClick }
/>
<Tooltip
label={ integrationText }
textAlign="center"
padding={ 2 }
openDelay={ 300 }
maxW={ 400 }
>
<IconSvg
name={ integrationIcon }
boxSize={ 5 }
color={ integrationIconColor }
position="relative"
cursor="pointer"
verticalAlign="middle"
marginBottom={ 1 }
/>
</Tooltip>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
</Skeleton>
<Skeleton
......
......@@ -8,17 +8,21 @@ type Props = {
url: string;
external?: boolean;
title: string;
onClick?: (event: MouseEvent) => void;
onClick?: (event: MouseEvent, id: string) => void;
}
const MarketplaceAppCardLink = ({ url, external, id, title, onClick }: Props) => {
const handleClick = React.useCallback((event: MouseEvent) => {
onClick?.(event, id);
}, [ onClick, id ]);
return external ? (
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 }>
{ title }
</LinkOverlay>
) : (
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref legacyBehavior>
<LinkOverlay onClick={ onClick } marginRight={ 2 }>
<LinkOverlay onClick={ handleClick } marginRight={ 2 }>
{ title }
</LinkOverlay>
</NextLink>
......
import { Tooltip } from '@chakra-ui/react';
import React from 'react';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
type Props = {
internalWallet: boolean | undefined;
external: boolean | undefined;
}
const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => {
const [ icon, iconColor, text ] = React.useMemo(() => {
let icon: IconName = 'integration/partial';
let color = 'gray.400';
let text = 'This app opens in Blockscout without Blockscout wallet functionality. Use your external web3 wallet to connect directly to this application';
if (external) {
icon = 'arrows/north-east';
text = 'This app opens in a separate tab';
} else if (internalWallet) {
icon = 'integration/full';
color = 'green.500';
text = 'This app opens in Blockscout and your Blockscout wallet connects automatically';
}
return [ icon, color, text ];
}, [ external, internalWallet ]);
return (
<Tooltip
label={ text }
textAlign="center"
padding={ 2 }
openDelay={ 300 }
maxW={ 400 }
>
<IconSvg
name={ icon }
boxSize={ 5 }
color={ iconColor }
position="relative"
cursor="pointer"
verticalAlign="middle"
marginBottom={ 1 }
/>
</Tooltip>
);
};
export default MarketplaceAppIntegrationIcon;
import { Grid } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import { MarketplaceCategory } from 'types/client/marketplace';
......@@ -12,15 +13,15 @@ import MarketplaceAppCard from './MarketplaceAppCard';
type Props = {
apps: Array<MarketplaceAppPreview>;
onAppClick: (id: string) => void;
showAppInfo: (id: string) => void;
favoriteApps: Array<string>;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
showDisclaimer: (id: string) => void;
selectedCategoryId?: string;
onAppClick: (event: MouseEvent, id: string) => void;
}
const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLoading, showDisclaimer, selectedCategoryId }: Props) => {
const MarketplaceList = ({ apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId, onAppClick }: Props) => {
return apps.length > 0 ? (
<Grid
templateColumns={{
......@@ -33,7 +34,7 @@ const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLo
{ apps.map((app, index) => (
<MarketplaceAppCard
key={ app.id + (isLoading ? index : '') }
onInfoClick={ onAppClick }
onInfoClick={ showAppInfo }
id={ app.id }
external={ app.external }
url={ app.url }
......@@ -45,8 +46,8 @@ const MarketplaceList = ({ apps, onAppClick, favoriteApps, onFavoriteClick, isLo
isFavorite={ favoriteApps.includes(app.id) }
onFavoriteClick={ onFavoriteClick }
isLoading={ isLoading }
showDisclaimer={ showDisclaimer }
internalWallet={ app.internalWallet }
onAppClick={ onAppClick }
/>
)) }
</Grid>
......
import { Hide, Show } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import { MarketplaceCategory } from 'types/client/marketplace';
import { apos } from 'lib/html-entities';
import DataListDisplay from 'ui/shared/DataListDisplay';
import EmptySearchResult from 'ui/shared/EmptySearchResult';
import IconSvg from 'ui/shared/IconSvg';
import ListItem from './MarketplaceListWithScores/ListItem';
import Table from './MarketplaceListWithScores/Table';
interface Props {
apps: Array<MarketplaceAppPreview>;
showAppInfo: (id: string) => void;
favoriteApps: Array<string>;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
selectedCategoryId?: string;
onAppClick: (event: MouseEvent, id: string) => void;
}
const MarketplaceListWithScores = ({ apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId, onAppClick }: Props) => {
const content = apps.length > 0 ? (
<>
<Show below="lg" ssr={ false }>
{ apps.map((app, index) => (
<ListItem
key={ app.id + (isLoading ? index : '') }
app={ app }
onInfoClick={ showAppInfo }
isFavorite={ favoriteApps.includes(app.id) }
onFavoriteClick={ onFavoriteClick }
isLoading={ isLoading }
onAppClick={ onAppClick }
/>
)) }
</Show>
<Hide below="lg" ssr={ false }>
<Table
apps={ apps }
isLoading={ isLoading }
onAppClick={ onAppClick }
favoriteApps={ favoriteApps }
onFavoriteClick={ onFavoriteClick }
onInfoClick={ showAppInfo }
/>
</Hide>
</>
) : null;
return apps.length > 0 ? (
<DataListDisplay
isError={ false }
items={ apps }
emptyText="No apps found."
content={ content }
/>
) : (
<EmptySearchResult
text={
(selectedCategoryId === MarketplaceCategory.FAVORITES && !favoriteApps.length) ? (
<>
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.`
)
}
/>
);
};
export default MarketplaceListWithScores;
import { Flex, Skeleton, LinkBox, Image, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import MarketplaceAppCardLink from '../MarketplaceAppCardLink';
import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon';
interface Props {
app: MarketplaceAppPreview;
isLoading: boolean | undefined;
onAppClick: (event: MouseEvent, id: string) => void;
isLarge?: boolean;
}
const AppLink = ({ app, isLoading, onAppClick, isLarge = false }: Props) => {
const { id, url, external, title, logo, logoDarkMode, internalWallet, categories } = app;
const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
const categoriesLabel = categories.join(', ');
return (
<LinkBox display="flex" height="100%" width="100%" role="group" alignItems="center" mb={ isLarge ? 0 : 4 }>
<Skeleton
isLoaded={ !isLoading }
w={ isLarge ? '56px' : '48px' }
h={ isLarge ? '56px' : '48px' }
display="flex"
alignItems="center"
justifyContent="center"
mr={ isLarge ? 3 : 4 }
>
<Image
src={ isLoading ? undefined : logoUrl }
alt={ `${ title } app icon` }
borderRadius="8px"
/>
</Skeleton>
<Flex direction="column">
<Skeleton
isLoaded={ !isLoading }
marginBottom={ 0 }
fontSize="sm"
fontWeight="semibold"
fontFamily="heading"
display="inline-block"
mb={ 1 }
>
<MarketplaceAppCardLink
id={ id }
url={ url }
external={ external }
title={ title }
onClick={ onAppClick }
/>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
</Skeleton>
<Skeleton
isLoaded={ !isLoading }
color="text_secondary"
fontSize={ isLarge ? 'sm' : 'xs' }
>
<span>{ categoriesLabel }</span>
</Skeleton>
</Flex>
</LinkBox>
);
};
export default AppLink;
import { Link } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
interface Props {
children: string;
onClick: (event: MouseEvent) => void;
icon?: IconName;
iconColor?: string;
fontSize?: string;
fontWeight?: string;
}
const LinkButton = ({ children, onClick, icon, iconColor = 'gray.500', fontSize = 'sm', fontWeight = '500' }: Props) => {
return (
<Link
fontSize={ fontSize }
href="#"
onClick={ onClick }
fontWeight={ fontWeight }
display="inline-flex"
>
{ icon && <IconSvg name={ icon } boxSize={ 5 } color={ iconColor } mr={ 1 }/> }
{ children }
</Link>
);
};
export default LinkButton;
import { Flex, LinkBox, IconButton } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
import AppLink from './AppLink';
import LinkButton from './LinkButton';
interface Props {
app: MarketplaceAppPreview;
onInfoClick: (id: string) => void;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
onAppClick: (event: MouseEvent, id: string) => void;
}
const ListItem = ({ app, onInfoClick, isFavorite, onFavoriteClick, isLoading, onAppClick }: Props) => {
const { id } = app;
const handleInfoClick = React.useCallback((event: MouseEvent) => {
event.preventDefault();
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id });
onInfoClick(id);
}, [ onInfoClick, id ]);
const handleFavoriteClick = React.useCallback(() => {
onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
return (
<ListItemMobile
rowGap={ 3 }
py={ 3 }
sx={{ ':first-child': { borderTop: 'none' }, ':last-child': { borderBottom: 'none' } }}
>
<LinkBox height="100%" width="100%" role="group">
<Flex
direction="column"
justifyContent="stretch"
padding={ 3 }
>
<AppLink app={ app } isLoading={ isLoading } onAppClick={ onAppClick }/>
<Flex>
<Flex flex={ 1 } gap={ 3 }>
<LinkButton onClick={ handleInfoClick } icon="contracts">13</LinkButton>
<LinkButton onClick={ handleInfoClick } icon="contracts_verified" iconColor="green.500">13</LinkButton>
</Flex>
{ !isLoading && (
<LinkButton onClick={ handleInfoClick }>More info</LinkButton>
) }
</Flex>
{ !isLoading && (
<IconButton
position="absolute"
right={ 0 }
top={ 0 }
aria-label="Mark as favorite"
title="Mark as favorite"
variant="ghost"
colorScheme="gray"
w={ 9 }
h={ 8 }
onClick={ handleFavoriteClick }
icon={ isFavorite ?
<IconSvg name="star_filled" w={ 4 } h={ 4 } color="yellow.400"/> :
<IconSvg name="star_outline" w={ 4 } h={ 4 } color="gray.300"/>
}
/>
) }
</Flex>
</LinkBox>
</ListItemMobile>
);
};
export default ListItem;
import { Table as ChakraTable, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import { default as Thead } from 'ui/shared/TheadSticky';
import TableItem from './TableItem';
type Props = {
apps: Array<MarketplaceAppPreview>;
isLoading?: boolean;
favoriteApps: Array<string>;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
onAppClick: (event: MouseEvent, id: string) => void;
onInfoClick: (id: string) => void;
}
const Table = ({ apps, isLoading, favoriteApps, onFavoriteClick, onAppClick, onInfoClick }: Props) => {
return (
<ChakraTable>
<Thead top={ 80 }>
<Tr>
<Th w="5%"></Th>
<Th w="40%">App</Th>
<Th w="15%">Contracts score</Th>
<Th w="10%">Total</Th>
<Th w="10%">Verified</Th>
<Th w="20%"></Th>
</Tr>
</Thead>
<Tbody>
{ apps.map((app, index) => (
<TableItem
key={ app.id + (isLoading ? index : '') }
app={ app }
isLoading={ isLoading }
isFavorite={ favoriteApps.includes(app.id) }
onFavoriteClick={ onFavoriteClick }
onAppClick={ onAppClick }
onInfoClick={ onInfoClick }
/>
)) }
</Tbody>
</ChakraTable>
);
};
export default Table;
import { Td, Tr, IconButton } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import AppLink from './AppLink';
import LinkButton from './LinkButton';
type Props = {
app: MarketplaceAppPreview;
isLoading?: boolean;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
onAppClick: (event: MouseEvent, id: string) => void;
onInfoClick: (id: string) => void;
}
const TableItem = ({
app,
isLoading,
isFavorite,
onFavoriteClick,
onAppClick,
onInfoClick,
}: Props) => {
const { id } = app;
const handleInfoClick = React.useCallback((event: MouseEvent) => {
event.preventDefault();
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id });
onInfoClick(id);
}, [ onInfoClick, id ]);
const handleFavoriteClick = React.useCallback(() => {
onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
return (
<Tr>
<Td verticalAlign="middle" px={ 2 }>
<IconButton
aria-label="Mark as favorite"
title="Mark as favorite"
variant="ghost"
colorScheme="gray"
w={ 9 }
h={ 8 }
onClick={ handleFavoriteClick }
icon={ isFavorite ?
<IconSvg name="star_filled" w={ 4 } h={ 4 } color="yellow.400"/> :
<IconSvg name="star_outline" w={ 4 } h={ 4 } color="gray.300"/>
}
/>
</Td>
<Td verticalAlign="middle">
<AppLink app={ app } isLoading={ isLoading } onAppClick={ onAppClick } isLarge/>
</Td>
<Td verticalAlign="middle"></Td>
<Td verticalAlign="middle">
<LinkButton onClick={ handleInfoClick } icon="contracts">13</LinkButton>
</Td>
<Td verticalAlign="middle">
<LinkButton onClick={ handleInfoClick } icon="contracts_verified" iconColor="green.500">13</LinkButton>
</Td>
<Td verticalAlign="middle" isNumeric>
<LinkButton onClick={ handleInfoClick }>More info</LinkButton>
</Td>
</Tr>
);
};
export default TableItem;
import { Box, Menu, MenuButton, MenuItem, MenuList, Flex, IconButton } from '@chakra-ui/react';
import React from 'react';
import type { MouseEvent } from 'react';
import { MarketplaceCategory } from 'types/client/marketplace';
import type { TabItem } from 'ui/shared/Tabs/types';
import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useIsMobile from 'lib/hooks/useIsMobile';
import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal';
import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal';
import MarketplaceList from 'ui/marketplace/MarketplaceList';
import MarketplaceListWithScores from 'ui/marketplace/MarketplaceListWithScores';
import FilterInput from 'ui/shared/filters/FilterInput';
import IconSvg from 'ui/shared/IconSvg';
import type { IconName } from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll';
......@@ -39,6 +44,8 @@ if (feature.isEnabled) {
}
}
type MarketplaceDisplayType = 'default' | 'scores';
const Marketplace = () => {
const {
isPlaceholderData,
......@@ -97,6 +104,22 @@ const Marketplace = () => {
onCategoryChange(categoryTabs[index].id);
}, [ categoryTabs, onCategoryChange ]);
const displayTypeCookie = cookies.get(cookies.NAMES.MARKETPLACE_DISPLAY_TYPE, useAppContext().cookies);
const [ displayType, setDisplayType ] = React.useState<MarketplaceDisplayType>(displayTypeCookie === 'default' ? 'default' : 'scores');
const handleNFTsDisplayTypeChange = React.useCallback((val: MarketplaceDisplayType) => {
cookies.set(cookies.NAMES.MARKETPLACE_DISPLAY_TYPE, val);
setDisplayType(val);
}, []);
const handleAppClick = React.useCallback((event: MouseEvent, id: string) => {
const isShown = window.localStorage.getItem('marketplace-disclaimer-shown');
if (!isShown) {
event.preventDefault();
showDisclaimer(id);
}
}, [ showDisclaimer ]);
throwOnResourceLoadError(isError && error ? { isError, error } : { isError: false, error: null });
if (!feature.isEnabled) {
......@@ -148,29 +171,53 @@ const Marketplace = () => {
tabs={ categoryTabs }
onTabChange={ handleCategoryChange }
defaultTabIndex={ selectedCategoryIndex }
marginBottom={{ base: 0, lg: -2 }}
marginBottom={ -2 }
/>
) }
</Box>
<FilterInput
initialValue={ filterQuery }
onChange={ onSearchInputChange }
marginBottom={{ base: '4', lg: '6' }}
w="100%"
placeholder="Find app"
isLoading={ isPlaceholderData }
/>
<Flex direction={{ base: 'column', lg: 'row' }} mb={{ base: 1, lg: 6 }} gap={{ base: 4, lg: 3 }}>
<RadioButtonGroup<MarketplaceDisplayType>
onChange={ handleNFTsDisplayTypeChange }
defaultValue={ displayType }
name="type"
options={ [
{ title: 'Discovery', value: 'default', icon: 'apps_xs', onlyIcon: false },
{ title: 'Apps scores', value: 'scores', icon: 'apps_list', onlyIcon: false },
] }
autoWidth
/>
<FilterInput
initialValue={ filterQuery }
onChange={ onSearchInputChange }
placeholder="Find app"
isLoading={ isPlaceholderData }
size="xs"
flex="1"
/>
</Flex>
<MarketplaceList
apps={ displayedApps }
onAppClick={ showAppInfo }
favoriteApps={ favoriteApps }
onFavoriteClick={ onFavoriteClick }
isLoading={ isPlaceholderData }
showDisclaimer={ showDisclaimer }
selectedCategoryId={ selectedCategoryId }
/>
{ (displayType === 'scores') ? (
<MarketplaceListWithScores
apps={ displayedApps }
showAppInfo={ showAppInfo }
favoriteApps={ favoriteApps }
onFavoriteClick={ onFavoriteClick }
isLoading={ isPlaceholderData }
selectedCategoryId={ selectedCategoryId }
onAppClick={ handleAppClick }
/>
) : (
<MarketplaceList
apps={ displayedApps }
showAppInfo={ showAppInfo }
favoriteApps={ favoriteApps }
onFavoriteClick={ onFavoriteClick }
isLoading={ isPlaceholderData }
selectedCategoryId={ selectedCategoryId }
onAppClick={ handleAppClick }
/>
) }
{ (selectedApp && isAppInfoModalOpen) && (
<MarketplaceAppModal
......
......@@ -80,15 +80,22 @@ type RadioButtonGroupProps<T extends string> = {
name: string;
defaultValue: string;
options: Array<{ value: T } & RadioItemProps>;
autoWidth?: boolean;
}
const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, options }: RadioButtonGroupProps<T>) => {
const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, options, autoWidth = false }: RadioButtonGroupProps<T>) => {
const { getRootProps, getRadioProps } = useRadioGroup({ name, defaultValue, onChange });
const group = getRootProps();
return (
<ButtonGroup { ...group } isAttached size="sm" display="grid" gridTemplateColumns={ `repeat(${ options.length }, 1fr)` }>
<ButtonGroup
{ ...group }
isAttached
size="sm"
display="grid"
gridTemplateColumns={ `repeat(${ options.length }, ${ autoWidth ? 'auto' : '1fr' })` }
>
{ options.map((option) => {
const props = getRadioProps({ value: option.value });
return <RadioButton { ...props } key={ option.value } { ...option }/>;
......
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