Skip to content

Commit de32ffd

Browse files
authored
Marketplace titles and ads (#3109)
1 parent ecb6426 commit de32ffd

File tree

64 files changed

+233
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+233
-89
lines changed

configs/app/features/marketplace.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import type { Feature } from './types';
2-
import type { EssentialDappsConfig } from 'types/client/marketplace';
2+
import type { EssentialDappsConfig, MarketplaceTitles } from 'types/client/marketplace';
33

44
import apis from '../apis';
55
import chain from '../chain';
66
import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from '../utils';
77
import blockchainInteraction from './blockchainInteraction';
88

9+
const defaultTitles: MarketplaceTitles = {
10+
entity_name: 'Dapp',
11+
menu_item: 'Dapps',
12+
title: 'Dappscout',
13+
subtitle_essential_dapps: 'Essential dapps',
14+
subtitle_list: 'Explore dapps',
15+
};
16+
917
// config file will be downloaded at run-time and saved in the public folder
1018
const enabled = getEnvValue('NEXT_PUBLIC_MARKETPLACE_ENABLED');
1119
const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL');
@@ -17,6 +25,8 @@ const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNE
1725
const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL');
1826
const graphLinksUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL');
1927
const essentialDappsConfig = parseEnvJson<EssentialDappsConfig>(getEnvValue('NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG'));
28+
const essentialDappsAdEnabled = getEnvValue('NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED') !== 'false';
29+
const customTitles = parseEnvJson(getEnvValue('NEXT_PUBLIC_MARKETPLACE_TITLES')) || {};
2030

2131
const title = 'Marketplace';
2232

@@ -31,6 +41,8 @@ const config: Feature<(
3141
banner: { contentUrl: string; linkUrl: string } | undefined;
3242
graphLinksUrl: string | undefined;
3343
essentialDapps: EssentialDappsConfig | undefined;
44+
essentialDappsAdEnabled: boolean;
45+
titles: MarketplaceTitles;
3446
}> = (() => {
3547
if (enabled === 'true' && chain.rpcUrls.length > 0 && submitFormUrl) {
3648
const props = {
@@ -44,6 +56,8 @@ const config: Feature<(
4456
} : undefined,
4557
graphLinksUrl,
4658
essentialDapps: blockchainInteraction.isEnabled ? (essentialDappsConfig || undefined) : undefined,
59+
essentialDappsAdEnabled,
60+
titles: { ...defaultTitles, ...customTitles },
4761
};
4862

4963
if (configUrl) {

configs/envs/.env.eth

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true
3939
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap']
4040
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
4141
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=<p>Launch your own fully functioning blockchain explorer in minutes. <a href="https://deploy.blockscout.com/?utm_source=blockscout_ad">Deploy now</a></p>
42-
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/0xdeval/1a7395339d20b603b9c1d0ee929f23dc/raw/55e2eebef3ba3ef064842d15555b59d5ba8cbcc7/badges-banner.html
43-
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://badges.blockscout.com/badges-list?utm_source=blockscout-explorer&utm_medium=marketplace-banner
42+
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/4888339a781c8384267bf4f37f33a2fc/raw/807c66bb2e4e7cd341309035984a1f2458d7e160/revokescout_banner.html
43+
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://eth.blockscout.com/apps/revokescout?chainId=1
4444
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
4545
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
4646
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '1923', '42793', '59144'], 'fee': '0.004', 'integrator': 'blockscout'}, 'multisend': {'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '59144', '7000'], 'posthogKey': 'phc_7O4WGsecqqDO1PeaKayHAxUWN1PjheOmQCiDxEMcmkx', 'posthogHost': 'https://us.i.posthog.com'}}
@@ -77,4 +77,4 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
7777
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://storage.googleapis.com/opensea-static/Logomark/Logomark-Blue.svg'},{'name':'Rarible','collection_url':'https://rarible.com/collection/{hash}/items','instance_url':'https://rarible.com/token/{hash}:{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/rarible.png'},{'name':'Blur','collection_url':'https://blur.io/eth/collection/{hash}','instance_url':'https://blur.io/eth/asset/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/blur.png'},{'name':'MagicEden','collection_url':'https://magiceden.io/collections/ethereum/{hash}','instance_url':'https://magiceden.io/item-details/ethereum/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/magiceden.png'}]
7878
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
7979
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
80-
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
80+
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address

deploy/tools/envs-validator/schema.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { DeFiDropdownItem } from '../../../types/client/deFiDropdown';
1818
import type { GasRefuelProviderConfig } from '../../../types/client/gasRefuelProviderConfig';
1919
import { GAS_UNITS } from '../../../types/client/gasTracker';
2020
import type { GasUnit } from '../../../types/client/gasTracker';
21-
import type { MarketplaceAppBase, MarketplaceAppSocialInfo, EssentialDappsConfig } from '../../../types/client/marketplace';
21+
import type { MarketplaceAppBase, MarketplaceAppSocialInfo, EssentialDappsConfig, MarketplaceTitles } from '../../../types/client/marketplace';
2222
import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig';
2323
import type { ApiDocsTabId } from '../../../types/views/apiDocs';
2424
import { API_DOCS_TABS } from '../../../types/views/apiDocs';
@@ -216,6 +216,38 @@ const marketplaceSchema = yup
216216
value => value === undefined,
217217
),
218218
}),
219+
NEXT_PUBLIC_MARKETPLACE_TITLES: yup
220+
.mixed()
221+
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
222+
is: true,
223+
then: (schema) => schema.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MARKETPLACE_TITLES', (data) => {
224+
const isUndefined = data === undefined;
225+
const valueSchema = yup.object<MarketplaceTitles>().transform(replaceQuotes).json().shape({
226+
menu_item: yup.string(),
227+
title: yup.string(),
228+
subtitle_essential_dapps: yup.string(),
229+
subtitle_list: yup.string(),
230+
});
231+
232+
return isUndefined || valueSchema.isValidSync(data);
233+
}),
234+
otherwise: (schema) => schema.test(
235+
'not-exist',
236+
'NEXT_PUBLIC_MARKETPLACE_TITLES cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED',
237+
value => value === undefined,
238+
),
239+
}),
240+
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED: yup
241+
.boolean()
242+
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
243+
is: true,
244+
then: (schema) => schema,
245+
otherwise: (schema) => schema.test(
246+
'not-exist',
247+
'NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED',
248+
value => value === undefined,
249+
),
250+
}),
219251
});
220252

221253
const beaconChainSchema = yup

deploy/tools/envs-validator/test/.env.marketplace

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=aave
88
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html
99
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://www.basename.app
1010
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'chains': ['1', '10', '100', '11155111'], 'fee': '0.004', 'integrator': 'blockscout'}, 'revoke': {'chains': ['1', '10', '100', '11155111']}, 'multisend': {'chains': ['1', '10', '100', '11155111'], 'posthogKey': '123', 'posthogHost': 'https://example.com'}}
11+
NEXT_PUBLIC_MARKETPLACE_TITLES={'menu_item': 'Dapps', 'title': 'Dappscout'}
12+
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED=true

docs/ENVS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,8 @@ Ads are enabled by default on all self-hosted instances. If you would like to di
588588
| NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ |
589589
| 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+ |
590590
| NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG | `EssentialDappsConfig`, see details [below](#essential-dapps-configuration-properties) | Configuration of the essential dapps to be displayed on the Marketplace page | - | - | `{'swap': {'chains': ['1', '10', '100', '11155111'], 'fee': '0.004', 'integrator': 'blockscout'}}` | v2.4.0+ |
591+
| NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED | `boolean` | The flag enables ad in essential dapps. *Feature is enabled by default; pass `false` to disable it.* | - | `true` | `false` | upcoming |
592+
| NEXT_PUBLIC_MARKETPLACE_TITLES | `{ entity_name?: string; menu_item?: string; title?: string; subtitle_essential_dapps?: string; subtitle_list?: string }` | Used to override default titles of the Marketplace and dapps | - | `{ 'entity_name': 'Dapp', 'menu_item': 'Dapps', 'title': 'Dappscout', 'subtitle_essential_dapps': 'Essential dapps', 'subtitle_list': 'Explore dapps' }` | `{ 'entity_name': 'App', 'menu_item': 'Apps', 'title': 'Marketplace', 'subtitle_essential_dapps': 'Essential apps', 'subtitle_list': 'Explore apps' }` | upcoming |
591593

592594
#### Marketplace app configuration properties
593595

@@ -888,7 +890,7 @@ If the feature is enabled, a single button or a dropdown (if more than 1 item is
888890

889891
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
890892
| --- | --- | --- | --- | --- | --- | --- |
891-
| NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon: string; dappId?: string, url?: string }]` | An array of dropdown items containing the button text, icon name and dappId in DAppscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` | v1.31.0+ |
893+
| NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon: string; dappId?: string, url?: string }]` | An array of dropdown items containing the button text, icon name and dappId in Dappscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` | v1.31.0+ |
892894

893895
&nbsp;
894896

lib/hooks/useNavItems.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/naviga
66
import config from 'configs/app';
77
import { rightLineArrow } from 'toolkit/utils/htmlEntities';
88

9+
const marketplaceFeature = config.features.marketplace;
10+
911
interface ReturnType {
1012
mainNavItems: Array<NavItem | NavGroupItem>;
1113
accountNavItems: Array<NavItem>;
@@ -346,8 +348,8 @@ export default function useNavItems(): ReturnType {
346348
isActive: tokensNavItems.flat().some(item => isInternalItem(item) && item.isActive),
347349
subItems: tokensNavItems,
348350
},
349-
config.features.marketplace.isEnabled ? {
350-
text: 'DApps',
351+
marketplaceFeature.isEnabled ? {
352+
text: marketplaceFeature.titles.menu_item,
351353
nextRoute: { pathname: '/apps' as const },
352354
icon: 'apps',
353355
isActive: pathname.startsWith('/app') || pathname.startsWith('/essential-dapps'),

lib/metadata/templates/title.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { getFeaturePayload } from 'configs/app/features/types';
2+
13
import type { Route } from 'nextjs-routes';
24

35
import config from 'configs/app';
46

7+
const dappEntityName = (getFeaturePayload(config.features.marketplace)?.titles.entity_name ?? '').toLowerCase();
8+
59
const TEMPLATE_MAP: Record<Route['pathname'], string> = {
610
'/': '%network_name% blockchain explorer - View %network_name% stats',
711
'/txs': '%network_name% transactions - %network_name% explorer',
@@ -21,8 +25,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
2125
'/tokens': 'Tokens list - %network_name% explorer',
2226
'/token/[hash]': '%network_name% token details',
2327
'/token/[hash]/instance/[id]': '%network_name% NFT instance',
24-
'/apps': '%network_name% DApps - Explore top apps',
25-
'/apps/[id]': '%network_name% marketplace app',
28+
'/apps': `%network_name% ${ dappEntityName }s - Explore top ${ dappEntityName }s`,
29+
'/apps/[id]': `%network_name% marketplace ${ dappEntityName }`,
2630
'/essential-dapps/[id]': '%id_cap%',
2731
'/stats': '%network_name% stats - %network_name% network insights',
2832
'/stats/[id]': '%network_name% stats - %id% chart',

types/client/marketplace.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@ export type EssentialDappsConfig = {
4949
posthogHost?: string;
5050
};
5151
};
52+
53+
export interface MarketplaceTitles {
54+
entity_name: string;
55+
menu_item: string;
56+
title: string;
57+
subtitle_essential_dapps: string;
58+
subtitle_list: string;
59+
}

ui/marketplace/Banner.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { Flex } from '@chakra-ui/react';
12
import type { MouseEvent } from 'react';
23
import React from 'react';
34

45
import type { MarketplaceApp } from 'types/client/marketplace';
56

67
import config from 'configs/app';
8+
import useIsMobile from 'lib/hooks/useIsMobile';
79
import { apps as appsMock } from 'mocks/apps/apps';
10+
import AdBanner from 'ui/shared/ad/AdBanner';
811

912
import FeaturedApp from './Banner/FeaturedApp';
1013
import IframeBanner from './Banner/IframeBanner';
@@ -21,17 +24,21 @@ type BannerProps = {
2124
};
2225

2326
const Banner = ({ apps = [], favoriteApps, isLoading, onInfoClick, onFavoriteClick, onAppClick }: BannerProps) => {
27+
const isMobile = useIsMobile();
28+
2429
if (!feature.isEnabled) {
2530
return null;
2631
}
2732

33+
let content = null;
34+
2835
if (feature.featuredApp) {
2936
const app = apps.find(app => app.id === feature.featuredApp);
3037
const isFavorite = favoriteApps.includes(feature.featuredApp);
3138
if (!isLoading && !app) {
3239
return null;
3340
}
34-
return (
41+
content = (
3542
<FeaturedApp
3643
app={ app || appsMock[0] }
3744
isFavorite={ isFavorite }
@@ -42,10 +49,27 @@ const Banner = ({ apps = [], favoriteApps, isLoading, onInfoClick, onFavoriteCli
4249
/>
4350
);
4451
} else if (feature.banner) {
45-
return <IframeBanner contentUrl={ feature.banner.contentUrl } linkUrl={ feature.banner.linkUrl }/>;
52+
content = <IframeBanner contentUrl={ feature.banner.contentUrl } linkUrl={ feature.banner.linkUrl }/>;
53+
}
54+
55+
if (!content) {
56+
return null;
4657
}
4758

48-
return null;
59+
return (
60+
<Flex gap={ 6 }>
61+
{ content }
62+
{ !isMobile && (
63+
<AdBanner
64+
format="mobile"
65+
w="fit-content"
66+
flexShrink={ 0 }
67+
borderRadius="md"
68+
overflow="hidden"
69+
/>
70+
) }
71+
</Flex>
72+
);
4973
};
5074

5175
export default Banner;

ui/marketplace/Banner/FeaturedApp.tsx

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import React, { useCallback } from 'react';
44

55
import type { MarketplaceApp } from 'types/client/marketplace';
66

7-
import { route } from 'nextjs-routes';
8-
97
import useIsMobile from 'lib/hooks/useIsMobile';
108
import * as mixpanel from 'lib/mixpanel/index';
119
import { useColorModeValue } from 'toolkit/chakra/color-mode';
10+
import { Heading } from 'toolkit/chakra/heading';
1211
import { IconButton } from 'toolkit/chakra/icon-button';
1312
import { Image } from 'toolkit/chakra/image';
14-
import { Link, LinkBox, LinkOverlay } from 'toolkit/chakra/link';
13+
import { Link, LinkBox } from 'toolkit/chakra/link';
1514
import { Skeleton } from 'toolkit/chakra/skeleton';
1615

1716
import FavoriteIcon from '../FavoriteIcon';
17+
import MarketplaceAppCardLink from '../MarketplaceAppCardLink';
1818
import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon';
1919
import FeaturedAppMobile from './FeaturedAppMobile';
2020

@@ -63,18 +63,16 @@ const FeaturedApp = ({
6363
return (
6464
<LinkBox>
6565
<Flex
66-
gap={ 6 }
66+
gap={ 4 }
6767
borderRadius="md"
68-
height="136px"
69-
padding={ 5 }
68+
height="100px"
69+
padding={ 3 }
7070
background={{ _light: 'purple.50', _dark: 'whiteAlpha.100' }}
71-
mb={ 2 }
72-
mt={ 6 }
7371
>
7472
<Skeleton
7573
loading={ isLoading }
76-
w="96px"
77-
h="96px"
74+
w="76px"
75+
h="76px"
7876
display="flex"
7977
alignItems="center"
8078
justifyContent="center"
@@ -86,22 +84,18 @@ const FeaturedApp = ({
8684
/>
8785
</Skeleton>
8886

89-
<Flex flexDirection="column" flex={ 1 } gap={ 2 }>
87+
<Flex flexDirection="column" flex={ 1 } gap={ 1 }>
9088
<Flex alignItems="center" gap={ 3 }>
91-
<Skeleton
92-
loading={ isLoading }
93-
fontSize="30px"
94-
fontWeight="semibold"
95-
fontFamily="heading"
96-
lineHeight="36px"
97-
>
98-
<LinkOverlay
99-
href={ external ? url : route({ pathname: '/apps/[id]', query: { id } }) }
100-
marginRight={ 2 }
101-
external={ external }
102-
>
103-
{ title }
104-
</LinkOverlay>
89+
<Skeleton loading={ isLoading } display="flex" alignItems="center">
90+
<Heading level="3">
91+
<MarketplaceAppCardLink
92+
id={ id }
93+
url={ url }
94+
external={ external }
95+
title={ title }
96+
onClick={ onAppClick }
97+
/>
98+
</Heading>
10599
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
106100
</Skeleton>
107101

0 commit comments

Comments
 (0)