Skip to content

Commit

Permalink
Graph integration
Browse files Browse the repository at this point in the history
  • Loading branch information
isstuev committed Oct 4, 2024
1 parent 10e5e93 commit 43a19df
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 19 deletions.
3 changes: 3 additions & 0 deletions configs/app/features/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNE
const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL');
const ratingAirtableApiKey = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY');
const ratingAirtableBaseId = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID');
const graphLinksUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL');

const title = 'Marketplace';

Expand All @@ -30,6 +31,7 @@ const config: Feature<(
featuredApp: string | undefined;
banner: { contentUrl: string; linkUrl: string } | undefined;
rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
graphLinksUrl: string | undefined;
}> = (() => {
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
const props = {
Expand All @@ -46,6 +48,7 @@ const config: Feature<(
airtableApiKey: ratingAirtableApiKey,
airtableBaseId: ratingAirtableBaseId,
} : undefined,
graphLinksUrl,
};

if (configUrl) {
Expand Down
1 change: 1 addition & 0 deletions configs/envs/.env.gnosis
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/marketplace-graph-test/test-configs/marketplace-graph-links.json
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_METASUITES_ENABLED=true
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
Expand Down
1 change: 1 addition & 0 deletions deploy/scripts/download_assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ASSETS_ENVS=(
"NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL"
"NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL"
"NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL"
"NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL"
"NEXT_PUBLIC_FEATURED_NETWORKS"
"NEXT_PUBLIC_FOOTER_LINKS"
"NEXT_PUBLIC_NETWORK_LOGO"
Expand Down
1 change: 1 addition & 0 deletions deploy/tools/envs-validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL',
'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL',
'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL',
'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL',
'NEXT_PUBLIC_FOOTER_LINKS',
];

Expand Down
24 changes: 24 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ const securityReportSchema: yup.ObjectSchema<MarketplaceAppSecurityReportRaw> =
chainsData: chainsDataSchema,
});

// const graphLinkSchema: yup.ObjectSchema<{ url: string; title: string }> = yup
// .object()
// .shape({
// url: yup.string().test(urlTest).required(),
// title: yup.string().required(),
// });

// const graphLinksSchema = yup.lazy((obj) => {
// return yup.object().shape(
// Object.keys(obj).reduce((acc, key) => {
// acc[key] = graphLinkSchema;
// return acc;
// }, {})
// );
// });

const marketplaceSchema = yup
.object()
.shape({
Expand Down Expand Up @@ -243,6 +259,14 @@ const marketplaceSchema = yup
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL: yup
.string()
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema,
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
});

const beaconChainSchema = yup
Expand Down
1 change: 1 addition & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ This feature is **always enabled**, but you can disable it by passing `none` val
| NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY | `string` | Airtable API key | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID | `string` | Airtable base ID with dapp ratings | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL | `string` | URL of the file (`.json` format only) which contains the list of The Graph links to be displayed on the Marketplace page | - | - | `https://example.com/graph_links.json` | v1.36.0+ |

#### Marketplace app configuration properties

Expand Down
4 changes: 4 additions & 0 deletions icons/brands/graph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions lib/hooks/useGraphLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';

import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';

const feature = config.features.marketplace;

export default function useIsSafeAddress() {
const fetch = useFetch();

return useQuery<unknown, ResourceError<unknown>, Record<string, Array<{text: string; url: string}>>>({
queryKey: [ 'graph-links' ],
queryFn: async() => fetch((feature.isEnabled && feature.graphLinksUrl) ? feature.graphLinksUrl : '', undefined, { resource: 'graph-links' }),
enabled: feature.isEnabled && Boolean(feature.graphLinksUrl),
staleTime: Infinity,
placeholderData: {},
});
}
1 change: 1 addition & 0 deletions public/icons/name.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
| "block"
| "brands/blockscout"
| "brands/celenium"
| "brands/graph"
| "brands/safe"
| "brands/solidity_scan"
| "burger"
Expand Down
17 changes: 13 additions & 4 deletions ui/marketplace/MarketplaceAppCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
Expand All @@ -28,6 +29,7 @@ interface Props extends MarketplaceAppWithSecurityReport {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinks: Array<{text: string; url: string}>;
}

const MarketplaceAppCard = ({
Expand All @@ -54,6 +56,7 @@ const MarketplaceAppCard = ({
isRatingSending,
isRatingLoading,
canRate,
graphLinks,
}: Props) => {
const isMobile = useIsMobile();
const categoriesLabel = categories.join(', ');
Expand Down Expand Up @@ -118,11 +121,7 @@ const MarketplaceAppCard = ({
>
<Skeleton
isLoaded={ !isLoading }
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
paddingRight={{ base: '40px', md: 0 }}
fontWeight="semibold"
fontFamily="heading"
display="inline-block"
>
<MarketplaceAppCardLink
Expand All @@ -131,8 +130,18 @@ const MarketplaceAppCard = ({
external={ external }
title={ title }
onClick={ onAppClick }
fontWeight="semibold"
fontFamily="heading"
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
/>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks
links={ graphLinks }
ml={ 2 }
verticalAlign="middle"
mb={{ base: 0, md: 1 }}
/>
</Skeleton>

<Skeleton
Expand Down
11 changes: 6 additions & 5 deletions ui/marketplace/MarketplaceAppCardLink.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LinkOverlay } from '@chakra-ui/react';
import { LinkOverlay, chakra } from '@chakra-ui/react';
import NextLink from 'next/link';
import React from 'react';
import type { MouseEvent } from 'react';
Expand All @@ -9,24 +9,25 @@ type Props = {
external?: boolean;
title: string;
onClick?: (event: MouseEvent, id: string) => void;
className?: string;
}

const MarketplaceAppCardLink = ({ url, external, id, title, onClick }: Props) => {
const MarketplaceAppCardLink = ({ url, external, id, title, onClick, className }: Props) => {
const handleClick = React.useCallback((event: MouseEvent) => {
onClick?.(event, id);
}, [ onClick, id ]);

return external ? (
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 }>
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 } className={ className }>
{ title }
</LinkOverlay>
) : (
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref legacyBehavior>
<LinkOverlay onClick={ handleClick } marginRight={ 2 }>
<LinkOverlay onClick={ handleClick } marginRight={ 2 } className={ className }>
{ title }
</LinkOverlay>
</NextLink>
);
};

export default MarketplaceAppCardLink;
export default chakra(MarketplaceAppCardLink);
47 changes: 47 additions & 0 deletions ui/marketplace/MarketplaceAppGraphLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
Text,
PopoverTrigger,
PopoverBody,
PopoverContent,
chakra,
Box,
VStack,
} from '@chakra-ui/react';
import React from 'react';

import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';

interface Props {
className?: string;
links?: Array<{ title: string; url: string }>;
}

const MarketplaceAppGraphLinks = ({ className, links }: Props) => {
if (!links || links.length === 0) {
return null;
}

return (
<Box position="relative" className={ className } display="inline-flex" alignItems="center" height={ 7 }>
<Popover placement="bottom" isLazy trigger="hover">
<PopoverTrigger>
<IconSvg name="brands/graph" boxSize={ 5 }/>
</PopoverTrigger>
<PopoverContent w="260px">
<PopoverBody fontSize="sm">
<VStack gap={ 4 } align="start">
<Text>{ `This dapp uses ${ links.length > 1 ? 'several subgraphs' : 'a subgraph' } provided by The Graph` }</Text>
{ links.map(link => (
<LinkExternal key={ link.url } href={ link.url }>{ link.title }</LinkExternal>
)) }
</VStack>
</PopoverBody>
</PopoverContent>
</Popover>
</Box>
);
};

export default React.memo(chakra(MarketplaceAppGraphLinks));
28 changes: 18 additions & 10 deletions ui/marketplace/MarketplaceAppModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import IconSvg from 'ui/shared/IconSvg';

import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import MarketplaceAppModalLink from './MarketplaceAppModalLink';
import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
Expand All @@ -36,6 +38,7 @@ type Props = {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinks?: Array<{text: string; url: string}>;
}

const MarketplaceAppModal = ({
Expand All @@ -49,6 +52,7 @@ const MarketplaceAppModal = ({
isRatingSending,
isRatingLoading,
canRate,
graphLinks,
}: Props) => {
const {
id,
Expand All @@ -67,6 +71,7 @@ const MarketplaceAppModal = ({
categories,
securityReport,
rating,
internalWallet,
} = data;

const socialLinks = [
Expand Down Expand Up @@ -148,16 +153,19 @@ const MarketplaceAppModal = ({
/>
</Flex>

<Heading
as="h2"
gridColumn={ 2 }
fontSize={{ base: '2xl', md: '32px' }}
fontWeight="medium"
lineHeight={{ md: 10 }}
mb={{ md: 2 }}
>
{ title }
</Heading>
<Flex alignItems="center" mb={{ md: 2 }} gridColumn={ 2 }>
<Heading
as="h2"
fontSize={{ base: '2xl', md: '32px' }}
fontWeight="medium"
lineHeight={{ md: 10 }}
mr={ 2 }
>
{ title }
</Heading>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks links={ graphLinks } ml={ 2 }/>
</Flex>

<Text
variant="secondary"
Expand Down
4 changes: 4 additions & 0 deletions ui/marketplace/MarketplaceList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Grid, Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { MouseEvent } from 'react';

Expand All @@ -25,11 +26,13 @@ type Props = {
isRatingSending: boolean;
isRatingLoading: boolean;
canRate: boolean | undefined;
graphLinksQuery: UseQueryResult<Record<string, Array<{text: string; url: string}>>, unknown>;
}

const MarketplaceList = ({
apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId,
onAppClick, showContractList, userRatings, rateApp, isRatingSending, isRatingLoading, canRate,
graphLinksQuery,
}: Props) => {
const { cutRef, renderedItemsNum } = useLazyRenderedList(apps, !isLoading, 16);

Expand Down Expand Up @@ -75,6 +78,7 @@ const MarketplaceList = ({
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinks={ graphLinksQuery.data?.[app.id] }
/>
)) }
</Grid>
Expand Down
5 changes: 5 additions & 0 deletions ui/pages/Marketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { TabItem } from 'ui/shared/Tabs/types';

import config from 'configs/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useGraphLinks from 'lib/hooks/useGraphLinks';
import useIsMobile from 'lib/hooks/useIsMobile';
import Banner from 'ui/marketplace/Banner';
import ContractListModal from 'ui/marketplace/ContractListModal';
Expand Down Expand Up @@ -80,6 +81,8 @@ const Marketplace = () => {

const isMobile = useIsMobile();

const graphLinksQuery = useGraphLinks();

const categoryTabs = React.useMemo(() => {
const tabs: Array<TabItem> = categories.map(category => ({
id: category.name,
Expand Down Expand Up @@ -236,6 +239,7 @@ const Marketplace = () => {
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinksQuery={ graphLinksQuery }
/>

{ (selectedApp && isAppInfoModalOpen) && (
Expand All @@ -250,6 +254,7 @@ const Marketplace = () => {
isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading }
canRate={ canRate }
graphLinks={ graphLinksQuery.data?.[selectedApp.id] }
/>
) }

Expand Down

0 comments on commit 43a19df

Please sign in to comment.