Redesign hero section on home page (#2050)
* change hero section * add AdBanner to hero block * show icon instead of text on "connect wallet" button on md desktops * redesign stats widgets * new design for home page charts * fix skeletons * update demo values * disable getit provider * fixes for adbutler provider * show token logo placeholder for secondary coin logo * add width to hype banner * update demo values * remove letter spacing for large headings * indicators block fixes * update icons * update screenshots
@@ -43,7 +43,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=hype
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER=slise
|
||||
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global
|
||||
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
|
||||
@@ -87,10 +87,7 @@ frontend:
|
||||
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout
|
||||
NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER: getit
|
||||
NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: adbutler
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: "{ \"id\": \"632019\", \"width\": \"728\", \"height\": \"90\" }"
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }"
|
||||
NEXT_PUBLIC_AD_BANNER_PROVIDER: slise
|
||||
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
|
||||
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
|
||||
|
||||
@@ -359,6 +359,7 @@ This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_T
|
||||
### Banner ads
|
||||
|
||||
This feature is **enabled by default** with the `slise` ads provider. To switch it off pass `NEXT_PUBLIC_AD_BANNER_PROVIDER=none`.
|
||||
*Note* that the `getit` ad provider is temporary disabled.
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 11a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0ZM11 1C5.477 1 1 5.477 1 11s4.477 10 10 10 10-4.477 10-10S16.523 1 11 1Zm1.25 5a1.25 1.25 0 1 0-2.5 0v5c0 .69.56 1.25 1.25 1.25h5a1.25 1.25 0 1 0 0-2.5h-3.75V6Z" fill="currentColor" stroke="transparent" stroke-width=".6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 .3C4.643.3.3 4.643.3 10c0 5.357 4.343 9.7 9.7 9.7 5.357 0 9.7-4.343 9.7-9.7 0-5.357-4.343-9.7-9.7-9.7ZM2.2 10a7.8 7.8 0 1 1 15.6 0 7.8 7.8 0 0 1-15.6 0ZM10 4.05a.95.95 0 0 0-.95.95v5c0 .525.425.95.95.95h5a.95.95 0 1 0 0-1.9h-3.75a.3.3 0 0 1-.3-.3V5a.95.95 0 0 0-.95-.95Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 446 B After Width: | Height: | Size: 429 B |
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5 23.75h1.25v2.5H2.5v-2.5h1.25V5A1.25 1.25 0 0 1 5 3.75h11.25A1.25 1.25 0 0 1 17.5 5v10H20a2.5 2.5 0 0 1 2.5 2.5v5a1.25 1.25 0 0 0 2.5 0v-8.75h-2.5a1.25 1.25 0 0 1-1.25-1.25V8.018l-2.071-2.072 1.767-1.767 6.188 6.187a1.244 1.244 0 0 1 .366.884V22.5a3.75 3.75 0 0 1-7.5 0v-5h-2.5v6.25Zm-11.25 0H15v-7.5H6.25v7.5Zm0-17.5v7.5H15v-7.5H6.25Z" fill="currentColor"/>
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.666 15.833h.834V17.5H1.666v-1.667H2.5v-12.5a.833.833 0 0 1 .833-.833h7.5a.833.833 0 0 1 .833.833V10h1.667A1.667 1.667 0 0 1 15 11.667V15a.833.833 0 0 0 1.666 0V9.167H15a.833.833 0 0 1-.834-.834V5.345l-1.38-1.38 1.178-1.18 4.125 4.126a.831.831 0 0 1 .244.589V15a2.5 2.5 0 0 1-5 0v-3.333h-1.667v4.166Zm-7.5 0H10v-5H4.167v5Zm0-11.666v5H10v-5H4.167Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 464 B |
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.16 3A2.16 2.16 0 0 0 3 5.16v10.18a2.16 2.16 0 0 0 2.16 2.16h10.18a2.16 2.16 0 0 0 2.16-2.16v-3.272h-1.614v3.114c0 .389-.315.704-.704.704H5.318a.705.705 0 0 1-.704-.704V5.318c0-.389.315-.704.704-.704h3.114V3H5.159Zm6.135 0a.41.41 0 0 0-.41.41v.793c0 .226.184.409.41.409h3.453l-4.244 4.245a.409.409 0 0 0 0 .578l.56.561c.16.16.42.16.58 0l4.244-4.245v3.454c0 .226.183.41.41.41h.793a.41.41 0 0 0 .409-.41V3h-6.205Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.16 3A2.16 2.16 0 0 0 3 5.16v10.18a2.16 2.16 0 0 0 2.16 2.16h10.18a2.16 2.16 0 0 0 2.16-2.16v-3.272h-1.614v3.114a.704.704 0 0 1-.704.704H5.318a.705.705 0 0 1-.704-.704V5.318c0-.389.315-.704.704-.704h3.114V3H5.159Zm6.135 0a.41.41 0 0 0-.41.41v.793a.41.41 0 0 0 .41.409h3.453l-4.244 4.245a.409.409 0 0 0 0 .578l.56.561c.16.16.42.16.58 0l4.244-4.245v3.454c0 .226.183.41.41.41h.793a.41.41 0 0 0 .409-.41V3h-6.205Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 568 B After Width: | Height: | Size: 566 B |
@@ -1,11 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 31 30">
|
||||
<g fill="currentColor" clip-path="url(#wallet_svg__a)">
|
||||
<path d="M22.75 27.188h-15A4.688 4.688 0 0 1 3.062 22.5V9.375A.937.937 0 0 1 4 8.437h18.75a4.688 4.688 0 0 1 4.688 4.688V22.5a4.688 4.688 0 0 1-4.688 4.688ZM4.937 10.313V22.5a2.812 2.812 0 0 0 2.813 2.813h15a2.812 2.812 0 0 0 2.813-2.813v-9.375a2.812 2.812 0 0 0-2.813-2.813H4.937Z"/>
|
||||
<path d="M24.625 10.312a.937.937 0 0 1-.937-.937V6.797a2.184 2.184 0 0 0-.732-1.754 1.82 1.82 0 0 0-1.565-.3L5.669 8.315a.938.938 0 0 0-.731.938.938.938 0 0 1-1.875 0 2.813 2.813 0 0 1 2.184-2.766l15.731-3.572a3.656 3.656 0 0 1 3.15.666 4.069 4.069 0 0 1 1.435 3.216v2.578a.938.938 0 0 1-.938.937ZM26.5 21.563h-6.563a3.75 3.75 0 1 1 0-7.5H26.5a.938.938 0 0 1 .938.937v5.625a.938.938 0 0 1-.938.938Zm-6.563-5.625a1.875 1.875 0 1 0 0 3.75h5.625v-3.75h-5.625Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="wallet_svg__a">
|
||||
<path fill="#fff" d="M.25 0h30v30h-30z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.139 20H4.83a3.847 3.847 0 0 1-3.846-3.845V5.39a.769.769 0 0 1 .77-.769h15.384a3.847 3.847 0 0 1 3.846 3.845v7.69A3.844 3.844 0 0 1 17.139 20ZM2.524 6.16v9.996a2.307 2.307 0 0 0 2.307 2.307H17.14a2.308 2.308 0 0 0 2.308-2.307v-7.69a2.306 2.306 0 0 0-2.308-2.306H2.524Z" fill="currentColor"/>
|
||||
<path d="M18.677 6.159a.77.77 0 0 1-.769-.77V3.276a1.79 1.79 0 0 0-.6-1.438 1.493 1.493 0 0 0-1.284-.246l-12.9 2.93a.77.77 0 0 0-.6.769.769.769 0 0 1-1.539 0A2.306 2.306 0 0 1 2.778 3.02L15.685.091a3 3 0 0 1 2.585.546 3.338 3.338 0 0 1 1.177 2.638V5.39a.77.77 0 0 1-.77.769ZM20.216 15.386H14.83a3.077 3.077 0 0 1-2.175-5.25c.577-.578 1.36-.902 2.175-.902h5.385a.77.77 0 0 1 .769.77v4.613a.77.77 0 0 1-.77.769Zm-5.385-4.614a1.539 1.539 0 1 0 0 3.076h4.616v-3.076H14.83Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1015 B After Width: | Height: | Size: 880 B |
@@ -17,7 +17,7 @@ const TEST_URLS: Record<AdBannerProviders, string> = {
|
||||
adbutler: 'https://servedbyadbutler.com/app.js',
|
||||
hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js',
|
||||
// I don't have an url for getit to test
|
||||
getit: DEFAULT_URL,
|
||||
// getit: DEFAULT_URL,
|
||||
none: DEFAULT_URL,
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ export function ad(): CspDev.DirectiveDescriptor {
|
||||
// adbutler
|
||||
'servedbyadbutler.com',
|
||||
`'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`,
|
||||
`'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`,
|
||||
`'sha256-${ Base64.stringify(sha256(placeAd(undefined) ?? '')) }'`,
|
||||
`'sha256-${ Base64.stringify(sha256(placeAd('mobile') ?? '')) }'`,
|
||||
|
||||
// slise
|
||||
'*.slise.xyz',
|
||||
|
||||
@@ -17,7 +17,6 @@ const sizes = {
|
||||
xl: defineStyle({
|
||||
fontSize: '40px',
|
||||
lineHeight: '48px',
|
||||
letterSpacing: '-1px',
|
||||
}),
|
||||
lg: defineStyle({
|
||||
fontSize: '32px',
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { ArrayElement } from 'types/utils';
|
||||
|
||||
export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'hype', 'getit', 'none' ] as const;
|
||||
export const SUPPORTED_AD_BANNER_PROVIDERS = [
|
||||
'slise',
|
||||
'adbutler',
|
||||
'coinzilla',
|
||||
'hype',
|
||||
// 'getit', // temporary disabled
|
||||
'none',
|
||||
] as const;
|
||||
export type AdBannerProviders = ArrayElement<typeof SUPPORTED_AD_BANNER_PROVIDERS>;
|
||||
|
||||
export const SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS = [ 'adbutler' ] as const;
|
||||
|
||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -53,7 +53,7 @@ const TokenBalances = () => {
|
||||
name="Net Worth"
|
||||
value={ addressData?.exchange_rate ? `${ prefix }$${ totalUsd.toFormat(2) }` : 'N/A' }
|
||||
isLoading={ addressQuery.isPending || tokenQuery.isPending }
|
||||
icon={ <IconSvg name="wallet" boxSize="24px" flexShrink={ 0 } color="text_secondary"/> }
|
||||
icon={ <IconSvg name="wallet" boxSize="20px" flexShrink={ 0 } color="text_secondary"/> }
|
||||
/>
|
||||
<TokenBalancesItem
|
||||
name={ `${ currencyUnits.ether } Balance` }
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
|
||||
import * as statsMock from 'mocks/stats/index';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
import * as pwConfig from 'playwright/utils/config';
|
||||
|
||||
import Stats from './Stats';
|
||||
|
||||
@@ -18,14 +17,6 @@ test.describe('all items', () => {
|
||||
test('+@mobile +@dark-mode', async() => {
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('screen xl', () => {
|
||||
test.use({ viewport: pwConfig.viewport.xl });
|
||||
|
||||
test('', async() => {
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('4 items default view +@mobile -@default', async({ render, mockApiResponse, mockEnvs }) => {
|
||||
|
||||
@@ -2,8 +2,6 @@ import { Grid } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { WEI } from 'lib/consts';
|
||||
@@ -11,8 +9,7 @@ import { HOMEPAGE_STATS } from 'stubs/stats';
|
||||
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
|
||||
import GasPrice from 'ui/shared/gas/GasPrice';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import StatsItem from './StatsItem';
|
||||
import StatsWidget from 'ui/shared/stats/StatsWidget';
|
||||
|
||||
const hasAvgBlockTime = config.UI.homepage.showAvgBlockTime;
|
||||
const rollupFeature = config.features.rollup;
|
||||
@@ -58,7 +55,7 @@ const Stats = () => {
|
||||
|
||||
let content;
|
||||
|
||||
const lastItemTouchStyle = { gridColumn: { base: 'span 2', lg: 'unset' } };
|
||||
const lastItemStyle = { gridColumn: 'span 2' };
|
||||
|
||||
let itemsCount = 5;
|
||||
!hasGasTracker && itemsCount--;
|
||||
@@ -75,12 +72,9 @@ const Stats = () => {
|
||||
isLoading={ isLoading }
|
||||
name="info"
|
||||
boxSize={ 5 }
|
||||
display="block"
|
||||
flexShrink={ 0 }
|
||||
cursor="pointer"
|
||||
_hover={{ color: 'link_hovered' }}
|
||||
position="absolute"
|
||||
top={{ base: 'calc(50% - 12px)', lg: '10px', xl: 'calc(50% - 12px)' }}
|
||||
right="10px"
|
||||
/>
|
||||
</GasInfoTooltip>
|
||||
) : null;
|
||||
@@ -88,80 +82,80 @@ const Stats = () => {
|
||||
content = (
|
||||
<>
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && (
|
||||
<StatsItem
|
||||
icon="txn_batches"
|
||||
title="Latest batch"
|
||||
<StatsWidget
|
||||
icon="txn_batches_slim"
|
||||
label="Latest batch"
|
||||
value={ (zkEvmLatestBatchQuery.data || 0).toLocaleString() }
|
||||
url={ route({ pathname: '/batches' }) }
|
||||
href={{ pathname: '/batches' }}
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && (
|
||||
<StatsItem
|
||||
icon="txn_batches"
|
||||
title="Latest batch"
|
||||
<StatsWidget
|
||||
icon="txn_batches_slim"
|
||||
label="Latest batch"
|
||||
value={ (zkSyncLatestBatchQuery.data || 0).toLocaleString() }
|
||||
url={ route({ pathname: '/batches' }) }
|
||||
href={{ pathname: '/batches' }}
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ !(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && (
|
||||
<StatsItem
|
||||
icon="block"
|
||||
title="Total blocks"
|
||||
<StatsWidget
|
||||
icon="block_slim"
|
||||
label="Total blocks"
|
||||
value={ Number(data.total_blocks).toLocaleString() }
|
||||
url={ route({ pathname: '/blocks' }) }
|
||||
href={{ pathname: '/blocks' }}
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
{ hasAvgBlockTime && (
|
||||
<StatsItem
|
||||
icon="clock-light"
|
||||
title="Average block time"
|
||||
<StatsWidget
|
||||
icon="clock"
|
||||
label="Average block time"
|
||||
value={ `${ (data.average_block_time / 1000).toFixed(1) }s` }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
<StatsItem
|
||||
icon="transactions"
|
||||
title="Total transactions"
|
||||
<StatsWidget
|
||||
icon="transactions_slim"
|
||||
label="Total transactions"
|
||||
value={ Number(data.total_transactions).toLocaleString() }
|
||||
url={ route({ pathname: '/txs' }) }
|
||||
href={{ pathname: '/txs' }}
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
{ rollupFeature.isEnabled && data.last_output_root_size && (
|
||||
<StatsItem
|
||||
icon="txn_batches"
|
||||
title="Latest L1 state batch"
|
||||
<StatsWidget
|
||||
icon="txn_batches_slim"
|
||||
label="Latest L1 state batch"
|
||||
value={ data.last_output_root_size }
|
||||
url={ route({ pathname: '/batches' }) }
|
||||
href={{ pathname: '/batches' }}
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
) }
|
||||
<StatsItem
|
||||
<StatsWidget
|
||||
icon="wallet"
|
||||
title="Wallet addresses"
|
||||
label="Wallet addresses"
|
||||
value={ Number(data.total_addresses).toLocaleString() }
|
||||
_last={ isOdd ? lastItemTouchStyle : undefined }
|
||||
isLoading={ isLoading }
|
||||
_last={ isOdd ? lastItemStyle : undefined }
|
||||
/>
|
||||
{ hasGasTracker && data.gas_prices && (
|
||||
<StatsItem
|
||||
<StatsWidget
|
||||
icon="gas"
|
||||
title="Gas tracker"
|
||||
label="Gas tracker"
|
||||
value={ data.gas_prices.average ? <GasPrice data={ data.gas_prices.average }/> : 'N/A' }
|
||||
_last={ isOdd ? lastItemTouchStyle : undefined }
|
||||
tooltip={ gasInfoTooltip }
|
||||
hint={ gasInfoTooltip }
|
||||
isLoading={ isLoading }
|
||||
_last={ isOdd ? lastItemStyle : undefined }
|
||||
/>
|
||||
) }
|
||||
{ data.rootstock_locked_btc && (
|
||||
<StatsItem
|
||||
<StatsWidget
|
||||
icon="coins/bitcoin"
|
||||
title="BTC Locked in 2WP"
|
||||
label="BTC Locked in 2WP"
|
||||
value={ `${ BigNumber(data.rootstock_locked_btc).div(WEI).dp(0).toFormat() } RBTC` }
|
||||
_last={ isOdd ? lastItemTouchStyle : undefined }
|
||||
isLoading={ isLoading }
|
||||
_last={ isOdd ? lastItemStyle : undefined }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
@@ -170,10 +164,10 @@ const Stats = () => {
|
||||
|
||||
return (
|
||||
<Grid
|
||||
gridTemplateColumns={{ lg: `repeat(${ itemsCount }, 1fr)`, base: '1fr 1fr' }}
|
||||
gridTemplateRows={{ lg: 'none', base: undefined }}
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
gridGap={{ base: 1, lg: 2 }}
|
||||
marginTop={ 3 }
|
||||
flexBasis="50%"
|
||||
flexGrow={ 1 }
|
||||
>
|
||||
{ content }
|
||||
</Grid>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { SystemStyleObject } from '@chakra-ui/react';
|
||||
import { Skeleton, Flex, useColorModeValue, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import breakpoints from 'theme/foundations/breakpoints';
|
||||
import type { IconName } from 'ui/shared/IconSvg';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
type Props = {
|
||||
icon: IconName;
|
||||
title: string;
|
||||
value: string | React.ReactNode;
|
||||
className?: string;
|
||||
tooltip?: React.ReactNode;
|
||||
url?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const LARGEST_BREAKPOINT = '1240px';
|
||||
|
||||
const StatsItem = ({ icon, title, value, className, tooltip, url, isLoading }: Props) => {
|
||||
const sxContainer: SystemStyleObject = {
|
||||
[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { flexDirection: 'column' },
|
||||
};
|
||||
|
||||
const sxText: SystemStyleObject = {
|
||||
[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { alignItems: 'center' },
|
||||
};
|
||||
|
||||
const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100');
|
||||
const loadingBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
|
||||
|
||||
return (
|
||||
<Flex
|
||||
backgroundColor={ isLoading ? loadingBgColor : bgColor }
|
||||
padding={ 3 }
|
||||
borderRadius="md"
|
||||
flexDirection="row"
|
||||
sx={ sxContainer }
|
||||
alignItems="center"
|
||||
columnGap={ 3 }
|
||||
rowGap={ 2 }
|
||||
className={ className }
|
||||
color={ useColorModeValue('black', 'white') }
|
||||
position="relative"
|
||||
{ ...(url && !isLoading ? {
|
||||
as: 'a',
|
||||
href: url,
|
||||
} : {}) }
|
||||
>
|
||||
<IconSvg name={ icon } boxSize={ 7 } isLoading={ isLoading } borderRadius="base"/>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
alignItems="start"
|
||||
sx={ sxText }
|
||||
>
|
||||
<Skeleton isLoaded={ !isLoading } color="text_secondary" fontSize="xs" lineHeight="16px" borderRadius="base">
|
||||
<span>{ title }</span>
|
||||
</Skeleton>
|
||||
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 } fontSize="md" color={ useColorModeValue('black', 'white') } borderRadius="base">
|
||||
{ typeof value === 'string' ? <span>{ value }</span> : value }
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
{ tooltip }
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(StatsItem);
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,4 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { chakra, Flex, Box } from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
@@ -15,21 +15,25 @@ const ChainIndicatorChartContainer = ({ data, isError, isPending }: Props) => {
|
||||
|
||||
const content = (() => {
|
||||
if (isPending) {
|
||||
return <ContentLoader mt="auto"/>;
|
||||
return <ContentLoader mt="auto" fontSize="xs"/>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <DataFetchAlert/>;
|
||||
return <DataFetchAlert fontSize="xs" p={ 3 }/>;
|
||||
}
|
||||
|
||||
if (data[0].items.length === 0) {
|
||||
return <span>no data</span>;
|
||||
return <chakra.span fontSize="xs">no data</chakra.span>;
|
||||
}
|
||||
|
||||
return <ChainIndicatorChart data={ data }/>;
|
||||
return (
|
||||
<Box mx="-10px" my="-5px" h="calc(100% + 10px)" w="calc(100% + 20px)">
|
||||
<ChainIndicatorChart data={ data }/>
|
||||
</Box>
|
||||
);
|
||||
})();
|
||||
|
||||
return <Flex h={{ base: '150px', lg: 'auto' }} minH="150px" alignItems="flex-start" flexGrow={ 1 }>{ content }</Flex>;
|
||||
return <Flex h={{ base: '80px', lg: '110px' }} alignItems="flex-start" flexGrow={ 1 }>{ content }</Flex>;
|
||||
};
|
||||
|
||||
export default React.memo(ChainIndicatorChartContainer);
|
||||
|
||||
@@ -6,8 +6,6 @@ import type { HomeStats } from 'types/api/stats';
|
||||
import type { ChainIndicatorId } from 'types/homepage';
|
||||
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
interface Props {
|
||||
id: ChainIndicatorId;
|
||||
@@ -21,42 +19,27 @@ interface Props {
|
||||
}
|
||||
|
||||
const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, stats }: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const activeBgColorDesktop = useColorModeValue('white', 'gray.900');
|
||||
const activeBgColorMobile = useColorModeValue('white', 'black');
|
||||
const activeBgColor = isMobile ? activeBgColorMobile : activeBgColorDesktop;
|
||||
const activeColor = useColorModeValue('gray.500', 'gray.400');
|
||||
const activeBgColor = useColorModeValue('white', 'black');
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
onClick(id);
|
||||
}, [ id, onClick ]);
|
||||
|
||||
const valueContent = (() => {
|
||||
if (isMobile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stats.isPlaceholderData) {
|
||||
return (
|
||||
<Skeleton
|
||||
h={ 3 }
|
||||
w="70px"
|
||||
my={ 1.5 }
|
||||
// ssr: isMobile = undefined, isLoading = true
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!stats.data) {
|
||||
return <Text variant="secondary" fontWeight={ 400 }>no data</Text>;
|
||||
}
|
||||
|
||||
return <Text variant="secondary" fontWeight={ 600 }>{ value(stats.data) }</Text>;
|
||||
return (
|
||||
<Skeleton isLoaded={ !stats.isPlaceholderData } variant="secondary" fontWeight={ 600 } minW="30px">
|
||||
{ value(stats.data) }
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const valueDiffContent = (() => {
|
||||
if (isMobile || !valueDiff) {
|
||||
if (!valueDiff) {
|
||||
return null;
|
||||
}
|
||||
const diff = valueDiff(stats.data);
|
||||
@@ -67,8 +50,8 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
|
||||
const diffColor = diff >= 0 ? 'green.500' : 'red.500';
|
||||
|
||||
return (
|
||||
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }>
|
||||
<IconSvg name="arrows/up-head" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
|
||||
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 1 } display="flex" alignItems="center" color={ diffColor }>
|
||||
<span>{ diff >= 0 ? '+' : '-' }</span>
|
||||
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
|
||||
</Skeleton>
|
||||
);
|
||||
@@ -77,25 +60,28 @@ const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onC
|
||||
return (
|
||||
<Flex
|
||||
alignItems="center"
|
||||
columnGap={ 3 }
|
||||
px={ 4 }
|
||||
py={ 2 }
|
||||
columnGap={ 2 }
|
||||
flexGrow={{ base: 0, lg: 1 }}
|
||||
px={{ base: '6px', lg: 2 }}
|
||||
py="6px"
|
||||
as="li"
|
||||
borderRadius="md"
|
||||
borderRadius="base"
|
||||
cursor="pointer"
|
||||
color={ isSelected ? activeColor : 'link' }
|
||||
bgColor={ isSelected ? activeBgColor : undefined }
|
||||
onClick={ handleClick }
|
||||
bgColor={ isSelected ? activeBgColor : 'inherit' }
|
||||
boxShadow={ isSelected ? 'lg' : 'none' }
|
||||
zIndex={ isSelected ? 1 : 'initial' }
|
||||
fontSize="xs"
|
||||
fontWeight={ 500 }
|
||||
_hover={{
|
||||
activeBgColor,
|
||||
bgColor: activeBgColor,
|
||||
color: isSelected ? activeColor : 'link_hovered',
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
{ icon }
|
||||
<Box>
|
||||
<Text fontFamily="heading" fontWeight={ 500 }>{ title }</Text>
|
||||
<Flex alignItems="center">
|
||||
<Box display={{ base: 'none', lg: 'block' }}>
|
||||
<span>{ title }</span>
|
||||
<Flex alignItems="center" color="text">
|
||||
{ valueContent }
|
||||
{ valueDiffContent }
|
||||
</Flex>
|
||||
|
||||
@@ -22,7 +22,7 @@ test.describe('daily txs chart', () => {
|
||||
await mockApiResponse('stats_charts_txs', dailyTxsMock.base);
|
||||
await mockAssetResponse(statsMock.withSecondaryCoin.coin_image as string, './playwright/mocks/image_svg.svg');
|
||||
component = await render(<ChainIndicators/>);
|
||||
await page.hover('.ChartOverlay', { position: { x: 100, y: 100 } });
|
||||
await page.hover('.ChartOverlay', { position: { x: 50, y: 50 } });
|
||||
});
|
||||
|
||||
test('+@mobile', async() => {
|
||||
|
||||
@@ -38,10 +38,7 @@ const ChainIndicators = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const bgColorDesktop = useColorModeValue('white', 'gray.900');
|
||||
const bgColorMobile = useColorModeValue('white', 'black');
|
||||
const listBgColorDesktop = useColorModeValue('gray.50', 'black');
|
||||
const listBgColorMobile = useColorModeValue('gray.50', 'gray.900');
|
||||
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
|
||||
|
||||
if (indicators.length === 0) {
|
||||
return null;
|
||||
@@ -49,15 +46,15 @@ const ChainIndicators = () => {
|
||||
|
||||
const valueTitle = (() => {
|
||||
if (statsQueryResult.isPlaceholderData) {
|
||||
return <Skeleton h="48px" w="215px" mt={ 3 } mb={ 4 }/>;
|
||||
return <Skeleton h="36px" w="215px"/>;
|
||||
}
|
||||
|
||||
if (!statsQueryResult.data) {
|
||||
return <Text mt={ 3 } mb={ 4 }>There is no data</Text>;
|
||||
return <Text fontSize="xs">There is no data</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text fontWeight={ 600 } fontFamily="heading" fontSize="48px" lineHeight="48px" mt={ 3 }>
|
||||
<Text fontWeight={ 700 } fontSize="30px" lineHeight="36px">
|
||||
{ indicator?.value(statsQueryResult.data) }
|
||||
</Text>
|
||||
);
|
||||
@@ -85,23 +82,22 @@ const ChainIndicators = () => {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
p={{ base: 0, lg: 8 }}
|
||||
borderRadius={{ base: 'none', lg: 'lg' }}
|
||||
boxShadow={{ base: 'none', lg: 'xl' }}
|
||||
bgColor={{ base: bgColorMobile, lg: bgColorDesktop }}
|
||||
columnGap={ 6 }
|
||||
px={{ base: 3, lg: 4 }}
|
||||
py={ 3 }
|
||||
borderRadius="base"
|
||||
bgColor={ bgColor }
|
||||
columnGap={{ base: 3, lg: 4 }}
|
||||
rowGap={ 0 }
|
||||
flexDir={{ base: 'column', lg: 'row' }}
|
||||
w="100%"
|
||||
flexBasis="50%"
|
||||
flexGrow={ 1 }
|
||||
alignItems="stretch"
|
||||
mt={{ base: 1, lg: 3 }}
|
||||
>
|
||||
<Flex flexGrow={ 1 } flexDir="column" order={{ base: 2, lg: 1 }} p={{ base: 6, lg: 0 }}>
|
||||
<Flex flexGrow={ 1 } flexDir="column">
|
||||
<Flex alignItems="center">
|
||||
<Text fontWeight={ 500 } fontFamily="heading" fontSize="lg">{ indicator?.title }</Text>
|
||||
<Text fontWeight={ 500 }>{ indicator?.title }</Text>
|
||||
{ indicator?.hint && <Hint label={ indicator.hint } ml={ 1 }/> }
|
||||
</Flex>
|
||||
<Flex mb={ 4 } alignItems="end">
|
||||
<Flex mb={{ base: 0, lg: 2 }} mt={ 1 } alignItems="end">
|
||||
{ valueTitle }
|
||||
{ valueDiff }
|
||||
</Flex>
|
||||
@@ -112,11 +108,9 @@ const ChainIndicators = () => {
|
||||
flexShrink={ 0 }
|
||||
flexDir="column"
|
||||
as="ul"
|
||||
p={ 3 }
|
||||
borderRadius="lg"
|
||||
bgColor={{ base: listBgColorMobile, lg: listBgColorDesktop }}
|
||||
rowGap={ 3 }
|
||||
order={{ base: 1, lg: 2 }}
|
||||
rowGap="6px"
|
||||
m={{ base: 'auto 0', lg: 0 }}
|
||||
>
|
||||
{ indicators.map((indicator) => (
|
||||
<ChainIndicatorItem
|
||||
|
||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
@@ -7,6 +7,7 @@ import config from 'configs/app';
|
||||
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import NativeTokenIcon from 'ui/shared/NativeTokenIcon';
|
||||
import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder';
|
||||
|
||||
const nonNullTailReducer = (result: Array<TimeChartItemRaw>, item: TimeChartItemRaw) => {
|
||||
if (item.value === null && result.length === 0) {
|
||||
@@ -70,7 +71,7 @@ const secondaryCoinPriceIndicator: TChainIndicator<'stats_charts_secondary_coin_
|
||||
'$N/A' :
|
||||
'$' + Number(stats.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
|
||||
valueDiff: () => null,
|
||||
icon: <NativeTokenIcon boxSize={ 6 }/>,
|
||||
icon: <TokenLogoPlaceholder boxSize={ 6 }/>,
|
||||
hint: `${ config.chain.secondaryCoin.symbol } token daily price in USD.`,
|
||||
api: {
|
||||
resourceName: 'stats_charts_secondary_coin_price',
|
||||
|
||||
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
@@ -2,6 +2,7 @@ import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import ChainIndicators from 'ui/home/indicators/ChainIndicators';
|
||||
import LatestBlocks from 'ui/home/LatestBlocks';
|
||||
import LatestZkEvmL2Batches from 'ui/home/LatestZkEvmL2Batches';
|
||||
@@ -15,44 +16,51 @@ import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';
|
||||
const rollupFeature = config.features.rollup;
|
||||
|
||||
const Home = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Box as="main">
|
||||
<Box
|
||||
<Flex
|
||||
w="100%"
|
||||
background={ config.UI.homepage.plate.background }
|
||||
borderRadius={{ base: 'md', lg: 'xl' }}
|
||||
px={{ base: 4, lg: 10 }}
|
||||
py={{ base: 3, lg: 8 }}
|
||||
minW={{ base: 'unset', lg: '900px' }}
|
||||
borderRadius="md"
|
||||
p={{ base: 4, lg: 8 }}
|
||||
columnGap={ 8 }
|
||||
alignItems="center"
|
||||
data-label="hero plate"
|
||||
>
|
||||
<Flex mb={{ base: 2, lg: 6 }} justifyContent="space-between" alignItems="center">
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize={{ base: '18px', lg: '40px' }}
|
||||
lineHeight={{ base: '24px', lg: '48px' }}
|
||||
fontWeight={ 600 }
|
||||
color={ config.UI.homepage.plate.textColor }
|
||||
>
|
||||
{
|
||||
config.meta.seo.enhancedDataEnabled ?
|
||||
`${ config.chain.name } blockchain explorer` :
|
||||
`${ config.chain.name } explorer`
|
||||
}
|
||||
</Heading>
|
||||
{ config.UI.navigation.layout === 'vertical' && (
|
||||
<Box display={{ base: 'none', lg: 'flex' }}>
|
||||
{ config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> }
|
||||
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> }
|
||||
</Box>
|
||||
) }
|
||||
</Flex>
|
||||
<SearchBar isHomepage/>
|
||||
</Box>
|
||||
<Stats/>
|
||||
<ChainIndicators/>
|
||||
<AdBanner mt={ 6 } mx="auto" display="flex" justifyContent="center"/>
|
||||
<Flex mt={ 6 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 6 }>
|
||||
<Box flexGrow={ 1 }>
|
||||
<Flex mb={{ base: 2, lg: 3 }} justifyContent="space-between" alignItems="center" columnGap={ 2 }>
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize={{ base: '18px', lg: '30px' }}
|
||||
lineHeight={{ base: '24px', lg: '36px' }}
|
||||
fontWeight={{ base: 500, lg: 700 }}
|
||||
color={ config.UI.homepage.plate.textColor }
|
||||
>
|
||||
{
|
||||
config.meta.seo.enhancedDataEnabled ?
|
||||
`${ config.chain.name } blockchain explorer` :
|
||||
`${ config.chain.name } explorer`
|
||||
}
|
||||
</Heading>
|
||||
{ config.UI.navigation.layout === 'vertical' && (
|
||||
<Box display={{ base: 'none', lg: 'flex' }}>
|
||||
{ config.features.account.isEnabled && <ProfileMenuDesktop isHomePage/> }
|
||||
{ config.features.blockchainInteraction.isEnabled && <WalletMenuDesktop isHomePage/> }
|
||||
</Box>
|
||||
) }
|
||||
</Flex>
|
||||
<SearchBar isHomepage/>
|
||||
</Box>
|
||||
{ !isMobile && <AdBanner platform="mobile" w="fit-content" flexShrink={ 0 } borderRadius="md" overflow="hidden"/> }
|
||||
</Flex>
|
||||
<Flex flexDir={{ base: 'column', lg: 'row' }} columnGap={ 2 } rowGap={ 1 } mt={ 3 } _empty={{ mt: 0 }}>
|
||||
<Stats/>
|
||||
<ChainIndicators/>
|
||||
</Flex>
|
||||
{ isMobile && <AdBanner mt={ 6 } mx="auto" display="flex" justifyContent="center"/> }
|
||||
<Flex mt={ 8 } direction={{ base: 'column', lg: 'row' }} columnGap={ 12 } rowGap={ 6 }>
|
||||
{ rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' ? <LatestZkEvmL2Batches/> : <LatestBlocks/> }
|
||||
<Box flexGrow={ 1 }>
|
||||
<Transactions/>
|
||||
|
||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@@ -1,6 +1,8 @@
|
||||
import { chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerPlatform } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { useAppContext } from 'lib/contexts/app';
|
||||
import * as cookies from 'lib/cookies';
|
||||
@@ -9,7 +11,13 @@ import AdBannerContent from './AdBannerContent';
|
||||
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => {
|
||||
interface Props {
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
platform?: BannerPlatform;
|
||||
}
|
||||
|
||||
const AdBanner = ({ className, isLoading, platform }: Props) => {
|
||||
const provider = useAppContext().adBannerProvider;
|
||||
|
||||
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies);
|
||||
@@ -23,6 +31,7 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo
|
||||
className={ className }
|
||||
isLoading={ isLoading }
|
||||
provider={ provider }
|
||||
platform={ platform }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
import { chakra, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerPlatform } from './types';
|
||||
import type { AdBannerProviders } from 'types/client/adProviders';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import AdbutlerBanner from './AdbutlerBanner';
|
||||
import CoinzillaBanner from './CoinzillaBanner';
|
||||
import GetitBanner from './GetitBanner';
|
||||
// import GetitBanner from './GetitBanner';
|
||||
import HypeBanner from './HypeBanner';
|
||||
import SliseBanner from './SliseBanner';
|
||||
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
const AdBannerContent = ({ className, isLoading, provider }: { className?: string; isLoading?: boolean; provider: AdBannerProviders }) => {
|
||||
interface Props {
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
platform?: BannerPlatform;
|
||||
provider: AdBannerProviders;
|
||||
}
|
||||
|
||||
const AdBannerContent = ({ className, isLoading, provider, platform }: Props) => {
|
||||
const content = (() => {
|
||||
switch (provider) {
|
||||
case 'adbutler':
|
||||
return <AdbutlerBanner/>;
|
||||
return <AdbutlerBanner platform={ platform }/>;
|
||||
case 'coinzilla':
|
||||
return <CoinzillaBanner/>;
|
||||
case 'getit':
|
||||
return <GetitBanner/>;
|
||||
return <CoinzillaBanner platform={ platform }/>;
|
||||
// case 'getit':
|
||||
// return <GetitBanner platform={ platform }/>;
|
||||
case 'hype':
|
||||
return <HypeBanner/>;
|
||||
return <HypeBanner platform={ platform }/>;
|
||||
case 'slise':
|
||||
return <SliseBanner/>;
|
||||
return <SliseBanner platform={ platform }/>;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import isBrowser from 'lib/isBrowser';
|
||||
@@ -10,9 +12,10 @@ import { connectAdbutler, placeAd, ADBUTLER_ACCOUNT } from 'ui/shared/ad/adbutle
|
||||
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
const AdbutlerBanner = ({ className }: { className?: string }) => {
|
||||
const AdbutlerBanner = ({ className, platform }: BannerProps) => {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const isMobileViewport = useIsMobile();
|
||||
const isMobile = platform === 'mobile' || isMobileViewport;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!('adButler' in feature)) {
|
||||
@@ -24,10 +27,10 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
|
||||
if (!window.AdButler.ads) {
|
||||
window.AdButler.ads = [];
|
||||
}
|
||||
const adButlerConfig = isMobile ? feature.adButler.config.mobile : feature.adButler.config.desktop;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore:
|
||||
let plc = window[`plc${ feature.adButler.config.mobile.id }`] || 0;
|
||||
const adButlerConfig = isMobile ? feature.adButler.config.mobile : feature.adButler.config.desktop;
|
||||
let plc = window[`plc${ adButlerConfig.id }`] || 0;
|
||||
const banner = document.getElementById('ad-banner');
|
||||
if (banner) {
|
||||
banner.innerHTML = '<' + 'div id="placement_' + adButlerConfig?.id + '_' + plc + '"></' + 'div>';
|
||||
@@ -46,10 +49,34 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
|
||||
}
|
||||
}, [ router, isMobile ]);
|
||||
|
||||
if (!('adButler' in feature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { width, height } = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop':
|
||||
return { width: `${ feature.adButler.config.desktop.width }px`, height: `${ feature.adButler.config.desktop.height }px` };
|
||||
case 'mobile':
|
||||
return { width: `${ feature.adButler.config.mobile.width }px`, height: `${ feature.adButler.config.mobile.height }px` };
|
||||
default:
|
||||
return {
|
||||
width: {
|
||||
base: `${ feature.adButler.config.mobile.width }px`,
|
||||
lg: `${ feature.adButler.config.desktop.width }px`,
|
||||
},
|
||||
height: {
|
||||
base: `${ feature.adButler.config.mobile.height }px`,
|
||||
lg: `${ feature.adButler.config.desktop.height }px`,
|
||||
},
|
||||
};
|
||||
}
|
||||
})() ?? { width: '0', height: '0' };
|
||||
|
||||
return (
|
||||
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
|
||||
<Flex className={ className } id="adBanner" h={ height } w={ width }>
|
||||
<Script strategy="lazyOnload" id="ad-butler-1">{ connectAdbutler }</Script>
|
||||
<Script strategy="lazyOnload" id="ad-butler-2">{ placeAd }</Script>
|
||||
<Script strategy="lazyOnload" id="ad-butler-2">{ placeAd(platform) }</Script>
|
||||
<div id="ad-banner"></div>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -2,25 +2,44 @@ import { Flex, chakra } from '@chakra-ui/react';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import isBrowser from 'lib/isBrowser';
|
||||
|
||||
const CoinzillaBanner = ({ className }: { className?: string }) => {
|
||||
const CoinzillaBanner = ({ className, platform }: BannerProps) => {
|
||||
const isInBrowser = isBrowser();
|
||||
|
||||
const { width, height } = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop':
|
||||
return { width: 728, height: 90 };
|
||||
case 'mobile':
|
||||
return { width: 320, height: 100 };
|
||||
default:
|
||||
return { width: undefined, height: undefined };
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isInBrowser) {
|
||||
window.coinzilla_display = window.coinzilla_display || [];
|
||||
const cDisplayPreferences = {
|
||||
zone: '26660bf627543e46851',
|
||||
width: '728',
|
||||
height: '90',
|
||||
width: width ? String(width) : '728',
|
||||
height: height ? String(height) : '90',
|
||||
};
|
||||
window.coinzilla_display.push(cDisplayPreferences);
|
||||
}
|
||||
}, [ isInBrowser ]);
|
||||
}, [ height, isInBrowser, width ]);
|
||||
|
||||
return (
|
||||
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
|
||||
<Flex
|
||||
className={ className }
|
||||
id={ 'adBanner' + (platform ? `_${ platform }` : '') }
|
||||
h={ height ? `${ height }px` : { base: '100px', lg: '90px' } }
|
||||
w={ width ? `${ width }px` : undefined }
|
||||
>
|
||||
<Script strategy="lazyOnload" src="https://coinzillatag.com/lib/display.js"/>
|
||||
<div className="coinzilla" data-zone="C-26660bf627543e46851"></div>
|
||||
</Flex>
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Flex, chakra } from '@chakra-ui/react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
|
||||
@@ -9,17 +11,17 @@ const GetitAdPlugin = dynamic(() => import('getit-sdk').then(module => module.Ge
|
||||
|
||||
const GETIT_API_KEY = 'ZmGXVvwYUAW4yXL8RzWQHNKmpSyQmt3TDXsXUxqFqXPdoaiSSFyca3BOyunDcWdyOwTkX3UVVQel28qbjoOoWPxYVpPdNzbUNkAHyFyJX7Lk9TVcPDZKTQmwHlSMzO3a';
|
||||
|
||||
const GetitBanner = ({ className }: { className?: string }) => {
|
||||
const GetitBanner = ({ className, platform }: BannerProps) => {
|
||||
const isMobile = Boolean(useIsMobile());
|
||||
const { address } = useAccount();
|
||||
|
||||
return (
|
||||
<Flex className={ className } h="90px">
|
||||
<Flex className={ className } h="90px" w={{ base: '270px', lg: platform === 'mobile' ? '270px' : undefined }}>
|
||||
<GetitAdPlugin
|
||||
key={ isMobile.toString() }
|
||||
apiKey={ GETIT_API_KEY }
|
||||
walletConnected={ address ? address : '' }
|
||||
isMobile={ isMobile }
|
||||
isMobile={ platform === 'mobile' || isMobile }
|
||||
slotId="0"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Banner, setWalletAddresses } from '@hypelab/sdk-react';
|
||||
import Script from 'next/script';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
|
||||
import { hypeInit } from './hypeBannerScript';
|
||||
@@ -10,7 +12,7 @@ import { hypeInit } from './hypeBannerScript';
|
||||
const DESKTOP_BANNER_SLUG = 'b1559fc3e7';
|
||||
const MOBILE_BANNER_SLUG = '668ed80a9e';
|
||||
|
||||
const HypeBanner = ({ className }: { className?: string }) => {
|
||||
const HypeBanner = ({ className, platform }: BannerProps) => {
|
||||
const { address } = useAccount();
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -19,18 +21,46 @@ const HypeBanner = ({ className }: { className?: string }) => {
|
||||
}
|
||||
}, [ address ]);
|
||||
|
||||
const banner = (() => {
|
||||
switch (platform) {
|
||||
case 'desktop': {
|
||||
return (
|
||||
<Flex className={ className } w="728px" h="90px">
|
||||
<Banner placement={ DESKTOP_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
case 'mobile': {
|
||||
return (
|
||||
<Flex className={ className } w="320px" h="50px">
|
||||
<Banner placement={ MOBILE_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<>
|
||||
<Flex className={ className } w="728px" h="90px" display={{ base: 'none', lg: 'flex' }}>
|
||||
<Banner placement={ DESKTOP_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
<Flex className={ className } w="320px" h="50px" display={{ base: 'flex', lg: 'none' }}>
|
||||
<Banner placement={ MOBILE_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
id="hypelab"
|
||||
strategy="afterInteractive"
|
||||
>{ hypeInit }</Script>
|
||||
<Flex className={ className } h="90px" display={{ base: 'none', lg: 'flex' }}>
|
||||
<Banner placement={ DESKTOP_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
<Flex className={ className } h="50px" display={{ base: 'flex', lg: 'none' }}>
|
||||
<Banner placement={ MOBILE_BANNER_SLUG }/>
|
||||
</Flex>
|
||||
>
|
||||
{ hypeInit }
|
||||
</Script>
|
||||
{ banner }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,9 +2,35 @@ import { Flex, chakra } from '@chakra-ui/react';
|
||||
import { SliseAd } from '@slise/embed-react';
|
||||
import React from 'react';
|
||||
|
||||
import type { BannerProps } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
const SliseBanner = ({ className }: { className?: string }) => {
|
||||
const SliseBanner = ({ className, platform }: BannerProps) => {
|
||||
|
||||
if (platform === 'desktop') {
|
||||
return (
|
||||
<Flex className={ className } h="90px">
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="728x90"
|
||||
style={{ width: '728px', height: '90px' }}/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'mobile') {
|
||||
return (
|
||||
<Flex className={ className } h="90px">
|
||||
<SliseAd
|
||||
slotId={ config.chain.name || '' }
|
||||
pub="pub-10"
|
||||
format="270x90"
|
||||
style={{ width: '270px', height: '90px' }}/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
/* eslint-disable max-len */
|
||||
import type { BannerPlatform } from './types';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
export const ADBUTLER_ACCOUNT = 182226;
|
||||
|
||||
export const connectAdbutler = `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);}());}`;
|
||||
|
||||
export const placeAd = (() => {
|
||||
export const placeAd = ((platform: BannerPlatform | undefined) => {
|
||||
const feature = config.features.adsBanner;
|
||||
|
||||
if (!('adButler' in feature)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (platform === 'mobile') {
|
||||
return `
|
||||
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
|
||||
var abkw = window.abkw || '';
|
||||
var plc${ feature.adButler.config.mobile.id } = window.plc${ feature.adButler.config.mobile.id } || 0;
|
||||
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ feature.adButler.config.mobile.id }_'+plc${ feature.adButler.config.mobile.id }+'"></'+'div>';
|
||||
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.mobile.id }, [${ feature.adButler.config.mobile.width },${ feature.adButler.config.mobile.height }], 'placement_${ feature.adButler.config.mobile.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.mobile.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
|
||||
var abkw = window.abkw || '';
|
||||
@@ -26,4 +38,4 @@ export const placeAd = (() => {
|
||||
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.desktop.id }, [${ feature.adButler.config.desktop.width },${ feature.adButler.config.desktop.height }], 'placement_${ feature.adButler.config.desktop.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.desktop.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
|
||||
}
|
||||
`;
|
||||
})();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type BannerPlatform = 'mobile' | 'desktop';
|
||||
|
||||
export interface BannerProps {
|
||||
className?: string;
|
||||
platform?: BannerPlatform;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -5,20 +5,23 @@ import React from 'react';
|
||||
import type { Route } from 'nextjs-routes';
|
||||
|
||||
import Hint from 'ui/shared/Hint';
|
||||
import IconSvg, { type IconName } from 'ui/shared/IconSvg';
|
||||
import TruncatedValue from 'ui/shared/TruncatedValue';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
value: string | React.ReactNode;
|
||||
valuePrefix?: string;
|
||||
valuePostfix?: string;
|
||||
hint?: string;
|
||||
hint?: string | React.ReactNode;
|
||||
isLoading?: boolean;
|
||||
diff?: string | number;
|
||||
diffFormatted?: string;
|
||||
diffPeriod?: '24h';
|
||||
period?: '1h' | '24h';
|
||||
href?: Route;
|
||||
icon?: IconName;
|
||||
}
|
||||
|
||||
const Container = ({ href, children }: { href?: Route; children: JSX.Element }) => {
|
||||
@@ -33,31 +36,57 @@ const Container = ({ href, children }: { href?: Route; children: JSX.Element })
|
||||
return children;
|
||||
};
|
||||
|
||||
const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint, diff, diffPeriod = '24h', diffFormatted, period, href }: Props) => {
|
||||
const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100');
|
||||
const StatsWidget = ({
|
||||
className,
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
valuePrefix,
|
||||
valuePostfix,
|
||||
isLoading,
|
||||
hint,
|
||||
diff,
|
||||
diffPeriod = '24h',
|
||||
diffFormatted,
|
||||
period,
|
||||
href,
|
||||
}: Props) => {
|
||||
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');
|
||||
const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
|
||||
const hintColor = useColorModeValue('gray.600', 'gray.400');
|
||||
|
||||
return (
|
||||
<Container href={ !isLoading ? href : undefined }>
|
||||
<Flex
|
||||
alignItems="flex-start"
|
||||
className={ className }
|
||||
alignItems="center"
|
||||
bgColor={ isLoading ? skeletonBgColor : bgColor }
|
||||
px={ 3 }
|
||||
py={{ base: 2, lg: 3 }}
|
||||
borderRadius="md"
|
||||
p={ 3 }
|
||||
borderRadius="base"
|
||||
justifyContent="space-between"
|
||||
columnGap={ 3 }
|
||||
columnGap={ 2 }
|
||||
{ ...(href && !isLoading ? {
|
||||
as: 'a',
|
||||
href,
|
||||
} : {}) }
|
||||
>
|
||||
{ icon && (
|
||||
<IconSvg
|
||||
name={ icon }
|
||||
p={ 2 }
|
||||
boxSize="40px"
|
||||
isLoading={ isLoading }
|
||||
borderRadius="base"
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
flexShrink={ 0 }
|
||||
/>
|
||||
) }
|
||||
<Box w="100%">
|
||||
<Skeleton
|
||||
isLoaded={ !isLoading }
|
||||
color="text_secondary"
|
||||
fontSize="xs"
|
||||
lineHeight="16px"
|
||||
w="fit-content"
|
||||
>
|
||||
<span>{ label }</span>
|
||||
@@ -66,30 +95,36 @@ const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint,
|
||||
isLoaded={ !isLoading }
|
||||
display="flex"
|
||||
alignItems="baseline"
|
||||
mt={ 1 }
|
||||
fontWeight={ 500 }
|
||||
fontSize="lg"
|
||||
lineHeight={ 6 }
|
||||
>
|
||||
{ valuePrefix && <chakra.span fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } whiteSpace="pre">{ valuePrefix }</chakra.span> }
|
||||
<TruncatedValue isLoading={ isLoading } fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } value={ value }/>
|
||||
{ valuePostfix && <chakra.span fontWeight={ 500 } fontSize="lg" lineHeight={ 6 } whiteSpace="pre">{ valuePostfix }</chakra.span> }
|
||||
{ valuePrefix && <chakra.span whiteSpace="pre">{ valuePrefix }</chakra.span> }
|
||||
{ typeof value === 'string' ? (
|
||||
<TruncatedValue isLoading={ isLoading } value={ value }/>
|
||||
) : (
|
||||
value
|
||||
) }
|
||||
{ valuePostfix && <chakra.span whiteSpace="pre">{ valuePostfix }</chakra.span> }
|
||||
{ diff && Number(diff) > 0 && (
|
||||
<>
|
||||
<Text fontWeight={ 500 } ml={ 2 } mr={ 1 } fontSize="lg" lineHeight={ 6 } color="green.500">
|
||||
<Text ml={ 2 } mr={ 1 } color="green.500">
|
||||
+{ diffFormatted || Number(diff).toLocaleString() }
|
||||
</Text>
|
||||
<Text variant="secondary" fontSize="sm">({ diffPeriod })</Text>
|
||||
</>
|
||||
) }
|
||||
{ period && <Text variant="secondary" fontSize="xs" ml={ 1 }>({ period })</Text> }
|
||||
{ period && <Text variant="secondary" fontSize="xs" fontWeight={ 400 } ml={ 1 }>({ period })</Text> }
|
||||
</Skeleton>
|
||||
</Box>
|
||||
{ hint && (
|
||||
{ typeof hint === 'string' ? (
|
||||
<Skeleton isLoaded={ !isLoading } alignSelf="center" borderRadius="base">
|
||||
<Hint label={ hint } boxSize={ 6 } color={ hintColor }/>
|
||||
</Skeleton>
|
||||
) }
|
||||
) : hint }
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatsWidget;
|
||||
export default chakra(StatsWidget);
|
||||
|
||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.7 KiB |