Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitattributes

This file was deleted.

3 changes: 0 additions & 3 deletions .husky/pre-push

This file was deleted.

1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ export default tseslint.config(
// rules customizations
eqeqeq: [ 'error', 'allow-null' ],
'id-match': [ 'error', '^[\\w$]+$' ],
'max-len': [ 'error', 160, 4 ],
'no-console': 'error',
'no-implicit-coercion': [ 'error', {
number: true,
Expand Down
31 changes: 0 additions & 31 deletions lib/hooks/useNavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,31 +223,6 @@ export default function useNavItems(): ReturnType {
},
].filter(Boolean);

const apiNavItems: Array<NavItem> = [
config.features.restApiDocs.isEnabled ? {
text: 'REST API',
nextRoute: { pathname: '/api-docs' as const },
icon: 'restAPI',
isActive: pathname === '/api-docs',
} : null,
config.features.graphqlApiDocs.isEnabled ? {
text: 'GraphQL',
nextRoute: { pathname: '/graphiql' as const },
icon: 'graphQL',
isActive: pathname === '/graphiql',
} : null,
!config.UI.navigation.hiddenLinks?.rpc_api && {
text: 'RPC API',
icon: 'RPC',
url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints',
},
!config.UI.navigation.hiddenLinks?.eth_rpc_api && {
text: 'Eth RPC API',
icon: 'RPC',
url: ' https://docs.blockscout.com/for-users/api/eth-rpc',
},
].filter(Boolean);

const otherNavItems: Array<NavItem> | Array<Array<NavItem>> = [
{
text: 'Verify contract',
Expand Down Expand Up @@ -303,12 +278,6 @@ export default function useNavItems(): ReturnType {
icon: 'stats',
isActive: pathname.startsWith('/stats'),
} : null,
apiNavItems.length > 0 && {
text: 'API',
icon: 'restAPI',
isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive),
subItems: apiNavItems,
},
{
text: 'Other',
icon: 'gear',
Expand Down
2 changes: 1 addition & 1 deletion toolkit/chakra/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
const { loading = false, ...rest } = props;
return (
<ChakraSkeleton
loading={ loading ? 'true' : 'false' }
loading={ loading ? true : false }
css={ !loading ? { animation: 'none' } : {} }
{ ...(loading ? { 'data-loading': true } : {}) }
{ ...rest }
Expand Down
145 changes: 97 additions & 48 deletions ui/home/HeroBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// we use custom heading size for hero banner
// eslint-disable-next-line no-restricted-imports
import { Box, Flex, Heading } from '@chakra-ui/react';
import { Box, Flex, Heading, VStack } from '@chakra-ui/react';
import React from 'react';

import config from 'configs/app';
Expand All @@ -10,23 +10,26 @@ import SearchBar from 'ui/snippets/searchBar/SearchBar';
import UserProfileDesktop from 'ui/snippets/user/profile/UserProfileDesktop';
import UserWalletDesktop from 'ui/snippets/user/wallet/UserWalletDesktop';

export const BACKGROUND_DEFAULT =
'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)';
export const BACKGROUND_DEFAULT = { _light: 'gray.900', _dark: 'gray.800' };
const TEXT_COLOR_DEFAULT = 'white';
const BORDER_DEFAULT = 'none';

const HeroBanner = () => {
const background = {
_light:
config.UI.homepage.heroBanner?.background?.[0] ||
config.UI.homepage.plate.background ||
BACKGROUND_DEFAULT,
_dark:
config.UI.homepage.heroBanner?.background?.[1] ||
config.UI.homepage.heroBanner?.background?.[0] ||
config.UI.homepage.plate.background ||
BACKGROUND_DEFAULT,
};
// Use config background if provided (could be gradient string), otherwise use premium gradient
const configBackgroundLight = config.UI.homepage.heroBanner?.background?.[0] || config.UI.homepage.plate.background;
const configBackgroundDark = config.UI.homepage.heroBanner?.background?.[1] ||
config.UI.homepage.heroBanner?.background?.[0] ||
config.UI.homepage.plate.background;

const hasConfigBackground = Boolean(configBackgroundLight);

// Bright cyan-based gradient matching OpenGradient branding
const premiumGradientLight = 'linear-gradient(135deg, #00d4ff 0%, #00a3cc 25%, #0066ff 50%, #00d4ff 75%, #00ffff 100%)';
const premiumGradientDark = 'linear-gradient(135deg, #001a33 0%, #003366 25%, #004080 50%, #001a33 75%, #002244 100%)';

const backgroundValue = hasConfigBackground ?
{ _light: configBackgroundLight, _dark: configBackgroundDark } :
{ _light: premiumGradientLight, _dark: premiumGradientDark };

const textColor = {
_light:
Expand All @@ -50,44 +53,90 @@ const HeroBanner = () => {
};

return (
<Flex
<Box
position="relative"
w="100%"
background={ background }
overflow="hidden"
{ ...(hasConfigBackground && typeof configBackgroundLight === 'string' ?
{ background: backgroundValue } :
{ background: backgroundValue }) }
border={ border }
borderRadius="md"
p={{ base: 4, lg: 8 }}
columnGap={ 8 }
alignItems="center"
p={{ base: 6, lg: 12 }}
mb={{ base: 6, lg: 8 }}
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 50%, rgba(0, 212, 255, 0.4) 0%, transparent 50%), radial-gradient(circle at 80% 80%, rgba(0, 102, 255, 0.4) 0%, transparent 50%)',
pointerEvents: 'none',
_dark: {
background: 'radial-gradient(circle at 20% 50%, rgba(0, 212, 255, 0.25) 0%, transparent 50%), radial-gradient(circle at 80% 80%, rgba(0, 102, 255, 0.25) 0%, transparent 50%)',
},
}}
>
<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={ textColor }
>
{
config.meta.seo.enhancedDataEnabled ?
`${ config.chain.name } Testnet Explorer` :
`${ config.chain.name } Testnet Explorer`
}
</Heading>
{ config.UI.navigation.layout === 'vertical' && (
<Box display={{ base: 'none', lg: 'flex' }} gap={ 2 }>
{ config.features.rewards.isEnabled && <RewardsButton variant="hero"/> }
{
(config.features.account.isEnabled && <UserProfileDesktop buttonVariant="hero"/>) ||
(config.features.blockchainInteraction.isEnabled && <UserWalletDesktop buttonVariant="hero"/>)
}
<Flex
position="relative"
zIndex={ 1 }
direction={{ base: 'column', lg: 'row' }}
columnGap={ 8 }
rowGap={ 6 }
alignItems={{ base: 'stretch', lg: 'center' }}
>
<Box flexGrow={ 1 } minW={ 0 }>
<VStack gap={{ base: 3, lg: 4 }} alignItems={{ base: 'stretch', lg: 'flex-start' }} mb={{ base: 4, lg: 6 }}>
<Flex
justifyContent="space-between"
alignItems={{ base: 'flex-start', lg: 'center' }}
columnGap={ 4 }
flexDirection={{ base: 'column', lg: 'row' }}
width="100%"
>
<VStack gap={ 2 } alignItems={{ base: 'stretch', lg: 'flex-start' }} flex={ 1 }>
<Heading
as="h1"
fontSize={{ base: '30px', md: '40px', lg: '50px' }}
lineHeight={{ base: '1.2', lg: '1.1' }}
fontWeight={ 700 }
color={ textColor }
letterSpacing="-0.02em"
>
{
config.meta.seo.enhancedDataEnabled ?
`${ config.chain.name } Explorer` :
`${ config.chain.name } Explorer`
}
</Heading>
</VStack>
{ config.UI.navigation.layout === 'vertical' && (
<Box display={{ base: 'none', lg: 'flex' }} gap={ 2 } flexShrink={ 0 }>
{ config.features.rewards.isEnabled && <RewardsButton variant="hero"/> }
{
(config.features.account.isEnabled && <UserProfileDesktop buttonVariant="hero"/>) ||
(config.features.blockchainInteraction.isEnabled && <UserWalletDesktop buttonVariant="hero"/>)
}
</Box>
) }
</Flex>
<Box
w="100%"
maxW={{ lg: '800px' }}
>
<SearchBar isHomepage/>
</Box>
) }
</Flex>
<SearchBar isHomepage/>
</Box>
<AdBanner platform="mobile" w="fit-content" flexShrink={ 0 } borderRadius="md" overflow="hidden" display={{ base: 'none', lg: 'block ' }}/>
</Flex>
</VStack>
</Box>
<AdBanner
platform="mobile"
w="fit-content"
flexShrink={ 0 }
overflow="hidden"
display={{ base: 'none', lg: 'block' }}
/>
</Flex>
</Box>
);
};

Expand Down
85 changes: 51 additions & 34 deletions ui/home/LatestBlocks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { chakra, Box, Flex, Text, VStack } from '@chakra-ui/react';
import { chakra, Box, Text } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';

Expand All @@ -11,14 +11,12 @@ import config from 'configs/app';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useInitialList from 'lib/hooks/useInitialList';
import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { BLOCK } from 'stubs/block';
import { HOMEPAGE_STATS } from 'stubs/stats';
import { Heading } from 'toolkit/chakra/heading';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';

import LatestBlocksItem from './LatestBlocksItem';

Expand All @@ -27,9 +25,9 @@ const LatestBlocks = () => {
// const blocksMaxCount = isMobile ? 2 : 3;
let blocksMaxCount: number;
if (config.features.rollup.isEnabled || config.UI.views.block.hiddenFields?.total_reward) {
blocksMaxCount = isMobile ? 4 : 5;
blocksMaxCount = isMobile ? 6 : 10;
} else {
blocksMaxCount = isMobile ? 2 : 3;
blocksMaxCount = isMobile ? 4 : 8;
}
const { data, isPlaceholderData, isError } = useApiQuery('homepage_blocks', {
queryOptions: {
Expand Down Expand Up @@ -84,43 +82,62 @@ const LatestBlocks = () => {

content = (
<>
<VStack gap={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<Box width="100%">
{ dataToShow.map(((block, index) => (
<LatestBlocksItem
key={ block.height + (isPlaceholderData ? String(index) : '') }
block={ block }
isLoading={ isPlaceholderData }
animation={ initialList.getAnimationProp(block) }
/>
<Box key={ block.height + (isPlaceholderData ? String(index) : '') } mb={ 2 }>
<LatestBlocksItem
block={ block }
isLoading={ isPlaceholderData }
animation={ initialList.getAnimationProp(block) }
/>
</Box>
))) }
</VStack>
<Flex justifyContent="center">
<Link textStyle="sm" href={ route({ pathname: '/blocks' }) }>View all blocks</Link>
</Flex>
</Box>
<Box mt={ 4 }>
<Link
textStyle="sm"
href={ route({ pathname: '/blocks' }) }
color={{ _light: 'blue.600', _dark: 'blue.300' }}
fontWeight={ 500 }
px={ 4 }
py={ 2 }
width="100%"
display="block"
textAlign="center"
transition="all 0.2s"
_hover={{
textDecoration: 'none',
bg: { _light: 'blue.50', _dark: 'blue.900' },
color: { _light: 'blue.700', _dark: 'blue.200' },
}}
>
View all blocks
</Link>
</Box>
</>
);
}

return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading level="3">Latest blocks</Heading>
{ statsQueryResult.data?.network_utilization_percentage !== undefined && (
<Skeleton loading={ statsQueryResult.isPlaceholderData } mt={ 2 } display="inline-block" textStyle="sm">
<Text as="span">
Network utilization:{ nbsp }
</Text>
<Text as="span" color="blue.400" fontWeight={ 700 }>
{ statsQueryResult.data?.network_utilization_percentage.toFixed(2) }%
</Text>
</Skeleton>
) }
{ statsQueryResult.data?.celo && (
<Box whiteSpace="pre-wrap" textStyle="sm" mt={ 2 }>
<span>Current epoch: </span>
<chakra.span fontWeight={ 700 }>#{ statsQueryResult.data.celo.epoch_number }</chakra.span>
</Box>
) }
<Box mt={ 3 }>
<Box px={{ base: 4, lg: 6 }} pt={{ base: 4, lg: 6 }} pb={ 4 }>
<Heading
level="3"
fontSize={{ base: 'xl', lg: '2xl' }}
fontWeight={ 700 }
letterSpacing="-0.02em"
mb={ statsQueryResult.data?.celo ? 2 : 0 }
>
Latest blocks
</Heading>
{ statsQueryResult.data?.celo && (
<Box whiteSpace="pre-wrap" textStyle="sm" mt={ 2 }>
<span>Current epoch: </span>
<chakra.span fontWeight={ 700 }>#{ statsQueryResult.data.celo.epoch_number }</chakra.span>
</Box>
) }
</Box>
<Box>
{ content }
</Box>
</Box>
Expand Down
Loading
Loading