Commit 67272a9c authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #130 from blockscout/lighthouse-report-fixes

some improvements from lighthouse report
parents 4a32e7de 6bc9c5f8
import { withSentry } from '@sentry/nextjs';
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import fetch from 'lib/api/fetch'; import fetch from 'lib/api/fetch';
...@@ -5,28 +6,39 @@ import getUrlWithNetwork from 'lib/api/getUrlWithNetwork'; ...@@ -5,28 +6,39 @@ import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE'; type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
export default function handler<TRes, TErrRes>(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>) { export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>) {
return async(_req: NextApiRequest, res: NextApiResponse<TRes | TErrRes>) => { const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
if (_req.method && allowedMethods.includes(_req.method as Methods)) { if (!_req.method || !allowedMethods.includes(_req.method as Methods)) {
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD'; res.setHeader('Allow', allowedMethods);
res.status(405).end(`Method ${ _req.method } Not Allowed`);
return;
}
const url = getUrlWithNetwork(_req, `/api${ getUrl(_req) }`); const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const response = await fetch(url, {
method: _req.method,
body: isBodyDisallowed ? undefined : _req.body,
});
if (response.status !== 200) { const url = getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const error = await response.json() as { errors: TErrRes }; const response = await fetch(url, {
res.status(500).json(error?.errors || {} as TErrRes); method: _req.method,
return; body: isBodyDisallowed ? undefined : _req.body,
} });
const data = await response.json() as TRes; if (response.status === 200) {
const data = await response.json();
res.status(200).json(data); res.status(200).json(data);
} else { return;
res.setHeader('Allow', allowedMethods);
res.status(405).end(`Method ${ _req.method } Not Allowed`);
} }
let responseError;
try {
const error = await response.json() as { errors: unknown };
responseError = error?.errors || {};
} catch (error) {
responseError = { statusText: response.statusText, status: response.status };
}
res.status(500).json(responseError);
}; };
return withSentry(handler);
} }
...@@ -7,7 +7,14 @@ const headers = require('./configs/nextjs/headers'); ...@@ -7,7 +7,14 @@ const headers = require('./configs/nextjs/headers');
const moduleExports = { const moduleExports = {
include: path.resolve(__dirname, 'icons'), include: path.resolve(__dirname, 'icons'),
reactStrictMode: true, reactStrictMode: true,
webpack(config) { webpack(config, { webpack }) {
config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_DEBUG__: false,
__SENTRY_TRACING__: false,
}),
);
return config; return config;
}, },
async redirects() { async redirects() {
......
...@@ -7,7 +7,7 @@ import theme from 'theme'; ...@@ -7,7 +7,7 @@ import theme from 'theme';
class MyDocument extends Document { class MyDocument extends Document {
render() { render() {
return ( return (
<Html> <Html lang="en">
<Head> <Head>
<link <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
......
import type { ApiKeys, ApiKeyErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const apiKeysHandler = handler<ApiKeys, ApiKeyErrors>(() => '/account/v1/user/api_keys', [ 'GET', 'POST' ]); const apiKeysHandler = handler(() => '/account/v1/user/api_keys', [ 'GET', 'POST' ]);
export default apiKeysHandler; export default apiKeysHandler;
import type { CustomAbis, CustomAbiErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const customAbiHandler = handler<CustomAbis, CustomAbiErrors>(() => '/account/v1/user/custom_abis', [ 'GET', 'POST' ]); const customAbiHandler = handler(() => '/account/v1/user/custom_abis', [ 'GET', 'POST' ]);
export default customAbiHandler; export default customAbiHandler;
import type { AddressTags, AddressTagErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const addressHandler = handler<AddressTags, AddressTagErrors>(() => '/account/v1/user/tags/address', [ 'GET', 'POST' ]); const addressHandler = handler(() => '/account/v1/user/tags/address', [ 'GET', 'POST' ]);
export default addressHandler; export default addressHandler;
import type { TransactionTags, TransactionTagErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const transactionHandler = handler<TransactionTags, TransactionTagErrors>(() => '/account/v1/user/tags/transaction', [ 'GET', 'POST' ]); const transactionHandler = handler(() => '/account/v1/user/tags/transaction', [ 'GET', 'POST' ]);
export default transactionHandler; export default transactionHandler;
import type { UserInfo } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const profileHandler = handler<UserInfo, unknown>(() => '/account/v1/user/info', [ 'GET' ]); const profileHandler = handler(() => '/account/v1/user/info', [ 'GET' ]);
export default profileHandler; export default profileHandler;
import type { PublicTags, PublicTagErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const publicKeysHandler = handler<PublicTags, PublicTagErrors>(() => '/account/v1/user/public_tags', [ 'GET', 'POST' ]); const publicKeysHandler = handler(() => '/account/v1/user/public_tags', [ 'GET', 'POST' ]);
export default publicKeysHandler; export default publicKeysHandler;
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import type { WatchlistAddresses, WatchlistErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => { const getUrl = (req: NextApiRequest) => {
return `/account/v1/user/watchlist/${ req.query.id }`; return `/account/v1/user/watchlist/${ req.query.id }`;
}; };
const addressEditHandler = handler<WatchlistAddresses, WatchlistErrors>(getUrl, [ 'DELETE', 'PUT' ]); const addressEditHandler = handler(getUrl, [ 'DELETE', 'PUT' ]);
export default addressEditHandler; export default addressEditHandler;
import type { WatchlistAddresses, WatchlistErrors } from 'types/api/account';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const watchlistHandler = handler<WatchlistAddresses, WatchlistErrors>(() => '/account/v1/user/watchlist', [ 'GET', 'POST' ]); const watchlistHandler = handler(() => '/account/v1/user/watchlist', [ 'GET', 'POST' ]);
export default watchlistHandler; export default watchlistHandler;
...@@ -9,10 +9,10 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -9,10 +9,10 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
const SOCIAL_LINKS = [ const SOCIAL_LINKS = [
{ link: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, icon: ghIcon }, { link: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, icon: ghIcon, label: 'Github link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK, icon: twIcon }, { link: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK, icon: twIcon, label: 'Twitter link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK, icon: tgIcon }, { link: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK, icon: tgIcon, label: 'Telegram link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK, icon: statsIcon }, { link: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK, icon: statsIcon, label: 'Staking analytic link' },
].filter(({ link }) => link !== undefined); ].filter(({ link }) => link !== undefined);
const BLOCKSCOUT_VERSION = process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION; const BLOCKSCOUT_VERSION = process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION;
...@@ -51,7 +51,7 @@ const NavFooter = ({ isCollapsed }: Props) => { ...@@ -51,7 +51,7 @@ const NavFooter = ({ isCollapsed }: Props) => {
<Stack direction={ isCollapsed ? 'column' : 'row' }> <Stack direction={ isCollapsed ? 'column' : 'row' }>
{ SOCIAL_LINKS.map(sl => { { SOCIAL_LINKS.map(sl => {
return ( return (
<Link href={ sl.link } key={ sl.link } variant="secondary" w={ 5 } h={ 5 }> <Link href={ sl.link } key={ sl.link } variant="secondary" w={ 5 } h={ 5 } aria-label={ sl.label }>
<Icon as={ sl.icon } boxSize={ 5 }/> <Icon as={ sl.icon } boxSize={ 5 }/>
</Link> </Link>
); );
......
...@@ -24,6 +24,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => { ...@@ -24,6 +24,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
overflow="hidden" overflow="hidden"
onClick={ onClick } onClick={ onClick }
{ ...getDefaultTransitionProps({ transitionProperty: 'width' }) } { ...getDefaultTransitionProps({ transitionProperty: 'width' }) }
aria-label="Link to main page"
> >
<Icon <Icon
as={ logoIcon } as={ logoIcon }
......
...@@ -26,6 +26,8 @@ const NetworkMenuButton = ({ isMobile, isActive, onClick }: Props, ref: React.Fo ...@@ -26,6 +26,8 @@ const NetworkMenuButton = ({ isMobile, isActive, onClick }: Props, ref: React.Fo
borderRadius="base" borderRadius="base"
backgroundColor={ isActive ? bgColorMobile : 'none' } backgroundColor={ isActive ? bgColorMobile : 'none' }
onClick={ onClick } onClick={ onClick }
aria-label="Network menu"
aria-roledescription="menu"
> >
<Icon <Icon
as={ networksIcon } as={ networksIcon }
......
...@@ -56,7 +56,7 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -56,7 +56,7 @@ const WatchListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%"> <Flex alignItems="center" justifyContent="space-between" mt={ 6 } w="100%">
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Email notification</Text> <Text fontSize="sm" fontWeight={ 500 }>Email notification</Text>
<Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch }/> <Switch colorScheme="blue" size="md" isChecked={ notificationEnabled } onChange={ onSwitch } aria-label="Email notification"/>
</HStack> </HStack>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</Flex> </Flex>
......
...@@ -84,6 +84,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -84,6 +84,7 @@ const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
isChecked={ notificationEnabled } isChecked={ notificationEnabled }
onChange={ onSwitch } onChange={ onSwitch }
isDisabled={ switchDisabled } isDisabled={ switchDisabled }
aria-label="Email notification"
/> />
</Td> </Td>
<Td> <Td>
......
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