Skip to content

Commit

Permalink
Latest blocks layout issue
Browse files Browse the repository at this point in the history
Fixes #1189
  • Loading branch information
tom2drum committed Sep 25, 2023
2 parents d787f1a + a8412d2 commit 31ccec9
Show file tree
Hide file tree
Showing 61 changed files with 679 additions and 132 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY=xxx
16 changes: 16 additions & 0 deletions configs/app/ui/views/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { IdenticonType } from 'types/views/address';
import { IDENTICON_TYPES } from 'types/views/address';

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

const identiconType: IdenticonType = (() => {
const value = getEnvValue(process.env.NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE);

return IDENTICON_TYPES.find((type) => value === type) || 'jazzicon';
})();

const config = Object.freeze({
identiconType: identiconType,
});

export default config;
1 change: 1 addition & 0 deletions configs/app/ui/views/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as block } from './block';
export { default as address } from './address';
2 changes: 2 additions & 0 deletions configs/envs/.env.main.L2
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/base.svg
## footer
## misc
## views
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar

# app features
NEXT_PUBLIC_APP_INSTANCE=local
Expand Down
2 changes: 2 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
import type { ChainIndicatorId } from '../../../types/homepage';
import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
import { IDENTICON_TYPES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
import type { BlockFieldId } from '../../../types/views/block';

Expand Down Expand Up @@ -294,6 +295,7 @@ const schema = yup
.transform(getEnvValue)
.json()
.of(yup.string<BlockFieldId>().oneOf(BLOCK_FIELDS_IDS)),
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES),

// e. misc
NEXT_PUBLIC_NETWORK_EXPLORERS: yup
Expand Down
2 changes: 2 additions & 0 deletions deploy/values/review/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,5 @@ frontend:
_default: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY
NEXT_PUBLIC_WEB3_WALLETS:
_default: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE:
_default: gradient_avatar
8 changes: 8 additions & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ By default, the app has generic favicon. You can override this behavior by provi

&nbsp;

#### Address views

| Variable | Type | Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` |

&nbsp;

### Misc

| Variable | Type| Description | Compulsoriness | Default value | Example value |
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
"d3": "^7.6.1",
"dayjs": "^1.11.5",
"dom-to-image": "^2.6.0",
"ethereum-blockies-base64": "^1.0.2",
"framer-motion": "^6.5.1",
"gradient-avatar": "^1.0.2",
"graphiql": "^2.2.0",
"graphql": "^16.6.0",
"graphql-ws": "^5.11.3",
Expand Down
8 changes: 8 additions & 0 deletions theme/components/Modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ const baseStyle = definePartsStyle((props) => ({
}));

const sizes = {
sm: definePartsStyle({
dialogContainer: {
height: '100%',
},
dialog: {
maxW: '536px',
},
}),
md: definePartsStyle({
dialogContainer: {
height: '100%',
Expand Down
10 changes: 10 additions & 0 deletions types/views/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ArrayElement } from 'types/utils';

export const IDENTICON_TYPES = [
'github',
'jazzicon',
'gradient_avatar',
'blockie',
] as const;

export type IdenticonType = ArrayElement<typeof IDENTICON_TYPES>;
8 changes: 7 additions & 1 deletion ui/address/AddressTxs.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ base.describe('base view', () => {
base.beforeEach(async({ page, mount }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ txMock.base, txMock.base ], next_page_params: { block: 1 } }),
body: JSON.stringify({ items: [
txMock.base,
{
...txMock.base,
hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3194',
},
], next_page_params: { block: 1 } }),
}));

component = await mount(
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion ui/address/details/AddressQrCode.pw.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';

import * as addressMock from 'mocks/address/address';
import TestApp from 'playwright/TestApp';

import AddressQrCode from './AddressQrCode';

test('default view +@mobile +@dark-mode', async({ mount, page }) => {
await mount(
<TestApp>
<AddressQrCode hash="0x363574E6C5C71c343d7348093D84320c76d5Dd29"/>
<AddressQrCode address={ addressMock.withoutName }/>
</TestApp>,
);
await page.getByRole('button', { name: /qr code/i }).click();
Expand Down
57 changes: 42 additions & 15 deletions ui/address/details/AddressQrCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
ModalBody,
ModalContent,
ModalCloseButton,
ModalHeader,
ModalOverlay,
LightMode,
Box,
useDisclosure,
Tooltip,
Expand All @@ -18,24 +20,26 @@ import { useRouter } from 'next/router';
import QRCode from 'qrcode';
import React from 'react';

import type { Address as AddressType } from 'types/api/address';

import qrCodeIcon from 'icons/qr_code.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';

const SVG_OPTIONS = {
margin: 0,
};

interface Props {
className?: string;
hash: string;
address: AddressType;
isLoading?: boolean;
}

const AddressQrCode = ({ hash, className, isLoading }: Props) => {
const AddressQrCode = ({ address, className, isLoading }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const isMobile = useIsMobile();

const router = useRouter();

const [ qr, setQr ] = React.useState('');
Expand All @@ -45,7 +49,7 @@ const AddressQrCode = ({ hash, className, isLoading }: Props) => {

React.useEffect(() => {
if (isOpen) {
QRCode.toString(hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => {
QRCode.toString(address.hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => {
if (error) {
setError('We were unable to generate QR code.');
Sentry.captureException(error, { tags: { source: 'qr_code' } });
Expand All @@ -57,7 +61,7 @@ const AddressQrCode = ({ hash, className, isLoading }: Props) => {
mixpanel.logEvent(mixpanel.EventTypes.QR_CODE, { 'Page type': pageType });
});
}
}, [ hash, isOpen, onClose, pageType ]);
}, [ address.hash, isOpen, onClose, pageType ]);

if (isLoading) {
return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>;
Expand All @@ -77,15 +81,38 @@ const AddressQrCode = ({ hash, className, isLoading }: Props) => {
icon={ <Icon as={ qrCodeIcon } boxSize={ 5 }/> }
/>
</Tooltip>
<Modal isOpen={ isOpen } onClose={ onClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
<ModalContent bgColor={ error ? undefined : 'white' }>
{ isMobile && <ModalCloseButton/> }
<ModalBody mb={ 0 }>
{ error ? <Alert status="warning">{ error }</Alert> : <Box dangerouslySetInnerHTML={{ __html: qr }}/> }
</ModalBody>
</ModalContent>
</Modal>

{ error && (
<Modal isOpen={ isOpen } onClose={ onClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
<ModalContent>
<ModalBody mb={ 0 }>
<Alert status="warning">{ error }</Alert>
</ModalBody>
</ModalContent>
</Modal>
) }
{ !error && (
<LightMode>
<Modal isOpen={ isOpen } onClose={ onClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
<ModalContent>
<ModalHeader fontWeight="500" textStyle="h3" mb={ 4 }>Address QR code</ModalHeader>
<ModalCloseButton/>
<ModalBody mb={ 0 }>
<AddressEntity
mb={ 3 }
fontWeight={ 500 }
color="text"
address={ address }
noLink
/>
<Box p={ 4 } dangerouslySetInnerHTML={{ __html: qr }}/>
</ModalBody>
</ModalContent>
</Modal>
</LightMode>
) }
</>
);
};
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 7 additions & 8 deletions ui/address/tokens/NFTItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Skeleton } from '@chakra-ui/react';
import { Box, Flex, Link, Text, useColorModeValue, Skeleton } from '@chakra-ui/react';
import React from 'react';

import type { AddressTokenBalance } from 'types/api/address';
Expand All @@ -12,27 +12,26 @@ import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = AddressTokenBalance & { isLoading: boolean };

const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLoading }: Props) => {
const tokenLink = route({ pathname: '/token/[hash]', query: { hash: token.address } });
const tokenInstanceLink = tokenId ? route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) : undefined;

return (
<LinkBox
<Box
w={{ base: '100%', lg: '210px' }}
border="1px solid"
borderColor={ useColorModeValue('blackAlpha.100', 'whiteAlpha.200') }
borderRadius="12px"
p="10px"
_hover={{ boxShadow: 'md' }}
fontSize="sm"
fontWeight={ 500 }
lineHeight="20px"
>
<LinkOverlay href={ isLoading ? undefined : tokenLink }>
<Link href={ isLoading ? undefined : tokenInstanceLink }>
<NftMedia
mb="18px"
url={ tokenInstance?.animation_url || tokenInstance?.image_url || null }
isLoading={ isLoading }
/>
</LinkOverlay>
</Link>
{ tokenId && (
<Flex mb={ 2 } ml={ 1 }>
<Text whiteSpace="pre" variant="secondary">ID# </Text>
Expand All @@ -44,7 +43,7 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLo
whiteSpace="nowrap"
textOverflow="ellipsis"
overflow="hidden"
href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) }
href={ tokenInstanceLink }
>
{ tokenId }
</Link>
Expand All @@ -58,7 +57,7 @@ const NFTItem = ({ token, token_id: tokenId, token_instance: tokenInstance, isLo
noCopy
noSymbol
/>
</LinkBox>
</Box>
);
};

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ui/shared/AddressHeadingInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props
{ !isLoading && !address.is_contract && config.features.account.isEnabled && (
<AddressFavoriteButton hash={ address.hash } watchListId={ address.watchlist_address_id } ml={ 3 }/>
) }
<AddressQrCode hash={ address.hash } ml={ 2 } isLoading={ isLoading } flexShrink={ 0 }/>
<AddressQrCode address={ address } ml={ 2 } isLoading={ isLoading } flexShrink={ 0 }/>
{ config.features.account.isEnabled && <AddressActionsMenu isLoading={ isLoading }/> }
</Flex>
);
Expand Down
43 changes: 43 additions & 0 deletions ui/shared/IdenticonGithub.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useColorModeValue, useToken, Box, chakra, Skeleton } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import React from 'react';

const Identicon = dynamic<{ bg: string; string: string; size: number }>(
async() => {
const lib = await import('react-identicons');
return typeof lib === 'object' && 'default' in lib ? lib.default : lib;
},
{
loading: () => <Skeleton w="100%" h="100%"/>,
ssr: false,
},
);

interface Props {
className?: string;
size: number;
seed: string;
}

const IdenticonGithub = ({ size, seed }: Props) => {
const bgColor = useToken('colors', useColorModeValue('gray.100', 'white'));

return (
<Box
boxSize={ `${ size * 2 }px` }
transformOrigin="left top"
transform="scale(0.5)"
borderRadius="full"
overflow="hidden"
>
<Identicon
bg={ bgColor }
string={ seed }
// the displayed size is doubled for retina displays and then scaled down
size={ size * 2 }
/>
</Box>
);
};

export default React.memo(chakra(IdenticonGithub));
Loading

0 comments on commit 31ccec9

Please sign in to comment.