Commit 334ebdf9 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #464 from blockscout/text-ads

Text ads + txn page ad banner
parents b8575a7a 24ced255
...@@ -129,6 +129,7 @@ function makePolicyMap() { ...@@ -129,6 +129,7 @@ function makePolicyMap() {
// ad // ad
'servedbyadbutler.com', 'servedbyadbutler.com',
'cdn.coinzilla.io',
], ],
'font-src': [ 'font-src': [
......
export default async function insertAdText() {
const ad = document.getElementsByClassName('coinzilla');
ad[0].textContent = 'coinzilla banner!';
}
import type { Page } from 'playwright-core';
export default async function insertAdPlaceholder(page: Page) {
await page.waitForSelector('#adBanner', { state: 'attached' });
await page.evaluate(() => {
const adContainer = document.getElementById('adBanner');
const adReplacer = document.createElement('div');
adReplacer.style.width = '200px';
adReplacer.style.height = '100px';
adReplacer.style.background = '#f00';
adContainer?.replaceChildren(adReplacer);
});
}
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import NextLink from 'next/link'; import NextLink from 'next/link';
...@@ -52,6 +52,8 @@ const BlockDetails = () => { ...@@ -52,6 +52,8 @@ const BlockDetails = () => {
router.push(url, undefined); router.push(url, undefined);
}, [ router ]); }, [ router ]);
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
if (isLoading) { if (isLoading) {
return <BlockDetailsSkeleton/>; return <BlockDetailsSkeleton/>;
} }
...@@ -68,7 +70,15 @@ const BlockDetails = () => { ...@@ -68,7 +70,15 @@ const BlockDetails = () => {
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>; const sectionGap = (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor={ borderColor }
/>
);
const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data); const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data);
const validatorTitle = getNetworkValidatorTitle(); const validatorTitle = getNetworkValidatorTitle();
...@@ -262,7 +272,7 @@ const BlockDetails = () => { ...@@ -262,7 +272,7 @@ const BlockDetails = () => {
{ /* ADDITIONAL INFO */ } { /* ADDITIONAL INFO */ }
{ isExpanded && ( { isExpanded && (
<> <>
{ sectionGap } <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailsInfoItem <DetailsInfoItem
title="Difficulty" title="Difficulty"
......
import { Grid, GridItem, Skeleton } from '@chakra-ui/react'; import { Grid, GridItem, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow'; import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const BlockDetailsSkeleton = () => { const BlockDetailsSkeleton = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const sectionGap = (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor={ borderColor }
/>
);
return ( return (
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px"> <Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
......
...@@ -11,6 +11,7 @@ import AddressDetails from 'ui/address/AddressDetails'; ...@@ -11,6 +11,7 @@ import AddressDetails from 'ui/address/AddressDetails';
import AddressInternalTxs from 'ui/address/AddressInternalTxs'; import AddressInternalTxs from 'ui/address/AddressInternalTxs';
import AddressTokenTransfers from 'ui/address/AddressTokenTransfers'; import AddressTokenTransfers from 'ui/address/AddressTokenTransfers';
import AddressTxs from 'ui/address/AddressTxs'; import AddressTxs from 'ui/address/AddressTxs';
import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
...@@ -40,16 +41,15 @@ const AddressPageContent = () => { ...@@ -40,16 +41,15 @@ const AddressPageContent = () => {
{ id: 'blocks_validated', title: 'Blocks validated', component: <AddressBlocksValidated addressQuery={ addressQuery }/> }, { id: 'blocks_validated', title: 'Blocks validated', component: <AddressBlocksValidated addressQuery={ addressQuery }/> },
]; ];
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
return ( return (
<Page> <Page>
<Flex alignItems="center" columnGap={ 3 }> <TextAd mb={ 6 }/>
<PageTitle text={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }/> <PageTitle
{ tags.length > 0 && ( text={ `${ addressQuery.data?.is_contract ? 'Contract' : 'Address' } details` }
<Flex mb={ 6 } columnGap={ 2 }> additionals={ tagsNode }
{ tags } />
</Flex>
) }
</Flex>
<AddressDetails addressQuery={ addressQuery }/> <AddressDetails addressQuery={ addressQuery }/>
<RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/> <RoutedTabs tabs={ tabs } tabListProps={{ mt: 8 }}/>
</Page> </Page>
......
import { Flex, Icon, Link, Tooltip } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import BlockDetails from 'ui/block/BlockDetails'; import BlockDetails from 'ui/block/BlockDetails';
import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
...@@ -52,16 +51,12 @@ const BlockPageContent = () => { ...@@ -52,16 +51,12 @@ const BlockPageContent = () => {
return ( return (
<Page> <Page>
<Flex alignItems="center" columnGap={ 3 }> <TextAd mb={ 6 }/>
{ hasGoBackLink && ( <PageTitle
<Tooltip label="Back to blocks list"> text={ `Block #${ router.query.id }` }
<Link mb={ 6 } display="inline-flex" href={ referrer }> backLinkUrl={ hasGoBackLink ? referrer : undefined }
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> backLinkLabel="Back to blocks list"
</Link> />
</Tooltip>
) }
<PageTitle text={ `Block #${ router.query.id }` }/>
</Flex>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
...@@ -42,7 +42,7 @@ const BlocksPageContent = () => { ...@@ -42,7 +42,7 @@ const BlocksPageContent = () => {
return ( return (
<Page> <Page>
<PageTitle text="Blocks"/> <PageTitle text="Blocks" withTextAd/>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
...@@ -5,9 +5,9 @@ import * as blockMock from 'mocks/blocks/block'; ...@@ -5,9 +5,9 @@ import * as blockMock from 'mocks/blocks/block';
import * as dailyTxsMock from 'mocks/stats/daily_txs'; import * as dailyTxsMock from 'mocks/stats/daily_txs';
import * as statsMock from 'mocks/stats/index'; import * as statsMock from 'mocks/stats/index';
import * as txMock from 'mocks/txs/tx'; import * as txMock from 'mocks/txs/tx';
import insertAdText from 'playwright/scripts/insertAdText';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import Home from './Home'; import Home from './Home';
...@@ -42,7 +42,7 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount, ...@@ -42,7 +42,7 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount,
</TestApp>, </TestApp>,
); );
await page.evaluate(insertAdText); await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
import { Flex, Link, Icon, Tag, Tooltip } from '@chakra-ui/react'; import { Flex, Tag } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import eastArrowIcon from 'icons/arrows/east.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import networkExplorers from 'lib/networks/networkExplorers'; import networkExplorers from 'lib/networks/networkExplorers';
import AdBanner from 'ui/shared/ad/AdBanner'; import TextAd from 'ui/shared/ad/TextAd';
import ExternalLink from 'ui/shared/ExternalLink'; import ExternalLink from 'ui/shared/ExternalLink';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -52,36 +51,33 @@ const TransactionPageContent = () => { ...@@ -52,36 +51,33 @@ const TransactionPageContent = () => {
return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>; return <ExternalLink key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>;
}); });
const additionals = (
<Flex justifyContent="space-between" alignItems="center" flexGrow={ 1 }>
{ data?.tx_tag && <Tag my={ 2 }>{ data.tx_tag }</Tag> }
{ explorersLinks.length > 0 && (
<Flex
alignItems="center"
flexWrap="wrap"
columnGap={ 6 }
rowGap={ 3 }
ml={{ base: 'initial', lg: 'auto' }}
>
{ explorersLinks }
</Flex>
) }
</Flex>
);
return ( return (
<Page> <Page>
<Flex alignItems="flex-start" flexDir={{ base: 'column', lg: 'row' }}> <TextAd mb={ 6 }/>
<Flex alignItems="center" columnGap={ 3 }> <PageTitle
{ hasGoBackLink && ( text="Transaction details"
<Tooltip label="Back to transactions list"> additionals={ additionals }
<Link display="inline-flex" href={ referrer } mb={ 6 }> backLinkUrl={ hasGoBackLink ? referrer : undefined }
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/> backLinkLabel="Back to transactions list"
</Link> />
</Tooltip>
) }
<PageTitle text="Transaction details"/>
</Flex>
{ data?.tx_tag && <Tag my={ 2 } ml={ 3 }>{ data.tx_tag }</Tag> }
{ explorersLinks.length > 0 && (
<Flex
alignItems="center"
flexWrap="wrap"
columnGap={ 6 }
rowGap={ 3 }
ml={{ base: 'initial', lg: 'auto' }}
mb={{ base: 6, lg: 'initial' }}
py={ 2.5 }
>
{ explorersLinks }
</Flex>
) }
</Flex>
<RoutedTabs tabs={ TABS }/> <RoutedTabs tabs={ TABS }/>
<AdBanner mt={ 6 } justifyContent={{ base: 'center', lg: 'start' }}/>
</Page> </Page>
); );
}; };
......
...@@ -37,7 +37,7 @@ const Transactions = () => { ...@@ -37,7 +37,7 @@ const Transactions = () => {
return ( return (
<Page hideMobileHeaderOnScrollDown> <Page hideMobileHeaderOnScrollDown>
<Box h="100%"> <Box h="100%">
<PageTitle text="Transactions"/> <PageTitle text="Transactions" withTextAd/>
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS } tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import PageTitle from './PageTitle';
const textAdMock = {
ad: {
name: 'Hello utia!',
description_short: 'Utia is the best! Go with utia! Utia is the best! Go with utia!',
thumbnail: 'https://utia.utia',
url: 'https://test.url',
cta_button: 'Click me!',
},
};
test.beforeEach(async({ page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: JSON.stringify(textAdMock),
}));
await page.route(textAdMock.ad.thumbnail, (route) => {
return route.fulfill({
status: 200,
path: './playwright/image_s.jpg',
});
});
});
test('default view +@mobile', async({ mount }) => {
const component = await mount(
<TestApp>
<PageTitle
text="Title"
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('with text ad, back link and addons +@mobile +@dark-mode', async({ mount }) => {
const component = await mount(
<TestApp>
<PageTitle
text="Title"
withTextAd
backLinkLabel="Back"
backLinkUrl="back"
additionals="Privet"
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('long title with text ad, back link and addons +@mobile', async({ mount }) => {
const component = await mount(
<TestApp>
<PageTitle
text="This title is long, really long"
withTextAd
backLinkLabel="Back"
backLinkUrl="back"
additionals="Privet, kak dela?"
/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Heading, chakra } from '@chakra-ui/react'; import { Heading, Flex, Tooltip, Link, Icon, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const PageTitle = ({ text, className }: {text: string; className?: string}) => { import eastArrowIcon from 'icons/arrows/east.svg';
import TextAd from 'ui/shared/ad/TextAd';
type Props = {
text: string;
additionals?: React.ReactNode;
withTextAd?: boolean;
className?: string;
backLinkLabel?: string;
backLinkUrl?: string;
}
const PageTitle = ({ text, additionals, withTextAd, backLinkUrl, backLinkLabel, className }: Props) => {
const title = (
<Heading
as="h1"
size="lg"
flex="none"
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
width={ backLinkUrl ? 'calc(100% - 36px)' : '100%' }
>
{ text }
</Heading>
);
return ( return (
<Heading as="h1" size="lg" marginBottom={ 6 } className={ className }>{ text }</Heading> <Flex
columnGap={ 3 }
rowGap={ 3 }
alignItems={{ base: 'start', lg: 'center' }}
flexDirection={{ base: 'column', lg: 'row' }}
mb={ 6 }
justifyContent="space-between"
className={ className }
>
<Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' }>
<Flex
flexWrap="nowrap"
alignItems="center"
columnGap={ 3 }
overflow="hidden"
>
{ backLinkUrl && (
<Tooltip label={ backLinkLabel }>
<Link display="inline-flex" href={ backLinkUrl }>
<Icon as={ eastArrowIcon } boxSize={ 6 } transform="rotate(180deg)"/>
</Link>
</Tooltip>
) }
{ title }
</Flex>
{ additionals }
</Flex>
{ withTextAd && <TextAd flexShrink={ 100 }/> }
</Flex>
); );
}; };
......
/* eslint-disable max-len */ /* eslint-disable max-len */
import { Flex, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import Script from 'next/script';
import React from 'react'; import React from 'react';
// eslint-disable-next-line @typescript-eslint/no-var-requires const scriptText1 = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`;
// const adbutlerHTML = require('ui/shared/ad/adbutler.html'); const scriptText2 = `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
// didn't find a way to raw-load html that works both for webpack (app build) and vite (playwright build)
const adbutlerHTML = `
<div id="ad-banner"></div>
<script type="text/javascript">if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}</script>
<script type="text/javascript">
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || ''; var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches; const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) { if (isMobile) {
...@@ -23,12 +18,15 @@ const adbutlerHTML = ` ...@@ -23,12 +18,15 @@ const adbutlerHTML = `
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>'; document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }}); AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} }
</script>
`; `;
const AdbutlerBanner = ({ className }: { className?: string }) => { const AdbutlerBanner = ({ className }: { className?: string }) => {
return ( return (
<Flex className={ className } dangerouslySetInnerHTML={{ __html: adbutlerHTML }}/> <Flex className={ className } id="adBanner">
<div id="ad-banner"></div>
<Script id="ad-butler-1">{ scriptText1 }</Script>
<Script id="ad-butler-2">{ scriptText2 }</Script>
</Flex>
); );
}; };
......
import { Flex, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import Script from 'next/script';
import React from 'react'; import React from 'react';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
...@@ -18,18 +19,21 @@ type CPreferences = { ...@@ -18,18 +19,21 @@ type CPreferences = {
const CoinzillaBanner = ({ className }: { className?: string }) => { const CoinzillaBanner = ({ className }: { className?: string }) => {
const isInBrowser = isBrowser(); const isInBrowser = isBrowser();
if (isInBrowser) {
window.coinzilla_display = window.coinzilla_display || []; React.useEffect(() => {
const cDisplayPreferences = {} as CPreferences; if (isInBrowser) {
cDisplayPreferences.zone = '26660bf627543e46851'; window.coinzilla_display = window.coinzilla_display || [];
cDisplayPreferences.width = '728'; const cDisplayPreferences = {} as CPreferences;
cDisplayPreferences.height = '90'; cDisplayPreferences.zone = '26660bf627543e46851';
window.coinzilla_display.push(cDisplayPreferences); cDisplayPreferences.width = '728';
} cDisplayPreferences.height = '90';
window.coinzilla_display.push(cDisplayPreferences);
}
}, [ isInBrowser ]);
return ( return (
<Flex className={ className }> <Flex className={ className } id="adBanner">
<script async src="https://coinzillatag.com/lib/display.js"></script> <Script src="https://coinzillatag.com/lib/display.js"/>
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div> <div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
</Flex> </Flex>
); );
......
import { Box, Image, Link, Text, chakra } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import { ndash } from 'lib/html-entities';
type AdData = {
ad: {
name: string;
description_short: string;
thumbnail: string;
url: string;
cta_button: string;
impressionUrl: string;
};
}
const CoinzillaTextAd = ({ className }: {className?: string}) => {
const [ adData, setAdData ] = React.useState<AdData | null>(null);
useEffect(() => {
fetch('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242')
.then(res => res.status === 200 ? res.json() : null)
.then((data) => {
setAdData(data);
if (data?.ad?.impressionUrl) {
fetch(data.ad.impressionUrl);
}
});
}, []);
if (!adData) {
return null;
}
const urlObject = new URL(adData.ad.url);
return (
<Box className={ className }>
<Text
as="span"
whiteSpace="pre-wrap"
fontWeight={ 700 }
mr={ 3 }
display={{ base: 'none', lg: 'inline' }}
>
ADs:
</Text>
{ urlObject.hostname === 'nifty.ink' ?
<Text as="span" mr={ 1 }>🎨</Text> :
<Image src={ adData.ad.thumbnail } width="20px" height="20px" mb="-4px" mr={ 1 } display="inline" alt=""/>
}
<Text as="span" whiteSpace="pre-wrap">{ `${ adData.ad.name } ${ ndash } ${ adData.ad.description_short } ` }</Text>
<Link href={ adData.ad.url }>{ adData.ad.cta_button }</Link>
</Box>
);
};
export default chakra(CoinzillaTextAd);
import { chakra } from '@chakra-ui/react';
import React from 'react';
import isSelfHosted from 'lib/isSelfHosted';
import CoinzillaTextAd from './CoinzillaTextAd';
const TextAd = ({ className }: {className?: string}) => {
if (!isSelfHosted()) {
return null;
}
return <CoinzillaTextAd className={ className }/>;
};
export default chakra(TextAd);
<div id="ad-banner"></div>
<script type="text/javascript">if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}</script>
<script type="text/javascript">
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) {
var plc539876 = window.plc539876 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_539876_'+plc539876+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3";
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 539876, [320,100], 'placement_539876_'+opt.place, opt); }, opt: { place: plc539876++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc523705 = window.plc523705 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
</script>
\ No newline at end of file
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import * as txMock from 'mocks/txs/tx'; import * as txMock from 'mocks/txs/tx';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import TxDetails from './TxDetails'; import TxDetails from './TxDetails';
...@@ -28,6 +29,7 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -28,6 +29,7 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => {
); );
await page.getByText('View details').click(); await page.getByText('View details').click();
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -45,6 +47,8 @@ test('creating contact', async({ mount, page }) => { ...@@ -45,6 +47,8 @@ test('creating contact', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -61,6 +65,8 @@ test('with token transfer +@mobile', async({ mount, page }) => { ...@@ -61,6 +65,8 @@ test('with token transfer +@mobile', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -77,6 +83,8 @@ test('with decoded revert reason', async({ mount, page }) => { ...@@ -77,6 +83,8 @@ test('with decoded revert reason', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -93,6 +101,8 @@ test('with decoded raw reason', async({ mount, page }) => { ...@@ -93,6 +101,8 @@ test('with decoded raw reason', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -110,6 +120,7 @@ test('pending', async({ mount, page }) => { ...@@ -110,6 +120,7 @@ test('pending', async({ mount, page }) => {
); );
await page.getByText('View details').click(); await page.getByText('View details').click();
await insertAdPlaceholder(page);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
import { Grid, GridItem, Text, Box, Icon, Link, Spinner, Tag, Flex, Tooltip, chakra } from '@chakra-ui/react'; import {
Grid,
GridItem,
Text,
Box,
Icon,
Link,
Spinner,
Tag,
Flex,
Tooltip,
chakra,
useColorModeValue,
} from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
...@@ -10,7 +23,9 @@ import errorIcon from 'icons/status/error.svg'; ...@@ -10,7 +23,9 @@ import errorIcon from 'icons/status/error.svg';
import successIcon from 'icons/status/success.svg'; import successIcon from 'icons/status/success.svg';
import { WEI, WEI_IN_GWEI } from 'lib/consts'; import { WEI, WEI_IN_GWEI } from 'lib/consts';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile';
import getConfirmationDuration from 'lib/tx/getConfirmationDuration'; import getConfirmationDuration from 'lib/tx/getConfirmationDuration';
import AdBanner from 'ui/shared/ad/AdBanner';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -34,6 +49,10 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo'; ...@@ -34,6 +49,10 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxDetails = () => { const TxDetails = () => {
const { data, isLoading, isError, socketStatus, error } = useFetchTxInfo(); const { data, isLoading, isError, socketStatus, error } = useFetchTxInfo();
const isMobile = useIsMobile();
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const [ isExpanded, setIsExpanded ] = React.useState(false); const [ isExpanded, setIsExpanded ] = React.useState(false);
const handleCutClick = React.useCallback(() => { const handleCutClick = React.useCallback(() => {
...@@ -148,7 +167,29 @@ const TxDetails = () => { ...@@ -148,7 +167,29 @@ const TxDetails = () => {
<Text variant="secondary">{ getConfirmationDuration(data.confirmation_duration) }</Text> <Text variant="secondary">{ getConfirmationDuration(data.confirmation_duration) }</Text>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 3, lg: 8 }}/> { isMobile ?
(
<GridItem
colSpan={{ base: undefined, lg: 2 }}
>
<AdBanner justifyContent="center"/>
</GridItem>
) :
(
<DetailsInfoItem
title="Sponsored"
hint="Sponsored banner advertisement"
>
<AdBanner/>
</DetailsInfoItem>
) }
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor={ borderColor }
/>
<DetailsInfoItem <DetailsInfoItem
title="From" title="From"
hint="Address (external or contract) sending the transaction." hint="Address (external or contract) sending the transaction."
...@@ -199,7 +240,13 @@ const TxDetails = () => { ...@@ -199,7 +240,13 @@ const TxDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
{ data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash }/> } { data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash }/> }
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 3, lg: 8 }}/> <GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor={ borderColor }
/>
<DetailsInfoItem <DetailsInfoItem
title="Value" title="Value"
hint="Value sent in the native token (and USD) if applicable." hint="Value sent in the native token (and USD) if applicable."
......
import { Grid, GridItem, Skeleton } from '@chakra-ui/react'; import { Grid, GridItem, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow'; import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const TxDetailsSkeleton = () => { const TxDetailsSkeleton = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>; const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const sectionGap = (
<GridItem
colSpan={{ base: undefined, lg: 2 }}
mt={{ base: 2, lg: 3 }}
mb={{ base: 0, lg: 3 }}
borderBottom="1px solid"
borderColor={ borderColor }
/>
);
return ( return (
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px"> <Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
......
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