Commit c58a3fdf authored by tom goriunov's avatar tom goriunov Committed by GitHub

refactor page layout components (#1087)

* update csv export text

* fix pw tests

* base layout and customization for home page and search results page

* refactor rest of the pages

* refactor AppError

* move header padding to layout

* move nextjs utils to separate folder

* combine PageServer and Page

* make useQueryClientConfig hook

* add tests

* remove unused var

* fix envs for pw

* fix pw tests
parent e36711f2
......@@ -3,3 +3,5 @@ node_modules_linux
playwright/envs.js
deploy/tools/envs-validator/index.js
deploy/tools/feature-reporter/build/**
deploy/tools/feature-reporter/index.js
\ No newline at end of file
......@@ -199,7 +199,23 @@ module.exports = {
groups: [
'module',
'/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^jest/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^stubs/', '/^theme/', '/^ui/' ],
[
'/^nextjs/',
],
[
'/^configs/',
'/^data/',
'/^deploy/',
'/^icons/',
'/^jest/',
'/^lib/',
'/^mocks/',
'/^pages/',
'/^playwright/',
'/^stubs/',
'/^theme/',
'/^ui/',
],
[ 'parent', 'sibling', 'index' ],
],
alphabetize: { order: 'asc', ignoreCase: true },
......
......@@ -46,6 +46,9 @@ NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.jso
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
NEXT_PUBLIC_IS_L2_NETWORK=false
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3100
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
......
# Set of ENVs for Playwright components tests
#
# be aware that ALL ENVs should be present in order to make a correct variables list for the browser
# leave empty values for the ones that are not required
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
......@@ -45,11 +48,15 @@ NEXT_PUBLIC_APP_INSTANCE=pw
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
NEXT_PUBLIC_IS_L2_NETWORK=false
NEXT_PUBLIC_L1_BASE_URL=
NEXT_PUBLIC_L2_WITHDRAWAL_URL=
NEXT_PUBLIC_AD_BANNER_PROVIDER=slise
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3100
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_HAS_BEACON_CHAIN=
......@@ -5,7 +5,7 @@
"module": "CommonJS",
"outDir": "./build",
"paths": {
"nextjs-routes": ["./types/nextjs-routes.d.ts"],
"nextjs-routes": ["./nextjs/nextjs-routes.d.ts"],
}
},
"include": [ "../../../configs/app/index.ts" ],
......
import { QueryClient } from '@tanstack/react-query';
import React from 'react';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
export default function useQueryClientConfig() {
const [ queryClient ] = React.useState(() => new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: (failureCount, error) => {
const errorPayload = getErrorObjPayload<{ status: number }>(error);
const status = errorPayload?.status || getErrorObjStatusCode(error);
if (status && status >= 400 && status < 500) {
// don't do retry for client error responses
return false;
}
return failureCount < 2;
},
useErrorBoundary: (error) => {
const status = getErrorObjStatusCode(error);
// don't catch error for "Too many requests" response
return status === 429;
},
},
},
}));
return queryClient;
}
import React, { createContext, useContext } from 'react';
import type { Props as PageProps } from 'lib/next/getServerSideProps';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
type Props = {
children: React.ReactNode;
......
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import config from 'configs/app';
......
import type { Route } from 'nextjs-routes';
import type { ApiData } from './types';
import type { Route } from 'nextjs-routes';
import generate from './generate';
interface TestCase<R extends Route> {
......
import type { Route } from 'nextjs-routes';
import type { ApiData, Metadata } from './types';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
......
import type { Route } from 'nextjs-routes';
import type { ApiData } from './types';
import type { Route } from 'nextjs-routes';
import generate from './generate';
export default function update<R extends Route>(route: R, apiData: ApiData<R>) {
......
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import generateCspPolicy from 'lib/csp/generateCspPolicy';
import * as middlewares from 'lib/next/middlewares/index';
import generateCspPolicy from 'nextjs/csp/generateCspPolicy';
import * as middlewares from 'nextjs/middlewares/index';
const cspPolicy = generateCspPolicy();
......
......@@ -3,14 +3,15 @@ const withTM = require('next-transpile-modules')([
'swagger-client',
'swagger-ui-react',
]);
const path = require('path');
const withRoutes = require('nextjs-routes/config')({
outDir: 'types',
outDir: 'nextjs',
});
const path = require('path');
const headers = require('./configs/nextjs/headers');
const redirects = require('./configs/nextjs/redirects');
const rewrites = require('./configs/nextjs/rewrites');
const headers = require('./nextjs/headers');
const redirects = require('./nextjs/redirects');
const rewrites = require('./nextjs/rewrites');
const moduleExports = withTM({
include: path.resolve(__dirname, 'icons'),
......
import Head from 'next/head';
import type { Route } from 'nextjs-routes';
import React from 'react';
import type { Route } from 'nextjs-routes';
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useConfigSentry from 'lib/hooks/useConfigSentry';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel';
type Props = Route & {
children: React.ReactNode;
}
const PageServer = (props: Props) => {
const PageNextJs = (props: Props) => {
const { title, description } = metadata.generate(props);
useGetCsrfToken();
useAdblockDetect();
useConfigSentry();
const isMixpanelInited = mixpanel.useInit();
mixpanel.useLogPageView(isMixpanelInited);
return (
<>
<Head>
......@@ -22,4 +34,4 @@ const PageServer = (props: Props) => {
);
};
export default React.memo(PageServer);
export default React.memo(PageNextJs);
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { route } from 'nextjs-routes';
import config from 'configs/app';
......
import type { NextPage } from 'next';
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
}
import { compile } from 'path-to-regexp';
import config from 'configs/app';
import { RESOURCES } from 'lib/api/resources';
import type { ApiResource, ResourceName } from 'lib/api/resources';
import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
export default function buildUrlNode(
export default function buildUrl(
_resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>,
queryParams?: Record<string, string | number | undefined>,
......
......@@ -4,7 +4,8 @@ import type { NextApiRequestCookies } from 'next/dist/server/api-utils';
import type { RequestInit, Response } from 'node-fetch';
import nodeFetch from 'node-fetch';
import { httpLogger } from 'lib/api/logger';
import { httpLogger } from 'nextjs/utils/logger';
import * as cookies from 'lib/cookies';
export default function fetchFactory(
......
import React from 'react';
import PageServer from 'lib/next/PageServer';
import type { NextPageWithLayout } from 'nextjs/types';
import PageNextJs from 'nextjs/PageNextJs';
import AppError from 'ui/shared/AppError/AppError';
import Page from 'ui/shared/Page/Page';
import LayoutError from 'ui/shared/layout/LayoutError';
const error = new Error('Not found', { cause: { status: 404 } });
const Page: NextPageWithLayout = () => {
return (
<PageNextJs pathname="/404">
<AppError error={ error }/>
</PageNextJs>
);
};
const Custom404 = () => {
Page.getLayout = function getLayout(page: React.ReactElement) {
return (
<PageServer pathname="/404">
<Page>
<AppError statusCode={ 404 } mt="50px"/>
</Page>
</PageServer>
<LayoutError>
{ page }
</LayoutError>
);
};
export default Custom404;
export default Page;
import type { ChakraProps } from '@chakra-ui/react';
import * as Sentry from '@sentry/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app';
import React, { useState } from 'react';
import React from 'react';
import type { NextPageWithLayout } from 'nextjs/types';
import config from 'configs/app';
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
import { AppContextProvider } from 'lib/contexts/app';
import { ChakraProvider } from 'lib/contexts/chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
import useConfigSentry from 'lib/hooks/useConfigSentry';
import { SocketProvider } from 'lib/socket/context';
import theme from 'theme';
import AppError from 'ui/shared/AppError/AppError';
import AppErrorTooManyRequests from 'ui/shared/AppError/AppErrorTooManyRequests';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
import Layout from 'ui/shared/layout/Layout';
import 'lib/setLocale';
function MyApp({ Component, pageProps }: AppProps) {
useConfigSentry();
const [ queryClient ] = useState(() => new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: (failureCount, error) => {
const errorPayload = getErrorObjPayload<{ status: number }>(error);
const status = errorPayload?.status || getErrorObjStatusCode(error);
if (status && status >= 400 && status < 500) {
// don't do retry for client error responses
return false;
}
return failureCount < 2;
},
useErrorBoundary: (error) => {
const status = getErrorObjStatusCode(error);
// don't catch error for "Too many requests" response
return status === 429;
},
},
},
}));
const renderErrorScreen = React.useCallback((error?: Error) => {
const statusCode = getErrorCauseStatusCode(error) || getErrorObjStatusCode(error);
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
}
const styles: ChakraProps = {
const ERROR_SCREEN_STYLES: ChakraProps = {
h: '100vh',
display: 'flex',
flexDirection: 'column',
......@@ -61,39 +34,36 @@ function MyApp({ Component, pageProps }: AppProps) {
maxW: '800px',
margin: '0 auto',
p: { base: 4, lg: 0 },
};
};
if (statusCode === 429) {
return <AppErrorTooManyRequests { ...styles }/>;
}
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
return (
<AppError
statusCode={ statusCode || 500 }
{ ...styles }
/>
);
}, []);
const queryClient = useQueryClientConfig();
const handleError = React.useCallback((error: Error) => {
Sentry.captureException(error);
}, []);
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
return (
<ChakraProvider theme={ theme } cookies={ pageProps.cookies }>
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
<AppErrorBoundary
{ ...ERROR_SCREEN_STYLES }
onError={ handleError }
>
<AppContextProvider pageProps={ pageProps }>
<QueryClientProvider client={ queryClient }>
<ScrollDirectionProvider>
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
<Component { ...pageProps }/>
{ getLayout(<Component { ...pageProps }/>) }
</SocketProvider>
</ScrollDirectionProvider>
<ReactQueryDevtools/>
<GoogleAnalytics/>
</QueryClientProvider>
</AppContextProvider>
</ErrorBoundary>
</AppErrorBoundary>
</ChakraProvider>
);
}
......
......@@ -3,8 +3,9 @@ import type { DocumentContext } from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import React from 'react';
import * as serverTiming from 'nextjs/utils/serverTiming';
import config from 'configs/app';
import * as serverTiming from 'lib/next/serverTiming';
import theme from 'theme';
class MyDocument extends Document {
......
......@@ -21,10 +21,11 @@ import type { GetServerSideProps } from 'next';
import NextErrorComponent from 'next/error';
import React from 'react';
import type { Props as ServerSidePropsCommon } from 'nextjs/getServerSideProps';
import { base as getServerSidePropsCommon } from 'nextjs/getServerSideProps';
import sentryConfig from 'configs/sentry/nextjs';
import * as cookies from 'lib/cookies';
import type { Props as ServerSidePropsCommon } from 'lib/next/getServerSideProps';
import { base as getServerSidePropsCommon } from 'lib/next/getServerSideProps';
type Props = ServerSidePropsCommon & {
statusCode: number;
......
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const ApiKeys = dynamic(() => import('ui/pages/ApiKeys'), { ssr: false });
const ApiKeysPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/api-key">
<Page>
<PageNextJs pathname="/account/api-key">
<ApiKeys/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default ApiKeysPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const CustomAbi = dynamic(() => import('ui/pages/CustomAbi'), { ssr: false });
const CustomAbiPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/custom-abi">
<Page>
<PageNextJs pathname="/account/custom-abi">
<CustomAbi/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default CustomAbiPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const PublicTags = dynamic(() => import('ui/pages/PublicTags'), { ssr: false });
const PublicTagsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/public-tags-request">
<Page>
<PageNextJs pathname="/account/public-tags-request">
<PublicTags/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default PublicTagsPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const PrivateTags = dynamic(() => import('ui/pages/PrivateTags'), { ssr: false });
const PrivateTagsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/tag-address">
<Page>
<PageNextJs pathname="/account/tag-address">
<PrivateTags/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default PrivateTagsPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const VerifiedAddresses = dynamic(() => import('ui/pages/VerifiedAddresses'), { ssr: false });
const VerifiedAddressesPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/verified-addresses">
<Page>
<PageNextJs pathname="/account/verified-addresses">
<VerifiedAddresses/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default VerifiedAddressesPage;
export default Page;
export { verifiedAddresses as getServerSideProps } from 'lib/next/getServerSideProps';
export { verifiedAddresses as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const WatchList = dynamic(() => import('ui/pages/Watchlist'), { ssr: false });
const WatchListPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/account/watchlist">
<Page>
<PageNextJs pathname="/account/watchlist">
<WatchList/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default WatchListPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const Accounts = dynamic(() => import('ui/pages/Accounts'), { ssr: false });
const AccountsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/accounts">
<Page>
<PageNextJs pathname="/accounts">
<Accounts/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default AccountsPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import ContractVerification from 'ui/pages/ContractVerification';
import Page from 'ui/shared/Page/Page';
const ContractVerificationPage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/address/[hash]/contract-verification" query={ props }>
<Page>
<PageNextJs pathname="/address/[hash]/contract-verification" query={ props }>
<ContractVerification/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default ContractVerificationPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,22 +2,19 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const Address = dynamic(() => import('ui/pages/Address'), { ssr: false });
const AddressPage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/address/[hash]" query={ props }>
<Page>
<PageNextJs pathname="/address/[hash]" query={ props }>
<Address/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default AddressPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import SwaggerUI from 'ui/apiDocs/SwaggerUI';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
const APIDocsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/api-docs">
<Page>
<PageNextJs pathname="/api-docs">
<PageTitle title="API Documentation"/>
<SwaggerUI/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default APIDocsPage;
export default Page;
export { apiDocs as getServerSideProps } from 'lib/next/getServerSideProps';
export { apiDocs as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextApiRequest, NextApiResponse } from 'next';
import buildUrlNode from 'lib/api/buildUrlNode';
import { httpLogger } from 'lib/api/logger';
import fetchFactory from 'lib/api/nodeFetch';
import buildUrl from 'nextjs/utils/buildUrl';
import fetchFactory from 'nextjs/utils/fetch';
import { httpLogger } from 'nextjs/utils/logger';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
httpLogger(_req, res);
const url = buildUrlNode('csrf');
const url = buildUrl('csrf');
const response = await fetchFactory(_req)(url);
if (response.status === 200) {
......
import type { NextApiRequest, NextApiResponse } from 'next';
import nodeFetch from 'node-fetch';
import { httpLogger } from 'lib/api/logger';
import { httpLogger } from 'nextjs/utils/logger';
import getQueryParamString from 'lib/router/getQueryParamString';
export default async function mediaTypeHandler(req: NextApiRequest, res: NextApiResponse) {
......
......@@ -2,8 +2,9 @@ import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'nextjs/utils/fetch';
import config from 'configs/app';
import fetchFactory from 'lib/api/nodeFetch';
const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
if (!nextReq.url) {
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { NextPageWithLayout } from 'nextjs/types';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false });
const MarketplaceAppPage: NextPage<Props> = (props: Props) => {
const Page: NextPageWithLayout<Props> = (props: Props) => {
return (
<PageServer pathname="/apps/[id]" query={ props }>
<Page wrapChildren={ false }>
<PageNextJs pathname="/apps/[id]" query={ props }>
<MarketplaceApp/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default MarketplaceAppPage;
export default Page;
export { marketplace as getServerSideProps } from 'lib/next/getServerSideProps';
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,23 +2,23 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
import PageTitle from 'ui/shared/Page/PageTitle';
const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false });
const MarketplacePage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/apps">
<Page>
<PageNextJs pathname="/apps">
<>
<PageTitle title="Marketplace"/>
<Marketplace/>
</Page>
</PageServer>
</>
</PageNextJs>
);
};
export default MarketplacePage;
export default Page;
export { marketplace as getServerSideProps } from 'lib/next/getServerSideProps';
export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
const Auth0Page: NextPage = () => {
const Page: NextPage = () => {
return null;
};
export default Auth0Page;
export default Page;
export async function getServerSideProps() {
return {
......
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import MyProfile from 'ui/pages/MyProfile';
import Page from 'ui/shared/Page/Page';
const MyProfilePage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/auth/profile">
<Page>
<PageNextJs pathname="/auth/profile">
<MyProfile/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default MyProfilePage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import UnverifiedEmail from 'ui/pages/UnverifiedEmail';
import Page from 'ui/shared/Page/Page';
const UnverifiedEmailPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/auth/unverified-email">
<Page>
<PageNextJs pathname="/auth/unverified-email">
<UnverifiedEmail/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default UnverifiedEmailPage;
export default Page;
export { account as getServerSideProps } from 'lib/next/getServerSideProps';
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,22 +2,19 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
const BlockPage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/block/[height_or_hash]" query={ props }>
<Page>
<PageNextJs pathname="/block/[height_or_hash]" query={ props }>
<Block/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default BlockPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const Blocks = dynamic(() => import('ui/pages/Blocks'), { ssr: false });
const BlockPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/blocks">
<Page>
<PageNextJs pathname="/blocks">
<Blocks/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default BlockPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import CsvExport from 'ui/pages/CsvExport';
const CsvExportPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/csv-export">
<PageNextJs pathname="/csv-export">
<CsvExport/>
</PageServer>
</PageNextJs>
);
};
export default CsvExportPage;
export default Page;
export { csvExport as getServerSideProps } from 'lib/next/getServerSideProps';
export { csvExport as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,9 +2,9 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import ContentLoader from 'ui/shared/ContentLoader';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
const GraphQL = dynamic(() => import('ui/graphQL/GraphQL'), {
......@@ -12,18 +12,16 @@ const GraphQL = dynamic(() => import('ui/graphQL/GraphQL'), {
ssr: false,
});
const GraphiqlPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/graphiql">
<Page>
<PageNextJs pathname="/graphiql">
<PageTitle title="GraphQL playground"/>
<GraphQL/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default GraphiqlPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import type { NextPageWithLayout } from 'nextjs/types';
import PageNextJs from 'nextjs/PageNextJs';
import Home from 'ui/pages/Home';
import LayoutHome from 'ui/shared/layout/LayoutHome';
const HomePage: NextPage = () => {
const Page: NextPageWithLayout = () => {
return (
<PageServer pathname="/">
<PageNextJs pathname="/">
<Home/>
</PageServer>
</PageNextJs>
);
};
Page.getLayout = function getLayout(page: React.ReactElement) {
return (
<LayoutHome>
{ page }
</LayoutHome>
);
};
export default HomePage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const L2Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false });
const DepositsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/l2-deposits">
<Page>
<PageNextJs pathname="/l2-deposits">
<L2Deposits/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default DepositsPage;
export default Page;
export { L2 as getServerSideProps } from 'lib/next/getServerSideProps';
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const L2OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false });
const OutputRootsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/l2-output-roots">
<Page>
<PageNextJs pathname="/l2-output-roots">
<L2OutputRoots/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default OutputRootsPage;
export default Page;
export { L2 as getServerSideProps } from 'lib/next/getServerSideProps';
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const L2TxnBatches = dynamic(() => import('ui/pages/L2TxnBatches'), { ssr: false });
const TxnBatchesPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/l2-txn-batches">
<Page>
<PageNextJs pathname="/l2-txn-batches">
<L2TxnBatches/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TxnBatchesPage;
export default Page;
export { L2 as getServerSideProps } from 'lib/next/getServerSideProps';
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const L2Withdrawals = dynamic(() => import('ui/pages/L2Withdrawals'), { ssr: false });
const WithdrawalsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/l2-withdrawals">
<Page>
<PageNextJs pathname="/l2-withdrawals">
<L2Withdrawals/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default WithdrawalsPage;
export default Page;
export { L2 as getServerSideProps } from 'lib/next/getServerSideProps';
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import Login from 'ui/pages/Login';
const LoginPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/login">
<PageNextJs pathname="/login">
<Login/>
</PageServer>
</PageNextJs>
);
};
export default LoginPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import type { NextPageWithLayout } from 'nextjs/types';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
import LayoutSearchResults from 'ui/shared/layout/LayoutSearchResults';
const SearchResults = dynamic(() => import('ui/pages/SearchResults'), { ssr: false });
const SearchResultsPage: NextPage<Props> = (props: Props) => {
const Page: NextPageWithLayout<Props> = (props: Props) => {
return (
<PageServer pathname="/search-results" query={ props }>
<PageNextJs pathname="/search-results" query={ props }>
<SearchResults/>
</PageServer>
</PageNextJs>
);
};
Page.getLayout = function getLayout(page: React.ReactElement) {
return (
<LayoutSearchResults>
{ page }
</LayoutSearchResults>
);
};
export default SearchResultsPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import Stats from '../ui/pages/Stats';
import Stats from 'ui/pages/Stats';
const StatsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/stats">
<PageNextJs pathname="/stats">
<Stats/>
</PageServer>
</PageNextJs>
);
};
export default StatsPage;
export default Page;
export { stats as getServerSideProps } from 'lib/next/getServerSideProps';
export { stats as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,22 +2,19 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const Token = dynamic(() => import('ui/pages/Token'), { ssr: false });
const TokenPage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/token/[hash]" query={ props }>
<Page>
<PageNextJs pathname="/token/[hash]" query={ props }>
<Token/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TokenPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,22 +2,19 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const TokenInstance = dynamic(() => import('ui/pages/TokenInstance'), { ssr: false });
const TokenInstancePage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/token/[hash]/instance/[id]" query={ props }>
<Page>
<PageNextJs pathname="/token/[hash]/instance/[id]" query={ props }>
<TokenInstance/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TokenInstancePage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const Tokens = dynamic(() => import('ui/pages/Tokens'), { ssr: false });
const TokensPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/tokens">
<Page>
<PageNextJs pathname="/tokens">
<Tokens/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TokensPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,22 +2,19 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { Props } from 'lib/next/getServerSideProps';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
const Transaction = dynamic(() => import('ui/pages/Transaction'), { ssr: false });
const TransactionPage: NextPage<Props> = (props: Props) => {
const Page: NextPage<Props> = (props: Props) => {
return (
<PageServer pathname="/tx/[hash]" query={ props }>
<Page>
<PageNextJs pathname="/tx/[hash]" query={ props }>
<Transaction/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TransactionPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const Transactions = dynamic(() => import('ui/pages/Transactions'), { ssr: false });
const TxsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/txs">
<Page>
<PageNextJs pathname="/txs">
<Transactions/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default TxsPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const VerifiedContracts = dynamic(() => import('ui/pages/VerifiedContracts'), { ssr: false });
const VerifiedContractsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/verified-contracts">
<Page>
<PageNextJs pathname="/verified-contracts">
<VerifiedContracts/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default VerifiedContractsPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
import type { NextPage } from 'next';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import PageNextJs from 'nextjs/PageNextJs';
import Sol2Uml from 'ui/pages/Sol2Uml';
const Sol2UmlPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/visualize/sol2uml">
<PageNextJs pathname="/visualize/sol2uml">
<Sol2Uml/>
</PageServer>
</PageNextJs>
);
};
export default Sol2UmlPage;
export default Page;
export { base as getServerSideProps } from 'lib/next/getServerSideProps';
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -2,21 +2,18 @@ import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageServer from 'lib/next/PageServer';
import Page from 'ui/shared/Page/Page';
import PageNextJs from 'nextjs/PageNextJs';
const Withdrawals = dynamic(() => import('ui/pages/Withdrawals'), { ssr: false });
const WithdrawalsPage: NextPage = () => {
const Page: NextPage = () => {
return (
<PageServer pathname="/withdrawals">
<Page>
<PageNextJs pathname="/withdrawals">
<Withdrawals/>
</Page>
</PageServer>
</PageNextJs>
);
};
export default WithdrawalsPage;
export default Page;
export { beaconChain as getServerSideProps } from 'lib/next/getServerSideProps';
export { beaconChain as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -5,8 +5,9 @@ import React from 'react';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
import { AppContextProvider } from 'lib/contexts/app';
import type { Props as PageProps } from 'lib/next/getServerSideProps';
import { SocketProvider } from 'lib/socket/context';
import * as app from 'playwright/utils/app';
import theme from 'theme';
......
......@@ -8,3 +8,14 @@ export const viewport = {
export const maskColor = '#4299E1'; // blue.400
export const adsBannerSelector = '.adsbyslise';
export const featureEnvs = {
beaconChain: [
{ name: 'NEXT_PUBLIC_HAS_BEACON_CHAIN', value: 'true' },
],
rollup: [
{ name: 'NEXT_PUBLIC_IS_L2_NETWORK', value: 'true' },
{ name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' },
{ name: 'NEXT_PUBLIC_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' },
],
};
import type { Route } from 'nextjs-routes';
import type React from 'react';
import type { Route } from 'nextjs-routes';
type NavIconOrComponent = {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
} | {
......
import { chakra, Icon, Tooltip, Hide, Skeleton, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { CsvExportParams } from 'types/client/address';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import svgFileIcon from 'icons/files/csv.svg';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
......
import { Box, Text, Grid, Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Address as TAddress } from 'types/api/address';
import { route } from 'nextjs-routes';
import blockIcon from 'icons/block.svg';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
......
import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
......
import { Td, Tr, Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import LinkInternal from 'ui/shared/LinkInternal';
......
import { Text, Stat, StatHelpText, StatArrow, Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import { WEI, ZERO } from 'lib/consts';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
......
import { Td, Tr, Text, Stat, StatHelpText, StatArrow, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { route } from 'nextjs-routes';
import { WEI, ZERO } from 'lib/consts';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Address from 'ui/shared/address/Address';
......
import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Address as AddressInfo } from 'types/api/address';
import { route } from 'nextjs-routes';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs';
import useSocketChannel from 'lib/socket/useSocketChannel';
......
import { AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Box, Icon, Tooltip, useClipboard, useDisclosure } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { Element } from 'react-scroll';
import type { SmartContractMethod } from 'types/api/contract';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import iconLink from 'icons/link.svg';
import Hint from 'ui/shared/Hint';
......
import { Box, Flex, Select, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SmartContract } from 'types/api/contract';
import type { ArrayElement } from 'types/utils';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import * as stubs from 'stubs/contract';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
......
import { Box, chakra, Spinner } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { ContractMethodWriteResult } from './types';
import { route } from 'nextjs-routes';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
......
import { Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCounters } from 'types/api/address';
import { route } from 'nextjs-routes';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
......
import { Flex, Box, HStack, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import eastArrowIcon from 'icons/arrows/east.svg';
import dayjs from 'lib/date/dayjs';
......
import { Tr, Td, Box, Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import rightArrowIcon from 'icons/arrows/east.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
......
import { chakra, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
import TruncatedValue from 'ui/shared/TruncatedValue';
......
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import { route } from 'nextjs-routes';
import NftMedia from 'ui/shared/nft/NftMedia';
import TokenLogo from 'ui/shared/TokenLogo';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
......
import { Alert, Box, Button, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
......@@ -12,6 +11,8 @@ import type {
RootFields,
} from '../types';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
......
......@@ -3,12 +3,13 @@ import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
......
import { Flex, Skeleton, Text, Box, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import flameIcon from 'icons/flame.svg';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
......
import { Tr, Td, Flex, Box, Tooltip, Skeleton, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import flameIcon from 'icons/flame.svg';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
......
import { Button, chakra, useUpdateEffect } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
......@@ -8,6 +7,8 @@ import type { FormFields } from './types';
import type { SocketMessage } from 'lib/socket/types';
import type { SmartContractVerificationMethod, SmartContractVerificationConfig } from 'types/api/contract';
import { route } from 'nextjs-routes';
import useApiFetch from 'lib/api/useApiFetch';
import delay from 'lib/delay';
import useToast from 'lib/hooks/useToast';
......
import { Box, Heading, Flex, Text, VStack, Skeleton } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Block } from 'types/api/block';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import * as depositMock from 'mocks/l2deposits/deposits';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import LatestDeposits from './LatestDeposits';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
test('default view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route(buildApiUrl('homepage_deposits'), (route) => route.fulfill({
status: 200,
......
import { Box, Flex, Text } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import useIsMobile from 'lib/hooks/useIsMobile';
......
......@@ -4,11 +4,12 @@ import {
Grid,
Skeleton,
} from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
......
import { Box, Flex, Text } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
......
import { Box, Flex, Text } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
......
import { Grid } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import clockIcon from 'icons/clock-light.svg';
......
import { Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
......
import { Td, Tr, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2DepositsItem } from 'types/api/l2Deposits';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
......
import { Flex, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2OutputRootsItem } from 'types/api/l2OutputRoots';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
......
import { Flex, Td, Tr, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2OutputRootsItem } from 'types/api/l2OutputRoots';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
......
import { Skeleton, VStack } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2TxnBatchesItem } from 'types/api/l2TxnBatches';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
......
import { Td, Tr, VStack, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2TxnBatchesItem } from 'types/api/l2TxnBatches';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
......
import { Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2WithdrawalsItem } from 'types/api/l2Withdrawals';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
......
import { Td, Tr, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { L2WithdrawalsItem } from 'types/api/l2Withdrawals';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
......
......@@ -34,7 +34,7 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => {
await page.waitForResponse('https://www.google.com/recaptcha/api2/**');
await expect(component.locator('main')).toHaveScreenshot({
await expect(component).toHaveScreenshot({
mask: [ page.locator('.recaptcha') ],
maskColor: configs.maskColor,
});
......
......@@ -16,7 +16,6 @@ import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
interface ExportTypeEntity {
......@@ -127,7 +126,7 @@ const CsvExport = () => {
})();
return (
<Page>
<>
<PageTitle
title="Export data to CSV file"
backLink={ backLink }
......@@ -144,7 +143,7 @@ const CsvExport = () => {
<span>Exports are limited to the last 10K { EXPORT_TYPES[exportType].text }.</span>
</Flex>
{ content }
</Page>
</>
);
};
......
......@@ -49,7 +49,7 @@ test.describe('default view', () => {
});
test('-@default +@dark-mode', async({ page }) => {
await expect(component.locator('main')).toHaveScreenshot({
await expect(component).toHaveScreenshot({
mask: [ page.locator(configs.adsBannerSelector) ],
maskColor: configs.maskColor,
});
......@@ -59,7 +59,7 @@ test.describe('default view', () => {
test.use({ viewport: configs.viewport.xl });
test('', async({ page }) => {
await expect(component.locator('main')).toHaveScreenshot({
await expect(component).toHaveScreenshot({
mask: [ page.locator(configs.adsBannerSelector) ],
maskColor: configs.maskColor,
});
......@@ -134,7 +134,7 @@ test.describe('mobile', () => {
</TestApp>,
);
await expect(component.locator('main')).toHaveScreenshot({
await expect(component).toHaveScreenshot({
mask: [ page.locator(configs.adsBannerSelector) ],
maskColor: configs.maskColor,
});
......
......@@ -7,13 +7,12 @@ import LatestBlocks from 'ui/home/LatestBlocks';
import Stats from 'ui/home/Stats';
import Transactions from 'ui/home/Transactions';
import AdBanner from 'ui/shared/ad/AdBanner';
import Page from 'ui/shared/Page/Page';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import SearchBar from 'ui/snippets/searchBar/SearchBar';
const Home = () => {
return (
<Page isHomePage>
<>
<Box
w="100%"
background={ config.UI.homepage.plate.background }
......@@ -49,7 +48,7 @@ const Home = () => {
<Transactions/>
</Box>
</Flex>
</Page>
</>
);
};
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as depositsData } from 'mocks/l2deposits/deposits';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import L2Deposits from './L2Deposits';
const DEPOSITS_API_URL = buildApiUrl('l2_deposits');
const DEPOSITS_COUNT_API_URL = buildApiUrl('l2_deposits_count');
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
test('base view +@mobile', async({ mount, page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { outputRootsData } from 'mocks/l2outputRoots/outputRoots';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import OutputRoots from './L2OutputRoots';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
const OUTPUT_ROOTS_API_URL = buildApiUrl('l2_output_roots');
const OUTPUT_ROOTS_COUNT_API_URL = buildApiUrl('l2_output_roots_count');
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { txnBatchesData } from 'mocks/l2txnBatches/txnBatches';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import L2TxnBatches from './L2TxnBatches';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
const TXN_BATCHES_API_URL = buildApiUrl('l2_txn_batches');
const TXN_BATCHES_COUNT_API_URL = buildApiUrl('l2_txn_batches_count');
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as withdrawalsData } from 'mocks/l2withdrawals/withdrawals';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import L2Withdrawals from './L2Withdrawals';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
const WITHDRAWALS_API_URL = buildApiUrl('l2_withdrawals');
const WITHDRAWALS_COUNT_API_URL = buildApiUrl('l2_withdrawals_count');
......
......@@ -8,7 +8,6 @@ import config from 'configs/app';
import * as cookies from 'lib/cookies';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import useToast from 'lib/hooks/useToast';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
{ /* will be deleted when we fix login in preview CI stands */ }
......@@ -22,6 +21,7 @@ const Login = () => {
React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && config.features.account.isEnabled));
// throw new Error('Test error');
}, []);
const checkSentry = React.useCallback(() => {
......@@ -59,7 +59,6 @@ const Login = () => {
}, [ setNum ]);
return (
<Page>
<VStack gap={ 4 } alignItems="flex-start" maxW="1000px">
<PageTitle title="Login page 😂"/>
{ isFormVisible && (
......@@ -85,7 +84,6 @@ const Login = () => {
<Button onClick={ handleNumIncrement } size="sm">add</Button>
</Flex>
</VStack>
</Page>
);
};
......
import { Box, Center, useColorMode } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { MarketplaceAppOverview } from 'types/client/marketplace';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import { useAppContext } from 'lib/contexts/app';
......@@ -105,10 +106,10 @@ const MarketplaceApp = () => {
return (
<>
{ !isLoading && <PageTitle title={ data.title } px={{ base: 4, lg: 12 }} pt={{ base: '138px', lg: 0 }} backLink={ backLink }/> }
{ !isLoading && <PageTitle title={ data.title } backLink={ backLink }/> }
<Center
as="main"
h="100vh"
mx={{ base: -4, lg: -12 }}
>
{ (isFrameLoading) && (
<ContentLoader/>
......
......@@ -6,6 +6,8 @@ import * as searchMock from 'mocks/search/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import LayoutMainColumn from 'ui/shared/layout/components/MainColumn';
import SearchResults from './SearchResults';
......@@ -35,12 +37,17 @@ test('search by name +@mobile +@dark-mode', async({ mount, page }) => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
test('search by address hash +@mobile', async({ mount, page }) => {
......@@ -60,12 +67,17 @@ test('search by address hash +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
test('search by block number +@mobile', async({ mount, page }) => {
......@@ -85,12 +97,17 @@ test('search by block number +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
test('search by block hash +@mobile', async({ mount, page }) => {
......@@ -110,12 +127,17 @@ test('search by block hash +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
test('search by tx hash +@mobile', async({ mount, page }) => {
......@@ -135,12 +157,17 @@ test('search by tx hash +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
test.describe('with apps', () => {
......@@ -189,11 +216,16 @@ test.describe('with apps', () => {
const component = await mount(
<TestApp>
<LayoutMainColumn>
<SearchResults/>
</LayoutMainColumn>
</TestApp>,
{ hooksConfig },
);
await expect(component.locator('main')).toHaveScreenshot();
await expect(component).toHaveScreenshot({
mask: [ page.locator('header'), page.locator('form') ],
maskColor: configs.maskColor,
});
});
});
......@@ -3,14 +3,16 @@ import { useRouter } from 'next/router';
import type { FormEvent } from 'react';
import React from 'react';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
import useMarketplaceApps from 'ui/marketplace/useMarketplaceApps';
import SearchResultListItem from 'ui/searchResults/SearchResultListItem';
import SearchResultsInput from 'ui/searchResults/SearchResultsInput';
import SearchResultTableItem from 'ui/searchResults/SearchResultTableItem';
import ActionBar from 'ui/shared/ActionBar';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import * as Layout from 'ui/shared/layout/components';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import Thead from 'ui/shared/TheadSticky';
......@@ -169,10 +171,6 @@ const SearchResultsPageContent = () => {
);
}, [ handleSearchTermChange, handleSubmit, searchTerm ]);
const renderHeader = React.useCallback(() => {
return <Header renderSearchBar={ renderSearchBar }/>;
}, [ renderSearchBar ]);
const pageContent = !showContent ? <ContentLoader/> : (
<>
<PageTitle title="Search results"/>
......@@ -182,9 +180,15 @@ const SearchResultsPageContent = () => {
);
return (
<Page renderHeader={ renderHeader }>
<>
<IndexingAlertBlocks/>
<Header renderSearchBar={ renderSearchBar }/>
<AppErrorBoundary>
<Layout.Content>
{ pageContent }
</Page>
</Layout.Content>
</AppErrorBoundary>
</>
);
};
......
......@@ -7,7 +7,6 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Sol2UmlDiagram from 'ui/sol2uml/Sol2UmlDiagram';
......@@ -32,7 +31,7 @@ const Sol2Uml = () => {
}, [ appProps.referrer ]);
return (
<Page>
<>
<PageTitle
title="Solidity UML diagram"
backLink={ backLink }
......@@ -45,7 +44,7 @@ const Sol2Uml = () => {
</Address>
</Flex>
<Sol2UmlDiagram addressHash={ addressHash }/>
</Page>
</>
);
};
......
......@@ -2,7 +2,6 @@ import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import ChartsWidgetsList from '../stats/ChartsWidgetsList';
......@@ -25,7 +24,7 @@ const Stats = () => {
} = useStats();
return (
<Page>
<>
<PageTitle title={ `${ config.chain.name } stats` }/>
<Box mb={{ base: 6, sm: 8 }}>
......@@ -50,7 +49,7 @@ const Stats = () => {
charts={ displayedCharts }
interval={ interval }
/>
</Page>
</>
);
};
......
import { test, expect } from '@playwright/experimental-ct-react';
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as withdrawalsData } from 'mocks/withdrawals/withdrawals';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import Withdrawals from './Withdrawals';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.beaconChain) as any,
});
const WITHDRAWALS_API_URL = buildApiUrl('withdrawals');
const WITHDRAWALS_COUNTERS_API_URL = buildApiUrl('withdrawals_counters');
......
import { Flex, Grid, Icon, Image, Box, Text, chakra, Skeleton, useColorMode } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import { route } from 'nextjs-routes';
import blockIcon from 'icons/block.svg';
import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg';
......
import { chakra, Tr, Td, Text, Flex, Icon, Image, Box, Skeleton, useColorMode } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import { route } from 'nextjs-routes';
import blockIcon from 'icons/block.svg';
import labelIcon from 'icons/publictags.svg';
import iconSuccess from 'icons/status/success.svg';
......
import { MenuItem, Icon, chakra, useDisclosure } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import type { Route } from 'nextjs-routes';
import React from 'react';
import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import iconEdit from 'icons/edit.svg';
import useApiQuery from 'lib/api/useApiQuery';
......
......@@ -2,34 +2,78 @@ import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import AppError from './AppError';
test.use({ viewport: { width: 900, height: 400 } });
test('status code 404', async({ mount }) => {
const error = { message: 'Not found', cause: { status: 404 } } as Error;
const component = await mount(
<TestApp>
<AppError statusCode={ 404 }/>
<AppError error={ error }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('status code 422', async({ mount }) => {
const error = { message: 'Unprocessable entry', cause: { status: 422 } } as Error;
const component = await mount(
<TestApp>
<AppError statusCode={ 422 }/>
<AppError error={ error }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('status code 500', async({ mount }) => {
const error = { message: 'Unknown error', cause: { status: 500 } } as Error;
const component = await mount(
<TestApp>
<AppError error={ error }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('invalid tx hash', async({ mount }) => {
const error = { message: 'Invalid tx hash', cause: { status: 404 } } as Error;
const component = await mount(
<TestApp>
<AppError statusCode={ 500 }/>
<AppError error={ error }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('block lost consensus', async({ mount }) => {
const error = {
message: 'Not found',
cause: { payload: { message: 'Block lost consensus', hash: 'hash' } },
} as Error;
const component = await mount(
<TestApp>
<AppError error={ error }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('too many requests +@mobile', async({ mount, page }) => {
const error = {
message: 'Too many requests',
cause: { status: 429 },
} as Error;
const component = await mount(
<TestApp>
<AppError error={ error }/>
</TestApp>,
);
await page.waitForResponse('https://www.google.com/recaptcha/api2/**');
await expect(component).toHaveScreenshot({
mask: [ page.locator('.recaptcha') ],
maskColor: configs.maskColor,
});
});
import { Box, Button, Heading, Icon, Text, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import { Box, Button, Text } from '@chakra-ui/react';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
import icon422 from 'icons/error-pages/422.svg';
import icon500 from 'icons/error-pages/500.svg';
import { route } from 'nextjs-routes';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
import getResourceErrorPayload from 'lib/errors/getResourceErrorPayload';
import AppErrorIcon from './AppErrorIcon';
import AppErrorTitle from './AppErrorTitle';
import AppErrorBlockConsensus from './custom/AppErrorBlockConsensus';
import AppErrorInvalidTxHash from './custom/AppErrorInvalidTxHash';
import AppErrorTooManyRequests from './custom/AppErrorTooManyRequests';
interface Props {
statusCode: number;
className?: string;
error: Error | undefined;
}
const ERRORS: Record<string, {icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>; text: string; title: string }> = {
const ERROR_TEXTS: Record<string, { title: string; text: string }> = {
'404': {
icon: icon404,
title: 'Page not found',
text: 'This page is no longer explorable! If you are lost, use the search bar to find what you are looking for.',
},
'422': {
icon: icon422,
title: 'Request cannot be processed',
text: 'Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info.',
},
'500': {
icon: icon500,
title: 'Oops! Something went wrong',
text: 'An unexpected error has occurred. Try reloading the page, or come back soon and try again.',
},
};
const AppError = ({ statusCode, className }: Props) => {
const error = ERRORS[String(statusCode)] || ERRORS['500'];
const AppError = ({ error, className }: Props) => {
const content = (() => {
const resourceErrorPayload = getResourceErrorPayload(error);
const messageInPayload =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'message' in resourceErrorPayload &&
typeof resourceErrorPayload.message === 'string' ?
resourceErrorPayload.message :
undefined;
const isInvalidTxHash = error?.message?.includes('Invalid tx hash');
const isBlockConsensus = messageInPayload?.includes('Block lost consensus');
if (isInvalidTxHash) {
return <AppErrorInvalidTxHash/>;
}
if (isBlockConsensus) {
const hash =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'hash' in resourceErrorPayload &&
typeof resourceErrorPayload.hash === 'string' ?
resourceErrorPayload.hash :
undefined;
return <AppErrorBlockConsensus hash={ hash }/>;
}
const statusCode = getErrorCauseStatusCode(error) || getErrorObjStatusCode(error);
switch (statusCode) {
case 429: {
return <AppErrorTooManyRequests/>;
}
default: {
const { title, text } = ERROR_TEXTS[String(statusCode)] ?? ERROR_TEXTS[500];
return (
<Box className={ className }>
<Icon as={ error.icon } width="200px" height="auto"/>
<Heading mt={ 8 } size="2xl" fontFamily="body">{ error.title }</Heading>
<Text variant="secondary" mt={ 3 }> { error.text } </Text>
<>
<AppErrorIcon statusCode={ statusCode }/>
<AppErrorTitle title={ title }/>
<Text variant="secondary" mt={ 3 }>{ text }</Text>
<Button
mt={ 8 }
size="lg"
......@@ -46,8 +86,17 @@ const AppError = ({ statusCode, className }: Props) => {
>
Back to home
</Button>
</>
);
}
}
})();
return (
<Box className={ className } mt={{ base: '52px', lg: '104px' }} maxW="800px">
{ content }
</Box>
);
};
export default chakra(AppError);
export default React.memo(AppError);
import { chakra } from '@chakra-ui/react';
import React from 'react';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
import AppError from './AppError';
interface Props {
className?: string;
children: React.ReactNode;
onError?: (error: Error) => void;
}
const AppErrorBoundary = ({ className, children, onError }: Props) => {
const renderErrorScreen = React.useCallback((error?: Error) => {
return <AppError error={ error } className={ className }/>;
}, [ className ]);
return (
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ onError }>
{ children }
</ErrorBoundary>
);
};
export default React.memo(chakra(AppErrorBoundary));
import { Icon } from '@chakra-ui/react';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
import icon422 from 'icons/error-pages/422.svg';
import icon500 from 'icons/error-pages/500.svg';
const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>> > = {
'404': icon404,
'422': icon422,
'500': icon500,
};
interface Props {
statusCode: number | undefined;
}
const AppErrorIcon = ({ statusCode }: Props) => {
return <Icon as={ ICONS[String(statusCode)] || ICONS['500'] } width="200px" height="auto"/>;
};
export default AppErrorIcon;
import { Heading } from '@chakra-ui/react';
import React from 'react';
interface Props {
title: string;
}
const AppErrorTitle = ({ title }: Props) => {
return <Heading mt={ 8 } size="2xl" fontFamily="body">{ title }</Heading>;
};
export default AppErrorTitle;
import { Box, Button, Heading, Icon, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import { Button } from '@chakra-ui/react';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
import { route } from 'nextjs-routes';
import AppErrorIcon from '../AppErrorIcon';
import AppErrorTitle from '../AppErrorTitle';
interface Props {
hash?: string;
className?: string;
}
const AppErrorBlockConsensus = ({ hash, className }: Props) => {
const AppErrorBlockConsensus = ({ hash }: Props) => {
return (
<Box className={ className }>
<Icon as={ icon404 } width="200px" height="auto"/>
<Heading mt={ 8 } size="2xl" fontFamily="body">Block removed due to chain reorganization</Heading>
<>
<AppErrorIcon statusCode={ 404 }/>
<AppErrorTitle title="Block removed due to chain reorganization"/>
<Button
mt={ 8 }
size="lg"
......@@ -23,8 +24,8 @@ const AppErrorBlockConsensus = ({ hash, className }: Props) => {
>
{ hash ? 'View reorg' : 'Back to home' }
</Button>
</Box>
</>
);
};
export default chakra(AppErrorBlockConsensus);
export default AppErrorBlockConsensus;
/* eslint-disable max-len */
import { Box, Heading, OrderedList, ListItem, Icon, useColorModeValue, Flex } from '@chakra-ui/react';
import { Box, OrderedList, ListItem, Icon, useColorModeValue, Flex } from '@chakra-ui/react';
import React from 'react';
import txIcon from 'icons/transactions.svg';
import AppErrorTitle from '../AppErrorTitle';
const AppErrorInvalidTxHash = () => {
const textColor = useColorModeValue('gray.500', 'gray.400');
const snippet = {
......@@ -13,7 +15,7 @@ const AppErrorInvalidTxHash = () => {
};
return (
<Box mt="50px">
<>
<Box p={ 4 } borderColor={ snippet.borderColor } borderRadius="md" w="230px" borderWidth="1px">
<Flex alignItems="center" pb={ 4 } borderBottomWidth="1px" borderColor={ snippet.borderColor }>
<Icon as={ txIcon } boxSize={ 8 } color={ snippet.iconColor } bgColor={ snippet.iconBg } p={ 1 } borderRadius="md"/>
......@@ -33,9 +35,7 @@ const AppErrorInvalidTxHash = () => {
</Flex>
</Flex>
</Box>
<Heading size="2xl" fontFamily="body" mt={ 6 }>
Sorry, we are unable to locate this transaction hash
</Heading>
<AppErrorTitle title="Sorry, we are unable to locate this transaction hash"/>
<OrderedList color={ textColor } mt={ 3 } spacing={ 3 }>
<ListItem>
If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page.
......@@ -50,7 +50,7 @@ const AppErrorInvalidTxHash = () => {
If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information.
</ListItem>
</OrderedList>
</Box>
</>
);
};
......
import { Box, Heading, Icon, Text, chakra } from '@chakra-ui/react';
import { Box, Heading, Icon, Text } from '@chakra-ui/react';
import React from 'react';
import ReCaptcha from 'react-google-recaptcha';
......@@ -8,11 +8,7 @@ import buildUrl from 'lib/api/buildUrl';
import useFetch from 'lib/hooks/useFetch';
import useToast from 'lib/hooks/useToast';
interface Props {
className?: string;
}
const AppErrorTooManyRequests = ({ className }: Props) => {
const AppErrorTooManyRequests = () => {
const toast = useToast();
const fetch = useFetch();
......@@ -47,7 +43,6 @@ const AppErrorTooManyRequests = ({ className }: Props) => {
return (
<Box
className={ className }
sx={{
'.recaptcha': {
mt: 8,
......@@ -71,4 +66,4 @@ const AppErrorTooManyRequests = ({ className }: Props) => {
);
};
export default chakra(AppErrorTooManyRequests);
export default AppErrorTooManyRequests;
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import getResourceErrorPayload from 'lib/errors/getResourceErrorPayload';
import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import * as mixpanel from 'lib/mixpanel';
import AppError from 'ui/shared/AppError/AppError';
import AppErrorBlockConsensus from 'ui/shared/AppError/AppErrorBlockConsensus';
import AppErrorInvalidTxHash from 'ui/shared/AppError/AppErrorInvalidTxHash';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
import PageContent from 'ui/shared/Page/PageContent';
import Footer from 'ui/snippets/footer/Footer';
import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
interface Props {
children: React.ReactNode;
wrapChildren?: boolean;
isHomePage?: boolean;
renderHeader?: () => React.ReactNode;
}
const Page = ({
children,
wrapChildren = true,
isHomePage,
renderHeader,
}: Props) => {
useGetCsrfToken();
useAdblockDetect();
const isMixpanelInited = mixpanel.useInit();
mixpanel.useLogPageView(isMixpanelInited);
const renderErrorScreen = React.useCallback((error?: Error) => {
const statusCode = getErrorCauseStatusCode(error) || 500;
const resourceErrorPayload = getResourceErrorPayload(error);
const messageInPayload =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'message' in resourceErrorPayload &&
typeof resourceErrorPayload.message === 'string' ?
resourceErrorPayload.message :
undefined;
const isInvalidTxHash = error?.message?.includes('Invalid tx hash');
const isBlockConsensus = messageInPayload?.includes('Block lost consensus');
if (isInvalidTxHash) {
return <PageContent isHomePage={ isHomePage }><AppErrorInvalidTxHash/></PageContent>;
}
if (isBlockConsensus) {
const hash =
resourceErrorPayload &&
typeof resourceErrorPayload === 'object' &&
'hash' in resourceErrorPayload &&
typeof resourceErrorPayload.hash === 'string' ?
resourceErrorPayload.hash :
undefined;
return <PageContent isHomePage={ isHomePage }><AppErrorBlockConsensus hash={ hash } mt="50px"/></PageContent>;
}
return <PageContent isHomePage={ isHomePage }><AppError statusCode={ statusCode } mt="50px"/></PageContent>;
}, [ isHomePage ]);
const renderedChildren = wrapChildren ? (
<PageContent isHomePage={ isHomePage }>{ children }</PageContent>
) : children;
return (
<Box minWidth={{ base: '100vw', lg: 'fit-content' }}>
<Flex w="100%" minH="100vh" alignItems="stretch">
<NavigationDesktop/>
<Flex flexDir="column" flexGrow={ 1 } w={{ base: '100%', lg: 'auto' }}>
{ renderHeader ?
renderHeader() :
<Header isHomePage={ isHomePage }/>
}
<ErrorBoundary renderErrorScreen={ renderErrorScreen }>
{ renderedChildren }
</ErrorBoundary>
</Flex>
</Flex>
<Footer/>
</Box>
);
};
export default Page;
import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
interface Props {
children: React.ReactNode;
isHomePage?: boolean;
}
const PageContent = ({ children, isHomePage }: Props) => {
return (
<Box
as="main"
w="100%"
paddingX={{ base: 4, lg: 12 }}
paddingBottom={ 10 }
paddingTop={{ base: isHomePage ? '88px' : '138px', lg: 0 }}
>
{ !config.UI.indexingAlert.isHidden && <IndexingAlertBlocks display={{ base: 'block', lg: 'none' }}/> }
{ children }
</Box>
);
};
export default PageContent;
import { Box, chakra, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import nftPlaceholder from 'icons/nft_shield.svg';
import Icon from 'ui/shared/chakra/Icon';
import HashStringShorten from 'ui/shared/HashStringShorten';
......
import { chakra, shouldForwardProp, Tooltip, Box, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react';
import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile';
import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
......
......@@ -4,21 +4,11 @@ import React from 'react';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import Page from './Page';
import Layout from './Layout';
const API_URL = buildApiUrl('homepage_indexing_status');
test('without indexing alert +@mobile', async({ mount }) => {
const component = await mount(
<TestApp>
<Page>Page Content</Page>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('with indexing alert +@mobile', async({ mount, page }) => {
test('base view +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }),
......@@ -26,7 +16,7 @@ test('with indexing alert +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<Page>Page Content</Page>
<Layout>Page Content</Layout>
</TestApp>,
);
......
import React from 'react';
import type { Props } from './types';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import Header from 'ui/snippets/header/Header';
import * as Layout from './components';
const LayoutDefault = ({ children }: Props) => {
return (
<Layout.Container>
<Layout.MainArea>
<Layout.SideBar/>
<Layout.MainColumn>
<IndexingAlertBlocks/>
<Header/>
<AppErrorBoundary>
<Layout.Content>
{ children }
</Layout.Content>
</AppErrorBoundary>
</Layout.MainColumn>
</Layout.MainArea>
<Layout.Footer/>
</Layout.Container>
);
};
export default LayoutDefault;
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import LayoutHome from './LayoutHome';
const API_URL = buildApiUrl('homepage_indexing_status');
test('base view +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }),
}));
const component = await mount(
<TestApp>
<LayoutHome>
<Box pt={ 10 }>Error</Box>
</LayoutHome>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import React from 'react';
import type { Props } from './types';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import Header from 'ui/snippets/header/Header';
import * as Layout from './components';
const LayoutError = ({ children }: Props) => {
return (
<Layout.Container>
<Layout.MainArea>
<Layout.SideBar/>
<Layout.MainColumn>
<IndexingAlertBlocks/>
<Header/>
<AppErrorBoundary>
<main>
{ children }
</main>
</AppErrorBoundary>
</Layout.MainColumn>
</Layout.MainArea>
<Layout.Footer/>
</Layout.Container>
);
};
export default LayoutError;
......@@ -2,20 +2,23 @@ import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import AppErrorTooManyRequests from './AppErrorTooManyRequests';
import LayoutHome from './LayoutHome';
const API_URL = buildApiUrl('homepage_indexing_status');
test('base view +@mobile', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }),
}));
test('default view +@mobile', async({ mount, page }) => {
const component = await mount(
<TestApp>
<AppErrorTooManyRequests/>
<LayoutHome>Page Content</LayoutHome>
</TestApp>,
);
await page.waitForResponse('https://www.google.com/recaptcha/api2/**');
await expect(component).toHaveScreenshot({
mask: [ page.locator('.recaptcha') ],
maskColor: configs.maskColor,
});
await expect(component).toHaveScreenshot();
});
import React from 'react';
import type { Props } from './types';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import Header from 'ui/snippets/header/Header';
import * as Layout from './components';
const LayoutHome = ({ children }: Props) => {
return (
<Layout.Container>
<Layout.MainArea>
<Layout.SideBar/>
<Layout.MainColumn
paddingTop={{ base: '88px', lg: 9 }}
>
<IndexingAlertBlocks/>
<Header isHomePage/>
<AppErrorBoundary>
{ children }
</AppErrorBoundary>
</Layout.MainColumn>
</Layout.MainArea>
<Layout.Footer/>
</Layout.Container>
);
};
export default LayoutHome;
import React from 'react';
import type { Props } from './types';
import * as Layout from './components';
const LayoutSearchResults = ({ children }: Props) => {
return (
<Layout.Container>
<Layout.MainArea>
<Layout.SideBar/>
<Layout.MainColumn>
{ children }
</Layout.MainColumn>
</Layout.MainArea>
<Layout.Footer/>
</Layout.Container>
);
};
export default LayoutSearchResults;
import { Box } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
}
const Container = ({ children }: Props) => {
return (
<Box minWidth={{ base: '100vw', lg: 'fit-content' }}>
{ children }
</Box>
);
};
export default React.memo(Container);
import { Box } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
}
const Content = ({ children }: Props) => {
return (
<Box pt={{ base: 0, lg: '52px' }} as="main">
{ children }
</Box>
);
};
export default React.memo(Content);
import { Flex } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
}
const MainArea = ({ children }: Props) => {
return (
<Flex w="100%" minH="100vh" alignItems="stretch">
{ children }
</Flex>
);
};
export default React.memo(MainArea);
import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
className?: string;
children: React.ReactNode;
}
const MainColumn = ({ children, className }: Props) => {
return (
<Flex
className={ className }
flexDir="column"
flexGrow={ 1 }
w={{ base: '100%', lg: 'auto' }}
paddingX={{ base: 4, lg: 12 }}
paddingTop={{ base: '138px', lg: 9 }}
paddingBottom={ 10 }
>
{ children }
</Flex>
);
};
export default React.memo(chakra(MainColumn));
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
export default NavigationDesktop;
import Footer from 'ui/snippets/footer/Footer';
import Container from './Container';
import Content from './Content';
import MainArea from './MainArea';
import MainColumn from './MainColumn';
import SideBar from './SideBar';
export {
Container,
Content,
MainArea,
SideBar,
MainColumn,
Footer,
};
// Container
// MainArea
// SideBar
// MainColumn
// Content
// Footer
export interface Props {
children: React.ReactNode;
}
import { Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Log } from 'types/api/log';
import { route } from 'nextjs-routes';
// import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities';
import Address from 'ui/shared/address/Address';
......
import { AspectRatio, chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StaticRoute } from 'nextjs-routes';
import { route } from 'nextjs-routes';
import React from 'react';
import useFetch from 'lib/hooks/useFetch';
......
......@@ -78,7 +78,7 @@ const Footer = () => {
});
return (
<Flex direction={{ base: 'column', lg: 'row' }} p={{ base: 4, lg: 9 }} borderTop="1px solid" borderColor="divider">
<Flex direction={{ base: 'column', lg: 'row' }} p={{ base: 4, lg: 9 }} borderTop="1px solid" borderColor="divider" as="footer">
<Box flexGrow="1" mb={{ base: 8, lg: 0 }}>
<Flex>
<ColorModeToggler/>
......
......@@ -3,7 +3,6 @@ import React from 'react';
import config from 'configs/app';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
import IndexingAlertBlocks from 'ui/home/IndexingAlertBlocks';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
import ProfileMenuMobile from 'ui/snippets/profileMenu/ProfileMenuMobile';
......@@ -47,12 +46,7 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
</Flex>
{ !isHomePage && searchBar }
</Box>
<Box
paddingX={ 12 }
paddingTop={ 9 }
display={{ base: 'none', lg: 'block' }}
>
{ !config.UI.indexingAlert.isHidden && <IndexingAlertBlocks/> }
<Box display={{ base: 'none', lg: 'block' }}>
{ !isHomePage && (
<HStack
as="header"
......@@ -60,7 +54,6 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
alignItems="center"
justifyContent="center"
gap={ 12 }
paddingBottom="52px"
>
<Box width="100%">
{ searchBar }
......
import { Link, Text, HStack, Tooltip, Box, useBreakpointValue, chakra, shouldForwardProp } from '@chakra-ui/react';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
import React from 'react';
import type { NavItem } from 'types/client/navigation-items';
import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile';
import { isInternalItem } from 'lib/hooks/useNavItems';
......
import { Icon, Box, Image, useColorModeValue, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import iconPlaceholder from 'icons/networks/icon-placeholder.svg';
import logoPlaceholder from 'icons/networks/logo-placeholder.svg';
......
......@@ -5,7 +5,6 @@ import React from 'react';
import * as textAdMock from 'mocks/ad/textAd';
import { apps as appsMock } from 'mocks/apps/apps';
import * as searchMock from 'mocks/search/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
......@@ -285,15 +284,9 @@ test('recent keywords suggest +@mobile', async({ mount, page }) => {
});
test.describe('with apps', () => {
const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.url';
const extendedTest = test.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
const MARKETPLACE_CONFIG_URL = 'https://localhost:3000/marketplace-config.json';
extendedTest('default view +@mobile', async({ mount, page }) => {
test('default view +@mobile', async({ mount, page }) => {
const API_URL = buildApiUrl('search') + '?q=o';
await page.route(API_URL, (route) => route.fulfill({
status: 200,
......
import { Box, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure, PopoverFooter } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import type { FormEvent, FocusEvent } from 'react';
import React from 'react';
import { Element } from 'react-scroll';
import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import { getRecentSearchKeywords, saveToRecentKeywords } from 'lib/recentSearchKeywords';
......
import type { LinkProps as NextLinkProps } from 'next/link';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
import { route } from 'nextjs-routes';
import SearchBarSuggestAddress from './SearchBarSuggestAddress';
import SearchBarSuggestBlock from './SearchBarSuggestBlock';
import SearchBarSuggestItemLink from './SearchBarSuggestItemLink';
......
import { Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
......
import { Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import type { Query } from 'nextjs-routes';
import React, { useCallback } from 'react';
import type { TokenType } from 'types/api/token';
import type { TokensSorting } from 'types/api/tokens';
import type { Query } from 'nextjs-routes';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useDebounce from 'lib/hooks/useDebounce';
import { apos } from 'lib/html-entities';
......
import { Flex, Link, Text, Icon, Box } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
import { route } from 'nextjs-routes';
import nftIcon from 'icons/nft_shield.svg';
import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShorten from 'ui/shared/HashStringShorten';
......
......@@ -154,10 +154,8 @@ test('with actions uniswap +@mobile +@dark-mode', async({ mount, page }) => {
});
const l2Test = test.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_IS_L2_NETWORK', value: 'true' },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
context: contextWithEnvs(configs.featureEnvs.rollup) as any,
});
l2Test('l2', async({ mount, page }) => {
......
......@@ -14,10 +14,11 @@ import {
Alert,
} from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import { scroller, Element } from 'react-scroll';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
......
import { Flex, Link, Icon, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TxAction, TxActionGeneral } from 'types/api/txAction';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import uniswapIcon from 'icons/uniswap.svg';
import AddressLink from 'ui/shared/address/AddressLink';
......
import { Icon, GridItem, Show, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import { route } from 'nextjs-routes';
import tokenIcon from 'icons/token.svg';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
......
import { Box, Heading, Text, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import getValueWithUnit from 'lib/getValueWithUnit';
import CurrencyValue from 'ui/shared/CurrencyValue';
......
......@@ -4,11 +4,12 @@ import {
Flex,
Skeleton,
} from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import rightArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
......
......@@ -9,11 +9,12 @@ import {
Box,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import { route } from 'nextjs-routes';
import rightArrowIcon from 'icons/arrows/east.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Address from 'ui/shared/address/Address';
......
import { Flex, Icon, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressWithdrawalsItem } from 'types/api/address';
import type { BlockWithdrawalsItem } from 'types/api/block';
import type { WithdrawalsItem } from 'types/api/withdrawals';
import { route } from 'nextjs-routes';
import config from 'configs/app';
import blockIcon from 'icons/block.svg';
import dayjs from 'lib/date/dayjs';
......
import { Td, Tr, Icon, Skeleton, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressWithdrawalsItem } from 'types/api/address';
import type { BlockWithdrawalsItem } from 'types/api/block';
import type { WithdrawalsItem } from 'types/api/withdrawals';
import { route } from 'nextjs-routes';
import blockIcon from 'icons/block.svg';
import dayjs from 'lib/date/dayjs';
import Address from 'ui/shared/address/Address';
......
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