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

Graph integration #2284

Merged
merged 1 commit into from
Oct 22, 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
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
8 changes: 8 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,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 useGraphLinks() {
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);
58 changes: 58 additions & 0 deletions ui/marketplace/MarketplaceAppGraphLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
Text,
PopoverTrigger,
PopoverBody,
PopoverContent,
chakra,
Box,
VStack,
} from '@chakra-ui/react';
import React from 'react';

import useIsMobile from 'lib/hooks/useIsMobile';
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) => {
const isMobile = useIsMobile();

const handleButtonClick = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
}, []);

if (!links || links.length === 0) {
return null;
}

return (
<Box position="relative" className={ className } display="inline-flex" alignItems="center" height={ 7 } onClick={ handleButtonClick }>
<Popover
placement={ isMobile ? 'bottom-end' : 'bottom' }
isLazy
trigger="hover"
>
<PopoverTrigger>
<IconSvg name="brands/graph" boxSize={ 5 } onClick={ handleButtonClick }/>
</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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading