Skip to content
Open
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
1 change: 1 addition & 0 deletions configs/app/ui/views/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as address } from './address';
export { default as block } from './block';
export { default as marketplace } from './marketplace';
export { default as nft } from './nft';
export { default as token } from './token';
export { default as tx } from './tx';
10 changes: 10 additions & 0 deletions configs/app/ui/views/marketplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { MarketplaceTitles } from 'types/views/marketplace';

import { getEnvValue, parseEnvJson } from 'configs/app/utils';

const config = Object.freeze({
titles: parseEnvJson<MarketplaceTitles>(getEnvValue('NEXT_PUBLIC_VIEWS_MARKETPLACE_TITLES')) || {},
essentialDappsAdEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED') !== 'false',
});

export default config;
2 changes: 1 addition & 1 deletion configs/envs/.env.eth
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap']
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
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>
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/0xdeval/b27a4aecaad513fa033e37430a4f9a47/raw/3a2fa70068ea27c3e6d58dc4cdbeb732968d62f3/revokescout-banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/4888339a781c8384267bf4f37f33a2fc/raw/807c66bb2e4e7cd341309035984a1f2458d7e160/revokescout_banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://eth.blockscout.com/apps/revokescout?chainId=1
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
Expand Down
15 changes: 15 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import type { BlockFieldId } from '../../../types/views/block';
import type { NftMarketplaceItem } from '../../../types/views/nft';
import type { TxAdditionalFieldsId, TxFieldsId } from '../../../types/views/tx';
import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx';
import type { MarketplaceTitles } from '../../../types/views/marketplace';
import type { VerifiedContractsFilter } from '../../../types/api/contracts';
import type { TxExternalTxsConfig } from '../../../types/client/externalTxsConfig';

Expand Down Expand Up @@ -1023,6 +1024,20 @@ const schema = yup
.of(nftMarketplaceSchema),
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED: yup.boolean(),
NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED: yup.boolean(),
NEXT_PUBLIC_VIEWS_MARKETPLACE_TITLES: yup
.mixed()
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_VIEWS_MARKETPLACE_TITLES', (data) => {
const isUndefined = data === undefined;
const valueSchema = yup.object<MarketplaceTitles>().transform(replaceQuotes).json().shape({
menu_item: yup.string(),
title: yup.string(),
subtitle_essential_dapps: yup.string(),
subtitle_list: yup.string(),
});

return isUndefined || valueSchema.isValidSync(data);
}),
NEXT_PUBLIC_VIEWS_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED: yup.boolean(),

// e. misc
NEXT_PUBLIC_NETWORK_EXPLORERS: yup
Expand Down
2 changes: 2 additions & 0 deletions deploy/tools/envs-validator/test/.env.marketplace
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=aave
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://www.basename.app
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'}}
NEXT_PUBLIC_VIEWS_MARKETPLACE_TITLES={'menu_item': 'Dapps', 'title': 'Dappscout'}
NEXT_PUBLIC_VIEWS_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED=true
12 changes: 11 additions & 1 deletion docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All json-like values should be single-quoted. If it contains a hash (`#`) or a d
- [Address](#address-views)
- [Transaction](#transaction-views)
- [NFT](#nft-views)
- [Marketplace](#marketplace-views)
- [Misc](#misc)
- [App features](#app-features)
- [My account](#my-account)
Expand Down Expand Up @@ -346,6 +347,15 @@ Settings for meta tags, OG tags and SEO

&nbsp;

#### Marketplace views

| Variable | Type | Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_MARKETPLACE_TITLES | `{ entity_name?: string; menu_item?: string; title?: string; subtitle_essential_dapps?: string; subtitle_list?: string }` | Used to change titles of the Marketplace and dapps | - | - | `{ 'entity_name': 'Dapp', 'menu_item': 'Dapps', 'title': 'Dappscout', 'subtitle_essential_dapps': 'Essential dapps', 'subtitle_list': 'Explore dapps' }` | upcoming |
| NEXT_PUBLIC_VIEWS_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 |

&nbsp;

### Misc

| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
Expand Down Expand Up @@ -887,7 +897,7 @@ If the feature is enabled, a single button or a dropdown (if more than 1 item is

| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| 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+ |
| 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+ |

&nbsp;

Expand Down
2 changes: 1 addition & 1 deletion lib/hooks/useNavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export default function useNavItems(): ReturnType {
subItems: tokensNavItems,
},
config.features.marketplace.isEnabled ? {
text: 'DApps',
text: config.UI.views.marketplace.titles.menu_item || 'Dapps',
nextRoute: { pathname: '/apps' as const },
icon: 'apps',
isActive: pathname.startsWith('/app') || pathname.startsWith('/essential-dapps'),
Expand Down
6 changes: 4 additions & 2 deletions lib/metadata/templates/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Route } from 'nextjs-routes';

import config from 'configs/app';

const dappEntityName = (config.UI.views.marketplace.titles.entity_name || 'Dapp').toLowerCase();

const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/': '%network_name% blockchain explorer - View %network_name% stats',
'/txs': '%network_name% transactions - %network_name% explorer',
Expand All @@ -21,8 +23,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/tokens': 'Tokens list - %network_name% explorer',
'/token/[hash]': '%network_name% token details',
'/token/[hash]/instance/[id]': '%network_name% NFT instance',
'/apps': '%network_name% DApps - Explore top apps',
'/apps/[id]': '%network_name% marketplace app',
'/apps': `%network_name% ${ dappEntityName }s - Explore top ${ dappEntityName }s`,
'/apps/[id]': `%network_name% marketplace ${ dappEntityName }`,
'/essential-dapps/[id]': '%id_cap%',
'/stats': '%network_name% stats - %network_name% network insights',
'/stats/[id]': '%network_name% stats - %id% chart',
Expand Down
7 changes: 7 additions & 0 deletions types/views/marketplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface MarketplaceTitles {
entity_name?: string;
menu_item?: string;
title?: string;
subtitle_essential_dapps?: string;
subtitle_list?: string;
}
28 changes: 25 additions & 3 deletions ui/marketplace/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Flex } from '@chakra-ui/react';
import type { MouseEvent } from 'react';
import React from 'react';

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

import config from 'configs/app';
import { apps as appsMock } from 'mocks/apps/apps';
import AdBanner from 'ui/shared/ad/AdBanner';

import FeaturedApp from './Banner/FeaturedApp';
import IframeBanner from './Banner/IframeBanner';
Expand All @@ -25,13 +27,15 @@ const Banner = ({ apps = [], favoriteApps, isLoading, onInfoClick, onFavoriteCli
return null;
}

let content = null;

if (feature.featuredApp) {
const app = apps.find(app => app.id === feature.featuredApp);
const isFavorite = favoriteApps.includes(feature.featuredApp);
if (!isLoading && !app) {
return null;
}
return (
content = (
<FeaturedApp
app={ app || appsMock[0] }
isFavorite={ isFavorite }
Expand All @@ -42,10 +46,28 @@ const Banner = ({ apps = [], favoriteApps, isLoading, onInfoClick, onFavoriteCli
/>
);
} else if (feature.banner) {
return <IframeBanner contentUrl={ feature.banner.contentUrl } linkUrl={ feature.banner.linkUrl }/>;
content = <IframeBanner contentUrl={ feature.banner.contentUrl } linkUrl={ feature.banner.linkUrl }/>;
}

if (!content) {
return null;
}

return null;
return (
<Flex gap={ 6 }>
{ content }
{ (feature.featuredApp || feature.banner) && (
<AdBanner
platform="mobile"
w="fit-content"
flexShrink={ 0 }
borderRadius="md"
overflow="hidden"
display={{ base: 'none', lg: 'block ' }}
/>
) }
</Flex>
);
};

export default Banner;
44 changes: 19 additions & 25 deletions ui/marketplace/Banner/FeaturedApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import React, { useCallback } from 'react';

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

import { route } from 'nextjs-routes';

import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import { Heading } from 'toolkit/chakra/heading';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Image } from 'toolkit/chakra/image';
import { Link, LinkBox, LinkOverlay } from 'toolkit/chakra/link';
import { Link, LinkBox } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';

import FavoriteIcon from '../FavoriteIcon';
import MarketplaceAppCardLink from '../MarketplaceAppCardLink';
import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon';
import FeaturedAppMobile from './FeaturedAppMobile';

Expand Down Expand Up @@ -63,18 +63,16 @@ const FeaturedApp = ({
return (
<LinkBox>
<Flex
gap={ 6 }
gap={ 4 }
borderRadius="md"
height="136px"
padding={ 5 }
height="100px"
padding={ 3 }
background={{ _light: 'purple.50', _dark: 'whiteAlpha.100' }}
mb={ 2 }
mt={ 6 }
>
<Skeleton
loading={ isLoading }
w="96px"
h="96px"
w="76px"
h="76px"
display="flex"
alignItems="center"
justifyContent="center"
Expand All @@ -86,22 +84,18 @@ const FeaturedApp = ({
/>
</Skeleton>

<Flex flexDirection="column" flex={ 1 } gap={ 2 }>
<Flex flexDirection="column" flex={ 1 } gap={ 1 }>
<Flex alignItems="center" gap={ 3 }>
<Skeleton
loading={ isLoading }
fontSize="30px"
fontWeight="semibold"
fontFamily="heading"
lineHeight="36px"
>
<LinkOverlay
href={ external ? url : route({ pathname: '/apps/[id]', query: { id } }) }
marginRight={ 2 }
external={ external }
>
{ title }
</LinkOverlay>
<Skeleton loading={ isLoading } display="flex" alignItems="center">
<Heading level="3">
<MarketplaceAppCardLink
id={ id }
url={ url }
external={ external }
title={ title }
onClick={ onAppClick }
/>
</Heading>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
</Skeleton>

Expand Down
27 changes: 13 additions & 14 deletions ui/marketplace/Banner/FeaturedAppMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import type { MarketplaceApp } from 'types/client/marketplace';

import { useColorModeValue } from 'toolkit/chakra/color-mode';
import { Heading } from 'toolkit/chakra/heading';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Image } from 'toolkit/chakra/image';
import { Link, LinkBox } from 'toolkit/chakra/link';
Expand Down Expand Up @@ -47,8 +48,7 @@ const FeaturedAppMobile = ({
borderRadius="md"
padding={{ base: 3, sm: '20px' }}
role="group"
background={{ base: 'purple.50', sm: 'whiteAlpha.100' }}
mt={ 6 }
background={{ _light: 'purple.50', _dark: 'whiteAlpha.100' }}
>
<Flex
flexDirection="row"
Expand Down Expand Up @@ -98,20 +98,19 @@ const FeaturedAppMobile = ({
<Flex flexDirection="column" gap={ 2 }>
<Skeleton
loading={ isLoading }
fontSize={{ base: 'sm', sm: 'lg' }}
lineHeight={{ base: '20px', sm: '28px' }}
paddingRight={{ base: '25px', sm: '110px' }}
fontWeight="semibold"
fontFamily="heading"
display="inline-block"
display="flex"
alignItems="center"
>
<MarketplaceAppCardLink
id={ id }
url={ url }
external={ external }
title={ title }
onClick={ onAppClick }
/>
<Heading level="3">
<MarketplaceAppCardLink
id={ id }
url={ url }
external={ external }
title={ title }
onClick={ onAppClick }
/>
</Heading>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
</Skeleton>

Expand Down
4 changes: 1 addition & 3 deletions ui/marketplace/Banner/IframeBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ const IframeBanner = ({ contentUrl, linkUrl }: { contentUrl: string; linkUrl: st
<Skeleton
loading={ isFrameLoading }
position="relative"
h="136px"
h="100px"
w="100%"
borderRadius="md"
mb={{ base: 0, sm: 2 }}
mt={ 6 }
overflow="hidden"
>
<Link
Expand Down
3 changes: 2 additions & 1 deletion ui/marketplace/Rating/TriggerButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { chakra, Text } from '@chakra-ui/react';
import React from 'react';

import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import type { ButtonProps } from 'toolkit/chakra/button';
Expand All @@ -18,7 +19,7 @@ interface Props extends ButtonProps {

const getTooltipText = (canRate: boolean) => {
if (!canRate) {
return <>Please log in to Blockscout to rate this DApp.</>;
return <>Please log in to Blockscout to rate this { (config.UI.views.marketplace.titles.entity_name || 'dapp').toLowerCase() }.</>;
}
return <>Ratings come from verified users.<br/>Click here to rate!</>;
};
Expand Down
8 changes: 6 additions & 2 deletions ui/marketplace/essentialDapps/multisend/Multisend.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import React from 'react';

import type { TestFnArgs } from 'playwright/lib';
import { test, expect } from 'playwright/lib';
import * as pwConfig from 'playwright/utils/config';

import Multisend from './Multisend';

const ESSENTIAL_DAPPS_CONFIG = JSON.stringify({
multisend: { chains: [ '1' ] },
});

test('base view +@dark-mode +@mobile', async({ render, mockEnvs }: TestFnArgs) => {
test('base view +@dark-mode +@mobile', async({ render, mockEnvs, page }: TestFnArgs) => {
await mockEnvs([
[ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG', ESSENTIAL_DAPPS_CONFIG ],
]);

const component = await render(<Multisend/>);

await expect(component).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ],
maskColor: pwConfig.maskColor,
});
});
Loading
Loading