= async(context) => {
diff --git a/nextjs/types.ts b/nextjs/types.ts
index 60c007daff..c0366ae090 100644
--- a/nextjs/types.ts
+++ b/nextjs/types.ts
@@ -1,6 +1,13 @@
import type { NextPage } from 'next';
+import type { Route } from 'nextjs-routes';
+
// eslint-disable-next-line @typescript-eslint/ban-types
export type NextPageWithLayout = NextPage
& {
getLayout?: (page: React.ReactElement) => React.ReactNode;
}
+
+export interface RouteParams {
+ pathname: Pathname;
+ query?: Route['query'];
+}
diff --git a/nextjs/utils/detectBotRequest.ts b/nextjs/utils/detectBotRequest.ts
new file mode 100644
index 0000000000..1eecf6333a
--- /dev/null
+++ b/nextjs/utils/detectBotRequest.ts
@@ -0,0 +1,52 @@
+import type { IncomingMessage } from 'http';
+
+type SocialPreviewBot = 'twitter' | 'facebook' | 'telegram' | 'slack';
+type SearchEngineBot = 'google' | 'bing' | 'yahoo' | 'duckduckgo';
+
+type ReturnType = {
+ type: 'social_preview';
+ bot: SocialPreviewBot;
+} | {
+ type: 'search_engine';
+ bot: SearchEngineBot;
+} | undefined
+
+export default function detectBotRequest(req: IncomingMessage): ReturnType {
+ const userAgent = req.headers['user-agent'];
+
+ if (!userAgent) {
+ return;
+ }
+
+ if (userAgent.toLowerCase().includes('twitter')) {
+ return { type: 'social_preview', bot: 'twitter' };
+ }
+
+ if (userAgent.toLowerCase().includes('facebook')) {
+ return { type: 'social_preview', bot: 'facebook' };
+ }
+
+ if (userAgent.toLowerCase().includes('telegram')) {
+ return { type: 'social_preview', bot: 'telegram' };
+ }
+
+ if (userAgent.toLowerCase().includes('slack')) {
+ return { type: 'social_preview', bot: 'slack' };
+ }
+
+ if (userAgent.toLowerCase().includes('googlebot')) {
+ return { type: 'search_engine', bot: 'google' };
+ }
+
+ if (userAgent.toLowerCase().includes('bingbot')) {
+ return { type: 'search_engine', bot: 'bing' };
+ }
+
+ if (userAgent.toLowerCase().includes('yahoo')) {
+ return { type: 'search_engine', bot: 'yahoo' };
+ }
+
+ if (userAgent.toLowerCase().includes('duckduck')) {
+ return { type: 'search_engine', bot: 'duckduckgo' };
+ }
+}
diff --git a/nextjs/utils/fetchApi.ts b/nextjs/utils/fetchApi.ts
new file mode 100644
index 0000000000..63eff42384
--- /dev/null
+++ b/nextjs/utils/fetchApi.ts
@@ -0,0 +1,53 @@
+import fetch, { AbortError } from 'node-fetch';
+
+import buildUrl from 'nextjs/utils/buildUrl';
+import { httpLogger } from 'nextjs/utils/logger';
+
+import { RESOURCES } from 'lib/api/resources';
+import type { ResourceName, ResourcePathParams, ResourcePayload } from 'lib/api/resources';
+import { SECOND } from 'lib/consts';
+import metrics from 'lib/monitoring/metrics';
+
+type Params = (
+ {
+ resource: R;
+ pathParams?: ResourcePathParams;
+ } | {
+ url: string;
+ route: string;
+ }
+) & {
+ timeout?: number;
+}
+
+export default async function fetchApi>(params: Params): Promise {
+ const controller = new AbortController();
+
+ const timeout = setTimeout(() => {
+ controller.abort();
+ }, params.timeout || SECOND);
+
+ const url = 'url' in params ? params.url : buildUrl(params.resource, params.pathParams);
+ const route = 'route' in params ? params.route : RESOURCES[params.resource]['path'];
+
+ const end = metrics?.apiRequestDuration.startTimer();
+
+ try {
+ const response = await fetch(url, { signal: controller.signal });
+
+ const duration = end?.({ route, code: response.status });
+ if (response.status === 200) {
+ httpLogger.logger.info({ message: 'API fetch', url, code: response.status, duration });
+ } else {
+ httpLogger.logger.error({ message: 'API fetch', url, code: response.status, duration });
+ }
+
+ return await response.json() as Promise;
+ } catch (error) {
+ const code = error instanceof AbortError ? 504 : 500;
+ const duration = end?.({ route, code });
+ httpLogger.logger.error({ message: 'API fetch', url, code, duration });
+ } finally {
+ clearTimeout(timeout);
+ }
+}
diff --git a/nextjs/utils/fetch.ts b/nextjs/utils/fetchProxy.ts
similarity index 91%
rename from nextjs/utils/fetch.ts
rename to nextjs/utils/fetchProxy.ts
index 22bfc32ff6..0081781215 100644
--- a/nextjs/utils/fetch.ts
+++ b/nextjs/utils/fetchProxy.ts
@@ -30,14 +30,9 @@ export default function fetchFactory(
};
httpLogger.logger.info({
- message: 'Trying to call API',
+ message: 'API fetch via Next.js proxy',
url,
- req: _req,
- });
-
- httpLogger.logger.info({
- message: 'API request headers',
- headers,
+ // headers,
});
const body = (() => {
diff --git a/nextjs/utils/logRequestFromBot.ts b/nextjs/utils/logRequestFromBot.ts
new file mode 100644
index 0000000000..6c22b63258
--- /dev/null
+++ b/nextjs/utils/logRequestFromBot.ts
@@ -0,0 +1,28 @@
+import type { IncomingMessage, ServerResponse } from 'http';
+
+import metrics from 'lib/monitoring/metrics';
+
+import detectBotRequest from './detectBotRequest';
+
+export default async function logRequestFromBot(req: IncomingMessage | undefined, res: ServerResponse | undefined, pathname: string) {
+ if (!req || !res || !metrics) {
+ return;
+ }
+
+ const botInfo = detectBotRequest(req);
+
+ if (!botInfo) {
+ return;
+ }
+
+ switch (botInfo.type) {
+ case 'search_engine': {
+ metrics.searchEngineBotRequests.inc({ route: pathname, bot: botInfo.bot });
+ return;
+ }
+ case 'social_preview': {
+ metrics.socialPreviewBotRequests.inc({ route: pathname, bot: botInfo.bot });
+ return;
+ }
+ }
+}
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 15fc5edd91..38b3c1d358 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -12,6 +12,7 @@ import config from 'configs/app';
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
import { AppContextProvider } from 'lib/contexts/app';
import { ChakraProvider } from 'lib/contexts/chakra';
+import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { growthBook } from 'lib/growthbook/init';
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
@@ -67,7 +68,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
- { getLayout() }
+
+ { getLayout() }
+
diff --git a/pages/_document.tsx b/pages/_document.tsx
index 2101c04c4f..fafa653e85 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -3,9 +3,9 @@ import type { DocumentContext } from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import React from 'react';
+import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
import * as serverTiming from 'nextjs/utils/serverTiming';
-import getApiDataForSocialPreview from 'lib/metadata/getApiDataForSocialPreview';
import theme from 'theme';
import * as svgSprite from 'ui/shared/IconSvg';
@@ -22,7 +22,7 @@ class MyDocument extends Document {
return result;
};
- await getApiDataForSocialPreview(ctx.req, ctx.res, ctx.pathname);
+ await logRequestFromBot(ctx.req, ctx.res, ctx.pathname);
const initialProps = await Document.getInitialProps(ctx);
diff --git a/pages/address/[hash]/contract-verification.tsx b/pages/address/[hash]/contract-verification.tsx
index 7e7fd704d2..25f56fa4c6 100644
--- a/pages/address/[hash]/contract-verification.tsx
+++ b/pages/address/[hash]/contract-verification.tsx
@@ -8,7 +8,7 @@ import ContractVerificationForAddress from 'ui/pages/ContractVerificationForAddr
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/address/[hash]/index.tsx b/pages/address/[hash]/index.tsx
index 2319a31605..fc13029cc0 100644
--- a/pages/address/[hash]/index.tsx
+++ b/pages/address/[hash]/index.tsx
@@ -1,15 +1,22 @@
-import type { NextPage } from 'next';
-import dynamic from 'next/dynamic';
+import type { GetServerSideProps, NextPage } from 'next';
import React from 'react';
+import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
+import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
+import detectBotRequest from 'nextjs/utils/detectBotRequest';
+import fetchApi from 'nextjs/utils/fetchApi';
-const Address = dynamic(() => import('ui/pages/Address'), { ssr: false });
+import config from 'configs/app';
+import getQueryParamString from 'lib/router/getQueryParamString';
+import Address from 'ui/pages/Address';
-const Page: NextPage = (props: Props) => {
+const pathname: Route['pathname'] = '/address/[hash]';
+
+const Page: NextPage> = (props: Props) => {
return (
-
+
);
@@ -17,4 +24,24 @@ const Page: NextPage = (props: Props) => {
export default Page;
-export { base as getServerSideProps } from 'nextjs/getServerSideProps';
+export const getServerSideProps: GetServerSideProps> = async(ctx) => {
+ const baseResponse = await gSSP.base(ctx);
+
+ if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
+ const botInfo = detectBotRequest(ctx.req);
+
+ if (botInfo?.type === 'social_preview') {
+ const addressData = await fetchApi({
+ resource: 'address',
+ pathParams: { hash: getQueryParamString(ctx.query.hash) },
+ timeout: 1_000,
+ });
+
+ (await baseResponse.props).apiData = addressData && addressData.ens_domain_name ? {
+ domain_name: addressData.ens_domain_name,
+ } : null;
+ }
+ }
+
+ return baseResponse;
+};
diff --git a/pages/api/csrf.ts b/pages/api/csrf.ts
index 8d4857b6f1..409ab8a1da 100644
--- a/pages/api/csrf.ts
+++ b/pages/api/csrf.ts
@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import buildUrl from 'nextjs/utils/buildUrl';
-import fetchFactory from 'nextjs/utils/fetch';
+import fetchFactory from 'nextjs/utils/fetchProxy';
import { httpLogger } from 'nextjs/utils/logger';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
diff --git a/pages/api/media-type.ts b/pages/api/media-type.ts
index 7489fecdf1..95d4b3728a 100644
--- a/pages/api/media-type.ts
+++ b/pages/api/media-type.ts
@@ -3,16 +3,20 @@ import nodeFetch from 'node-fetch';
import { httpLogger } from 'nextjs/utils/logger';
+import metrics from 'lib/monitoring/metrics';
import getQueryParamString from 'lib/router/getQueryParamString';
export default async function mediaTypeHandler(req: NextApiRequest, res: NextApiResponse) {
- httpLogger(req, res);
try {
const url = getQueryParamString(req.query.url);
+
+ const end = metrics?.apiRequestDuration.startTimer();
const response = await nodeFetch(url, { method: 'HEAD' });
+ const duration = end?.({ route: '/media-type', code: response.status });
if (response.status !== 200) {
+ httpLogger.logger.error({ message: 'API fetch', url, code: response.status, duration });
throw new Error();
}
@@ -30,6 +34,8 @@ export default async function mediaTypeHandler(req: NextApiRequest, res: NextApi
return 'html';
}
})();
+ httpLogger.logger.info({ message: 'API fetch', url, code: response.status, duration });
+
res.status(200).json({ type: mediaType });
} catch (error) {
res.status(200).json({ type: undefined });
diff --git a/pages/api/metrics.ts b/pages/api/metrics.ts
index d2f00ff430..c474dd7552 100644
--- a/pages/api/metrics.ts
+++ b/pages/api/metrics.ts
@@ -4,7 +4,7 @@ import * as promClient from 'prom-client';
// eslint-disable-next-line no-restricted-properties
const isEnabled = process.env.PROMETHEUS_METRICS_ENABLED === 'true';
-isEnabled && promClient.collectDefaultMetrics();
+isEnabled && promClient.collectDefaultMetrics({ prefix: 'frontend_' });
export default async function metricsHandler(req: NextApiRequest, res: NextApiResponse) {
const metrics = await promClient.register.metrics();
diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts
index 0756f3639c..95b168413b 100644
--- a/pages/api/proxy.ts
+++ b/pages/api/proxy.ts
@@ -2,7 +2,7 @@ import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next';
-import fetchFactory from 'nextjs/utils/fetch';
+import fetchFactory from 'nextjs/utils/fetchProxy';
import appConfig from 'configs/app';
diff --git a/pages/apps/[id].tsx b/pages/apps/[id].tsx
index f99be74026..936c485f01 100644
--- a/pages/apps/[id].tsx
+++ b/pages/apps/[id].tsx
@@ -1,18 +1,29 @@
+import type { GetServerSideProps } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import type { NextPageWithLayout } from 'nextjs/types';
+import type { MarketplaceAppOverview } from 'types/client/marketplace';
+import type { Route } from 'nextjs-routes';
+import * as gSSP from 'nextjs/getServerSideProps';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
+import detectBotRequest from 'nextjs/utils/detectBotRequest';
+import fetchApi from 'nextjs/utils/fetchApi';
+import config from 'configs/app';
+import getQueryParamString from 'lib/router/getQueryParamString';
import LayoutApp from 'ui/shared/layout/LayoutApp';
const MarketplaceApp = dynamic(() => import('ui/pages/MarketplaceApp'), { ssr: false });
-const Page: NextPageWithLayout = (props: Props) => {
+const pathname: Route['pathname'] = '/apps/[id]';
+const feature = config.features.marketplace;
+
+const Page: NextPageWithLayout> = (props: Props) => {
return (
-
+
);
@@ -28,4 +39,40 @@ Page.getLayout = function getLayout(page: React.ReactElement) {
export default Page;
-export { marketplace as getServerSideProps } from 'nextjs/getServerSideProps';
+export const getServerSideProps: GetServerSideProps> = async(ctx) => {
+ const baseResponse = await gSSP.marketplace(ctx);
+
+ if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse && feature.isEnabled) {
+ const botInfo = detectBotRequest(ctx.req);
+
+ if (botInfo?.type === 'social_preview') {
+
+ const appData = await(async() => {
+ if ('configUrl' in feature) {
+ const appList = await fetchApi>({
+ url: config.app.baseUrl + feature.configUrl,
+ route: '/marketplace_config',
+ timeout: 1_000,
+ });
+
+ if (appList && Array.isArray(appList)) {
+ return appList.find(app => app.id === getQueryParamString(ctx.query.id));
+ }
+
+ } else {
+ return await fetchApi({
+ resource: 'marketplace_dapp',
+ pathParams: { dappId: getQueryParamString(ctx.query.id), chainId: config.chain.id },
+ timeout: 1_000,
+ });
+ }
+ })();
+
+ (await baseResponse.props).apiData = appData && appData.title ? {
+ app_name: appData.title,
+ } : null;
+ }
+ }
+
+ return baseResponse;
+};
diff --git a/pages/batches/[number].tsx b/pages/batches/[number].tsx
index 870a1cc7b4..814f8358ea 100644
--- a/pages/batches/[number].tsx
+++ b/pages/batches/[number].tsx
@@ -25,7 +25,7 @@ const Batch = dynamic(() => {
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/blobs/[hash].tsx b/pages/blobs/[hash].tsx
index f1056d36fa..c042c862aa 100644
--- a/pages/blobs/[hash].tsx
+++ b/pages/blobs/[hash].tsx
@@ -9,7 +9,7 @@ const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false });
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/block/[height_or_hash].tsx b/pages/block/[height_or_hash].tsx
index 7d74d59c49..ef2c7652d2 100644
--- a/pages/block/[height_or_hash].tsx
+++ b/pages/block/[height_or_hash].tsx
@@ -9,7 +9,7 @@ const Block = dynamic(() => import('ui/pages/Block'), { ssr: false });
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/contract-verification.tsx b/pages/contract-verification.tsx
index a14df711e1..59dc7064b7 100644
--- a/pages/contract-verification.tsx
+++ b/pages/contract-verification.tsx
@@ -8,7 +8,7 @@ import ContractVerification from 'ui/pages/ContractVerification';
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/name-domains/[name].tsx b/pages/name-domains/[name].tsx
index 7a01829baa..d9346260b4 100644
--- a/pages/name-domains/[name].tsx
+++ b/pages/name-domains/[name].tsx
@@ -9,7 +9,7 @@ const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/op/[hash].tsx b/pages/op/[hash].tsx
index 63080f817c..8a37663e5e 100644
--- a/pages/op/[hash].tsx
+++ b/pages/op/[hash].tsx
@@ -9,7 +9,7 @@ const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false });
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/search-results.tsx b/pages/search-results.tsx
index 460b71b6e3..f69311ef51 100644
--- a/pages/search-results.tsx
+++ b/pages/search-results.tsx
@@ -12,7 +12,7 @@ const SearchResults = dynamic(() => import('ui/pages/SearchResults'), { ssr: fal
const Page: NextPageWithLayout = (props: Props) => {
return (
-
+
);
diff --git a/pages/token/[hash]/index.tsx b/pages/token/[hash]/index.tsx
index 70f41b66fd..27749fcdfb 100644
--- a/pages/token/[hash]/index.tsx
+++ b/pages/token/[hash]/index.tsx
@@ -1,15 +1,24 @@
-import type { NextPage } from 'next';
+import type { GetServerSideProps, NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
+import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
+import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
+import detectBotRequest from 'nextjs/utils/detectBotRequest';
+import fetchApi from 'nextjs/utils/fetchApi';
+
+import config from 'configs/app';
+import getQueryParamString from 'lib/router/getQueryParamString';
const Token = dynamic(() => import('ui/pages/Token'), { ssr: false });
-const Page: NextPage = (props: Props) => {
+const pathname: Route['pathname'] = '/token/[hash]';
+
+const Page: NextPage> = (props: Props) => {
return (
-
+
);
@@ -17,4 +26,24 @@ const Page: NextPage = (props: Props) => {
export default Page;
-export { base as getServerSideProps } from 'nextjs/getServerSideProps';
+export const getServerSideProps: GetServerSideProps> = async(ctx) => {
+ const baseResponse = await gSSP.base(ctx);
+
+ if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
+ const botInfo = detectBotRequest(ctx.req);
+
+ if (botInfo?.type === 'social_preview') {
+ const tokenData = await fetchApi({
+ resource: 'token',
+ pathParams: { hash: getQueryParamString(ctx.query.hash) },
+ timeout: 1_000,
+ });
+
+ (await baseResponse.props).apiData = tokenData && tokenData.symbol ? {
+ symbol: tokenData.symbol,
+ } : null;
+ }
+ }
+
+ return baseResponse;
+};
diff --git a/pages/token/[hash]/instance/[id].tsx b/pages/token/[hash]/instance/[id].tsx
index 62f0e71b96..973c8fd168 100644
--- a/pages/token/[hash]/instance/[id].tsx
+++ b/pages/token/[hash]/instance/[id].tsx
@@ -1,15 +1,24 @@
-import type { NextPage } from 'next';
+import type { GetServerSideProps, NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
+import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps';
+import * as gSSP from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
+import detectBotRequest from 'nextjs/utils/detectBotRequest';
+import fetchApi from 'nextjs/utils/fetchApi';
+
+import config from 'configs/app';
+import getQueryParamString from 'lib/router/getQueryParamString';
const TokenInstance = dynamic(() => import('ui/pages/TokenInstance'), { ssr: false });
-const Page: NextPage = (props: Props) => {
+const pathname: Route['pathname'] = '/token/[hash]/instance/[id]';
+
+const Page: NextPage> = (props: Props) => {
return (
-
+
);
@@ -17,4 +26,24 @@ const Page: NextPage = (props: Props) => {
export default Page;
-export { base as getServerSideProps } from 'nextjs/getServerSideProps';
+export const getServerSideProps: GetServerSideProps> = async(ctx) => {
+ const baseResponse = await gSSP.base(ctx);
+
+ if (config.meta.og.enhancedDataEnabled && 'props' in baseResponse) {
+ const botInfo = detectBotRequest(ctx.req);
+
+ if (botInfo?.type === 'social_preview') {
+ const tokenData = await fetchApi({
+ resource: 'token',
+ pathParams: { hash: getQueryParamString(ctx.query.hash) },
+ timeout: 1_000,
+ });
+
+ (await baseResponse.props).apiData = tokenData && tokenData.symbol ? {
+ symbol: tokenData.symbol,
+ } : null;
+ }
+ }
+
+ return baseResponse;
+};
diff --git a/pages/tx/[hash].tsx b/pages/tx/[hash].tsx
index 5eae4aedaa..f90b534fc8 100644
--- a/pages/tx/[hash].tsx
+++ b/pages/tx/[hash].tsx
@@ -9,7 +9,7 @@ const Transaction = dynamic(() => import('ui/pages/Transaction'), { ssr: false }
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/pages/txs/kettle/[hash].tsx b/pages/txs/kettle/[hash].tsx
index 9b62f6dd0d..35a4470cb2 100644
--- a/pages/txs/kettle/[hash].tsx
+++ b/pages/txs/kettle/[hash].tsx
@@ -9,7 +9,7 @@ const KettleTxs = dynamic(() => import('ui/pages/KettleTxs'), { ssr: false });
const Page: NextPage = (props: Props) => {
return (
-
+
);
diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx
index 8db3aee3b2..51c03e1c14 100644
--- a/playwright/TestApp.tsx
+++ b/playwright/TestApp.tsx
@@ -6,6 +6,7 @@ import { WagmiProvider } from 'wagmi';
import type { Props as PageProps } from 'nextjs/getServerSideProps';
+import config from 'configs/app';
import { AppContextProvider } from 'lib/contexts/app';
import { SocketProvider } from 'lib/socket/context';
import wagmiConfig from 'lib/web3/wagmiConfig';
@@ -24,13 +25,9 @@ const defaultAppContext = {
pageProps: {
cookies: '',
referrer: '',
- id: '',
- height_or_hash: '',
- hash: '',
- number: '',
- q: '',
- name: '',
- adBannerProvider: 'slise',
+ query: {},
+ adBannerProvider: 'slise' as const,
+ apiData: null,
},
};
@@ -47,7 +44,7 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props
return (
-
+
diff --git a/playwright/fixtures/auth.ts b/playwright/fixtures/auth.ts
index 4a2aea71f1..0bda117392 100644
--- a/playwright/fixtures/auth.ts
+++ b/playwright/fixtures/auth.ts
@@ -1,8 +1,13 @@
-import type { BrowserContext } from '@playwright/test';
+import type { BrowserContext, TestFixture } from '@playwright/test';
+import config from 'configs/app';
import * as cookies from 'lib/cookies';
-import { domain } from 'playwright/utils/app';
-export default function authFixture(context: BrowserContext) {
- context.addCookies([ { name: cookies.NAMES.API_TOKEN, value: 'foo', domain, path: '/' } ]);
+export function authenticateUser(context: BrowserContext) {
+ context.addCookies([ { name: cookies.NAMES.API_TOKEN, value: 'foo', domain: config.app.host, path: '/' } ]);
}
+
+export const contextWithAuth: TestFixture = async({ context }, use) => {
+ authenticateUser(context);
+ use(context);
+};
diff --git a/playwright/fixtures/contextWithEnvs.ts b/playwright/fixtures/contextWithEnvs.ts
index e703a922fb..d9c1464462 100644
--- a/playwright/fixtures/contextWithEnvs.ts
+++ b/playwright/fixtures/contextWithEnvs.ts
@@ -7,7 +7,13 @@ interface Env {
value: string;
}
-// keep in mind that all passed variables here should be present in env config files (.env.pw or .env.poa)
+/**
+ * @deprecated please use mockEnvs fixture
+ *
+ * @export
+ * @param {Array} envs
+ * @return {*} {Parameters[0]['context']}
+ */
export default function contextWithEnvsFixture(envs: Array): Parameters[0]['context'] {
return async({ browser }, use) => {
const context = await createContextWithStorage(browser, envs);
diff --git a/playwright/fixtures/contextWithFeatures.ts b/playwright/fixtures/contextWithFeatures.ts
index a9c7836937..84f8a55f20 100644
--- a/playwright/fixtures/contextWithFeatures.ts
+++ b/playwright/fixtures/contextWithFeatures.ts
@@ -7,6 +7,13 @@ interface Feature {
value: unknown;
}
+/**
+ * @deprecated please use mockFeatures fixture
+ *
+ * @export
+ * @param {Array} envs
+ * @return {*} {Parameters[0]['context']}
+ */
export default function contextWithFeaturesFixture(envs: Array): Parameters[0]['context'] {
return async({ browser }, use) => {
const storageItems = envs.map(({ id, value }) => ({ name: `pw_feature:${ id }`, value: JSON.stringify(value) }));
diff --git a/playwright/fixtures/createContextWithStorage.ts b/playwright/fixtures/createContextWithStorage.ts
index f2555b37c0..178dbf8a30 100644
--- a/playwright/fixtures/createContextWithStorage.ts
+++ b/playwright/fixtures/createContextWithStorage.ts
@@ -1,12 +1,20 @@
import type { Browser } from '@playwright/test';
-import * as app from 'playwright/utils/app';
+import config from 'configs/app';
+/**
+ * @deprecated please use mockEnvs or mockFeatures fixture
+ *
+ * @export
+ * @param {Browser} browser
+ * @param {Array<{ name: string; value: string }>} localStorage
+ * @return {*}
+ */
export default async function createContextWithEnvs(browser: Browser, localStorage: Array<{ name: string; value: string }>) {
return browser.newContext({
storageState: {
origins: [
- { origin: app.url, localStorage },
+ { origin: config.app.baseUrl, localStorage },
],
cookies: [],
},
diff --git a/playwright/fixtures/injectMetaMaskProvider.ts b/playwright/fixtures/injectMetaMaskProvider.ts
new file mode 100644
index 0000000000..f09ee81a43
--- /dev/null
+++ b/playwright/fixtures/injectMetaMaskProvider.ts
@@ -0,0 +1,18 @@
+import type { TestFixture, Page } from '@playwright/test';
+
+import type { WalletProvider } from 'types/web3';
+
+export type InjectMetaMaskProvider = () => Promise;
+
+const fixture: TestFixture = async({ page }, use) => {
+ await use(async() => {
+ await page.evaluate(() => {
+ window.ethereum = {
+ isMetaMask: true,
+ _events: {},
+ } as WalletProvider;
+ });
+ });
+};
+
+export default fixture;
diff --git a/playwright/fixtures/mockConfigResponse.ts b/playwright/fixtures/mockConfigResponse.ts
new file mode 100644
index 0000000000..e8f97b1092
--- /dev/null
+++ b/playwright/fixtures/mockConfigResponse.ts
@@ -0,0 +1,27 @@
+import type { TestFixture, Page } from '@playwright/test';
+
+import config from 'configs/app';
+import { buildExternalAssetFilePath } from 'configs/app/utils';
+
+export type MockConfigResponseFixture = (envName: string, envValue: string, content: string, isImage?: boolean) => Promise;
+
+const fixture: TestFixture = async({ page }, use) => {
+ await use(async(envName, envValue, content, isImage) => {
+ const url = config.app.baseUrl + buildExternalAssetFilePath(envName, envValue);
+
+ if (isImage) {
+ await page.route(url, (route) => route.fulfill({
+ status: 200,
+ path: content,
+ }));
+ } else {
+ await page.route(url, (route) => route.fulfill({
+ status: 200,
+ body: content,
+ }));
+ }
+
+ });
+};
+
+export default fixture;
diff --git a/playwright/fixtures/mockEnvs.ts b/playwright/fixtures/mockEnvs.ts
new file mode 100644
index 0000000000..0ad44d5f0d
--- /dev/null
+++ b/playwright/fixtures/mockEnvs.ts
@@ -0,0 +1,30 @@
+/* eslint-disable max-len */
+import type { TestFixture, Page } from '@playwright/test';
+
+export type MockEnvsFixture = (envs: Array<[string, string]>) => Promise;
+
+const fixture: TestFixture = async({ page }, use) => {
+ await use(async(envs) => {
+ for (const [ name, value ] of envs) {
+ await page.evaluate(({ name, value }) => {
+ window.localStorage.setItem(name, value);
+ }, { name, value });
+ }
+ });
+};
+
+export default fixture;
+
+export const ENVS_MAP: Record> = {
+ shibariumRollup: [
+ [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ],
+ [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
+ ],
+ bridgedTokens: [
+ [ 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS', '[{"id":"1","title":"Ethereum","short_title":"ETH","base_url":"https://eth.blockscout.com/token/"},{"id":"56","title":"Binance Smart Chain","short_title":"BSC","base_url":"https://bscscan.com/token/"},{"id":"99","title":"POA","short_title":"POA","base_url":"https://blockscout.com/poa/core/token/"}]' ],
+ [ 'NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES', '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]' ],
+ ],
+ userOps: [
+ [ 'NEXT_PUBLIC_HAS_USER_OPS', 'true' ],
+ ],
+};
diff --git a/playwright/fixtures/mockFeatures.ts b/playwright/fixtures/mockFeatures.ts
new file mode 100644
index 0000000000..9b82992c70
--- /dev/null
+++ b/playwright/fixtures/mockFeatures.ts
@@ -0,0 +1,16 @@
+/* eslint-disable max-len */
+import type { TestFixture, Page } from '@playwright/test';
+
+export type MockFeaturesFixture = (features: Array<[string, unknown]>) => Promise;
+
+const fixture: TestFixture = async({ page }, use) => {
+ await use(async(features) => {
+ for (const [ name, value ] of features) {
+ await page.evaluate(({ name, value }) => {
+ window.localStorage.setItem(`pw_feature:${ name }`, JSON.stringify(value));
+ }, { name, value });
+ }
+ });
+};
+
+export default fixture;
diff --git a/playwright/lib.tsx b/playwright/lib.tsx
index 215f71d17e..40a4bc4ea3 100644
--- a/playwright/lib.tsx
+++ b/playwright/lib.tsx
@@ -3,27 +3,35 @@ import { test as base } from '@playwright/experimental-ct-react';
import * as textAdMock from 'mocks/ad/textAd';
-import type { MockApiResponseFixture } from './fixtures/mockApiResponse';
-import mockApiResponseFixture from './fixtures/mockApiResponse';
-import type { MockAssetResponseFixture } from './fixtures/mockAssetResponse';
-import mockAssetResponseFixture from './fixtures/mockAssetResponse';
-import type { RenderFixture } from './fixtures/render';
-import renderFixture from './fixtures/render';
-import type { CreateSocketFixture } from './fixtures/socketServer';
-import { createSocket as createSocketFixture } from './fixtures/socketServer';
+import * as injectMetaMaskProvider from './fixtures/injectMetaMaskProvider';
+import * as mockApiResponse from './fixtures/mockApiResponse';
+import * as mockAssetResponse from './fixtures/mockAssetResponse';
+import * as mockConfigResponse from './fixtures/mockConfigResponse';
+import * as mockEnvs from './fixtures/mockEnvs';
+import * as mockFeatures from './fixtures/mockFeatures';
+import * as render from './fixtures/render';
+import * as socketServer from './fixtures/socketServer';
interface Fixtures {
- render: RenderFixture;
- mockApiResponse: MockApiResponseFixture;
- mockAssetResponse: MockAssetResponseFixture;
- createSocket: CreateSocketFixture;
+ render: render.RenderFixture;
+ mockApiResponse: mockApiResponse.MockApiResponseFixture;
+ mockAssetResponse: mockAssetResponse.MockAssetResponseFixture;
+ mockConfigResponse: mockConfigResponse.MockConfigResponseFixture;
+ mockEnvs: mockEnvs.MockEnvsFixture;
+ mockFeatures: mockFeatures.MockFeaturesFixture;
+ createSocket: socketServer.CreateSocketFixture;
+ injectMetaMaskProvider: injectMetaMaskProvider.InjectMetaMaskProvider;
}
const test = base.extend({
- render: renderFixture,
- mockApiResponse: mockApiResponseFixture,
- mockAssetResponse: mockAssetResponseFixture,
- createSocket: createSocketFixture,
+ render: render.default,
+ mockApiResponse: mockApiResponse.default,
+ mockAssetResponse: mockAssetResponse.default,
+ mockConfigResponse: mockConfigResponse.default,
+ mockEnvs: mockEnvs.default,
+ mockFeatures: mockFeatures.default,
+ createSocket: socketServer.createSocket,
+ injectMetaMaskProvider: injectMetaMaskProvider.default,
});
test.beforeEach(async({ page }) => {
diff --git a/playwright/utils/app.ts b/playwright/utils/app.ts
index 475376830e..466757ee9b 100644
--- a/playwright/utils/app.ts
+++ b/playwright/utils/app.ts
@@ -1,5 +1 @@
-export const url = `${ process.env.NEXT_PUBLIC_APP_PROTOCOL }://${ process.env.NEXT_PUBLIC_APP_HOST }:${ process.env.NEXT_PUBLIC_APP_PORT }`;
-
-export const domain = process.env.NEXT_PUBLIC_APP_HOST;
-
export const socketPort = 3200;
diff --git a/playwright/utils/buildApiUrl.ts b/playwright/utils/buildApiUrl.ts
index f8ce6bc063..346828c618 100644
--- a/playwright/utils/buildApiUrl.ts
+++ b/playwright/utils/buildApiUrl.ts
@@ -4,8 +4,6 @@ import config from 'configs/app';
import type { ResourceName, ResourcePathParams } from 'lib/api/resources';
import { RESOURCES } from 'lib/api/resources';
-// DEPRECATED
-
/**
* @deprecated please use fixture mockApiResponse from playwright/lib.tsx for rendering test suite
*
diff --git a/playwright/utils/configs.ts b/playwright/utils/configs.ts
index d0a7ac6da9..20896e17d2 100644
--- a/playwright/utils/configs.ts
+++ b/playwright/utils/configs.ts
@@ -20,20 +20,6 @@ export const featureEnvs = {
{ name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
{ name: 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' },
],
- shibariumRollup: [
- { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'shibarium' },
- { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
- ],
- bridgedTokens: [
- {
- name: 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS',
- value: '[{"id":"1","title":"Ethereum","short_title":"ETH","base_url":"https://eth.blockscout.com/token/"},{"id":"56","title":"Binance Smart Chain","short_title":"BSC","base_url":"https://bscscan.com/token/"},{"id":"99","title":"POA","short_title":"POA","base_url":"https://blockscout.com/poa/core/token/"}]',
- },
- {
- name: 'NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES',
- value: '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]',
- },
- ],
txInterpretation: [
{ name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' },
],
diff --git a/stubs/tx.ts b/stubs/tx.ts
index 1f7eb1c0a6..2e80408689 100644
--- a/stubs/tx.ts
+++ b/stubs/tx.ts
@@ -1,5 +1,5 @@
import type { RawTracesResponse } from 'types/api/rawTrace';
-import type { Transaction } from 'types/api/transaction';
+import type { Transaction, TransactionsStats } from 'types/api/transaction';
import { ADDRESS_PARAMS } from './addressParams';
@@ -59,3 +59,10 @@ export const TX_ZKEVM_L2: Transaction = {
};
export const TX_RAW_TRACE: RawTracesResponse = [];
+
+export const TXS_STATS: TransactionsStats = {
+ pending_transactions_count: '4200',
+ transaction_fees_avg_24h: '22342870314428',
+ transaction_fees_sum_24h: '22184012506492688277',
+ transactions_count_24h: '992890',
+};
diff --git a/theme/components/Tag/Tag.ts b/theme/components/Tag/Tag.ts
index 5cd5b2bd97..2224f19c01 100644
--- a/theme/components/Tag/Tag.ts
+++ b/theme/components/Tag/Tag.ts
@@ -23,7 +23,7 @@ const sizes = {
minH: 6,
minW: 6,
fontSize: 'sm',
- px: 2,
+ px: 1,
py: '2px',
lineHeight: 5,
},
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png
index e2964560a2..5fc7bad237 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_blue-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png
index 8a1efa746b..28d17a8fe0 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_cyan-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png
index a956655fb5..6afe64a5a7 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_gray-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png
index 18f0765090..a14814713f 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_green-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png
index 51d848855b..fd9bb6cfc6 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_orange-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png
index 9fd2e54028..a9b36a72cd 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_purple-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png
index e4b5437ff4..bbaed04293 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_dark-color-mode_teal-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png
index 475e4b3500..ce7946c035 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_blue-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png
index e2df67339c..1cac3c3c0c 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_cyan-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png
index 533fb98865..f37bae331a 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_gray-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png
index 89b0f21d9b..24fb120a47 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_green-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png
index 78612cf01f..7a3f917391 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_orange-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png
index bcad901b56..f9b325adf1 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_purple-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png
index 6d1b5b96e0..cccd73491f 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_teal-color-scheme-dark-mode-1.png differ
diff --git a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png
index 3b47811eaa..ee9d7d6dbd 100644
Binary files a/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png and b/theme/components/Tag/__screenshots__/Tag.pw.tsx_default_with-long-text-1.png differ
diff --git a/types/api/address.ts b/types/api/address.ts
index 37955b7f28..cbaaeafb77 100644
--- a/types/api/address.ts
+++ b/types/api/address.ts
@@ -148,11 +148,20 @@ export interface AddressCoinBalanceHistoryResponse {
} | null;
}
-export type AddressCoinBalanceHistoryChart = Array<{
+// remove after api release
+export type AddressCoinBalanceHistoryChartOld = Array<{
date: string;
value: string;
}>
+export type AddressCoinBalanceHistoryChart = {
+ items: Array<{
+ date: string;
+ value: string;
+ }>;
+ days: number;
+};
+
export interface AddressBlocksValidatedResponse {
items: Array;
next_page_params: {
diff --git a/types/api/charts.ts b/types/api/charts.ts
index 5d74b38410..a5504ded12 100644
--- a/types/api/charts.ts
+++ b/types/api/charts.ts
@@ -18,3 +18,8 @@ export interface ChartMarketResponse {
available_supply: string;
chart_data: Array;
}
+
+export interface ChartSecondaryCoinPriceResponse {
+ available_supply: string;
+ chart_data: Array;
+}
diff --git a/types/api/search.ts b/types/api/search.ts
index da8194eef5..bb9330a8a0 100644
--- a/types/api/search.ts
+++ b/types/api/search.ts
@@ -31,6 +31,20 @@ export interface SearchResultAddressOrContract {
};
}
+export interface SearchResultDomain {
+ type: 'ens_domain';
+ name: string | null;
+ address: string;
+ is_smart_contract_verified: boolean;
+ url?: string; // not used by the frontend, we build the url ourselves
+ ens_info: {
+ address_hash: string;
+ expiry_date?: string;
+ name: string;
+ names_count: number;
+ };
+}
+
export interface SearchResultLabel {
type: 'label';
address: string;
@@ -69,7 +83,7 @@ export interface SearchResultUserOp {
}
export type SearchResultItem = SearchResultToken | SearchResultAddressOrContract | SearchResultBlock | SearchResultTx | SearchResultLabel | SearchResultUserOp |
-SearchResultBlob;
+SearchResultBlob | SearchResultDomain;
export interface SearchResult {
items: Array;
diff --git a/types/api/stats.ts b/types/api/stats.ts
index 40fe7d34e2..5f3467597c 100644
--- a/types/api/stats.ts
+++ b/types/api/stats.ts
@@ -16,6 +16,8 @@ export type HomeStats = {
network_utilization_percentage: number;
tvl: string | null;
rootstock_locked_btc?: string | null;
+ last_output_root_size?: string | null;
+ secondary_coin_price?: string | null;
}
export type GasPrices = {
diff --git a/types/api/tokens.ts b/types/api/tokens.ts
index adeca8d768..6538e690ec 100644
--- a/types/api/tokens.ts
+++ b/types/api/tokens.ts
@@ -8,7 +8,7 @@ export type TokensResponse = {
items_count: number;
name: string;
market_cap: string | null;
- };
+ } | null;
}
export type TokensFilters = { q: string; type: Array | undefined };
diff --git a/types/api/transaction.ts b/types/api/transaction.ts
index 8f1fee017e..f6a3841354 100644
--- a/types/api/transaction.ts
+++ b/types/api/transaction.ts
@@ -97,6 +97,13 @@ export type Transaction = {
export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ];
+export interface TransactionsStats {
+ pending_transactions_count: string;
+ transaction_fees_avg_24h: string;
+ transaction_fees_sum_24h: string;
+ transactions_count_24h: string;
+}
+
export type TransactionsResponse = TransactionsResponseValidated | TransactionsResponsePending;
export interface TransactionsResponseValidated {
diff --git a/types/homepage.ts b/types/homepage.ts
index b773176288..2492134e97 100644
--- a/types/homepage.ts
+++ b/types/homepage.ts
@@ -1 +1,2 @@
-export type ChainIndicatorId = 'daily_txs' | 'coin_price' | 'market_cap' | 'tvl';
+export const CHAIN_INDICATOR_IDS = [ 'daily_txs', 'coin_price', 'secondary_coin_price', 'market_cap', 'tvl' ] as const;
+export type ChainIndicatorId = typeof CHAIN_INDICATOR_IDS[number];
diff --git a/ui/address/AddressAccountHistory.tsx b/ui/address/AddressAccountHistory.tsx
index 5b7cf3aa7e..6d76d04302 100644
--- a/ui/address/AddressAccountHistory.tsx
+++ b/ui/address/AddressAccountHistory.tsx
@@ -7,6 +7,7 @@ import type { NovesHistoryFilterValue } from 'types/api/noves';
import { NovesHistoryFilterValues } from 'types/api/noves';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate';
import { generateListStub } from 'stubs/utils';
@@ -25,10 +26,12 @@ const getFilterValue = (getFilterValueFromQuery).bind(n
type Props = {
scrollRef?: React.RefObject;
+ shouldRender?: boolean;
}
-const AddressAccountHistory = ({ scrollRef }: Props) => {
+const AddressAccountHistory = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
+ const isMounted = useIsMounted();
const currentAddress = getQueryParamString(router.query.hash).toLowerCase();
@@ -49,6 +52,10 @@ const AddressAccountHistory = ({ scrollRef }: Props) => {
setFilterValue(newVal);
}, [ ]);
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const actionBar = (
;
+ shouldRender?: boolean;
}
-const AddressBlocksValidated = ({ scrollRef }: Props) => {
+const AddressBlocksValidated = ({ scrollRef, shouldRender = true }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient();
const router = useRouter();
+ const isMounted = useIsMounted();
const addressHash = String(router.query.hash);
const query = useQueryWithPages({
@@ -84,6 +87,10 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
handler: handleNewSocketMessage,
});
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const content = query.data?.items ? (
<>
{ socketAlert && }
diff --git a/ui/address/AddressCoinBalance.tsx b/ui/address/AddressCoinBalance.tsx
index 1f109cf366..d4b366530d 100644
--- a/ui/address/AddressCoinBalance.tsx
+++ b/ui/address/AddressCoinBalance.tsx
@@ -6,6 +6,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
@@ -17,10 +18,16 @@ import SocketAlert from 'ui/shared/SocketAlert';
import AddressCoinBalanceChart from './coinBalance/AddressCoinBalanceChart';
import AddressCoinBalanceHistory from './coinBalance/AddressCoinBalanceHistory';
-const AddressCoinBalance = () => {
+type Props = {
+ shouldRender?: boolean;
+}
+
+const AddressCoinBalance = ({ shouldRender = true }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false);
const queryClient = useQueryClient();
const router = useRouter();
+ const isMounted = useIsMounted();
+
const scrollRef = React.useRef(null);
const addressHash = getQueryParamString(router.query.hash);
@@ -78,6 +85,10 @@ const AddressCoinBalance = () => {
handler: handleNewSocketMessage,
});
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
return (
<>
{ socketAlert && }
diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx
index 662fd86a70..3234694572 100644
--- a/ui/address/AddressDetails.tsx
+++ b/ui/address/AddressDetails.tsx
@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
@@ -60,6 +61,8 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
has_validated_blocks: false,
}), [ addressHash ]);
+ const isMounted = useIsMounted();
+
// error handling (except 404 codes)
if (addressQuery.isError) {
if (isCustomAppError(addressQuery.error)) {
@@ -74,7 +77,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
const data = addressQuery.isError ? error404Data : addressQuery.data;
- if (!data) {
+ if (!data || !isMounted) {
return null;
}
diff --git a/ui/address/AddressInternalTxs.tsx b/ui/address/AddressInternalTxs.tsx
index d4d302e39b..ebaf11cbaf 100644
--- a/ui/address/AddressInternalTxs.tsx
+++ b/ui/address/AddressInternalTxs.tsx
@@ -6,6 +6,7 @@ import type { AddressFromToFilter } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
+import useIsMounted from 'lib/hooks/useIsMounted';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { INTERNAL_TX } from 'stubs/internalTx';
@@ -22,8 +23,14 @@ import AddressIntTxsList from './internals/AddressIntTxsList';
const getFilterValue = (getFilterValueFromQuery).bind(null, AddressFromToFilterValues);
-const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject}) => {
+type Props = {
+ scrollRef?: React.RefObject;
+ shouldRender?: boolean;
+}
+const AddressInternalTxs = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
+ const isMounted = useIsMounted();
+
const [ filterValue, setFilterValue ] = React.useState(getFilterValue(router.query.filter));
const hash = getQueryParamString(router.query.hash);
@@ -55,6 +62,10 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject
diff --git a/ui/address/AddressLogs.tsx b/ui/address/AddressLogs.tsx
index 5de923c0fd..286a3c6fac 100644
--- a/ui/address/AddressLogs.tsx
+++ b/ui/address/AddressLogs.tsx
@@ -1,6 +1,7 @@
import { useRouter } from 'next/router';
import React from 'react';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { LOG } from 'stubs/log';
import { generateListStub } from 'stubs/utils';
@@ -12,8 +13,14 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink';
-const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject}) => {
+type Props ={
+ scrollRef?: React.RefObject;
+ shouldRender?: boolean;
+}
+
+const AddressLogs = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
+ const isMounted = useIsMounted();
const hash = getQueryParamString(router.query.hash);
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
@@ -41,6 +48,10 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject
);
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const content = data?.items ? data.items.map((item, index) => ) : null;
return (
diff --git a/ui/address/AddressTokenTransfers.tsx b/ui/address/AddressTokenTransfers.tsx
index 0ebba17f8e..a275478e12 100644
--- a/ui/address/AddressTokenTransfers.tsx
+++ b/ui/address/AddressTokenTransfers.tsx
@@ -13,6 +13,7 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
+import useIsMounted from 'lib/hooks/useIsMounted';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
@@ -63,14 +64,16 @@ const matchFilters = (filters: Filters, tokenTransfer: TokenTransfer, address?:
type Props = {
scrollRef?: React.RefObject;
+ shouldRender?: boolean;
// for tests only
overloadCount?: number;
}
-const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
+const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = true }: Props) => {
const router = useRouter();
const queryClient = useQueryClient();
const isMobile = useIsMobile();
+ const isMounted = useIsMounted();
const currentAddress = getQueryParamString(router.query.hash);
@@ -179,6 +182,18 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
handler: handleNewSocketMessage,
});
+ const tokenData = React.useMemo(() => ({
+ address: tokenFilter || '',
+ name: '',
+ icon_url: '',
+ symbol: '',
+ type: 'ERC-20' as const,
+ }), [ tokenFilter ]);
+
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const numActiveFilters = (filters.type?.length || 0) + (filters.filter ? 1 : 0);
const isActionBarHidden = !tokenFilter && !numActiveFilters && !data?.items.length && !currentAddress;
@@ -218,14 +233,6 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
>
) : null;
- const tokenData = React.useMemo(() => ({
- address: tokenFilter || '',
- name: '',
- icon_url: '',
- symbol: '',
- type: 'ERC-20' as const,
- }), [ tokenFilter ]);
-
const tokenFilterComponent = tokenFilter && (
Filtered by token
diff --git a/ui/address/AddressTokens.tsx b/ui/address/AddressTokens.tsx
index ea0059232e..3f2c35a403 100644
--- a/ui/address/AddressTokens.tsx
+++ b/ui/address/AddressTokens.tsx
@@ -9,6 +9,7 @@ import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes';
import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address';
@@ -41,9 +42,14 @@ const TAB_LIST_PROPS_MOBILE = {
const getTokenFilterValue = (getFilterValuesFromQuery).bind(null, NFT_TOKEN_TYPE_IDS);
-const AddressTokens = () => {
+type Props = {
+ shouldRender?: boolean;
+}
+
+const AddressTokens = ({ shouldRender = true }: Props) => {
const router = useRouter();
const isMobile = useIsMobile();
+ const isMounted = useIsMounted();
const scrollRef = React.useRef(null);
@@ -99,6 +105,10 @@ const AddressTokens = () => {
setTokenTypes(value);
}, [ nftsQuery, collectionsQuery ]);
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const nftTypeFilter = (
0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
nftOnly onChange={ handleTokenTypesChange } defaultValue={ tokenTypes }/>
diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx
index d272982c71..2bb002b8f7 100644
--- a/ui/address/AddressTxs.tsx
+++ b/ui/address/AddressTxs.tsx
@@ -10,6 +10,7 @@ import type { Transaction, TransactionsSortingField, TransactionsSortingValue, T
import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
@@ -47,13 +48,15 @@ const matchFilter = (filterValue: AddressFromToFilter, transaction: Transaction,
type Props = {
scrollRef?: React.RefObject;
+ shouldRender?: boolean;
// for tests only
overloadCount?: number;
}
-const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
+const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT, shouldRender = true }: Props) => {
const router = useRouter();
const queryClient = useQueryClient();
+ const isMounted = useIsMounted();
const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
@@ -156,6 +159,10 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
handler: handleNewSocketMessage,
});
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
const filter = (
;
+ shouldRender?: boolean;
}
-const AddressUserOps = ({ scrollRef }: Props) => {
+const AddressUserOps = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
+ const isMounted = useIsMounted();
const hash = getQueryParamString(router.query.hash);
@@ -29,6 +32,10 @@ const AddressUserOps = ({ scrollRef }: Props) => {
filters: { sender: hash },
});
+ if (!isMounted || !shouldRender) {
+ return null;
+ }
+
return ;
};
diff --git a/ui/address/AddressWithdrawals.tsx b/ui/address/AddressWithdrawals.tsx
index bbc61e8730..ee8f9c4651 100644
--- a/ui/address/AddressWithdrawals.tsx
+++ b/ui/address/AddressWithdrawals.tsx
@@ -2,6 +2,7 @@ import { Show, Hide } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
+import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { generateListStub } from 'stubs/utils';
import { WITHDRAWAL } from 'stubs/withdrawals';
@@ -12,8 +13,13 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import BeaconChainWithdrawalsListItem from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsListItem';
import BeaconChainWithdrawalsTable from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsTable';
-const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject}) => {
+type Props = {
+ scrollRef?: React.RefObject;
+ shouldRender?: boolean;
+}
+const AddressWithdrawals = ({ scrollRef, shouldRender = true }: Props) => {
const router = useRouter();
+ const isMounted = useIsMounted();
const hash = getQueryParamString(router.query.hash);
@@ -28,6 +34,11 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject
diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png
index 4f76f75b60..6923895237 100644
Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png
index 46777342c1..b6f13d887f 100644
Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png
index 9a5fc4ea5d..4543e5121d 100644
Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png
index bd3982620b..1bb1638dda 100644
Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png
index 28f43e103b..e31c1c3b3e 100644
Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png
index beb7cd8b55..006ae00ba0 100644
Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png
index 86b0488857..4e2a2f0ded 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png
index d38ef06070..2b18eaea58 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png
index baa0f2ec89..bf64920931 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png
index d1d4f7eab1..fce88a821b 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png
index eca7f88c7f..5075d144bd 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png
index 6a7c63dc6e..2e9b458cd6 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png
index 26e56490ac..77060ddcd5 100644
Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png
index 7432110133..40bd723d0d 100644
Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png differ
diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png
index 03bc8552cd..4943e339c3 100644
Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/address/coinBalance/AddressCoinBalanceChart.tsx b/ui/address/coinBalance/AddressCoinBalanceChart.tsx
index 4e551f1e8b..15003058a4 100644
--- a/ui/address/coinBalance/AddressCoinBalanceChart.tsx
+++ b/ui/address/coinBalance/AddressCoinBalanceChart.tsx
@@ -15,10 +15,17 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
pathParams: { hash: addressHash },
});
- const items = React.useMemo(() => data?.map(({ date, value }) => ({
- date: new Date(date),
- value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(),
- })), [ data ]);
+ const items = React.useMemo(() => {
+ if (!data) {
+ return undefined;
+ }
+
+ const dataItems = 'items' in data ? data.items : data;
+ return dataItems.map(({ date, value }) => ({
+ date: new Date(date),
+ value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(),
+ }));
+ }, [ data ]);
return (
{
isLoading={ isPending }
h="300px"
units={ currencyUnits.ether }
+ emptyText={ data && 'days' in data && `Insufficient data for the past ${ data.days } days` }
/>
);
};
diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx
index 7b77ebe3b5..2f59c386bc 100644
--- a/ui/address/contract/ContractCode.tsx
+++ b/ui/address/contract/ContractCode.tsx
@@ -1,4 +1,4 @@
-import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box } from '@chakra-ui/react';
+import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box, useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
@@ -16,6 +16,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import * as stubs from 'stubs/contract';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import Hint from 'ui/shared/Hint';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
@@ -34,11 +35,24 @@ type InfoItemProps = {
content: string | React.ReactNode;
className?: string;
isLoading: boolean;
+ hint?: string;
}
-const InfoItem = chakra(({ label, content, className, isLoading }: InfoItemProps) => (
+const InfoItem = chakra(({ label, content, hint, className, isLoading }: InfoItemProps) => (
- { label }
+
+
+ { label }
+ { hint && (
+
+ ) }
+
+
{ content }
));
@@ -251,7 +265,14 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
{ data.name && }
{ data.compiler_version && }
{ data.evm_version && }
- { licenseLink && }
+ { licenseLink && (
+
+ ) }
{ typeof data.optimization_enabled === 'boolean' &&
}
{ data.optimization_runs && }
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png
index d4e7526c59..05c6e15e3a 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png differ
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png
index f11a52a153..719afffe43 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png differ
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png
index 6c88ba36e0..fc9a51d73d 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png differ
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png
index 191d800a72..a3c93ab6fe 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png differ
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png
index ab936fc8f6..bdbd0774b3 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png differ
diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png
index 539f531700..16444113bf 100644
Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png differ
diff --git a/ui/address/ensDomains/AddressEnsDomains.tsx b/ui/address/ensDomains/AddressEnsDomains.tsx
index af57b5bd20..a67612535d 100644
--- a/ui/address/ensDomains/AddressEnsDomains.tsx
+++ b/ui/address/ensDomains/AddressEnsDomains.tsx
@@ -1,4 +1,18 @@
-import { Box, Button, chakra, Flex, Grid, Hide, Popover, PopoverBody, PopoverContent, PopoverTrigger, Show, Skeleton, useDisclosure } from '@chakra-ui/react';
+import {
+ Box,
+ Button,
+ Flex,
+ Grid,
+ Hide,
+ Popover,
+ PopoverBody,
+ PopoverContent,
+ PopoverTrigger,
+ Show,
+ Skeleton,
+ useDisclosure,
+ chakra,
+} from '@chakra-ui/react';
import _clamp from 'lodash/clamp';
import React from 'react';
@@ -12,6 +26,7 @@ import dayjs from 'lib/date/dayjs';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/LinkInternal';
+import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
interface Props {
addressHash: string;
@@ -90,25 +105,27 @@ const AddressEnsDomains = ({ addressHash, mainDomainName }: Props) => {
return (
-
+
+
+
diff --git a/ui/gasTracker/GasTrackerPriceSnippet.tsx b/ui/gasTracker/GasTrackerPriceSnippet.tsx
index 575fc2d732..0cae78edca 100644
--- a/ui/gasTracker/GasTrackerPriceSnippet.tsx
+++ b/ui/gasTracker/GasTrackerPriceSnippet.tsx
@@ -52,7 +52,7 @@ const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => {
{ data.price && data.fiat_price && }
per transaction
- { data.time && / { (data.time / SECOND).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s }
+ { typeof data.time === 'number' && data.time > 0 && / { (data.time / SECOND).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s }
{ data.base_fee && Base { data.base_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) } }
diff --git a/ui/home/Stats.tsx b/ui/home/Stats.tsx
index eb2b49cdd7..d57b296378 100644
--- a/ui/home/Stats.tsx
+++ b/ui/home/Stats.tsx
@@ -59,6 +59,7 @@ const Stats = () => {
if (data) {
!data.gas_prices && itemsCount--;
data.rootstock_locked_btc && itemsCount++;
+ rollupFeature.isEnabled && data.last_output_root_size && itemsCount++;
const isOdd = Boolean(itemsCount % 2);
const gasInfoTooltip = hasGasTracker && data.gas_prices ? (
@@ -120,6 +121,15 @@ const Stats = () => {
url={ route({ pathname: '/txs' }) }
isLoading={ isLoading }
/>
+ { rollupFeature.isEnabled && data.last_output_root_size && (
+
+ ) }
{
test.beforeEach(async({ page, mount }) => {
await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200,
- body: JSON.stringify(statsMock.base),
+ body: JSON.stringify(statsMock.withSecondaryCoin),
}));
await page.route(TX_CHART_API_URL, (route) => route.fulfill({
status: 200,
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png
index f0bd10b718..7974ccd078 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-dark-mode-mobile-1.png differ
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png
index 24d453ca53..76d7c5c10c 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_daily-txs-chart-mobile-1.png differ
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png
index e54da4cf27..ea54aa9b85 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_no-data-1.png differ
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png
index 671945380b..a153779a1c 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_default_partial-data-1.png differ
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-dark-mode-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-dark-mode-mobile-1.png
index 5049e90c57..2199ac671f 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-dark-mode-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-dark-mode-mobile-1.png differ
diff --git a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-mobile-1.png b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-mobile-1.png
index 31472481b5..54df3a78a4 100644
Binary files a/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-mobile-1.png and b/ui/home/indicators/__screenshots__/ChainIndicators.pw.tsx_mobile_daily-txs-chart-mobile-1.png differ
diff --git a/ui/home/indicators/types.ts b/ui/home/indicators/types.ts
index 7ae2453ada..05b7680be5 100644
--- a/ui/home/indicators/types.ts
+++ b/ui/home/indicators/types.ts
@@ -4,7 +4,7 @@ import type { TimeChartData } from 'ui/shared/chart/types';
import type { ResourcePayload } from 'lib/api/resources';
-export type ChartsResources = 'stats_charts_txs' | 'stats_charts_market';
+export type ChartsResources = 'stats_charts_txs' | 'stats_charts_market' | 'stats_charts_secondary_coin_price';
export interface TChainIndicator {
id: ChainIndicatorId;
diff --git a/ui/home/indicators/utils/indicators.tsx b/ui/home/indicators/utils/indicators.tsx
index 62783b7bee..5f17d1c163 100644
--- a/ui/home/indicators/utils/indicators.tsx
+++ b/ui/home/indicators/utils/indicators.tsx
@@ -50,13 +50,13 @@ const nativeTokenData = {
const coinPriceIndicator: TChainIndicator<'stats_charts_market'> = {
id: 'coin_price',
- title: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } price`,
+ title: `${ config.chain.currency.symbol } price`,
value: (stats) => stats.coin_price === null ?
'$N/A' :
'$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
valueDiff: (stats) => stats?.coin_price !== null ? stats?.coin_price_change_percentage : null,
icon: ,
- hint: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } token daily price in USD.`,
+ hint: `${ config.chain.currency.symbol } token daily price in USD.`,
api: {
resourceName: 'stats_charts_market',
dataFn: (response) => ([ {
@@ -65,7 +65,30 @@ const coinPriceIndicator: TChainIndicator<'stats_charts_market'> = {
.sort(sortByDateDesc)
.reduceRight(nonNullTailReducer, [] as Array)
.map(mapNullToZero),
- name: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } price`,
+ name: `${ config.chain.currency.symbol } price`,
+ valueFormatter: (x: number) => '$' + x.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
+ } ]),
+ },
+};
+
+const secondaryCoinPriceIndicator: TChainIndicator<'stats_charts_secondary_coin_price'> = {
+ id: 'secondary_coin_price',
+ title: `${ config.chain.secondaryCoin.symbol } price`,
+ value: (stats) => !stats.secondary_coin_price || stats.secondary_coin_price === null ?
+ '$N/A' :
+ '$' + Number(stats.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
+ valueDiff: () => null,
+ icon: ,
+ hint: `${ config.chain.secondaryCoin.symbol } token daily price in USD.`,
+ api: {
+ resourceName: 'stats_charts_secondary_coin_price',
+ dataFn: (response) => ([ {
+ items: response.chart_data
+ .map((item) => ({ date: new Date(item.date), value: item.closing_price }))
+ .sort(sortByDateDesc)
+ .reduceRight(nonNullTailReducer, [] as Array)
+ .map(mapNullToZero),
+ name: `${ config.chain.secondaryCoin.symbol } price`,
valueFormatter: (x: number) => '$' + x.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
} ]),
},
@@ -138,6 +161,7 @@ const tvlIndicator: TChainIndicator<'stats_charts_market'> = {
const INDICATORS = [
dailyTxsIndicator,
coinPriceIndicator,
+ secondaryCoinPriceIndicator,
marketPriceIndicator,
tvlIndicator,
];
diff --git a/ui/marketplace/AppSecurityReport.tsx b/ui/marketplace/AppSecurityReport.tsx
index 35301af312..70a060f1b5 100644
--- a/ui/marketplace/AppSecurityReport.tsx
+++ b/ui/marketplace/AppSecurityReport.tsx
@@ -54,6 +54,7 @@ const AppSecurityReport = ({ id, securityReport, height, showContractList, isLoa
onClick={ handleButtonClick }
height={ height }
onlyIcon={ onlyIcon }
+ label="The security score is based on analysis of a DApp's smart contracts."
/>
diff --git a/ui/marketplace/Banner.tsx b/ui/marketplace/Banner.tsx
new file mode 100644
index 0000000000..c70f6ade9e
--- /dev/null
+++ b/ui/marketplace/Banner.tsx
@@ -0,0 +1,51 @@
+import type { MouseEvent } from 'react';
+import React from 'react';
+
+import type { MarketplaceAppPreview } from 'types/client/marketplace';
+
+import config from 'configs/app';
+import { apps as appsMock } from 'mocks/apps/apps';
+
+import FeaturedApp from './Banner/FeaturedApp';
+import IframeBanner from './Banner/IframeBanner';
+
+const feature = config.features.marketplace;
+
+type BannerProps = {
+ apps: Array | undefined;
+ favoriteApps: Array;
+ isLoading: boolean;
+ onInfoClick: (id: string) => void;
+ onFavoriteClick: (id: string, isFavorite: boolean, source: 'Banner') => void;
+ onAppClick: (event: MouseEvent, id: string) => void;
+}
+
+const Banner = ({ apps = [], favoriteApps, isLoading, onInfoClick, onFavoriteClick, onAppClick }: BannerProps) => {
+ if (!feature.isEnabled) {
+ return null;
+ }
+
+ if (feature.featuredApp) {
+ const app = apps.find(app => app.id === feature.featuredApp);
+ const isFavorite = favoriteApps.includes(feature.featuredApp);
+ if (!isLoading && !app) {
+ return null;
+ }
+ return (
+
+ );
+ } else if (feature.banner) {
+ return ;
+ }
+
+ return null;
+};
+
+export default Banner;
diff --git a/ui/marketplace/Banner/FeaturedApp.tsx b/ui/marketplace/Banner/FeaturedApp.tsx
new file mode 100644
index 0000000000..c9c03021e7
--- /dev/null
+++ b/ui/marketplace/Banner/FeaturedApp.tsx
@@ -0,0 +1,160 @@
+import { Link, Skeleton, useColorModeValue, LinkBox, Flex, Image, LinkOverlay, IconButton } from '@chakra-ui/react';
+import NextLink from 'next/link';
+import type { MouseEvent } from 'react';
+import React, { useCallback } from 'react';
+
+import type { MarketplaceAppPreview } from 'types/client/marketplace';
+
+import useIsMobile from 'lib/hooks/useIsMobile';
+import * as mixpanel from 'lib/mixpanel/index';
+import IconSvg from 'ui/shared/IconSvg';
+
+import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon';
+import FeaturedAppMobile from './FeaturedAppMobile';
+
+type FeaturedAppProps = {
+ app: MarketplaceAppPreview;
+ isFavorite: boolean;
+ isLoading: boolean;
+ onInfoClick: (id: string) => void;
+ onFavoriteClick: (id: string, isFavorite: boolean, source: 'Banner') => void;
+ onAppClick: (event: MouseEvent, id: string) => void;
+}
+
+const FeaturedApp = ({
+ app, isFavorite, isLoading, onAppClick,
+ onInfoClick, onFavoriteClick,
+}: FeaturedAppProps) => {
+ const isMobile = useIsMobile();
+
+ const { id, url, external, title, logo, logoDarkMode, shortDescription, categories, internalWallet } = app;
+ const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
+ const categoriesLabel = categories.join(', ');
+
+ const backgroundColor = useColorModeValue('purple.50', 'whiteAlpha.100');
+
+ const handleInfoClick = useCallback((event: MouseEvent) => {
+ event.preventDefault();
+ mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Banner' });
+ onInfoClick(id);
+ }, [ onInfoClick, id ]);
+
+ const handleFavoriteClick = useCallback(() => {
+ onFavoriteClick(id, isFavorite, 'Banner');
+ }, [ onFavoriteClick, id, isFavorite ]);
+
+ if (isMobile) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ { external ? (
+
+ { title }
+
+ ) : (
+
+
+ { title }
+
+
+ ) }
+
+
+
+
+ { categoriesLabel }
+
+
+ { !isLoading && (
+
+ More info
+
+ ) }
+
+ { !isLoading && (
+ :
+
+ }
+ />
+ ) }
+
+
+
+ { shortDescription }
+
+
+
+
+ );
+};
+
+export default FeaturedApp;
diff --git a/ui/marketplace/Banner/FeaturedAppMobile.tsx b/ui/marketplace/Banner/FeaturedAppMobile.tsx
new file mode 100644
index 0000000000..0c9bb99f69
--- /dev/null
+++ b/ui/marketplace/Banner/FeaturedAppMobile.tsx
@@ -0,0 +1,158 @@
+import { IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, Flex } from '@chakra-ui/react';
+import type { MouseEvent } from 'react';
+import React from 'react';
+
+import type { MarketplaceAppPreview } from 'types/client/marketplace';
+
+import IconSvg from 'ui/shared/IconSvg';
+
+import MarketplaceAppCardLink from '../MarketplaceAppCardLink';
+import MarketplaceAppIntegrationIcon from '../MarketplaceAppIntegrationIcon';
+
+interface Props extends MarketplaceAppPreview {
+ onInfoClick: (event: MouseEvent) => void;
+ isFavorite: boolean;
+ onFavoriteClick: () => void;
+ isLoading: boolean;
+ onAppClick: (event: MouseEvent, id: string) => void;
+}
+
+const FeaturedAppMobile = ({
+ id,
+ url,
+ external,
+ title,
+ logo,
+ logoDarkMode,
+ shortDescription,
+ categories,
+ onInfoClick,
+ isFavorite,
+ onFavoriteClick,
+ isLoading,
+ internalWallet,
+ onAppClick,
+}: Props) => {
+ const categoriesLabel = categories.join(', ');
+
+ const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
+
+ return (
+
+
+
+
+
+
+
+ { !isLoading && (
+
+
+ More info
+
+
+ ) }
+
+
+
+
+
+
+
+
+
+ { categoriesLabel }
+
+
+
+ { shortDescription }
+
+
+
+ { !isLoading && (
+ :
+
+ }
+ />
+ ) }
+
+
+ );
+};
+
+export default React.memo(FeaturedAppMobile);
diff --git a/ui/marketplace/Banner/IframeBanner.tsx b/ui/marketplace/Banner/IframeBanner.tsx
new file mode 100644
index 0000000000..d3bb52c33a
--- /dev/null
+++ b/ui/marketplace/Banner/IframeBanner.tsx
@@ -0,0 +1,51 @@
+import { Link, Skeleton, Box } from '@chakra-ui/react';
+import React, { useCallback, useState } from 'react';
+
+import * as mixpanel from 'lib/mixpanel/index';
+
+const IframeBanner = ({ contentUrl, linkUrl }: { contentUrl: string; linkUrl: string }) => {
+ const [ isFrameLoading, setIsFrameLoading ] = useState(true);
+
+ const handleIframeLoad = useCallback(() => {
+ setIsFrameLoading(false);
+ }, []);
+
+ const handleClick = useCallback(() => {
+ mixpanel.logEvent(mixpanel.EventTypes.PROMO_BANNER, { Source: 'Marketplace', Link: linkUrl });
+ }, [ linkUrl ]);
+
+ return (
+
+
+
+
+ );
+};
+
+export default IframeBanner;
diff --git a/ui/marketplace/MarketplaceAppAlert.tsx b/ui/marketplace/MarketplaceAppAlert.tsx
deleted file mode 100644
index 88436a603d..0000000000
--- a/ui/marketplace/MarketplaceAppAlert.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Alert } from '@chakra-ui/react';
-import React from 'react';
-
-import type { IconName } from 'ui/shared/IconSvg';
-import IconSvg from 'ui/shared/IconSvg';
-
-type Props = {
- internalWallet: boolean | undefined;
- isWalletConnected: boolean;
-}
-
-const MarketplaceAppAlert = ({ internalWallet, isWalletConnected }: Props) => {
- const message = React.useMemo(() => {
- let icon: IconName = 'wallet';
- let text = 'Connect your wallet to Blockscout for full-featured access';
- let status: 'warning' | 'success' = 'warning';
-
- if (isWalletConnected && internalWallet) {
- icon = 'integration/full';
- text = 'Your wallet is connected with Blockscout';
- status = 'success';
- } else if (!internalWallet) {
- icon = 'integration/partial';
- text = 'Connect your wallet in the app below';
- }
-
- return { icon, text, status };
- }, [ isWalletConnected, internalWallet ]);
-
- return (
-
-
- { message.text }
-
- );
-};
-
-export default MarketplaceAppAlert;
diff --git a/ui/marketplace/MarketplaceAppCard.tsx b/ui/marketplace/MarketplaceAppCard.tsx
index e387cb0809..be9abd3fe1 100644
--- a/ui/marketplace/MarketplaceAppCard.tsx
+++ b/ui/marketplace/MarketplaceAppCard.tsx
@@ -1,10 +1,9 @@
-import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue } from '@chakra-ui/react';
+import { Box, IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, chakra, Flex } from '@chakra-ui/react';
import type { MouseEvent } from 'react';
import React, { useCallback } from 'react';
import type { MarketplaceAppPreview } from 'types/client/marketplace';
-import * as mixpanel from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg';
import MarketplaceAppCardLink from './MarketplaceAppCardLink';
@@ -13,9 +12,10 @@ import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
interface Props extends MarketplaceAppPreview {
onInfoClick: (id: string) => void;
isFavorite: boolean;
- onFavoriteClick: (id: string, isFavorite: boolean, source: 'Discovery view') => void;
+ onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
onAppClick: (event: MouseEvent, id: string) => void;
+ className?: string;
}
const MarketplaceAppCard = ({
@@ -33,23 +33,24 @@ const MarketplaceAppCard = ({
isLoading,
internalWallet,
onAppClick,
+ className,
}: Props) => {
const categoriesLabel = categories.join(', ');
const handleInfoClick = useCallback((event: MouseEvent) => {
event.preventDefault();
- mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Discovery view' });
onInfoClick(id);
}, [ onInfoClick, id ]);
const handleFavoriteClick = useCallback(() => {
- onFavoriteClick(id, isFavorite, 'Discovery view');
+ onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
return (
-
-
-
-
+
+
+
-
-
-
-
+ { !isLoading && (
+
+
+ More info
+
+
+ ) }
+
-
- { categoriesLabel }
-
+
+
+
+
-
- { shortDescription }
-
+
+ { categoriesLabel }
+
- { !isLoading && (
-
-
- More info
-
-
- ) }
+ { shortDescription }
+
+
{ !isLoading && (
) }
-
+
);
};
-export default React.memo(MarketplaceAppCard);
+export default React.memo(chakra(MarketplaceAppCard));
diff --git a/ui/marketplace/MarketplaceAppIntegrationIcon.tsx b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx
index 8ec443c35d..f884c11ca2 100644
--- a/ui/marketplace/MarketplaceAppIntegrationIcon.tsx
+++ b/ui/marketplace/MarketplaceAppIntegrationIcon.tsx
@@ -42,7 +42,7 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => {
position="relative"
cursor="pointer"
verticalAlign="middle"
- marginBottom={ 1 }
+ mb={ 1 }
/>
);
diff --git a/ui/marketplace/MarketplaceAppModal.tsx b/ui/marketplace/MarketplaceAppModal.tsx
index 1bf7c9f916..f20679e407 100644
--- a/ui/marketplace/MarketplaceAppModal.tsx
+++ b/ui/marketplace/MarketplaceAppModal.tsx
@@ -98,6 +98,12 @@ const MarketplaceAppModal = ({
const isMobile = useIsMobile();
const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
+ function getHostname(url: string | undefined) {
+ try {
+ return new URL(url || '').hostname;
+ } catch (err) {}
+ }
+
return (
@@ -259,7 +266,7 @@ const MarketplaceAppModal = ({
overflow="hidden"
textOverflow="ellipsis"
>
- { site }
+ { getHostname(site) }
) }
diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx
index 0b82faffeb..38654292c1 100644
--- a/ui/marketplace/MarketplaceAppTopBar.tsx
+++ b/ui/marketplace/MarketplaceAppTopBar.tsx
@@ -1,4 +1,4 @@
-import { chakra, Flex, Tooltip, Skeleton, useBoolean, Box } from '@chakra-ui/react';
+import { chakra, Flex, Tooltip, Skeleton, useBoolean } from '@chakra-ui/react';
import React from 'react';
import type { MarketplaceAppOverview, MarketplaceAppSecurityReport } from 'types/client/marketplace';
@@ -6,26 +6,28 @@ import { ContractListTypes } from 'types/client/marketplace';
import { route } from 'nextjs-routes';
+import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useIsMobile from 'lib/hooks/useIsMobile';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
+import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
+import ProfileMenuDesktop from 'ui/snippets/profileMenu/ProfileMenuDesktop';
+import WalletMenuDesktop from 'ui/snippets/walletMenu/WalletMenuDesktop';
import AppSecurityReport from './AppSecurityReport';
import ContractListModal from './ContractListModal';
-import MarketplaceAppAlert from './MarketplaceAppAlert';
import MarketplaceAppInfo from './MarketplaceAppInfo';
type Props = {
data: MarketplaceAppOverview | undefined;
isLoading: boolean;
- isWalletConnected: boolean;
securityReport?: MarketplaceAppSecurityReport;
}
-const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityReport }: Props) => {
+const MarketplaceAppTopBar = ({ data, isLoading, securityReport }: Props) => {
const [ showContractList, setShowContractList ] = useBoolean(false);
const appProps = useAppContext();
const isMobile = useIsMobile();
@@ -46,32 +48,14 @@ const MarketplaceAppTopBar = ({ data, isLoading, isWalletConnected, securityRepo
return (
<>
-
-
-
+
+ { !isMobile && }
+
+
-
-
-
-
-
-
- { (isExperiment && (securityReport || isLoading)) && (
-
-
-
- ) }
+
+
+
+ { (isExperiment && (securityReport || isLoading)) && (
+
+ ) }
+ { !isMobile && (
+
+ { config.features.account.isEnabled && }
+ { config.features.blockchainInteraction.isEnabled && }
+
+ ) }
{ showContractList && (
{
+ const handleInfoClick = useCallback((id: string) => {
+ mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Discovery view' });
+ showAppInfo(id);
+ }, [ showAppInfo ]);
+
+ const handleFavoriteClick = useCallback((id: string, isFavorite: boolean) => {
+ onFavoriteClick(id, isFavorite, 'Discovery view');
+ }, [ onFavoriteClick ]);
+
return apps.length > 0 ? (
(
(null);
const [ hasPreviousStep, setHasPreviousStep ] = React.useState(false);
- const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean, source: 'Discovery view' | 'Security view' | 'App modal') => {
+ const handleFavoriteClick = React.useCallback((id: string, isFavorite: boolean, source: 'Discovery view' | 'Security view' | 'App modal' | 'Banner') => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Favorite app', Info: id, Source: source });
const favoriteApps = getFavoriteApps();
@@ -146,6 +146,7 @@ export default function useMarketplace() {
isError,
error,
categories,
+ apps: data,
displayedApps,
showAppInfo,
selectedAppId,
@@ -167,6 +168,7 @@ export default function useMarketplace() {
categories,
clearSelectedAppId,
selectedAppId,
+ data,
displayedApps,
error,
favoriteApps,
@@ -179,7 +181,6 @@ export default function useMarketplace() {
isAppInfoModalOpen,
isDisclaimerModalOpen,
showDisclaimer,
- data?.length,
isCategoriesPlaceholderData,
showContractList,
contractListModalType,
diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx
index 9f3d0b4da6..11bdd7a4af 100644
--- a/ui/pages/Address.tsx
+++ b/ui/pages/Address.tsx
@@ -39,7 +39,6 @@ import IconSvg from 'ui/shared/IconSvg';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
-import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
const TOKEN_TABS = [ 'tokens_erc20', 'tokens_nfts', 'tokens_nfts_collection', 'tokens_nfts_list' ];
@@ -75,19 +74,22 @@ const AddressPageContent = () => {
const contractTabs = useContractTabs(addressQuery.data);
+ const isLoading = addressQuery.isPlaceholderData || (config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData);
+ const isTabsLoading = isLoading || addressTabsCountersQuery.isPlaceholderData;
+
const tabs: Array = React.useMemo(() => {
return [
{
id: 'txs',
title: 'Transactions',
count: addressTabsCountersQuery.data?.transactions_count,
- component: ,
+ component: ,
},
txInterpretation.isEnabled && txInterpretation.provider === 'noves' ?
{
id: 'account_history',
title: 'Account history',
- component: ,
+ component: ,
} :
undefined,
config.features.userOps.isEnabled && Boolean(userOpsAccountQuery.data?.total_ops) ?
@@ -95,7 +97,7 @@ const AddressPageContent = () => {
id: 'user_ops',
title: 'User operations',
count: userOpsAccountQuery.data?.total_ops,
- component: ,
+ component: ,
} :
undefined,
config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.withdrawals_count ?
@@ -103,39 +105,39 @@ const AddressPageContent = () => {
id: 'withdrawals',
title: 'Withdrawals',
count: addressTabsCountersQuery.data?.withdrawals_count,
- component: ,
+ component: ,
} :
undefined,
{
id: 'token_transfers',
title: 'Token transfers',
count: addressTabsCountersQuery.data?.token_transfers_count,
- component: ,
+ component: ,
},
{
id: 'tokens',
title: 'Tokens',
count: addressTabsCountersQuery.data?.token_balances_count,
- component: ,
+ component: ,
subTabs: TOKEN_TABS,
},
{
id: 'internal_txns',
title: 'Internal txns',
count: addressTabsCountersQuery.data?.internal_txs_count,
- component: ,
+ component: ,
},
{
id: 'coin_balance_history',
title: 'Coin balance history',
- component: ,
+ component: ,
},
config.chain.verificationType === 'validation' && addressTabsCountersQuery.data?.validations_count ?
{
id: 'blocks_validated',
title: 'Blocks validated',
count: addressTabsCountersQuery.data?.validations_count,
- component: ,
+ component: ,
} :
undefined,
addressTabsCountersQuery.data?.logs_count ?
@@ -143,9 +145,10 @@ const AddressPageContent = () => {
id: 'logs',
title: 'Logs',
count: addressTabsCountersQuery.data?.logs_count,
- component: ,
+ component: ,
} :
undefined,
+
addressQuery.data?.is_contract ? {
id: 'contract',
title: () => {
@@ -164,9 +167,7 @@ const AddressPageContent = () => {
subTabs: contractTabs.map(tab => tab.id),
} : undefined,
].filter(Boolean);
- }, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data ]);
-
- const isLoading = addressQuery.isPlaceholderData || (config.features.userOps.isEnabled && userOpsAccountQuery.isPlaceholderData);
+ }, [ addressQuery.data, contractTabs, addressTabsCountersQuery.data, userOpsAccountQuery.data, isTabsLoading ]);
const tags = (
{
/>
);
- const content = (addressQuery.isError || addressQuery.isDegradedData) ? null : ;
+ const content = (addressQuery.isError || addressQuery.isDegradedData) ?
+ null :
+ ;
const backLink = React.useMemo(() => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/accounts');
@@ -250,10 +253,7 @@ const AddressPageContent = () => {
{ /* should stay before tabs to scroll up with pagination */ }
- { (isLoading || addressTabsCountersQuery.isPlaceholderData) ?
- :
- content
- }
+ { content }
>
);
};
diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx
index 0c2478c649..abc6d71b2c 100644
--- a/ui/pages/Block.tsx
+++ b/ui/pages/Block.tsx
@@ -66,7 +66,7 @@ const BlockPageContent = () => {
>
),
},
- blockQuery.data?.blob_tx_count ?
+ config.features.dataAvailability.isEnabled && blockQuery.data?.blob_tx_count ?
{
id: 'blob_txs',
title: 'Blob txns',
diff --git a/ui/pages/Marketplace.pw.tsx b/ui/pages/Marketplace.pw.tsx
index 06a2e2cb85..bbf0605f57 100644
--- a/ui/pages/Marketplace.pw.tsx
+++ b/ui/pages/Marketplace.pw.tsx
@@ -1,86 +1,112 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
import { apps as appsMock } from 'mocks/apps/apps';
import { securityReports as securityReportsMock } from 'mocks/apps/securityReports';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { test, expect, devices } from 'playwright/lib';
import Marketplace from './Marketplace';
-const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
-const MARKETPLACE_SECURITY_REPORTS_URL =
- app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', 'https://marketplace-security-reports.json') || '';
+const MARKETPLACE_CONFIG_URL = 'http://localhost/marketplace-config.json';
-const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+test.beforeEach(async({ mockConfigResponse, mockEnvs, mockAssetResponse }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL, JSON.stringify(appsMock));
+ await Promise.all(appsMock.map(app => mockAssetResponse(app.logo, './playwright/mocks/image_s.jpg')));
});
-test('base view +@mobile +@dark-mode', async({ mount, page }) => {
- await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(appsMock),
- }));
-
- await Promise.all(appsMock.map(app =>
- page.route(app.logo, (route) =>
- route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- }),
- ),
- ));
-
- const component = await mount(
-
-
- ,
- );
+test('base view +@dark-mode', async({ render }) => {
+ const component = await render();
await expect(component).toHaveScreenshot();
});
-const testWithScoreFeature = test.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
- { name: 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', value: MARKETPLACE_SECURITY_REPORTS_URL },
- { name: 'pw_feature:security_score_exp', value: 'true' },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+test('with featured app +@dark-mode', async({ render, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_FEATURED_APP', 'hop-exchange' ],
+ ]);
+ const component = await render();
+
+ await expect(component).toHaveScreenshot();
});
-testWithScoreFeature('with scores +@mobile +@dark-mode', async({ mount, page }) => {
- await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(appsMock),
- }));
-
- await page.route(MARKETPLACE_SECURITY_REPORTS_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(securityReportsMock),
- }));
-
- await Promise.all(appsMock.map(app =>
- page.route(app.logo, (route) =>
- route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- }),
- ),
- ));
-
- const component = await mount(
-
-
- ,
- );
+test('with banner +@dark-mode', async({ render, mockEnvs, mockConfigResponse }) => {
+ const MARKETPLACE_BANNER_CONTENT_URL = 'https://localhost/marketplace-banner.html';
+ const MARKETPLACE_BANNER_LINK_URL = 'https://example.com';
+
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL', MARKETPLACE_BANNER_CONTENT_URL ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL', MARKETPLACE_BANNER_LINK_URL ],
+ ]);
+ await mockConfigResponse('MARKETPLACE_BANNER_CONTENT_URL', MARKETPLACE_BANNER_CONTENT_URL, './playwright/mocks/page.html', true);
+ const component = await render();
+ await expect(component).toHaveScreenshot();
+});
+
+test('with scores +@dark-mode', async({ render, mockConfigResponse, mockEnvs, mockFeatures }) => {
+ const MARKETPLACE_SECURITY_REPORTS_URL = 'https://marketplace-security-reports.json';
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', MARKETPLACE_SECURITY_REPORTS_URL ],
+ ]);
+ await mockFeatures([
+ [ 'security_score_exp', true ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', MARKETPLACE_SECURITY_REPORTS_URL, JSON.stringify(securityReportsMock));
+ const component = await render();
await component.getByText('Apps scores').click();
await expect(component).toHaveScreenshot();
});
+
+// I had a memory error while running tests in GH actions
+// separate run for mobile tests fixes it
+test.describe('mobile', () => {
+ test.use({ viewport: devices['iPhone 13 Pro'].viewport });
+
+ test('base view', async({ render }) => {
+ const component = await render();
+
+ await expect(component).toHaveScreenshot();
+ });
+
+ test('with featured app', async({ render, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_FEATURED_APP', 'hop-exchange' ],
+ ]);
+ const component = await render();
+
+ await expect(component).toHaveScreenshot();
+ });
+
+ test('with banner', async({ render, mockEnvs, mockConfigResponse }) => {
+ const MARKETPLACE_BANNER_CONTENT_URL = 'https://localhost/marketplace-banner.html';
+ const MARKETPLACE_BANNER_LINK_URL = 'https://example.com';
+
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL', MARKETPLACE_BANNER_CONTENT_URL ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL', MARKETPLACE_BANNER_LINK_URL ],
+ ]);
+ await mockConfigResponse('MARKETPLACE_BANNER_CONTENT_URL', MARKETPLACE_BANNER_CONTENT_URL, './playwright/mocks/page.html', true);
+ const component = await render();
+
+ await expect(component).toHaveScreenshot();
+ });
+
+ test('with scores', async({ render, mockConfigResponse, mockEnvs, mockFeatures }) => {
+ const MARKETPLACE_SECURITY_REPORTS_URL = 'https://marketplace-security-reports.json';
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', MARKETPLACE_SECURITY_REPORTS_URL ],
+ ]);
+ await mockFeatures([
+ [ 'security_score_exp', true ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', MARKETPLACE_SECURITY_REPORTS_URL, JSON.stringify(securityReportsMock));
+ const component = await render();
+ await component.getByText('Apps scores').click();
+
+ await expect(component).toHaveScreenshot();
+ });
+});
diff --git a/ui/pages/Marketplace.tsx b/ui/pages/Marketplace.tsx
index 7a57b14c97..b310d89a5a 100644
--- a/ui/pages/Marketplace.tsx
+++ b/ui/pages/Marketplace.tsx
@@ -9,6 +9,7 @@ import config from 'configs/app';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useIsMobile from 'lib/hooks/useIsMobile';
+import Banner from 'ui/marketplace/Banner';
import ContractListModal from 'ui/marketplace/ContractListModal';
import MarketplaceAppModal from 'ui/marketplace/MarketplaceAppModal';
import MarketplaceDisclaimerModal from 'ui/marketplace/MarketplaceDisclaimerModal';
@@ -20,7 +21,6 @@ import type { IconName } from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup';
-import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll';
import useMarketplace from '../marketplace/useMarketplace';
@@ -55,6 +55,7 @@ const Marketplace = () => {
filterQuery,
onSearchInputChange,
showAppInfo,
+ apps,
displayedApps,
selectedAppId,
clearSelectedAppId,
@@ -92,7 +93,7 @@ const Marketplace = () => {
tabs.unshift({
id: MarketplaceCategory.FAVORITES,
- title: () => ,
+ title: () => ,
count: null,
component: null,
});
@@ -167,17 +168,24 @@ const Marketplace = () => {
) }
/>
+
+
+
- { (isCategoriesPlaceholderData) ? (
-
- ) : (
-
- ) }
+
diff --git a/ui/pages/MarketplaceApp.pw.tsx b/ui/pages/MarketplaceApp.pw.tsx
index b6b86599f2..08d1be9b6d 100644
--- a/ui/pages/MarketplaceApp.pw.tsx
+++ b/ui/pages/MarketplaceApp.pw.tsx
@@ -1,17 +1,11 @@
import { Flex } from '@chakra-ui/react';
-import { test as base, expect, devices } from '@playwright/experimental-ct-react';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
import { apps as appsMock } from 'mocks/apps/apps';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { test, expect, devices } from 'playwright/lib';
import MarketplaceApp from './MarketplaceApp';
-const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
-
const hooksConfig = {
router: {
query: { id: appsMock[0].id },
@@ -19,39 +13,26 @@ const hooksConfig = {
},
};
-const testFn: Parameters[1] = async({ mount, page }) => {
- await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(appsMock),
- }));
-
- await page.route(appsMock[0].url, (route) =>
- route.fulfill({
- status: 200,
- path: './mocks/apps/app.html',
- }),
- );
+const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
+
+const testFn: Parameters[1] = async({ render, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL, JSON.stringify(appsMock));
+ await mockAssetResponse(appsMock[0].url, './mocks/apps/app.html');
- const component = await mount(
-
- { /* added Flex as a Layout because the iframe has negative margins */ }
-
-
-
- ,
+ const component = await render(
+
+
+ ,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
};
-const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
-});
-
test('base view +@dark-mode', testFn);
test.describe('mobile', () => {
diff --git a/ui/pages/MarketplaceApp.tsx b/ui/pages/MarketplaceApp.tsx
index c04ba4fecf..44f98d9de2 100644
--- a/ui/pages/MarketplaceApp.tsx
+++ b/ui/pages/MarketplaceApp.tsx
@@ -1,4 +1,4 @@
-import { Box, Center, useColorMode } from '@chakra-ui/react';
+import { Box, Center, useColorMode, Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { DappscoutIframeProvider, useDappscoutIframe } from 'dappscout-iframe';
import { useRouter } from 'next/router';
@@ -11,6 +11,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
+import { useMarketplaceContext } from 'lib/contexts/marketplace';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import useFetch from 'lib/hooks/useFetch';
import * as metadata from 'lib/metadata';
@@ -71,7 +72,7 @@ const MarketplaceAppContent = ({ address, data, isPending }: Props) => {
return (
{ (isFrameLoading) && (
@@ -129,6 +130,7 @@ const MarketplaceApp = () => {
enabled: feature.isEnabled,
});
const { data, isPending } = query;
+ const { setIsAutoConnectDisabled } = useMarketplaceContext();
useEffect(() => {
if (data) {
@@ -136,17 +138,17 @@ const MarketplaceApp = () => {
{ pathname: '/apps/[id]', query: { id: data.id } },
{ app_name: data.title },
);
+ setIsAutoConnectDisabled(!data.internalWallet);
}
- }, [ data ]);
+ }, [ data, setIsAutoConnectDisabled ]);
throwOnResourceLoadError(query);
return (
- <>
+
{
>
- >
+
);
};
diff --git a/ui/pages/SearchResults.pw.tsx b/ui/pages/SearchResults.pw.tsx
index f773fd56bb..3a492ad3ce 100644
--- a/ui/pages/SearchResults.pw.tsx
+++ b/ui/pages/SearchResults.pw.tsx
@@ -1,268 +1,184 @@
-import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
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 * as app from 'playwright/utils/app';
-import buildApiUrl from 'playwright/utils/buildApiUrl';
-import * as configs from 'playwright/utils/configs';
+import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
+import { test, expect } from 'playwright/lib';
import SearchResults from './SearchResults';
test.describe('search by name ', () => {
- const extendedTest = test.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: '' },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
-
- extendedTest('+@mobile +@dark-mode', async({ mount, page }) => {
+ test('+@mobile +@dark-mode', async({ render, mockApiResponse, mockAssetResponse, mockEnvs }) => {
const hooksConfig = {
router: {
query: { q: 'o' },
},
};
- await page.route(buildApiUrl('search') + '?q=o', (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- searchMock.address2,
- searchMock.label1,
- ],
- }),
- }));
- await page.route(searchMock.token1.icon_url as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ searchMock.address2,
+ searchMock.label1,
+ ],
+ next_page_params: null,
+ };
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'false' ],
+ ]);
+ await mockApiResponse('search', data, { queryParams: { q: 'o' } });
+ await mockAssetResponse(searchMock.token1.icon_url as string, './playwright/mocks/image_s.jpg');
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
});
-test('search by address hash +@mobile', async({ mount, page }) => {
+test('search by address hash +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: searchMock.address1.address },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.address1.address }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.address1,
- ],
- }),
- }));
+ const data = {
+ items: [ searchMock.address1 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.address1.address } });
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
-test('search by block number +@mobile', async({ mount, page }) => {
+test('search by block number +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: String(searchMock.block1.block_number) },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.block1.block_number }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.block1,
- searchMock.block2,
- searchMock.block3,
- ],
- }),
- }));
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.block1, searchMock.block2, searchMock.block3 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.block1.block_number } });
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
-test('search by block hash +@mobile', async({ mount, page }) => {
+test('search by block hash +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: searchMock.block1.block_hash },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.block1.block_hash }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.block1,
- ],
- }),
- }));
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.block1 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.block1.block_hash } });
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
-test('search by tx hash +@mobile', async({ mount, page }) => {
+test('search by tx hash +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: searchMock.tx1.tx_hash },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.tx1.tx_hash }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.tx1,
- ],
- }),
- }));
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.tx1 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.tx1.tx_hash } });
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
-test('search by blob hash +@mobile', async({ mount, page }) => {
+test('search by blob hash +@mobile', async({ render, mockApiResponse }) => {
const hooksConfig = {
router: {
query: { q: searchMock.blob1.blob_hash },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.blob1.blob_hash }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.blob1,
- ],
- }),
- }));
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.blob1 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.blob1.blob_hash } });
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
-const testWithUserOps = test.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.userOps) as any,
+test('search by domain name +@mobile', async({ render, mockApiResponse }) => {
+ const hooksConfig = {
+ router: {
+ query: { q: searchMock.domain1.ens_info.name },
+ },
+ };
+ const data = {
+ items: [ searchMock.domain1 ],
+ next_page_params: null,
+ };
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.domain1.ens_info.name } });
+ const component = await render(, { hooksConfig });
+ await expect(component.locator('main')).toHaveScreenshot();
});
-testWithUserOps('search by user op hash +@mobile', async({ mount, page }) => {
+test('search by user op hash +@mobile', async({ render, mockApiResponse, mockEnvs }) => {
const hooksConfig = {
router: {
query: { q: searchMock.userOp1.user_operation_hash },
},
};
- await page.route(buildApiUrl('search') + `?q=${ searchMock.userOp1.user_operation_hash }`, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.userOp1,
- ],
- }),
- }));
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.userOp1 ],
+ next_page_params: null,
+ };
+ await mockEnvs(ENVS_MAP.userOps);
+ await mockApiResponse('search', data, { queryParams: { q: searchMock.userOp1.user_operation_hash } });
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
test.describe('with apps', () => {
- const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
- 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,
- });
-
- extendedTest('default view +@mobile', async({ mount, page }) => {
+ test('default view +@mobile', async({ render, mockApiResponse, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
+ const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
const hooksConfig = {
router: {
query: { q: 'o' },
},
};
- const API_URL = buildApiUrl('search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- searchMock.token1,
- ],
- next_page_params: { foo: 'bar' },
- }),
- }));
-
- await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(appsMock),
- }));
-
- await page.route(appsMock[0].logo, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
- await page.route(appsMock[1].logo as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const data = {
+ items: [ searchMock.token1 ],
+ next_page_params: {
+ address_hash: null,
+ block_hash: null,
+ holder_count: null,
+ inserted_at: null,
+ item_type: 'token' as const,
+ items_count: 1,
+ name: 'foo',
+ q: 'o',
+ tx_hash: null,
+ },
+ };
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL ],
+ ]);
+ await mockApiResponse('search', data, { queryParams: { q: 'o' } });
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL, JSON.stringify(appsMock));
+ await mockAssetResponse(appsMock[0].logo, './playwright/mocks/image_s.jpg');
+ await mockAssetResponse(appsMock[1].logo, './playwright/mocks/image_s.jpg');
+ const component = await render(, { hooksConfig });
await expect(component.locator('main')).toHaveScreenshot();
});
diff --git a/ui/pages/ShibariumDeposits.pw.tsx b/ui/pages/ShibariumDeposits.pw.tsx
index 75fa82ba5e..d4b532aac4 100644
--- a/ui/pages/ShibariumDeposits.pw.tsx
+++ b/ui/pages/ShibariumDeposits.pw.tsx
@@ -1,43 +1,17 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as depositsData } from 'mocks/shibarium/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 { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
+import { test, expect } from 'playwright/lib';
import ShibariumDeposits from './ShibariumDeposits';
-const DEPOSITS_API_URL = buildApiUrl('shibarium_deposits');
-const DEPOSITS_COUNT_API_URL = buildApiUrl('shibarium_deposits_count');
+test('base view +@mobile', async({ render, mockApiResponse, mockEnvs }) => {
+ await mockEnvs(ENVS_MAP.shibariumRollup);
+ await mockApiResponse('shibarium_deposits', depositsData);
+ await mockApiResponse('shibarium_deposits_count', 3971111);
-const test = base.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.shibariumRollup) 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,
- body: '',
- }));
-
- await page.route(DEPOSITS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(depositsData),
- }));
-
- await page.route(DEPOSITS_COUNT_API_URL, (route) => route.fulfill({
- status: 200,
- body: '3971111',
- }));
-
- const component = await mount(
-
-
- ,
- );
+ const component = await render();
await expect(component).toHaveScreenshot();
});
diff --git a/ui/pages/ShibariumWithdrawals.pw.tsx b/ui/pages/ShibariumWithdrawals.pw.tsx
index 98d3ff3836..ff7ecb24d1 100644
--- a/ui/pages/ShibariumWithdrawals.pw.tsx
+++ b/ui/pages/ShibariumWithdrawals.pw.tsx
@@ -1,47 +1,22 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as withdrawalsData } from 'mocks/shibarium/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 { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
+import { test, expect } from 'playwright/lib';
-import ShibariuWithdrawals from './ShibariumWithdrawals';
+import ShibariumWithdrawals from './ShibariumWithdrawals';
-const test = base.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.shibariumRollup) as any,
-});
-
-const WITHDRAWALS_API_URL = buildApiUrl('shibarium_withdrawals');
-const WITHDRAWALS_COUNT_API_URL = buildApiUrl('shibarium_withdrawals_count');
-
-test('base view +@mobile', async({ mount, page }) => {
+test('base view +@mobile', async({ render, mockApiResponse, mockEnvs }) => {
// test on mobile is flaky
// my assumption is there is not enough time to calculate hashes truncation so component is unstable
// so I raised the test timeout to check if it helps
test.slow();
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: '',
- }));
-
- await page.route(WITHDRAWALS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(withdrawalsData),
- }));
- await page.route(WITHDRAWALS_COUNT_API_URL, (route) => route.fulfill({
- status: 200,
- body: '397',
- }));
+ await mockEnvs(ENVS_MAP.shibariumRollup);
+ await mockApiResponse('shibarium_withdrawals', withdrawalsData);
+ await mockApiResponse('shibarium_withdrawals_count', 397);
- const component = await mount(
-
-
- ,
- );
+ const component = await render();
await expect(component).toHaveScreenshot({ timeout: 10_000 });
});
diff --git a/ui/pages/Stats.tsx b/ui/pages/Stats.tsx
index 228226df27..d419ee3c52 100644
--- a/ui/pages/Stats.tsx
+++ b/ui/pages/Stats.tsx
@@ -21,6 +21,7 @@ const Stats = () => {
handleFilterChange,
displayedCharts,
filterQuery,
+ initialFilterQuery,
} = useStats();
return (
@@ -33,6 +34,8 @@ const Stats = () => {
{
({
- createSocket: socketServer.createSocket,
-});
-
// FIXME
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
-test.beforeEach(async({ page }) => {
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: '',
- }));
-
- await page.route(TOKEN_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(tokenInfo),
- }));
- await page.route(ADDRESS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(contract),
- }));
- await page.route(TOKEN_COUNTERS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(tokenCounters),
- }));
- await page.route(TOKEN_TRANSFERS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({}),
- }));
+test.beforeEach(async({ mockApiResponse }) => {
+ await mockApiResponse('token', tokenInfo, { pathParams: { hash: '1' } });
+ await mockApiResponse('address', contract, { pathParams: { hash: '1' } });
+ await mockApiResponse('token_counters', tokenCounters, { pathParams: { hash: '1' } });
+ await mockApiResponse('token_transfers', { items: [], next_page_params: null }, { pathParams: { hash: '1' } });
});
-test('base view', async({ mount, page, createSocket }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+test('base view', async({ render, page, createSocket }) => {
+ const component = await render(, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
@@ -73,24 +41,11 @@ test('base view', async({ mount, page, createSocket }) => {
});
});
-test('with verified info', async({ mount, page, createSocket }) => {
- const VERIFIED_INFO_URL = buildApiUrl('token_verified_info', { chainId: '1', hash: '1' });
- await page.route(VERIFIED_INFO_URL, (route) => route.fulfill({
- body: JSON.stringify(verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED),
- }));
- await page.route(tokenInfo.icon_url as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
+test('with verified info', async({ render, page, createSocket, mockApiResponse, mockAssetResponse }) => {
+ await mockApiResponse('token_verified_info', verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED, { pathParams: { chainId: '1', hash: '1' } });
+ await mockAssetResponse(tokenInfo.icon_url as string, './playwright/mocks/image_s.jpg');
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ const component = await render(, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
@@ -104,55 +59,16 @@ test('with verified info', async({ mount, page, createSocket }) => {
});
});
-const bridgedTokenTest = base.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.bridgedTokens) as any,
- createSocket: socketServer.createSocket,
-});
-
-bridgedTokenTest('bridged token', async({ mount, page, createSocket }) => {
-
- const VERIFIED_INFO_URL = buildApiUrl('token_verified_info', { chainId: '1', hash: '1' });
-
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: '',
- }));
-
- await page.route(TOKEN_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(bridgedTokenA),
- }));
- await page.route(ADDRESS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(contract),
- }));
- await page.route(TOKEN_COUNTERS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(tokenCounters),
- }));
- await page.route(TOKEN_TRANSFERS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({}),
- }));
- await page.route(VERIFIED_INFO_URL, (route) => route.fulfill({
- body: JSON.stringify(verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED),
- }));
-
- await page.route(tokenInfo.icon_url as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+test('bridged token', async({ render, page, createSocket, mockApiResponse, mockAssetResponse, mockEnvs }) => {
+ await mockEnvs(ENVS_MAP.bridgedTokens);
+ await mockApiResponse('token', bridgedTokenA, { pathParams: { hash: '1' } });
+ await mockApiResponse('address', contract, { pathParams: { hash: '1' } });
+ await mockApiResponse('token_counters', tokenCounters, { pathParams: { hash: '1' } });
+ await mockApiResponse('token_transfers', { items: [], next_page_params: null }, { pathParams: { hash: '1' } });
+ await mockApiResponse('token_verified_info', verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED, { pathParams: { chainId: '1', hash: '1' } });
+ await mockAssetResponse(tokenInfo.icon_url as string, './playwright/mocks/image_s.jpg');
+ const component = await render(, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
@@ -165,14 +81,9 @@ bridgedTokenTest('bridged token', async({ mount, page, createSocket }) => {
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
- test('base view', async({ mount, page, createSocket }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('base view', async({ render, page, createSocket }) => {
+ const component = await render(, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
@@ -183,25 +94,11 @@ test.describe('mobile', () => {
});
});
- test('with verified info', async({ mount, page, createSocket }) => {
- const VERIFIED_INFO_URL = buildApiUrl('token_verified_info', { chainId: '1', hash: '1' });
- await page.route(VERIFIED_INFO_URL, (route) => route.fulfill({
- body: JSON.stringify(verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED),
- }));
- await page.route(tokenInfo.icon_url as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('with verified info', async({ render, page, createSocket, mockApiResponse, mockAssetResponse }) => {
+ await mockApiResponse('token_verified_info', verifiedAddressesMocks.TOKEN_INFO_APPLICATION.APPROVED, { pathParams: { chainId: '1', hash: '1' } });
+ await mockAssetResponse(tokenInfo.icon_url as string, './playwright/mocks/image_s.jpg');
+ const component = await render(, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'tokens:1');
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
diff --git a/ui/pages/TokenInstance.tsx b/ui/pages/TokenInstance.tsx
index 6471e822b9..174ba5afc6 100644
--- a/ui/pages/TokenInstance.tsx
+++ b/ui/pages/TokenInstance.tsx
@@ -174,20 +174,34 @@ const TokenInstanceContent = () => {
pagination = holdersQuery.pagination;
}
+ const title = (() => {
+ if (typeof tokenInstanceQuery.data?.metadata?.name === 'string') {
+ return tokenInstanceQuery.data.metadata.name;
+ }
+
+ if (tokenQuery.data?.symbol) {
+ return (tokenQuery.data.name || tokenQuery.data.symbol) + ' #' + tokenInstanceQuery.data?.id;
+ }
+
+ return `ID ${ tokenInstanceQuery.data?.id }`;
+ })();
+
const titleSecondRow = (
-
+ { tokenQuery.data && (
+
+ ) }
{ !isLoading && tokenInstanceQuery.data && }
@@ -199,7 +213,7 @@ const TokenInstanceContent = () => {
<>
{
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(textAdMock.duck),
- }));
- await page.route(textAdMock.duck.ad.thumbnail, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-});
-
-base('base view +@mobile +@dark-mode', async({ mount, page }) => {
+test('base view +@mobile +@dark-mode', async({ render, mockApiResponse }) => {
const allTokens = {
items: [
tokens.tokenInfoERC20a, tokens.tokenInfoERC20b, tokens.tokenInfoERC20c, tokens.tokenInfoERC20d,
@@ -35,6 +18,7 @@ base('base view +@mobile +@dark-mode', async({ mount, page }) => {
holder_count: 1,
items_count: 1,
name: 'a',
+ market_cap: '0',
},
};
const filteredTokens = {
@@ -44,40 +28,25 @@ base('base view +@mobile +@dark-mode', async({ mount, page }) => {
next_page_params: null,
};
- const ALL_TOKENS_API_URL = buildApiUrl('tokens');
- const FILTERED_TOKENS_API_URL = buildApiUrl('tokens') + '?q=foo';
-
- await page.route(ALL_TOKENS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(allTokens),
- }));
-
- await page.route(FILTERED_TOKENS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(filteredTokens),
- }));
+ await mockApiResponse('tokens', allTokens);
+ await mockApiResponse('tokens', filteredTokens, { queryParams: { q: 'foo' } });
- const component = await mount(
-
+ const component = await render(
+
- ,
+
,
);
await expect(component).toHaveScreenshot();
await component.getByRole('textbox', { name: 'Token name or symbol' }).focus();
- await component.getByRole('textbox', { name: 'Token name or symbol' }).type('foo');
+ await component.getByRole('textbox', { name: 'Token name or symbol' }).fill('foo');
await expect(component).toHaveScreenshot();
});
-base.describe('bridged tokens', async() => {
- const test = base.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.bridgedTokens) as any,
- });
-
+test.describe('bridged tokens', async() => {
const bridgedTokens = {
items: [
tokens.bridgedTokenA,
@@ -88,6 +57,7 @@ base.describe('bridged tokens', async() => {
holder_count: 1,
items_count: 1,
name: 'a',
+ market_cap: null,
},
};
const bridgedFilteredTokens = {
@@ -101,26 +71,17 @@ base.describe('bridged tokens', async() => {
query: { tab: 'bridged' },
},
};
- const BRIDGED_TOKENS_API_URL = buildApiUrl('tokens_bridged');
-
- test.beforeEach(async({ page }) => {
- await page.route(BRIDGED_TOKENS_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(bridgedTokens),
- }));
- });
- test('base view', async({ mount, page }) => {
- await page.route(BRIDGED_TOKENS_API_URL + '?chain_ids=99', (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(bridgedFilteredTokens),
- }));
+ test('base view', async({ render, page, mockApiResponse, mockEnvs }) => {
+ await mockEnvs(ENVS_MAP.bridgedTokens);
+ await mockApiResponse('tokens_bridged', bridgedTokens);
+ await mockApiResponse('tokens_bridged', bridgedFilteredTokens, { queryParams: { chain_ids: '99' } });
- const component = await mount(
-
+ const component = await render(
+
- ,
+
,
{ hooksConfig },
);
diff --git a/ui/pages/Transaction.tsx b/ui/pages/Transaction.tsx
index 8e14c1d6cf..e93fafb237 100644
--- a/ui/pages/Transaction.tsx
+++ b/ui/pages/Transaction.tsx
@@ -63,7 +63,7 @@ const TransactionPageContent = () => {
{ id: 'user_ops', title: 'User operations', component: } :
undefined,
{ id: 'internal', title: 'Internal txns', component: },
- txQuery.data?.blob_versioned_hashes?.length ?
+ config.features.dataAvailability.isEnabled && txQuery.data?.blob_versioned_hashes?.length ?
{ id: 'blobs', title: 'Blobs', component: } :
undefined,
{ id: 'logs', title: 'Logs', component: },
diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx
index 4517b4d985..23a3566c5d 100644
--- a/ui/pages/Transactions.tsx
+++ b/ui/pages/Transactions.tsx
@@ -14,6 +14,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
+import TxsStats from 'ui/txs/TxsStats';
import TxsWatchlist from 'ui/txs/TxsWatchlist';
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
@@ -29,12 +30,6 @@ const Transactions = () => {
const isMobile = useIsMobile();
const tab = getQueryParamString(router.query.tab);
- React.useEffect(() => {
- if (tab === 'blob_txs' && !config.features.dataAvailability.isEnabled) {
- router.replace({ pathname: '/txs' }, undefined, { shallow: true });
- }
- }, [ router, tab ]);
-
const txsValidatedQuery = useQueryWithPages({
resourceName: 'txs_validated',
filters: { filter: 'validated' },
@@ -146,6 +141,7 @@ const Transactions = () => {
return (
<>
+
({
- context: contextWithEnvs(configs.featureEnvs.validators),
-});
+const chainType = 'stability';
-test('base view +@mobile', async({ render, mockApiResponse }) => {
- await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType: 'stability' } });
- await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType: 'stability' } });
+test('base view +@mobile', async({ render, mockApiResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
+ ]);
+ await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType } });
+ await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType } });
const component = await render();
diff --git a/ui/pages/VerifiedAddresses.pw.tsx b/ui/pages/VerifiedAddresses.pw.tsx
index 4b64d5e23b..6a147c6cc0 100644
--- a/ui/pages/VerifiedAddresses.pw.tsx
+++ b/ui/pages/VerifiedAddresses.pw.tsx
@@ -1,17 +1,15 @@
+import type { BrowserContext } from '@playwright/test';
import React from 'react';
import * as mocks from 'mocks/account/verifiedAddresses';
import * as profileMock from 'mocks/user/profile';
-import authFixture from 'playwright/fixtures/auth';
+import { contextWithAuth } from 'playwright/fixtures/auth';
import { test as base, expect } from 'playwright/lib';
import VerifiedAddresses from './VerifiedAddresses';
-const test = base.extend({
- context: ({ context }, use) => {
- authFixture(context);
- use(context);
- },
+const test = base.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
});
test.beforeEach(async({ mockAssetResponse }) => {
diff --git a/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png b/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png
index e2ed79ae58..894038a81c 100644
Binary files a/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_dark-color-mode_default-view---default-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png
index 7fb3bdf096..8356835e2e 100644
Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-1.png differ
diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png
index d5c4e1f321..655a4ad4a5 100644
Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-dark-mode-1.png
index 442d62ab16..e25ba73da9 100644
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png
deleted file mode 100644
index 124c2b86d6..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-banner-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-banner-dark-mode-1.png
new file mode 100644
index 0000000000..fceedc3185
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-banner-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-featured-app-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-featured-app-dark-mode-1.png
new file mode 100644
index 0000000000..5ae62491ab
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-featured-app-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-dark-mode-1.png
new file mode 100644
index 0000000000..cda2818ef2
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png
deleted file mode 100644
index 21e51e79fd..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_dark-color-mode_with-scores-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-dark-mode-1.png
index 575ce38e53..17c54d8959 100644
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-dark-mode-1.png and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png
deleted file mode 100644
index ab79db5a10..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_base-view-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-base-view-1.png
new file mode 100644
index 0000000000..fd0ab1cb5f
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-banner-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-banner-1.png
new file mode 100644
index 0000000000..fbb38d03af
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-banner-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-featured-app-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-featured-app-1.png
new file mode 100644
index 0000000000..35245e7d9f
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-featured-app-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-scores-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-scores-1.png
new file mode 100644
index 0000000000..6aa6e1999e
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_mobile-with-scores-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-banner-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-banner-dark-mode-1.png
new file mode 100644
index 0000000000..d1b2888b75
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-banner-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-featured-app-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-featured-app-dark-mode-1.png
new file mode 100644
index 0000000000..c6f93f138e
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-featured-app-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-dark-mode-1.png
new file mode 100644
index 0000000000..aedf4aa291
Binary files /dev/null and b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png
deleted file mode 100644
index 85c7dea12e..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_default_with-scores-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png
deleted file mode 100644
index 82585aee4f..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png
deleted file mode 100644
index 5d8c323fe8..0000000000
Binary files a/ui/pages/__screenshots__/Marketplace.pw.tsx_mobile_with-scores-mobile-dark-mode-1.png and /dev/null differ
diff --git a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_dark-color-mode_base-view-dark-mode-1.png
index 8f62ca6d83..3ebdbba8ef 100644
Binary files a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_base-view-dark-mode-1.png b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_base-view-dark-mode-1.png
index ce2c911b14..4bb6f375b8 100644
Binary files a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_base-view-dark-mode-1.png and b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_base-view-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png
index f03dd57c1e..de5e5f6786 100644
Binary files a/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/MarketplaceApp.pw.tsx_default_mobile-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/NameDomain.pw.tsx_default_history-tab-mobile-1.png b/ui/pages/__screenshots__/NameDomain.pw.tsx_default_history-tab-mobile-1.png
index 216d8daf6b..d135f542ad 100644
Binary files a/ui/pages/__screenshots__/NameDomain.pw.tsx_default_history-tab-mobile-1.png and b/ui/pages/__screenshots__/NameDomain.pw.tsx_default_history-tab-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/NameDomain.pw.tsx_mobile_history-tab-mobile-1.png b/ui/pages/__screenshots__/NameDomain.pw.tsx_mobile_history-tab-mobile-1.png
index bfeb78ed07..d37225d13f 100644
Binary files a/ui/pages/__screenshots__/NameDomain.pw.tsx_mobile_history-tab-mobile-1.png and b/ui/pages/__screenshots__/NameDomain.pw.tsx_mobile_history-tab-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png
index 17cc006cc0..2c4926aec8 100644
Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-block-number-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-domain-name-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-domain-name-mobile-1.png
new file mode 100644
index 0000000000..4a83be499a
Binary files /dev/null and b/ui/pages/__screenshots__/SearchResults.pw.tsx_default_search-by-domain-name-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png
index 95c8ef3f0a..ab3db505a4 100644
Binary files a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-block-number-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-domain-name-mobile-1.png b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-domain-name-mobile-1.png
new file mode 100644
index 0000000000..044408cac3
Binary files /dev/null and b/ui/pages/__screenshots__/SearchResults.pw.tsx_mobile_search-by-domain-name-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png
index 7cab3d682e..a7b79f5741 100644
Binary files a/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png
index 0335fb3110..7ac99bfd32 100644
Binary files a/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png
index 47e093f593..457bdbb859 100644
Binary files a/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png
index 9f9063b226..a56071f28e 100644
Binary files a/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png
index e8e194d881..3275b0e094 100644
Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_base-view-1.png differ
diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png
index 5873ca0484..f04e9f2426 100644
Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_bridged-token-1.png differ
diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png
index bf6ba7e403..4c799af178 100644
Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png
index d27cd36fc0..0ca82751f1 100644
Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_mobile-with-verified-info-1.png differ
diff --git a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png
index 94ca737987..83e9b9e25c 100644
Binary files a/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png and b/ui/pages/__screenshots__/Token.pw.tsx_default_with-verified-info-1.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png
index 9ca4d23064..1e3f0939a6 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png b/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png
index 06a1553f0d..d2915eb093 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-1.png
index c7e5cb7e25..3e5ce0641c 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-2.png b/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-2.png
index 2624b74d24..b7a34b56f7 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-2.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_default_base-view-mobile-dark-mode-2.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png
index 0dad636021..4109b922c1 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-2.png b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-2.png
index f2f79d6bcb..96e6b133eb 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-2.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_default_bridged-tokens-base-view-2.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-1.png
index a5d0f10c5b..8d91310520 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ
diff --git a/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-2.png b/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-2.png
index 5dee58eb04..9c2ea5a88a 100644
Binary files a/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-2.png and b/ui/pages/__screenshots__/Tokens.pw.tsx_mobile_base-view-mobile-dark-mode-2.png differ
diff --git a/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png b/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png
index eb4f533595..876c1c9b52 100644
Binary files a/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png and b/ui/pages/__screenshots__/UserOp.pw.tsx_default_base-view-1.png differ
diff --git a/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png
index fbe5973dc2..172f7d7acc 100644
Binary files a/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/UserOp.pw.tsx_default_mobile-base-view-1.png differ
diff --git a/ui/pages/__screenshots__/UserOps.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/UserOps.pw.tsx_default_base-view-mobile-1.png
index b17c113b1b..50ac818186 100644
Binary files a/ui/pages/__screenshots__/UserOps.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/UserOps.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/UserOps.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/UserOps.pw.tsx_mobile_base-view-mobile-1.png
index 3f7ecc7900..1c58075243 100644
Binary files a/ui/pages/__screenshots__/UserOps.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/UserOps.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/Validators.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/Validators.pw.tsx_default_base-view-mobile-1.png
index 23bfad3f62..4ac12cde51 100644
Binary files a/ui/pages/__screenshots__/Validators.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/Validators.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/Validators.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/Validators.pw.tsx_mobile_base-view-mobile-1.png
index 7b0bf0c8b9..554bdc3c23 100644
Binary files a/ui/pages/__screenshots__/Validators.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/Validators.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_default_base-view-mobile-1.png
index 6d51e2adf0..3766679027 100644
Binary files a/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png
index 1d1ba2a370..0e038dec5d 100644
Binary files a/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/ZkEvmL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png
index c61872abb9..6d14c9d8d0 100644
Binary files a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png and b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png
index a43ee77aaf..a12fa458f1 100644
Binary files a/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png and b/ui/pages/__screenshots__/ZkSyncL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/searchResults/SearchResultListItem.tsx b/ui/searchResults/SearchResultListItem.tsx
index 28b1dc0eac..d09cb77983 100644
--- a/ui/searchResults/SearchResultListItem.tsx
+++ b/ui/searchResults/SearchResultListItem.tsx
@@ -14,6 +14,7 @@ import { ADDRESS_REGEXP } from 'lib/validations/address';
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as BlobEntity from 'ui/shared/entities/blob/BlobEntity';
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
+import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
@@ -243,6 +244,30 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
);
}
+
+ case 'ens_domain': {
+ return (
+
+
+
+
+
+
+ );
+ }
}
})();
@@ -334,6 +359,21 @@ const SearchResultListItem = ({ data, searchTerm, isLoading }: Props) => {
) :
null;
}
+ case 'ens_domain': {
+ const expiresText = data.ens_info?.expiry_date ? ` expires ${ dayjs(data.ens_info.expiry_date).fromNow() }` : '';
+ return (
+
+
+
+
+ {
+ data.ens_info.names_count > 1 ?
+ ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` }) :
+ { expiresText }
+ }
+
+ );
+ }
default:
return null;
diff --git a/ui/searchResults/SearchResultTableItem.tsx b/ui/searchResults/SearchResultTableItem.tsx
index fde08af038..d9a1cf6d74 100644
--- a/ui/searchResults/SearchResultTableItem.tsx
+++ b/ui/searchResults/SearchResultTableItem.tsx
@@ -14,6 +14,7 @@ import { ADDRESS_REGEXP } from 'lib/validations/address';
import * as AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as BlobEntity from 'ui/shared/entities/blob/BlobEntity';
import * as BlockEntity from 'ui/shared/entities/block/BlockEntity';
+import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import * as TxEntity from 'ui/shared/entities/tx/TxEntity';
import * as UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
@@ -337,6 +338,48 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading }: Props) => {
>
);
}
+
+ case 'ens_domain': {
+ const expiresText = data.ens_info?.expiry_date ? ` expires ${ dayjs(data.ens_info.expiry_date).fromNow() }` : '';
+ return (
+ <>
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ { data.is_smart_contract_verified && }
+
+ |
+
+ { data.ens_info.names_count > 1 ?
+ ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` }) :
+ { expiresText } }
+ |
+ >
+ );
+ }
}
})();
diff --git a/ui/shared/AccountActionsMenu/AccountActionsMenu.pw.tsx b/ui/shared/AccountActionsMenu/AccountActionsMenu.pw.tsx
index 614764950c..814b380a81 100644
--- a/ui/shared/AccountActionsMenu/AccountActionsMenu.pw.tsx
+++ b/ui/shared/AccountActionsMenu/AccountActionsMenu.pw.tsx
@@ -1,20 +1,14 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
+import type { BrowserContext } from '@playwright/test';
import React from 'react';
import * as profileMock from 'mocks/user/profile';
-import authFixture from 'playwright/fixtures/auth';
-import TestApp from 'playwright/TestApp';
-import buildApiUrl from 'playwright/utils/buildApiUrl';
+import { contextWithAuth } from 'playwright/fixtures/auth';
+import { test as base, expect } from 'playwright/lib';
import AccountActionsMenu from './AccountActionsMenu';
-const USER_INFO_URL = buildApiUrl('user_info');
-
-const test = base.extend({
- context: ({ context }, use) => {
- authFixture(context);
- use(context);
- },
+const test = base.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
});
test.use({ viewport: { width: 200, height: 200 } });
@@ -28,56 +22,32 @@ test.describe('with multiple items', async() => {
},
};
- test.beforeEach(async({ page }) => {
- await page.route(USER_INFO_URL, (route) => route.fulfill({
- body: JSON.stringify(profileMock.base),
- }));
+ test.beforeEach(async({ mockApiResponse }) => {
+ mockApiResponse('user_info', profileMock.base);
});
- test('base view', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
-
+ test('base view', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.getByRole('button').click();
await expect(page).toHaveScreenshot();
});
- test('base view with styles', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
-
+ test('base view with styles', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.getByRole('button').click();
await expect(page).toHaveScreenshot();
});
- test('loading', async({ mount }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('loading', async({ render }) => {
+ const component = await render(, { hooksConfig });
await expect(component).toHaveScreenshot();
});
- test('loading with styles', async({ mount }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('loading with styles', async({ render }) => {
+ const component = await render(, { hooksConfig });
await expect(component).toHaveScreenshot();
});
@@ -92,39 +62,22 @@ test.describe('with one item', async() => {
},
};
- test('base view', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
-
+ test('base view', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.getByRole('button').hover();
await expect(page).toHaveScreenshot();
});
- test('base view with styles', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
-
+ test('base view with styles', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.getByRole('button').hover();
await expect(page).toHaveScreenshot();
});
- test('loading', async({ mount }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('loading', async({ render }) => {
+ const component = await render(, { hooksConfig });
await expect(component).toHaveScreenshot();
});
diff --git a/ui/shared/NetworkExplorers.tsx b/ui/shared/NetworkExplorers.tsx
index 6671a07274..200341f87e 100644
--- a/ui/shared/NetworkExplorers.tsx
+++ b/ui/shared/NetworkExplorers.tsx
@@ -20,6 +20,7 @@ import config from 'configs/app';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
+import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
interface Props {
className?: string;
@@ -55,26 +56,27 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => {
return (
-
+
+
+
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png
index d633cbb80a..479736d81a 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_default-view-mobile-1.png differ
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png
index 8d02d7da7c..1284ee5f7b 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-long-name-and-many-tags-mobile-1.png differ
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png
index 9a03708d13..d55d170ab8 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_default_with-text-ad-mobile-1.png differ
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png
index efabd99eed..39ea00f5a9 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_default-view-mobile-1.png differ
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png
index f2b21cc103..d7fd28ebf2 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-long-name-and-many-tags-mobile-1.png differ
diff --git a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png
index 01d7d12f52..389d966a56 100644
Binary files a/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png and b/ui/shared/Page/__screenshots__/PageTitle.pw.tsx_mobile_with-text-ad-mobile-1.png differ
diff --git a/ui/shared/PopoverTriggerTooltip.tsx b/ui/shared/PopoverTriggerTooltip.tsx
new file mode 100644
index 0000000000..b0ed7ab91e
--- /dev/null
+++ b/ui/shared/PopoverTriggerTooltip.tsx
@@ -0,0 +1,30 @@
+import { Skeleton, Tooltip, chakra } from '@chakra-ui/react';
+import React from 'react';
+
+import useIsMobile from 'lib/hooks/useIsMobile';
+
+type Props = {
+ label: string;
+ isLoading?: boolean;
+ className?: string;
+ children: React.ReactNode;
+}
+
+const PopoverTriggerTooltip = ({ label, isLoading, className, children }: Props, ref: React.ForwardedRef) => {
+ const isMobile = useIsMobile();
+ return (
+ // tooltip need to be wrapped in div for proper popover positioning
+
+
+ { children }
+
+
+ );
+};
+
+export default chakra(React.forwardRef(PopoverTriggerTooltip));
diff --git a/ui/shared/Tabs/AdaptiveTabsList.tsx b/ui/shared/Tabs/AdaptiveTabsList.tsx
index 5f5ff81f1f..de94bcc8d6 100644
--- a/ui/shared/Tabs/AdaptiveTabsList.tsx
+++ b/ui/shared/Tabs/AdaptiveTabsList.tsx
@@ -1,5 +1,5 @@
import type { StyleProps, ThemingProps } from '@chakra-ui/react';
-import { Box, Tab, TabList, useColorModeValue } from '@chakra-ui/react';
+import { Box, Skeleton, Tab, TabList, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import { useScrollDirection } from 'lib/contexts/scrollDirection';
@@ -24,6 +24,7 @@ interface Props extends TabsProps {
activeTabIndex: number;
onItemClick: (index: number) => void;
themeProps: ThemingProps<'Tabs'>;
+ isLoading?: boolean;
}
const AdaptiveTabsList = (props: Props) => {
@@ -113,8 +114,10 @@ const AdaptiveTabsList = (props: Props) => {
},
}}
>
- { typeof tab.title === 'function' ? tab.title() : tab.title }
-
+
+ { typeof tab.title === 'function' ? tab.title() : tab.title }
+
+
);
}) }
diff --git a/ui/shared/Tabs/RoutedTabs.tsx b/ui/shared/Tabs/RoutedTabs.tsx
index a57b68f222..cfc7036bd3 100644
--- a/ui/shared/Tabs/RoutedTabs.tsx
+++ b/ui/shared/Tabs/RoutedTabs.tsx
@@ -17,9 +17,10 @@ interface Props extends ThemingProps<'Tabs'> {
stickyEnabled?: boolean;
className?: string;
onTabChange?: (index: number) => void;
+ isLoading?: boolean;
}
-const RoutedTabs = ({ tabs, tabListProps, rightSlot, rightSlotProps, stickyEnabled, className, onTabChange, ...themeProps }: Props) => {
+const RoutedTabs = ({ tabs, tabListProps, rightSlot, rightSlotProps, stickyEnabled, className, onTabChange, isLoading, ...themeProps }: Props) => {
const router = useRouter();
const tabIndex = useTabIndexFromQuery(tabs);
const tabsRef = useRef(null);
@@ -63,6 +64,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, rightSlotProps, stickyEnabl
stickyEnabled={ stickyEnabled }
onTabChange={ handleTabChange }
defaultTabIndex={ tabIndex }
+ isLoading={ isLoading }
{ ...themeProps }
/>
);
diff --git a/ui/shared/Tabs/TabsWithScroll.tsx b/ui/shared/Tabs/TabsWithScroll.tsx
index f09644b675..5c2f2b39ce 100644
--- a/ui/shared/Tabs/TabsWithScroll.tsx
+++ b/ui/shared/Tabs/TabsWithScroll.tsx
@@ -25,6 +25,7 @@ export interface Props extends ThemingProps<'Tabs'> {
stickyEnabled?: boolean;
onTabChange?: (index: number) => void;
defaultTabIndex?: number;
+ isLoading?: boolean;
className?: string;
}
@@ -37,6 +38,7 @@ const TabsWithScroll = ({
stickyEnabled,
onTabChange,
defaultTabIndex,
+ isLoading,
className,
...themeProps
}: Props) => {
@@ -101,6 +103,7 @@ const TabsWithScroll = ({
activeTabIndex={ activeTabIndex }
onItemClick={ handleTabChange }
themeProps={ themeProps }
+ isLoading={ isLoading }
/>
{ tabsList.map((tab) => { tab.component }) }
diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_with-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_with-tx-info-1.png
index 523dbce1e9..c031b777c1 100644
Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_with-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_with-tx-info-1.png differ
diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_without-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_without-tx-info-1.png
index ccd697846d..3c2a6ee5a2 100644
Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_without-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferList.pw.tsx_default_without-tx-info-1.png differ
diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png
index c372d67923..725d066269 100644
Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_with-tx-info-1.png differ
diff --git a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png
index 4a5f12f4e8..f55a41ca6d 100644
Binary files a/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png and b/ui/shared/TokenTransfer/__screenshots__/TokenTransferTable.pw.tsx_default_without-tx-info-1.png differ
diff --git a/ui/shared/UserAvatar.tsx b/ui/shared/UserAvatar.tsx
index 818eaceda7..3a75d56105 100644
--- a/ui/shared/UserAvatar.tsx
+++ b/ui/shared/UserAvatar.tsx
@@ -8,9 +8,10 @@ import IconSvg from 'ui/shared/IconSvg';
interface Props {
size: number;
+ fallbackIconSize?: number;
}
-const UserAvatar = ({ size }: Props) => {
+const UserAvatar = ({ size, fallbackIconSize = 20 }: Props) => {
const appProps = useAppContext();
const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, appProps.cookies));
const [ isImageLoadError, setImageLoadError ] = React.useState(false);
@@ -34,7 +35,7 @@ const UserAvatar = ({ size }: Props) => {
boxSize={ `${ size }px` }
borderRadius="full"
overflow="hidden"
- fallback={ isImageLoadError || !data?.avatar ? : undefined }
+ fallback={ isImageLoadError || !data?.avatar ? : undefined }
onError={ handleImageLoadError }
/>
);
diff --git a/ui/shared/address/AddressFromTo.tsx b/ui/shared/address/AddressFromTo.tsx
index 5024c657da..0a5d0efbd3 100644
--- a/ui/shared/address/AddressFromTo.tsx
+++ b/ui/shared/address/AddressFromTo.tsx
@@ -80,7 +80,7 @@ const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading
const iconSize = 20;
return (
-
+
{
+const ChartWidget = ({ items, title, description, isLoading, className, isError, units, emptyText }: Props) => {
const ref = useRef(null);
const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
@@ -134,7 +135,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError,
if (!hasItems) {
return (
- No data
+ { emptyText || 'No data' }
);
}
diff --git a/ui/shared/gas/GasInfoTooltipRow.tsx b/ui/shared/gas/GasInfoTooltipRow.tsx
index 4df03189ac..3aae5220fb 100644
--- a/ui/shared/gas/GasInfoTooltipRow.tsx
+++ b/ui/shared/gas/GasInfoTooltipRow.tsx
@@ -16,7 +16,7 @@ const GasInfoTooltipRow = ({ name, info }: Props) => {
<>
{ name }
- { info && info.time && (
+ { info && typeof info.time === 'number' && info.time > 0 && (
{ space }{ (info.time / 1000).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s
diff --git a/ui/shared/layout/LayoutApp.tsx b/ui/shared/layout/LayoutApp.tsx
index 8eaebf342a..515c79bbcb 100644
--- a/ui/shared/layout/LayoutApp.tsx
+++ b/ui/shared/layout/LayoutApp.tsx
@@ -3,30 +3,33 @@ import React from 'react';
import type { Props } from './types';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
-import HeaderDesktop from 'ui/snippets/header/HeaderDesktop';
import HeaderMobile from 'ui/snippets/header/HeaderMobile';
import * as Layout from './components';
const LayoutDefault = ({ children }: Props) => {
return (
-
+
-
-
+
+
-
-
+
{ children }
-
);
};
diff --git a/ui/shared/layout/LayoutHome.tsx b/ui/shared/layout/LayoutHome.tsx
index 6274afeeea..b6786f2339 100644
--- a/ui/shared/layout/LayoutHome.tsx
+++ b/ui/shared/layout/LayoutHome.tsx
@@ -12,7 +12,7 @@ const LayoutHome = ({ children }: Props) => {
return (
-
+
{
+const Container = ({ children, className }: Props) => {
return (
-
+
{ children }
);
};
-export default React.memo(Container);
+export default React.memo(chakra(Container));
diff --git a/ui/shared/layout/components/MainArea.tsx b/ui/shared/layout/components/MainArea.tsx
index 804577425f..0c92777246 100644
--- a/ui/shared/layout/components/MainArea.tsx
+++ b/ui/shared/layout/components/MainArea.tsx
@@ -1,16 +1,17 @@
-import { Flex } from '@chakra-ui/react';
+import { Flex, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
children: React.ReactNode;
+ className?: string;
}
-const MainArea = ({ children }: Props) => {
+const MainArea = ({ children, className }: Props) => {
return (
-
+
{ children }
);
};
-export default React.memo(MainArea);
+export default React.memo(chakra(MainArea));
diff --git a/ui/shared/search/utils.ts b/ui/shared/search/utils.ts
index c24acc63b9..eff4ac90b0 100644
--- a/ui/shared/search/utils.ts
+++ b/ui/shared/search/utils.ts
@@ -3,7 +3,7 @@ import type { MarketplaceAppOverview } from 'types/client/marketplace';
import config from 'configs/app';
-export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block' | 'user_operation' | 'blob';
+export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block' | 'user_operation' | 'blob' | 'domain';
export type Category = ApiCategory | 'app';
export type ItemsCategoriesMap =
@@ -17,6 +17,7 @@ export type SearchResultAppItem = {
export const searchCategories: Array<{id: Category; title: string }> = [
{ id: 'app', title: 'DApps' },
+ { id: 'domain', title: 'Names' },
{ id: 'token', title: 'Tokens (ERC-20)' },
{ id: 'nft', title: 'NFTs (ERC-721 & 1155)' },
{ id: 'address', title: 'Addresses' },
@@ -32,6 +33,7 @@ if (config.features.userOps.isEnabled) {
export const searchItemTitles: Record = {
app: { itemTitle: 'DApp', itemTitleShort: 'App' },
+ domain: { itemTitle: 'Name', itemTitleShort: 'Name' },
token: { itemTitle: 'Token', itemTitleShort: 'Token' },
nft: { itemTitle: 'NFT', itemTitleShort: 'NFT' },
address: { itemTitle: 'Address', itemTitleShort: 'Address' },
@@ -72,5 +74,8 @@ export function getItemCategory(item: SearchResultItem | SearchResultAppItem): C
case 'blob': {
return 'blob';
}
+ case 'ens_domain': {
+ return 'domain';
+ }
}
}
diff --git a/ui/shared/solidityscanReport/SolidityscanReportButton.tsx b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx
index d5c43f107f..d278c38ec8 100644
--- a/ui/shared/solidityscanReport/SolidityscanReportButton.tsx
+++ b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx
@@ -1,8 +1,9 @@
-import { Button, Skeleton } from '@chakra-ui/react';
+import { Button } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
+import PopoverTriggerTooltip from '../PopoverTriggerTooltip';
import useScoreLevelAndColor from './useScoreLevelAndColor';
interface Props {
@@ -12,19 +13,19 @@ interface Props {
height?: string;
onlyIcon?: boolean;
onClick?: () => void;
+ label?: string;
}
const SolidityscanReportButton = (
- { className, score, isLoading, height = '32px', onlyIcon, onClick }: Props,
+ { className, score, isLoading, height = '32px', onlyIcon, onClick, label = 'Security score' }: Props,
ref: React.ForwardedRef,
) => {
const { scoreColor } = useScoreLevelAndColor(score);
return (
-
+
{ onlyIcon ? null : score }
-
+
);
};
diff --git a/ui/shared/stats/StatsWidget.pw.tsx b/ui/shared/stats/StatsWidget.pw.tsx
new file mode 100644
index 0000000000..cc9fad8da4
--- /dev/null
+++ b/ui/shared/stats/StatsWidget.pw.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import { test, expect } from 'playwright/lib';
+
+import StatsWidget from './StatsWidget';
+
+test.use({ viewport: { width: 300, height: 100 } });
+
+test('with positive diff +@dark-mode', async({ render }) => {
+ const component = await render(
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
+
+// according to current logic we don't show diff if it's negative
+test('with negative diff', async({ render }) => {
+ const component = await render(
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
+
+test('loading state', async({ render }) => {
+ const component = await render(
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
+
+test('with period only', async({ render }) => {
+ const component = await render(
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
diff --git a/ui/shared/stats/StatsWidget.tsx b/ui/shared/stats/StatsWidget.tsx
index 53dcbbd312..077dd1c4e4 100644
--- a/ui/shared/stats/StatsWidget.tsx
+++ b/ui/shared/stats/StatsWidget.tsx
@@ -1,66 +1,94 @@
-import { Box, Flex, Text, Skeleton, useColorModeValue } from '@chakra-ui/react';
+import { Box, Flex, Text, Skeleton, useColorModeValue, chakra } from '@chakra-ui/react';
+import NextLink from 'next/link';
import React from 'react';
+import type { Route } from 'nextjs-routes';
+
import Hint from 'ui/shared/Hint';
+import TruncatedValue from 'ui/shared/TruncatedValue';
type Props = {
label: string;
value: string;
+ valuePrefix?: string;
+ valuePostfix?: string;
hint?: string;
isLoading?: boolean;
diff?: string | number;
diffFormatted?: string;
diffPeriod?: '24h';
+ period?: '1h' | '24h';
+ href?: Route;
}
-const StatsWidget = ({ label, value, isLoading, hint, diff, diffPeriod = '24h', diffFormatted }: Props) => {
+const Container = ({ href, children }: { href?: Route; children: JSX.Element }) => {
+ if (href) {
+ return (
+
+ { children }
+
+ );
+ }
+
+ return children;
+};
+
+const StatsWidget = ({ label, value, valuePrefix, valuePostfix, isLoading, hint, diff, diffPeriod = '24h', diffFormatted, period, href }: Props) => {
const bgColor = useColorModeValue('blue.50', 'whiteAlpha.100');
const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const hintColor = useColorModeValue('gray.600', 'gray.400');
return (
-
-
-
- { label }
-
-
- { value }
- { diff && Number(diff) > 0 && (
- <>
-
- +{ diffFormatted || Number(diff).toLocaleString() }
-
- ({ diffPeriod })
- >
- ) }
-
-
- { hint && (
-
-
-
- ) }
-
+
+
+
+
+ { label }
+
+
+ { valuePrefix && { valuePrefix } }
+
+ { valuePostfix && { valuePostfix } }
+ { diff && Number(diff) > 0 && (
+ <>
+
+ +{ diffFormatted || Number(diff).toLocaleString() }
+
+ ({ diffPeriod })
+ >
+ ) }
+ { period && ({ period }) }
+
+
+ { hint && (
+
+
+
+ ) }
+
+
);
};
diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png
new file mode 100644
index 0000000000..72405549ae
Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png differ
diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png
new file mode 100644
index 0000000000..2e0a87888e
Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png differ
diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png
new file mode 100644
index 0000000000..ce20bc6d5c
Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png differ
diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png
new file mode 100644
index 0000000000..0aedb396c2
Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png differ
diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png
new file mode 100644
index 0000000000..11389c31ff
Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png differ
diff --git a/ui/shared/statusTag/StatusTag.tsx b/ui/shared/statusTag/StatusTag.tsx
index fb9d59a97d..dc2feebeba 100644
--- a/ui/shared/statusTag/StatusTag.tsx
+++ b/ui/shared/statusTag/StatusTag.tsx
@@ -38,7 +38,7 @@ const StatusTag = ({ type, text, errorText, isLoading }: Props) => {
return (
-
+
{ text }
diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png
index a5ab415a85..42b5ac17f2 100644
Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png differ
diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png
index 3224ee3df2..24dd6061e8 100644
Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png differ
diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png
index 1e4454c4ea..b9add0a99c 100644
Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png differ
diff --git a/ui/snippets/footer/Footer.pw.tsx b/ui/snippets/footer/Footer.pw.tsx
index a3dc45f5c4..3b8498e3b2 100644
--- a/ui/snippets/footer/Footer.pw.tsx
+++ b/ui/snippets/footer/Footer.pw.tsx
@@ -1,55 +1,28 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
-import type { WalletProvider } from 'types/web3';
-
-import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FOOTER_LINKS } from 'mocks/config/footerLinks';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
-import buildApiUrl from 'playwright/utils/buildApiUrl';
+import { test, expect } from 'playwright/lib';
import * as configs from 'playwright/utils/configs';
import Footer from './Footer';
-const FOOTER_LINKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS', 'https://localhost:3000/footer-links.json') || '';
-
-const BACKEND_VERSION_API_URL = buildApiUrl('config_backend_version');
-const INDEXING_ALERT_API_URL = buildApiUrl('homepage_indexing_status');
-
-base.describe('with custom links, max cols', () => {
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_FOOTER_LINKS', value: FOOTER_LINKS_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
-
- test.beforeEach(async({ page, mount }) => {
- await page.route(FOOTER_LINKS_URL, (route) => {
- return route.fulfill({
- body: JSON.stringify(FOOTER_LINKS),
- });
+const FOOTER_LINKS_URL = 'https://localhost:3000/footer-links.json';
+
+test.describe('with custom links, max cols', () => {
+ test.beforeEach(async({ render, mockApiResponse, mockConfigResponse, injectMetaMaskProvider, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL, JSON.stringify(FOOTER_LINKS));
+ await injectMetaMaskProvider();
+ await mockApiResponse('homepage_indexing_status', {
+ finished_indexing: false,
+ finished_indexing_blocks: false,
+ indexed_internal_transactions_ratio: '0.1',
+ indexed_blocks_ratio: '0.1',
});
- await page.evaluate(() => {
- window.ethereum = {
- isMetaMask: true,
- _events: {},
- } as WalletProvider;
- });
-
- await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({ finished_indexing: false, indexed_internal_transactions_ratio: 0.1 }),
- }));
-
- await mount(
-
-
- ,
- );
+ await render();
});
test('+@mobile +@dark-mode', async({ page }) => {
@@ -65,74 +38,35 @@ base.describe('with custom links, max cols', () => {
});
});
-base.describe('with custom links, min cols', () => {
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_FOOTER_LINKS', value: FOOTER_LINKS_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
-
- test('base view +@dark-mode +@mobile', async({ mount, page }) => {
- await page.route(FOOTER_LINKS_URL, (route) => {
- return route.fulfill({
- body: JSON.stringify([ FOOTER_LINKS[0] ]),
- });
- });
-
- await mount(
-
-
- ,
- );
-
+test.describe('with custom links, min cols', () => {
+ test('base view +@dark-mode +@mobile', async({ render, page, mockConfigResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL, JSON.stringify([ FOOTER_LINKS[0] ]));
+ await render();
await expect(page).toHaveScreenshot();
});
});
-base.describe('without custom links', () => {
- base('base view +@dark-mode +@mobile', async({ mount, page }) => {
- await page.evaluate(() => {
- window.ethereum = {
- isMetaMask: true,
- _events: {},
- } as WalletProvider;
- });
- await page.route(BACKEND_VERSION_API_URL, (route) => {
- return route.fulfill({
- body: JSON.stringify({
- backend_version: 'v5.2.0-beta.+commit.1ce1a355',
- }),
- });
- });
-
- await mount(
-
-
- ,
- );
-
+test.describe('without custom links', () => {
+ test('base view +@dark-mode +@mobile', async({ render, page, injectMetaMaskProvider, mockApiResponse }) => {
+ await injectMetaMaskProvider();
+ await mockApiResponse('config_backend_version', { backend_version: 'v5.2.0-beta.+commit.1ce1a355' });
+ await render();
await expect(page).toHaveScreenshot();
});
- base('with indexing alert +@dark-mode +@mobile', async({ mount, page }) => {
- await page.evaluate(() => {
- window.ethereum = {
- providers: [ { isMetaMask: true, _events: {} } ],
- } as WalletProvider;
+ test('with indexing alert +@dark-mode +@mobile', async({ render, injectMetaMaskProvider, mockApiResponse }) => {
+ await injectMetaMaskProvider();
+ await mockApiResponse('homepage_indexing_status', {
+ finished_indexing: false,
+ finished_indexing_blocks: false,
+ indexed_internal_transactions_ratio: '0.1',
+ indexed_blocks_ratio: '0.1',
});
- await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify({ finished_indexing: false, indexed_internal_transactions_ratio: 0.1 }),
- }));
-
- const component = await mount(
-
-
- ,
- );
-
+ const component = await render();
await expect(component).toHaveScreenshot();
});
});
diff --git a/ui/snippets/header/Burger.pw.tsx b/ui/snippets/header/Burger.pw.tsx
index 9f64412b9b..ebda473be5 100644
--- a/ui/snippets/header/Burger.pw.tsx
+++ b/ui/snippets/header/Burger.pw.tsx
@@ -1,20 +1,16 @@
-import { test as base, expect, devices } from '@playwright/experimental-ct-react';
+import type { BrowserContext } from '@playwright/test';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
-import authFixture from 'playwright/fixtures/auth';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import createContextWithStorage from 'playwright/fixtures/createContextWithStorage';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { contextWithAuth } from 'playwright/fixtures/auth';
+import { test, expect, devices } from 'playwright/lib';
import Burger from './Burger';
-const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
+const FEATURED_NETWORKS_URL = 'https://localhost:3000/featured-networks.json';
const LOGO_URL = 'https://localhost:3000/my-logo.png';
-base.use({ viewport: devices['iPhone 13 Pro'].viewport });
+test.use({ viewport: devices['iPhone 13 Pro'].viewport });
const hooksConfig = {
router: {
@@ -24,32 +20,16 @@ const hooksConfig = {
},
};
-const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+test.beforeEach(async({ mockEnvs, mockConfigResponse, mockAssetResponse }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL, FEATURED_NETWORKS_MOCK);
+ await mockAssetResponse(LOGO_URL, './playwright/mocks/image_s.jpg');
});
-test('base view', async({ mount, page }) => {
- await page.route(FEATURED_NETWORKS_URL, (route) => {
- return route.fulfill({
- body: FEATURED_NETWORKS_MOCK,
- });
- });
- await page.route(LOGO_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+test('base view', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.locator('div[aria-label="Menu button"]').click();
await expect(page.locator('.chakra-modal__content-container')).toHaveScreenshot();
@@ -61,25 +41,8 @@ test('base view', async({ mount, page }) => {
test.describe('dark mode', () => {
test.use({ colorScheme: 'dark' });
- test('base view', async({ mount, page }) => {
- await page.route(FEATURED_NETWORKS_URL, (route) => {
- return route.fulfill({
- body: FEATURED_NETWORKS_MOCK,
- });
- });
- await page.route(LOGO_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ test('base view', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.locator('div[aria-label="Menu button"]').click();
await expect(page).toHaveScreenshot();
@@ -89,39 +52,23 @@ test.describe('dark mode', () => {
});
});
-test('submenu', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+test('submenu', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.locator('div[aria-label="Menu button"]').click();
await page.locator('div[aria-label="Blockchain link group"]').click();
await expect(page).toHaveScreenshot();
});
-test.describe('auth', () => {
- const extendedTest = base.extend({
- context: async({ browser }, use) => {
- const context = await createContextWithStorage(browser, [
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- ]);
- authFixture(context);
- use(context);
- },
- });
+const authTest = test.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
+});
- extendedTest.use({ viewport: { width: devices['iPhone 13 Pro'].viewport.width, height: 800 } });
+authTest.describe('auth', () => {
+ authTest.use({ viewport: { width: devices['iPhone 13 Pro'].viewport.width, height: 800 } });
- extendedTest('base view', async({ mount, page }) => {
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
+ authTest('base view', async({ render, page }) => {
+ const component = await render(, { hooksConfig });
await component.locator('div[aria-label="Menu button"]').click();
await expect(page).toHaveScreenshot();
diff --git a/ui/snippets/header/HeaderMobile.tsx b/ui/snippets/header/HeaderMobile.tsx
index 570e0d157c..ca543562d2 100644
--- a/ui/snippets/header/HeaderMobile.tsx
+++ b/ui/snippets/header/HeaderMobile.tsx
@@ -16,11 +16,11 @@ const LOGO_IMAGE_PROPS = {
};
type Props = {
- isHomePage?: boolean;
+ hideSearchBar?: boolean;
renderSearchBar?: () => React.ReactNode;
}
-const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => {
+const HeaderMobile = ({ hideSearchBar, renderSearchBar }: Props) => {
const bgColor = useColorModeValue('white', 'black');
const scrollDirection = useScrollDirection();
const { ref, inView } = useInView({ threshold: 1 });
@@ -57,7 +57,7 @@ const HeaderMobile = ({ isHomePage, renderSearchBar }: Props) => {
{ config.features.blockchainInteraction.isEnabled && }
- { !isHomePage && searchBar }
+ { !hideSearchBar && searchBar }
);
};
diff --git a/ui/snippets/navigation/NavLink.tsx b/ui/snippets/navigation/NavLink.tsx
index 67dbbe1f6f..60f24323d1 100644
--- a/ui/snippets/navigation/NavLink.tsx
+++ b/ui/snippets/navigation/NavLink.tsx
@@ -61,7 +61,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick }: Props) => {
>
-
+
{ item.text }
{ !isInternalLink && }
diff --git a/ui/snippets/navigation/NavigationDesktop.pw.tsx b/ui/snippets/navigation/NavigationDesktop.pw.tsx
index 9ba7520405..27eb280a88 100644
--- a/ui/snippets/navigation/NavigationDesktop.pw.tsx
+++ b/ui/snippets/navigation/NavigationDesktop.pw.tsx
@@ -1,15 +1,12 @@
import { Box, Flex } from '@chakra-ui/react';
-import { test as base, expect } from '@playwright/experimental-ct-react';
-import type { Locator } from '@playwright/test';
+import type { BrowserContext, Locator } from '@playwright/test';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
+import config from 'configs/app';
import * as cookies from 'lib/cookies';
-import authFixture from 'playwright/fixtures/auth';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import createContextWithStorage from 'playwright/fixtures/createContextWithStorage';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
+import { contextWithAuth } from 'playwright/fixtures/auth';
+import { test, expect } from 'playwright/lib';
import * as configs from 'playwright/utils/configs';
import NavigationDesktop from './NavigationDesktop';
@@ -22,26 +19,24 @@ const hooksConfig = {
},
};
-const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/config.json') || '';
+const FEATURED_NETWORKS_URL = 'https://localhost:3000/featured-networks.json';
-const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+test.beforeEach(async({ mockEnvs, mockConfigResponse }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL, FEATURED_NETWORKS_MOCK);
});
test.describe('no auth', () => {
let component: Locator;
- test.beforeEach(async({ mount }) => {
- component = await mount(
-
-
-
-
-
- ,
+ test.beforeEach(async({ render }) => {
+ component = await render(
+
+
+
+ ,
{ hooksConfig },
);
});
@@ -59,39 +54,31 @@ test.describe('no auth', () => {
});
});
-base.describe('auth', () => {
- const test = base.extend({
- context: async({ browser }, use) => {
- const context = await createContextWithStorage(browser, [
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- ]);
- authFixture(context);
- use(context);
- },
- });
+const authTest = test.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
+});
+authTest.describe('auth', () => {
let component: Locator;
- test.beforeEach(async({ mount }) => {
- component = await mount(
-
-
-
-
-
- ,
+ authTest.beforeEach(async({ render }) => {
+ component = await render(
+
+
+
+ ,
{ hooksConfig },
);
});
- test('+@dark-mode', async() => {
+ authTest('+@dark-mode', async() => {
await expect(component).toHaveScreenshot();
});
- test.describe('xl screen', () => {
- test.use({ viewport: configs.viewport.xl });
+ authTest.describe('xl screen', () => {
+ authTest.use({ viewport: configs.viewport.xl });
- test('+@dark-mode', async() => {
+ authTest('+@dark-mode', async() => {
await expect(component).toHaveScreenshot();
});
});
@@ -100,14 +87,12 @@ base.describe('auth', () => {
test.describe('with tooltips', () => {
test.use({ viewport: configs.viewport.xl });
- test('', async({ mount, page }) => {
- const component = await mount(
-
-
-
-
-
- ,
+ test('', async({ render, page }) => {
+ const component = await render(
+
+
+
+ ,
{ hooksConfig },
);
@@ -122,14 +107,12 @@ test.describe('with tooltips', () => {
test.describe('with submenu', () => {
let component: Locator;
- test.beforeEach(async({ mount, page }) => {
- component = await mount(
-
-
-
-
-
- ,
+ test.beforeEach(async({ render, page }) => {
+ component = await render(
+
+
+
+ ,
{ hooksConfig },
);
await page.locator('a[aria-label="Blockchain link group"]').hover();
@@ -148,65 +131,55 @@ test.describe('with submenu', () => {
});
});
-base.describe('cookie set to false', () => {
- const test = base.extend({
- context: async({ browser }, use) => {
- const context = await createContextWithStorage(browser, [
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- ]);
- context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'false', domain: app.domain, path: '/' } ]);
- use(context);
- },
- });
+const noSideBarCookieTest = test.extend({
+ context: ({ context }, use) => {
+ context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'false', domain: config.app.host, path: '/' } ]);
+ use(context);
+ },
+});
+noSideBarCookieTest.describe('cookie set to false', () => {
let component: Locator;
- test.beforeEach(async({ mount }) => {
- component = await mount(
-
-
-
-
-
- ,
+ noSideBarCookieTest.beforeEach(async({ render }) => {
+ component = await render(
+
+
+
+ ,
{ hooksConfig },
);
});
- test('', async() => {
+ noSideBarCookieTest('', async() => {
const networkMenu = component.locator('button[aria-label="Network menu"]');
await expect(networkMenu).toBeVisible();
});
- test.describe('xl screen', () => {
- test.use({ viewport: configs.viewport.xl });
+ noSideBarCookieTest.describe('xl screen', () => {
+ noSideBarCookieTest.use({ viewport: configs.viewport.xl });
- test('', async() => {
+ noSideBarCookieTest('', async() => {
const networkMenu = component.locator('button[aria-label="Network menu"]');
await expect(networkMenu).toBeVisible();
});
});
});
-base.describe('cookie set to true', () => {
- const test = base.extend({
- context: async({ browser }, use) => {
- const context = await createContextWithStorage(browser, [
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- ]);
- context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'true', domain: 'localhost', path: '/' } ]);
- use(context);
- },
- });
+const sideBarCookieTest = test.extend({
+ context: ({ context }, use) => {
+ context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'true', domain: config.app.host, path: '/' } ]);
+ use(context);
+ },
+});
- test('navbar is collapsed', async({ mount }) => {
- const component = await mount(
-
-
-
-
-
- ,
+sideBarCookieTest.describe('cookie set to true', () => {
+ sideBarCookieTest('navbar is collapsed', async({ render }) => {
+ const component = await render(
+
+
+
+ ,
{ hooksConfig },
);
@@ -215,14 +188,12 @@ base.describe('cookie set to true', () => {
});
});
-test('hover +@dark-mode', async({ mount }) => {
- const component = await mount(
-
-
-
-
-
- ,
+test('hover +@dark-mode', async({ render }) => {
+ const component = await render(
+
+
+
+ ,
{ hooksConfig },
);
@@ -233,14 +204,12 @@ test('hover +@dark-mode', async({ mount }) => {
test.describe('hover xl screen', () => {
test.use({ viewport: configs.viewport.xl });
- test('+@dark-mode', async({ mount }) => {
- const component = await mount(
-
-
-
-
-
- ,
+ test('+@dark-mode', async({ render }) => {
+ const component = await render(
+
+
+
+ ,
{ hooksConfig },
);
diff --git a/ui/snippets/networkMenu/NetworkLogo.pw.tsx b/ui/snippets/networkMenu/NetworkLogo.pw.tsx
index 12c2d8da12..ad6172d5f7 100644
--- a/ui/snippets/networkMenu/NetworkLogo.pw.tsx
+++ b/ui/snippets/networkMenu/NetworkLogo.pw.tsx
@@ -1,30 +1,24 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { test, expect } from 'playwright/lib';
import * as configs from 'playwright/utils/configs';
import NetworkLogo from './NetworkLogo';
-base.describe('placeholder logo', () => {
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: '' },
- { name: 'NEXT_PUBLIC_NETWORK_ICON', value: '' },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+const LOGO_URL = 'https://localhost:3000/my-logo.png';
+const ICON_URL = 'https://localhost:3000/my-icon.png';
+
+test.describe('placeholder logo', () => {
+ test.beforeEach(async({ mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_NETWORK_LOGO', '' ],
+ [ 'NEXT_PUBLIC_NETWORK_ICON', '' ],
+ ]);
});
- test('+@dark-mode', async({ mount }) => {
- const component = await mount(
-
-
- ,
- );
+ test('+@dark-mode', async({ render }) => {
+ const component = await render();
await expect(component.locator('a')).toHaveScreenshot();
});
@@ -32,50 +26,25 @@ base.describe('placeholder logo', () => {
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
- test('+@dark-mode', async({ mount }) => {
- const component = await mount(
-
-
- ,
- );
+ test('+@dark-mode', async({ render }) => {
+ const component = await render();
await expect(component.locator('a')).toHaveScreenshot();
});
});
});
-base.describe('custom logo', () => {
- const LOGO_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
- const ICON_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
- { name: 'NEXT_PUBLIC_NETWORK_ICON', value: ICON_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
-
+test.describe('custom logo', () => {
let component: Locator;
- test.beforeEach(async({ page, mount }) => {
- await page.route(LOGO_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/network-logo.svg',
- });
- });
- await page.route(ICON_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_svg.svg',
- });
- });
-
- component = await mount(
-
-
- ,
- );
+ test.beforeEach(async({ render, mockConfigResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_NETWORK_LOGO', LOGO_URL ],
+ [ 'NEXT_PUBLIC_NETWORK_ICON', ICON_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_LOGO', LOGO_URL, './playwright/mocks/network-logo.svg', true);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_ICON', ICON_URL, './playwright/mocks/image_svg.svg', true);
+ component = await render();
});
test('+@dark-mode', async() => {
@@ -91,54 +60,22 @@ base.describe('custom logo', () => {
});
});
-base.describe('custom logo with dark option -@default +@dark-mode', () => {
- const LOGO_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO', 'https://localhost:3000/my-logo.png') || '';
- const LOGO_URL_DARK = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK', 'https://localhost:3000/my-logo.png') || '';
- const ICON_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON', 'https://localhost:3000/my-icon.png') || '';
- const ICON_URL_DARK = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK', 'https://localhost:3000/my-icon.png') || '';
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
- { name: 'NEXT_PUBLIC_NETWORK_LOGO_DARK', value: LOGO_URL_DARK },
- { name: 'NEXT_PUBLIC_NETWORK_ICON', value: ICON_URL },
- { name: 'NEXT_PUBLIC_NETWORK_ICON_DARK', value: ICON_URL_DARK },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
-
+test.describe('custom logo with dark option -@default +@dark-mode', () => {
let component: Locator;
- test.beforeEach(async({ page, mount }) => {
- await page.route(LOGO_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_long.jpg',
- });
- });
- await page.route(LOGO_URL_DARK, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_long.jpg',
- });
- });
- await page.route(ICON_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
- await page.route(ICON_URL_DARK, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- component = await mount(
-
-
- ,
- );
+ test.beforeEach(async({ render, mockConfigResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_NETWORK_LOGO', LOGO_URL ],
+ [ 'NEXT_PUBLIC_NETWORK_LOGO_DARK', LOGO_URL ],
+ [ 'NEXT_PUBLIC_NETWORK_ICON', ICON_URL ],
+ [ 'NEXT_PUBLIC_NETWORK_ICON_DARK', ICON_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_LOGO', LOGO_URL, './playwright/mocks/image_long.jpg', true);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_LOGO_DARK', LOGO_URL, './playwright/mocks/image_long.jpg', true);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_ICON', ICON_URL, './playwright/mocks/image_s.jpg', true);
+ await mockConfigResponse('NEXT_PUBLIC_NETWORK_ICON_DARK', ICON_URL, './playwright/mocks/image_s.jpg', true);
+
+ component = await render();
});
test('', async() => {
diff --git a/ui/snippets/networkMenu/NetworkMenu.pw.tsx b/ui/snippets/networkMenu/NetworkMenu.pw.tsx
index 808f198c2d..71159ec0f5 100644
--- a/ui/snippets/networkMenu/NetworkMenu.pw.tsx
+++ b/ui/snippets/networkMenu/NetworkMenu.pw.tsx
@@ -1,45 +1,23 @@
-import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FEATURED_NETWORKS_MOCK } from 'mocks/config/network';
-import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
+import { test, expect } from 'playwright/lib';
import NetworkMenu from './NetworkMenu';
-const FEATURED_NETWORKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS', 'https://localhost:3000/featured-networks.json') || '';
+const FEATURED_NETWORKS_URL = 'https://localhost:3000/featured-networks.json';
+const LOGO_URL = 'https://localhost:3000/my-logo.png';
-const extendedTest = test.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
-});
+test.use({ viewport: { width: 1600, height: 1000 } });
+
+test('base view +@dark-mode', async({ render, page, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL ],
+ ]);
+ await mockConfigResponse('NEXT_PUBLIC_FEATURED_NETWORKS', FEATURED_NETWORKS_URL, FEATURED_NETWORKS_MOCK);
+ await mockAssetResponse(LOGO_URL, './playwright/mocks/image_s.jpg');
-extendedTest.use({ viewport: { width: 1600, height: 1000 } });
-
-extendedTest('base view +@dark-mode', async({ mount, page }) => {
- const LOGO_URL = 'https://localhost:3000/my-logo.png';
- await page.route(LOGO_URL, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
- await page.route(FEATURED_NETWORKS_URL, (route) => {
- return route.fulfill({
- status: 200,
- body: FEATURED_NETWORKS_MOCK,
- });
- });
-
- const component = await mount(
-
-
- ,
- );
+ const component = await render();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 36, height: 36 } });
diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx
index 381777ecb2..5b1f67d618 100644
--- a/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx
+++ b/ui/snippets/profileMenu/ProfileMenuDesktop.pw.tsx
@@ -1,9 +1,10 @@
+import type { BrowserContext } from '@playwright/test';
import React from 'react';
+import config from 'configs/app';
import * as profileMock from 'mocks/user/profile';
-import authFixture from 'playwright/fixtures/auth';
+import { contextWithAuth } from 'playwright/fixtures/auth';
import { test, expect } from 'playwright/lib';
-import * as app from 'playwright/utils/app';
import ProfileMenuDesktop from './ProfileMenuDesktop';
@@ -17,14 +18,11 @@ test('no auth', async({ render, page }) => {
const component = await render(, { hooksConfig });
await component.locator('a').click();
- expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`);
+ expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`);
});
-const authTest = test.extend({
- context: ({ context }, use) => {
- authFixture(context);
- use(context);
- },
+const authTest = test.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
});
authTest('auth +@dark-mode', async({ render, page, mockApiResponse, mockAssetResponse }) => {
await mockApiResponse('user_info', profileMock.base);
diff --git a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx b/ui/snippets/profileMenu/ProfileMenuDesktop.tsx
index 90e445ec47..054b7cfe29 100644
--- a/ui/snippets/profileMenu/ProfileMenuDesktop.tsx
+++ b/ui/snippets/profileMenu/ProfileMenuDesktop.tsx
@@ -1,5 +1,5 @@
import type { IconButtonProps } from '@chakra-ui/react';
-import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box } from '@chakra-ui/react';
+import { Popover, PopoverContent, PopoverBody, PopoverTrigger, IconButton, Tooltip, Box, chakra } from '@chakra-ui/react';
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
@@ -12,9 +12,11 @@ import useMenuButtonColors from '../useMenuButtonColors';
type Props = {
isHomePage?: boolean;
+ className?: string;
+ fallbackIconSize?: number;
};
-const ProfileMenuDesktop = ({ isHomePage }: Props) => {
+const ProfileMenuDesktop = ({ isHomePage, className, fallbackIconSize }: Props) => {
const { data, error, isPending } = useFetchProfileInfo();
const loginUrl = useLoginUrl();
const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
@@ -81,8 +83,9 @@ const ProfileMenuDesktop = ({ isHomePage }: Props) => {
}
+ icon={ }
variant={ variant }
colorScheme="blue"
boxSize="40px"
@@ -104,4 +107,4 @@ const ProfileMenuDesktop = ({ isHomePage }: Props) => {
);
};
-export default ProfileMenuDesktop;
+export default chakra(ProfileMenuDesktop);
diff --git a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx b/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx
index b5d3fcc373..0867bf2bd8 100644
--- a/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx
+++ b/ui/snippets/profileMenu/ProfileMenuMobile.pw.tsx
@@ -1,61 +1,40 @@
-import { test, expect, devices } from '@playwright/experimental-ct-react';
+import type { BrowserContext } from '@playwright/test';
import React from 'react';
+import config from 'configs/app';
import * as profileMock from 'mocks/user/profile';
-import authFixture from 'playwright/fixtures/auth';
-import TestApp from 'playwright/TestApp';
-import * as app from 'playwright/utils/app';
-import buildApiUrl from 'playwright/utils/buildApiUrl';
+import { contextWithAuth } from 'playwright/fixtures/auth';
+import { test, expect, devices } from 'playwright/lib';
import ProfileMenuMobile from './ProfileMenuMobile';
-test('no auth', async({ mount, page }) => {
+test('no auth', async({ render, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
- const component = await mount(
-
-
- ,
- { hooksConfig },
- );
-
+ const component = await render(, { hooksConfig });
await component.locator('a').click();
- expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`);
+
+ expect(page.url()).toBe(`${ config.app.baseUrl }/auth/auth0?path=%2F`);
});
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
-test.describe('auth', () => {
- const extendedTest = test.extend({
- context: ({ context }, use) => {
- authFixture(context);
- use(context);
- },
- });
+const authTest = test.extend<{ context: BrowserContext }>({
+ context: contextWithAuth,
+});
- extendedTest('base view', async({ mount, page }) => {
- await page.route(buildApiUrl('user_info'), (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(profileMock.base),
- }));
- await page.route(profileMock.base.avatar, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- const component = await mount(
-
-
- ,
- );
+authTest.describe('auth', () => {
+ authTest('base view', async({ render, page, mockApiResponse, mockAssetResponse }) => {
+ await mockApiResponse('user_info', profileMock.base);
+ await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
+ const component = await render();
await component.getByAltText(/Profile picture/i).click();
+
await expect(page).toHaveScreenshot();
});
});
diff --git a/ui/snippets/searchBar/SearchBar.pw.tsx b/ui/snippets/searchBar/SearchBar.pw.tsx
index 9aef377007..6ae9cbe3a8 100644
--- a/ui/snippets/searchBar/SearchBar.pw.tsx
+++ b/ui/snippets/searchBar/SearchBar.pw.tsx
@@ -1,322 +1,190 @@
-import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
-import { buildExternalAssetFilePath } from 'configs/app/utils';
-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 * as app from 'playwright/utils/app';
-import buildApiUrl from 'playwright/utils/buildApiUrl';
-import * as configs from 'playwright/utils/configs';
+import { test, expect } from 'playwright/lib';
import SearchBar from './SearchBar';
-const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: '' },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
+test.beforeEach(async({ mockAssetResponse, mockEnvs }) => {
+ await mockAssetResponse(searchMock.token1.icon_url as string, './playwright/mocks/image_s.jpg');
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'false' ],
+ ]);
});
-test.beforeEach(async({ page }) => {
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(textAdMock.duck),
- }));
- await page.route(textAdMock.duck.ad.thumbnail, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
- await page.route(searchMock.token1.icon_url as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-});
-
-test('search by token name +@mobile +@dark-mode', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.token1,
- searchMock.token2,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by token name +@mobile +@dark-mode', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.token1,
+ searchMock.token2,
+ ], { queryParams: { q: 'o' } });
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('search by contract name +@mobile +@dark-mode', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.contract1,
- searchMock.address2,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by contract name +@mobile +@dark-mode', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.contract1,
+ searchMock.address2,
+ ], { queryParams: { q: 'o' } });
+
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('search by name homepage +@dark-mode', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by name homepage +@dark-mode', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ ], { queryParams: { q: 'o' } });
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('search by tag +@mobile +@dark-mode', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.label1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by tag +@mobile +@dark-mode', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.label1,
+ ], { queryParams: { q: 'o' } });
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('search by address hash +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.address1.address }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.address1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by address hash +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.address1,
+ ], { queryParams: { q: searchMock.address1.address } });
+ await render();
await page.getByPlaceholder(/search/i).fill(searchMock.address1.address);
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-test('search by block number +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.block1.block_number }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.block1,
- searchMock.block2,
- searchMock.block3,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by block number +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.block1,
+ searchMock.block2,
+ searchMock.block3,
+ ], { queryParams: { q: searchMock.block1.block_number } });
+ await render();
await page.getByPlaceholder(/search/i).fill(String(searchMock.block1.block_number));
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 600 } });
});
-test('search by block hash +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.block1.block_hash }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.block1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by block hash +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.block1,
+ ], { queryParams: { q: searchMock.block1.block_hash } });
+ await render();
await page.getByPlaceholder(/search/i).fill(searchMock.block1.block_hash);
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-test('search by tx hash +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.tx1.tx_hash }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.tx1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by tx hash +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.tx1,
+ ], { queryParams: { q: searchMock.tx1.tx_hash } });
+ await render();
await page.getByPlaceholder(/search/i).fill(searchMock.tx1.tx_hash);
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-test('search by blob hash +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.blob1.blob_hash }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.blob1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by blob hash +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.blob1,
+ ], { queryParams: { q: searchMock.blob1.blob_hash } });
+ await render();
await page.getByPlaceholder(/search/i).fill(searchMock.blob1.blob_hash);
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-const testWithUserOps = base.extend({
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- context: contextWithEnvs(configs.featureEnvs.userOps) as any,
+test('search by domain name +@mobile', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.domain1,
+ ], { queryParams: { q: searchMock.domain1.ens_info.name } });
+
+ await render();
+ await page.getByPlaceholder(/search/i).fill(searchMock.domain1.ens_info.name);
+ await page.waitForResponse(apiUrl);
+
+ await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-testWithUserOps('search by user op hash +@mobile', async({ mount, page }) => {
- await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(textAdMock.duck),
- }));
- const API_URL = buildApiUrl('quick_search') + `?q=${ searchMock.tx1.tx_hash }`;
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.userOp1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search by user op hash +@mobile', async({ render, page, mockApiResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_HAS_USER_OPS', 'true' ],
+ ]);
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.userOp1,
+ ], { queryParams: { q: searchMock.tx1.tx_hash } });
+ await render();
await page.getByPlaceholder(/search/i).fill(searchMock.tx1.tx_hash);
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
-test('search with view all link', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- ...Array(47).fill(searchMock.contract1),
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('search with view all link', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ ...Array(47).fill(searchMock.contract1),
+ ], { queryParams: { q: 'o' } });
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
-
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('scroll suggest to category', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- searchMock.token1,
- searchMock.token2,
- searchMock.contract1,
- ]),
- }));
-
- await mount(
-
-
- ,
- );
+test('scroll suggest to category', async({ render, page, mockApiResponse }) => {
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ searchMock.token1,
+ searchMock.token2,
+ searchMock.contract1,
+ ], { queryParams: { q: 'o' } });
+ await render();
await page.getByPlaceholder(/search/i).fill('o');
- await page.waitForResponse(API_URL);
+ await page.waitForResponse(apiUrl);
await page.getByRole('tab', { name: 'Addresses' }).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-test('recent keywords suggest +@mobile', async({ mount, page }) => {
- await mount(
-
-
- ,
- );
+test('recent keywords suggest +@mobile', async({ render, page }) => {
+ await render();
// eslint-disable-next-line max-len
await page.evaluate(() => window.localStorage.setItem('recent_search_keywords', '["10x2d311959270e0bbdc1fc7bc6dbd8ad645c4dd8d6aa32f5f89d54629a924f112b","0x1d311959270e0bbdc1fc7bc6dbd8ad645c4dd8d6aa32f5f89d54629a924f112b","usd","bob"]'));
await page.getByPlaceholder(/search/i).click();
@@ -324,50 +192,24 @@ test('recent keywords suggest +@mobile', async({ mount, page }) => {
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
-base.describe('with apps', () => {
- const MARKETPLACE_CONFIG_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://marketplace-config.json') || '';
- const test = base.extend({
- context: contextWithEnvs([
- { name: 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', value: MARKETPLACE_CONFIG_URL },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ]) as any,
- });
+test.describe('with apps', () => {
+ const MARKETPLACE_CONFIG_URL = 'https://marketplace-config.json';
- test('default view +@mobile', async({ mount, page }) => {
- const API_URL = buildApiUrl('quick_search') + '?q=o';
- await page.route(API_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify([
- searchMock.token1,
- ]),
- }));
-
- await page.route(MARKETPLACE_CONFIG_URL, (route) => route.fulfill({
- status: 200,
- body: JSON.stringify(appsMock),
- }));
-
- await page.route(appsMock[0].logo, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
- await page.route(appsMock[1].logo as string, (route) => {
- return route.fulfill({
- status: 200,
- path: './playwright/mocks/image_s.jpg',
- });
- });
-
- await mount(
-
-
- ,
- );
- await page.getByPlaceholder(/search/i).fill('o');
+ test('default view +@mobile', async({ render, page, mockApiResponse, mockConfigResponse, mockAssetResponse, mockEnvs }) => {
+ await mockEnvs([
+ [ 'NEXT_PUBLIC_MARKETPLACE_ENABLED', 'true' ],
+ [ 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL ],
+ ]);
+ const apiUrl = await mockApiResponse('quick_search', [
+ searchMock.token1,
+ ], { queryParams: { q: 'o' } });
+ await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', MARKETPLACE_CONFIG_URL, JSON.stringify(appsMock));
+ await mockAssetResponse(appsMock[0].logo, './playwright/mocks/image_s.jpg');
+ await mockAssetResponse(appsMock[1].logo, './playwright/mocks/image_s.jpg');
- await page.waitForResponse(API_URL);
+ await render();
+ await page.getByPlaceholder(/search/i).fill('o');
+ await page.waitForResponse(apiUrl);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 500 } });
});
diff --git a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestDomain.tsx b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestDomain.tsx
new file mode 100644
index 0000000000..2ec60ccc8d
--- /dev/null
+++ b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestDomain.tsx
@@ -0,0 +1,82 @@
+import { Grid, Text, Flex } from '@chakra-ui/react';
+import React from 'react';
+
+import type { SearchResultDomain } from 'types/api/search';
+
+import dayjs from 'lib/date/dayjs';
+import highlightText from 'lib/highlightText';
+import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
+import IconSvg from 'ui/shared/IconSvg';
+
+interface Props {
+ data: SearchResultDomain;
+ isMobile: boolean | undefined;
+ searchTerm: string;
+}
+
+const SearchBarSuggestDomain = ({ data, isMobile, searchTerm }: Props) => {
+ const icon = ;
+
+ const name = (
+
+
+
+ );
+
+ const address = (
+
+
+
+ );
+
+ const isContractVerified = data.is_smart_contract_verified && ;
+
+ const expiresText = data.ens_info?.expiry_date ? ` expires ${ dayjs(data.ens_info.expiry_date).fromNow() }` : '';
+ const ensNamesCount = data?.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }`;
+ const additionalInfo = (
+
+ { data?.ens_info.names_count > 1 ? ensNamesCount : expiresText }
+
+ );
+
+ if (isMobile) {
+ return (
+ <>
+
+ { icon }
+ { name }
+
+
+ { address }
+ { isContractVerified }
+
+ { additionalInfo }
+ >
+ );
+ }
+
+ return (
+
+
+ { icon }
+ { name }
+
+
+ { address }
+ { isContractVerified }
+
+ { additionalInfo }
+
+ );
+};
+
+export default React.memo(SearchBarSuggestDomain);
diff --git a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestItem.tsx b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestItem.tsx
index 3473b42e5c..b0eb20041d 100644
--- a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestItem.tsx
+++ b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggestItem.tsx
@@ -9,6 +9,7 @@ import { route } from 'nextjs-routes';
import SearchBarSuggestAddress from './SearchBarSuggestAddress';
import SearchBarSuggestBlob from './SearchBarSuggestBlob';
import SearchBarSuggestBlock from './SearchBarSuggestBlock';
+import SearchBarSuggestDomain from './SearchBarSuggestDomain';
import SearchBarSuggestItemLink from './SearchBarSuggestItemLink';
import SearchBarSuggestLabel from './SearchBarSuggestLabel';
import SearchBarSuggestToken from './SearchBarSuggestToken';
@@ -46,6 +47,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) =>
case 'blob': {
return route({ pathname: '/blobs/[hash]', query: { hash: data.blob_hash } });
}
+ case 'ens_domain': {
+ return route({ pathname: '/address/[hash]', query: { hash: data.address } });
+ }
}
})();
@@ -74,6 +78,9 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm, onClick }: Props) =>
case 'blob': {
return ;
}
+ case 'ens_domain': {
+ return ;
+ }
}
})();
diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png
index b40ca14719..d92e5332a5 100644
Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-block-number-mobile-1.png differ
diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-domain-name-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-domain-name-mobile-1.png
new file mode 100644
index 0000000000..14c634cb94
Binary files /dev/null and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_search-by-domain-name-mobile-1.png differ
diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-block-number-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-block-number-mobile-1.png
index e0fbd6a953..cd5d6d84ef 100644
Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-block-number-mobile-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-block-number-mobile-1.png differ
diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-domain-name-mobile-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-domain-name-mobile-1.png
new file mode 100644
index 0000000000..f265295b03
Binary files /dev/null and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_mobile_search-by-domain-name-mobile-1.png differ
diff --git a/ui/snippets/topBar/TopBar.pw.tsx b/ui/snippets/topBar/TopBar.pw.tsx
index 5b7702f1e1..1880147dfd 100644
--- a/ui/snippets/topBar/TopBar.pw.tsx
+++ b/ui/snippets/topBar/TopBar.pw.tsx
@@ -11,7 +11,8 @@ import TopBar from './TopBar';
const test = base.extend({
context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_SWAP_BUTTON_URL', value: 'uniswap' },
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ { name: 'NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL', value: 'DUCK' },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
]) as any,
});
@@ -28,8 +29,24 @@ test('default view +@dark-mode +@mobile', async({ mount, page }) => {
);
await component.getByText(/\$1\.39/).click();
+ await expect(page.getByText(/last update/i)).toBeVisible();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } });
await component.getByLabel('User settings').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 400 } });
});
+
+test('with secondary coin price +@mobile', async({ mount, page }) => {
+ await page.route(buildApiUrl('stats'), (route) => route.fulfill({
+ status: 200,
+ body: JSON.stringify(statsMock.withSecondaryCoin),
+ }));
+
+ const component = await mount(
+
+
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
diff --git a/ui/snippets/topBar/TopBarStats.tsx b/ui/snippets/topBar/TopBarStats.tsx
index 7bc180f0c6..6d5770b184 100644
--- a/ui/snippets/topBar/TopBarStats.tsx
+++ b/ui/snippets/topBar/TopBarStats.tsx
@@ -4,12 +4,15 @@ import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs';
+import useIsMobile from 'lib/hooks/useIsMobile';
import { HOMEPAGE_STATS } from 'stubs/stats';
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
import GasPrice from 'ui/shared/gas/GasPrice';
import TextSeparator from 'ui/shared/TextSeparator';
const TopBarStats = () => {
+ const isMobile = useIsMobile();
+
const { data, isPlaceholderData, isError, refetch, dataUpdatedAt } = useApiQuery('stats', {
queryOptions: {
placeholderData: HOMEPAGE_STATS,
@@ -51,7 +54,7 @@ const TopBarStats = () => {
{ data?.coin_price && (
- { config.chain.governanceToken.symbol || config.chain.currency.symbol }
+ { config.chain.currency.symbol }
${ Number(data.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) }
{ data.coin_price_change_percentage && (
@@ -63,6 +66,14 @@ const TopBarStats = () => {
) }
) }
+ { !isMobile && data?.secondary_coin_price && config.chain.secondaryCoin.symbol && (
+
+
+ { config.chain.secondaryCoin.symbol }
+ ${ Number(data.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) }
+
+
+ ) }
{ data?.coin_price && config.features.gasTracker.isEnabled && }
{ data?.gas_prices && data.gas_prices.average !== null && config.features.gasTracker.isEnabled && (
diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_with-secondary-coin-price-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_with-secondary-coin-price-mobile-1.png
new file mode 100644
index 0000000000..1eb1aeccd9
Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_default_with-secondary-coin-price-mobile-1.png differ
diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png
index 346904777a..e6c21cc8f1 100644
Binary files a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_default-view-dark-mode-mobile-1.png differ
diff --git a/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_with-secondary-coin-price-mobile-1.png b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_with-secondary-coin-price-mobile-1.png
new file mode 100644
index 0000000000..807843546d
Binary files /dev/null and b/ui/snippets/topBar/__screenshots__/TopBar.pw.tsx_mobile_with-secondary-coin-price-mobile-1.png differ
diff --git a/ui/snippets/useMenuButtonColors.tsx b/ui/snippets/useMenuButtonColors.tsx
index 1583e18fb5..cbd3e9bbd3 100644
--- a/ui/snippets/useMenuButtonColors.tsx
+++ b/ui/snippets/useMenuButtonColors.tsx
@@ -2,8 +2,9 @@ import { useColorModeValue } from '@chakra-ui/react';
export default function useMenuColors() {
const themedBackground = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
+ const themedBackgroundOrange = useColorModeValue('orange.100', 'orange.900');
const themedBorderColor = useColorModeValue('gray.300', 'gray.700');
const themedColor = useColorModeValue('blackAlpha.800', 'gray.400');
- return { themedBackground, themedBorderColor, themedColor };
+ return { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor };
}
diff --git a/ui/snippets/walletMenu/WalletIdenticon.tsx b/ui/snippets/walletMenu/WalletIdenticon.tsx
new file mode 100644
index 0000000000..95a10a7334
--- /dev/null
+++ b/ui/snippets/walletMenu/WalletIdenticon.tsx
@@ -0,0 +1,47 @@
+import { Box, Flex, chakra } from '@chakra-ui/react';
+import React from 'react';
+
+import useIsMobile from 'lib/hooks/useIsMobile';
+import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
+import IconSvg from 'ui/shared/IconSvg';
+
+import useMenuButtonColors from '../useMenuButtonColors';
+
+type Props = {
+ address: string;
+ isAutoConnectDisabled?: boolean;
+ className?: string;
+};
+
+const WalletIdenticon = ({ address, isAutoConnectDisabled, className }: Props) => {
+ const { themedBackgroundOrange } = useMenuButtonColors();
+ const isMobile = useIsMobile();
+
+ return (
+
+
+ { isAutoConnectDisabled && (
+
+
+
+ ) }
+
+ );
+};
+
+export default chakra(WalletIdenticon);
diff --git a/ui/snippets/walletMenu/WalletMenuContent.tsx b/ui/snippets/walletMenu/WalletMenuContent.tsx
index 0578d3c6c5..a3a0c967f6 100644
--- a/ui/snippets/walletMenu/WalletMenuContent.tsx
+++ b/ui/snippets/walletMenu/WalletMenuContent.tsx
@@ -1,22 +1,48 @@
-import { Box, Button, Text } from '@chakra-ui/react';
+import { Box, Button, Text, Flex } from '@chakra-ui/react';
import React from 'react';
import * as mixpanel from 'lib/mixpanel/index';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
+import IconSvg from 'ui/shared/IconSvg';
+
+import useMenuButtonColors from '../useMenuButtonColors';
type Props = {
address?: string;
disconnect?: () => void;
+ isAutoConnectDisabled?: boolean;
};
-const WalletMenuContent = ({ address, disconnect }: Props) => {
+const WalletMenuContent = ({ address, disconnect, isAutoConnectDisabled }: Props) => {
+ const { themedBackgroundOrange } = useMenuButtonColors();
+
const onAddressClick = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Address click' });
}, []);
return (
+ { isAutoConnectDisabled && (
+
+
+
+ Connect your wallet in the app below
+
+
+ ) }
{
+const WalletMenuDesktop = ({ isHomePage, className, size = 'md' }: Props) => {
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet({ source: 'Header' });
- const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
+ const { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor } = useMenuButtonColors();
const [ isPopoverOpen, setIsPopoverOpen ] = useBoolean(false);
const isMobile = useIsMobile();
+ const { isAutoConnectDisabled } = useMarketplaceContext();
const variant = React.useMemo(() => {
if (isWalletConnected) {
@@ -29,13 +33,16 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
return isHomePage ? 'solid' : 'outline';
}, [ isWalletConnected, isHomePage ]);
+ const themedColorForOrangeBg = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
let buttonStyles: Partial = {};
if (isWalletConnected) {
+ const backgroundColor = isAutoConnectDisabled ? themedBackgroundOrange : themedBackground;
+ const color = isAutoConnectDisabled ? themedColorForOrangeBg : themedColor;
buttonStyles = {
- bg: isHomePage ? 'blue.50' : themedBackground,
- color: isHomePage ? 'blackAlpha.800' : themedColor,
+ bg: isHomePage ? 'blue.50' : backgroundColor,
+ color: isHomePage ? 'blackAlpha.800' : color,
_hover: {
- color: isHomePage ? 'blackAlpha.800' : themedColor,
+ color: isHomePage ? 'blackAlpha.800' : color,
},
};
} else if (isHomePage) {
@@ -51,7 +58,7 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
- setIsPopoverOpen.on();
+ setIsPopoverOpen.toggle();
}, [ setIsPopoverOpen ]);
return (
@@ -63,10 +70,15 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
isOpen={ isPopoverOpen }
onClose={ setIsPopoverOpen.off }
>
-
-
-
+
+
+
-
-
-
+
+
+
{ isWalletConnected && (
-
+
) }
@@ -99,4 +110,4 @@ const WalletMenuDesktop = ({ isHomePage }: Props) => {
);
};
-export default WalletMenuDesktop;
+export default chakra(WalletMenuDesktop);
diff --git a/ui/snippets/walletMenu/WalletMenuMobile.tsx b/ui/snippets/walletMenu/WalletMenuMobile.tsx
index e8e6bcb48a..0711a107fd 100644
--- a/ui/snippets/walletMenu/WalletMenuMobile.tsx
+++ b/ui/snippets/walletMenu/WalletMenuMobile.tsx
@@ -1,41 +1,50 @@
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclosure, IconButton } from '@chakra-ui/react';
import React from 'react';
+import { useMarketplaceContext } from 'lib/contexts/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
-import AddressIdenticon from 'ui/shared/entities/address/AddressIdenticon';
import IconSvg from 'ui/shared/IconSvg';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import WalletMenuContent from 'ui/snippets/walletMenu/WalletMenuContent';
import useMenuButtonColors from '../useMenuButtonColors';
+import WalletIdenticon from './WalletIdenticon';
import WalletTooltip from './WalletTooltip';
const WalletMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { isWalletConnected, address, connect, disconnect, isModalOpening, isModalOpen } = useWallet({ source: 'Header' });
- const { themedBackground, themedBorderColor, themedColor } = useMenuButtonColors();
+ const { themedBackground, themedBackgroundOrange, themedBorderColor, themedColor } = useMenuButtonColors();
const isMobile = useIsMobile();
+ const { isAutoConnectDisabled } = useMarketplaceContext();
const openPopover = React.useCallback(() => {
mixpanel.logEvent(mixpanel.EventTypes.WALLET_ACTION, { Action: 'Open' });
onOpen();
}, [ onOpen ]);
+ const themedBg = isAutoConnectDisabled ? themedBackgroundOrange : themedBackground;
+
return (
<>
-
+
:
+ :
}
variant={ isWalletConnected ? 'subtle' : 'outline' }
colorScheme="gray"
boxSize="40px"
flexShrink={ 0 }
- bg={ isWalletConnected ? themedBackground : undefined }
+ bg={ isWalletConnected ? themedBg : undefined }
color={ themedColor }
borderColor={ !isWalletConnected ? themedBorderColor : undefined }
onClick={ isWalletConnected ? openPopover : connect }
@@ -52,7 +61,7 @@ const WalletMenuMobile = () => {
-
+
diff --git a/ui/snippets/walletMenu/WalletTooltip.tsx b/ui/snippets/walletMenu/WalletTooltip.tsx
index fc03e6dcee..d8ad0bcf79 100644
--- a/ui/snippets/walletMenu/WalletTooltip.tsx
+++ b/ui/snippets/walletMenu/WalletTooltip.tsx
@@ -1,4 +1,4 @@
-import { Tooltip, useBoolean, useOutsideClick } from '@chakra-ui/react';
+import { Tooltip, useBoolean, useOutsideClick, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
@@ -9,38 +9,46 @@ type Props = {
children: React.ReactNode;
isDisabled?: boolean;
isMobile?: boolean;
+ isWalletConnected?: boolean;
+ isAutoConnectDisabled?: boolean;
};
-const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
+const LOCAL_STORAGE_KEY = 'wallet-connect-tooltip-shown';
+
+const WalletTooltip = ({ children, isDisabled, isMobile, isWalletConnected, isAutoConnectDisabled }: Props, ref: React.ForwardedRef) => {
const router = useRouter();
const [ isTooltipShown, setIsTooltipShown ] = useBoolean(false);
- const ref = React.useRef(null);
- useOutsideClick({ ref, handler: setIsTooltipShown.off });
+ const innerRef = React.useRef(null);
+ useOutsideClick({ ref: innerRef, handler: setIsTooltipShown.off });
+
+ const label = React.useMemo(() => {
+ if (isWalletConnected) {
+ if (isAutoConnectDisabled) {
+ return Your wallet is not
connected to this app.
Connect your wallet
in the app directly;
+ }
+ return Your wallet is connected
with Blockscout;
+ }
+ return Connect your wallet
to Blockscout for
full-featured access;
+ }, [ isWalletConnected, isAutoConnectDisabled ]);
- const { defaultLabel, label, localStorageKey } = React.useMemo(() => {
- const isAppPage = router.pathname === '/apps/[id]';
- const defaultLabel = Your wallet is used to interact with
apps and contracts in the explorer;
- const label = isAppPage ?
- Connect once to use your wallet with
all apps in the DAppscout marketplace! :
- defaultLabel;
- const localStorageKey = `${ isAppPage ? 'dapp-' : '' }wallet-connect-tooltip-shown`;
- return { defaultLabel, label, localStorageKey };
- }, [ router.pathname ]);
+ const isAppPage = router.pathname === '/apps/[id]';
React.useEffect(() => {
- const wasShown = window.localStorage.getItem(localStorageKey);
- const isMarketplacePage = [ '/apps', '/apps/[id]' ].includes(router.pathname);
+ const wasShown = window.localStorage.getItem(LOCAL_STORAGE_KEY);
+ const isMarketplacePage = router.pathname === '/apps';
const isTooltipShowAction = router.query.action === 'tooltip';
const isConnectWalletAction = router.query.action === 'connect';
- const needToShow = (!wasShown && !isConnectWalletAction) || isTooltipShowAction;
+ const needToShow = (isAppPage && !isConnectWalletAction) || isTooltipShowAction || (!wasShown && isMarketplacePage);
let timer1: ReturnType;
let timer2: ReturnType;
- if (!isDisabled && isMarketplacePage && needToShow) {
+ if (!isDisabled && needToShow) {
timer1 = setTimeout(() => {
setIsTooltipShown.on();
- window.localStorage.setItem(localStorageKey, 'true');
timer2 = setTimeout(() => setIsTooltipShown.off(), 5 * SECOND);
+ if (!wasShown && isMarketplacePage) {
+ window.localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
+ }
if (isTooltipShowAction) {
removeQueryParam(router, 'action');
}
@@ -51,23 +59,25 @@ const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
clearTimeout(timer1);
clearTimeout(timer2);
};
- }, [ setIsTooltipShown, localStorageKey, isDisabled, router ]);
+ }, [ setIsTooltipShown, isDisabled, router, isAppPage ]);
return (
-
- { children }
-
+
+
+ { children }
+
+
);
};
-export default WalletTooltip;
+export default React.forwardRef(WalletTooltip);
diff --git a/ui/stats/ChartsWidgetsList.tsx b/ui/stats/ChartsWidgetsList.tsx
index 102ce73a7d..5002e599b5 100644
--- a/ui/stats/ChartsWidgetsList.tsx
+++ b/ui/stats/ChartsWidgetsList.tsx
@@ -15,16 +15,26 @@ import ChartWidgetContainer from './ChartWidgetContainer';
type Props = {
filterQuery: string;
+ initialFilterQuery: string;
isError: boolean;
isPlaceholderData: boolean;
charts?: Array;
interval: StatsIntervalIds;
}
-const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, interval }: Props) => {
+const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, interval, initialFilterQuery }: Props) => {
const [ isSomeChartLoadingError, setIsSomeChartLoadingError ] = useState(false);
const isAnyChartDisplayed = charts?.some((section) => section.charts.length > 0);
const isEmptyChartList = Boolean(filterQuery) && !isAnyChartDisplayed;
+ const sectionRef = React.useRef(null);
+
+ const shouldScrollToSection = Boolean(initialFilterQuery);
+
+ React.useEffect(() => {
+ if (shouldScrollToSection) {
+ sectionRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, [ shouldScrollToSection ]);
const homeStatsQuery = useApiQuery('stats', {
queryOptions: {
@@ -50,7 +60,7 @@ const ChartsWidgetsList = ({ filterQuery, isError, isPlaceholderData, charts, in
) }
-
+
{
charts?.map((section) => (
-
+
{ section.title }
{ section.id === 'gas' && homeStatsQuery.data && homeStatsQuery.data.gas_prices && (
diff --git a/ui/stats/StatsFilters.tsx b/ui/stats/StatsFilters.tsx
index 23f1bfce47..df96c3ac35 100644
--- a/ui/stats/StatsFilters.tsx
+++ b/ui/stats/StatsFilters.tsx
@@ -1,4 +1,4 @@
-import { Grid, GridItem } from '@chakra-ui/react';
+import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { StatsChartsSection } from 'types/api/stats';
@@ -21,6 +21,8 @@ type Props = {
interval: StatsIntervalIds;
onIntervalChange: (newInterval: StatsIntervalIds) => void;
onFilterInputChange: (q: string) => void;
+ isLoading: boolean;
+ initialFilterValue: string;
}
const StatsFilters = ({
@@ -30,8 +32,9 @@ const StatsFilters = ({
interval,
onIntervalChange,
onFilterInputChange,
+ isLoading,
+ initialFilterValue,
}: Props) => {
-
const sectionsList = [ {
id: 'all',
title: 'All',
@@ -51,22 +54,26 @@ const StatsFilters = ({
w={{ base: '100%', lg: 'auto' }}
area="section"
>
-
+ { isLoading ? : (
+
+ ) }
-
+ { isLoading ? : (
+
+ ) }
+ placeholder="Find chart, metric..."
+ initialValue={ initialFilterValue }
+ />
);
diff --git a/ui/stats/useStats.tsx b/ui/stats/useStats.tsx
index 565c413b11..96117b9622 100644
--- a/ui/stats/useStats.tsx
+++ b/ui/stats/useStats.tsx
@@ -1,10 +1,11 @@
+import { useRouter } from 'next/router';
import React, { useCallback, useMemo, useState } from 'react';
import type { StatsChartInfo, StatsChartsSection } from 'types/api/stats';
import type { StatsIntervalIds } from 'types/client/stats';
import useApiQuery from 'lib/api/useApiQuery';
-import useDebounce from 'lib/hooks/useDebounce';
+import getQueryParamString from 'lib/router/getQueryParamString';
import { STATS_CHARTS } from 'stubs/stats';
function isSectionMatches(section: StatsChartsSection, currentSection: string): boolean {
@@ -16,6 +17,8 @@ function isChartNameMatches(q: string, chart: StatsChartInfo) {
}
export default function useStats() {
+ const router = useRouter();
+
const { data, isPlaceholderData, isError } = useApiQuery('stats_lines', {
queryOptions: {
placeholderData: STATS_CHARTS,
@@ -24,22 +27,35 @@ export default function useStats() {
const [ currentSection, setCurrentSection ] = useState('all');
const [ filterQuery, setFilterQuery ] = useState('');
+ const [ initialFilterQuery, setInitialFilterQuery ] = React.useState('');
const [ interval, setInterval ] = useState('oneMonth');
const sectionIds = useMemo(() => data?.sections?.map(({ id }) => id), [ data ]);
- const debouncedFilterQuery = useDebounce(filterQuery, 500);
+ React.useEffect(() => {
+ if (!isPlaceholderData && !isError) {
+ const chartId = getQueryParamString(router.query.chartId);
+ const chartName = data?.sections.map((section) => section.charts.find((chart) => chart.id === chartId)).filter(Boolean)[0]?.title;
+ if (chartName) {
+ setInitialFilterQuery(chartName);
+ setFilterQuery(chartName);
+ router.replace({ pathname: '/stats' }, undefined, { scroll: false });
+ }
+ }
+ // run only when data is loaded
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [ isPlaceholderData ]);
const displayedCharts = React.useMemo(() => {
return data?.sections
?.map((section) => {
- const charts = section.charts.filter((chart) => isSectionMatches(section, currentSection) && isChartNameMatches(debouncedFilterQuery, chart));
+ const charts = section.charts.filter((chart) => isSectionMatches(section, currentSection) && isChartNameMatches(filterQuery, chart));
return {
...section,
charts,
};
}).filter((section) => section.charts.length > 0);
- }, [ currentSection, data?.sections, debouncedFilterQuery ]);
+ }, [ currentSection, data?.sections, filterQuery ]);
const handleSectionChange = useCallback((newSection: string) => {
setCurrentSection(newSection);
@@ -58,6 +74,7 @@ export default function useStats() {
sectionIds,
isPlaceholderData,
isError,
+ initialFilterQuery,
filterQuery,
currentSection,
handleSectionChange,
@@ -70,6 +87,7 @@ export default function useStats() {
sectionIds,
isPlaceholderData,
isError,
+ initialFilterQuery,
filterQuery,
currentSection,
handleSectionChange,
diff --git a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc20-mobile-1.png b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc20-mobile-1.png
index 78333858fb..ce065a90f0 100644
Binary files a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc20-mobile-1.png and b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc20-mobile-1.png differ
diff --git a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc721-mobile-1.png b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc721-mobile-1.png
index 29f273fc2b..e74f0267bd 100644
Binary files a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc721-mobile-1.png and b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_default_erc721-mobile-1.png differ
diff --git a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc20-mobile-1.png b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc20-mobile-1.png
index 7270ffe8b1..976f010181 100644
Binary files a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc20-mobile-1.png and b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc20-mobile-1.png differ
diff --git a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc721-mobile-1.png b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc721-mobile-1.png
index 559d804613..c5e0e1564d 100644
Binary files a/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc721-mobile-1.png and b/ui/token/TokenTransfer/__screenshots__/TokenTransfer.pw.tsx_mobile_erc721-mobile-1.png differ
diff --git a/ui/tx/__screenshots__/TxInternals.pw.tsx_default_base-view-mobile-1.png b/ui/tx/__screenshots__/TxInternals.pw.tsx_default_base-view-mobile-1.png
index 6edf2b297f..ee3f7056c3 100644
Binary files a/ui/tx/__screenshots__/TxInternals.pw.tsx_default_base-view-mobile-1.png and b/ui/tx/__screenshots__/TxInternals.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/tx/__screenshots__/TxInternals.pw.tsx_mobile_base-view-mobile-1.png b/ui/tx/__screenshots__/TxInternals.pw.tsx_mobile_base-view-mobile-1.png
index ee76f592c7..5c6240cf78 100644
Binary files a/ui/tx/__screenshots__/TxInternals.pw.tsx_mobile_base-view-mobile-1.png and b/ui/tx/__screenshots__/TxInternals.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/tx/__screenshots__/TxState.pw.tsx_default_base-view-mobile-1.png b/ui/tx/__screenshots__/TxState.pw.tsx_default_base-view-mobile-1.png
index f3e85c4fa4..642c85f70d 100644
Binary files a/ui/tx/__screenshots__/TxState.pw.tsx_default_base-view-mobile-1.png and b/ui/tx/__screenshots__/TxState.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/tx/__screenshots__/TxState.pw.tsx_mobile_base-view-mobile-1.png b/ui/tx/__screenshots__/TxState.pw.tsx_mobile_base-view-mobile-1.png
index 71988d6ce4..77c33765d1 100644
Binary files a/ui/tx/__screenshots__/TxState.pw.tsx_mobile_base-view-mobile-1.png and b/ui/tx/__screenshots__/TxState.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-has-method-called-1.png b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-has-method-called-1.png
index 9fb4c06b48..5f168b79d5 100644
Binary files a/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-has-method-called-1.png and b/ui/tx/__screenshots__/TxSubHeading.pw.tsx_default_no-interpretation-has-method-called-1.png differ
diff --git a/ui/tx/details/TxDetailsWithdrawalStatus.tsx b/ui/tx/details/TxDetailsWithdrawalStatus.tsx
index b2b7c88ce4..a1b4af7d8a 100644
--- a/ui/tx/details/TxDetailsWithdrawalStatus.tsx
+++ b/ui/tx/details/TxDetailsWithdrawalStatus.tsx
@@ -4,6 +4,7 @@ import React from 'react';
import type { OptimisticL2WithdrawalStatus } from 'types/api/optimisticL2';
import { WITHDRAWAL_STATUSES } from 'types/api/optimisticL2';
+import config from 'configs/app';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
@@ -12,8 +13,10 @@ interface Props {
l1TxHash: string | undefined;
}
+const rollupFeature = config.features.rollup;
+
const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
- if (!status || !WITHDRAWAL_STATUSES.includes(status)) {
+ if (!status || !WITHDRAWAL_STATUSES.includes(status) || !rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null;
}
@@ -46,7 +49,7 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
variant="outline"
size="sm"
as="a"
- href="https://app.optimism.io/bridge/withdraw"
+ href={ rollupFeature.L2WithdrawalUrl }
target="_blank"
>
Claim funds
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png
index 91953a5e74..e62d5116ce 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_with-actions-uniswap-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_with-actions-uniswap-mobile-dark-mode-1.png
index 8a71ebe581..345cf10948 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_with-actions-uniswap-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_dark-color-mode_with-actions-uniswap-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png
index 1e3bee9a6c..9dcf3e54e0 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_creating-contact-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_creating-contact-1.png
index 5fb55385df..3dbaa56157 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_creating-contact-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_creating-contact-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_l2-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_l2-1.png
index e86df21951..4bdb91a0bc 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_l2-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_l2-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png
index 560ea1cf2e..de3f4e9191 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_pending-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_stability-customization-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_stability-customization-1.png
index 8dffcc2499..93e060022d 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_stability-customization-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_stability-customization-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-actions-uniswap-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-actions-uniswap-mobile-dark-mode-1.png
index cef58d0109..11f5bbf5ea 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-actions-uniswap-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-actions-uniswap-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png
index 771512447d..1ce20160f1 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-blob-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-raw-reason-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-raw-reason-1.png
index e95db83c7c..0e942b7d8d 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-raw-reason-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-raw-reason-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-revert-reason-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-revert-reason-1.png
index 6ad9c3e012..cf66bc0911 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-revert-reason-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-decoded-revert-reason-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png
index c8d0a04f44..60899f1733 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_with-token-transfer-mobile-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_without-testnet-warning-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_without-testnet-warning-1.png
index 736a3bcad7..ec1255d1ad 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_without-testnet-warning-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_default_without-testnet-warning-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png
index 0aadfa46a6..dfcad5c049 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_between-addresses-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-actions-uniswap-mobile-dark-mode-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-actions-uniswap-mobile-dark-mode-1.png
index 647188a354..f4cd1283de 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-actions-uniswap-mobile-dark-mode-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-actions-uniswap-mobile-dark-mode-1.png differ
diff --git a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png
index d4476b8a86..7eaca093a8 100644
Binary files a/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png and b/ui/tx/details/__screenshots__/TxInfo.pw.tsx_mobile_with-token-transfer-mobile-1.png differ
diff --git a/ui/tx/internals/TxInternalsList.tsx b/ui/tx/internals/TxInternalsList.tsx
index f756c12eab..b057015525 100644
--- a/ui/tx/internals/TxInternalsList.tsx
+++ b/ui/tx/internals/TxInternalsList.tsx
@@ -8,7 +8,7 @@ import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data, isLoading }: { data: Array; isLoading?: boolean }) => {
return (
- { data.map((item, index) => ) }
+ { data.map((item, index) => ) }
);
};
diff --git a/ui/tx/internals/TxInternalsTable.tsx b/ui/tx/internals/TxInternalsTable.tsx
index 2a833310c6..aeb8ce7e48 100644
--- a/ui/tx/internals/TxInternalsTable.tsx
+++ b/ui/tx/internals/TxInternalsTable.tsx
@@ -44,7 +44,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
{ data.map((item, index) => (
-
+
)) }
diff --git a/ui/txs/TxsStats.pw.tsx b/ui/txs/TxsStats.pw.tsx
new file mode 100644
index 0000000000..704e084249
--- /dev/null
+++ b/ui/txs/TxsStats.pw.tsx
@@ -0,0 +1,31 @@
+import { test, expect } from '@playwright/experimental-ct-react';
+import React from 'react';
+
+import * as statsMock from 'mocks/stats';
+import * as txsStatsMock from 'mocks/txs/stats';
+import TestApp from 'playwright/TestApp';
+import buildApiUrl from 'playwright/utils/buildApiUrl';
+
+import TxsStats from './TxsStats';
+
+const TXS_STATS_API_URL = buildApiUrl('txs_stats');
+const STATS_API_URL = buildApiUrl('stats');
+
+test('base view +@mobile', async({ mount, page }) => {
+ await page.route(STATS_API_URL, (route) => route.fulfill({
+ status: 200,
+ body: JSON.stringify(statsMock.base),
+ }));
+ await page.route(TXS_STATS_API_URL, (route) => route.fulfill({
+ status: 200,
+ body: JSON.stringify(txsStatsMock.base),
+ }));
+
+ const component = await mount(
+
+
+ ,
+ );
+
+ await expect(component).toHaveScreenshot();
+});
diff --git a/ui/txs/TxsStats.tsx b/ui/txs/TxsStats.tsx
new file mode 100644
index 0000000000..256cdab6a2
--- /dev/null
+++ b/ui/txs/TxsStats.tsx
@@ -0,0 +1,81 @@
+import { Box } from '@chakra-ui/react';
+import React from 'react';
+
+import config from 'configs/app';
+import useApiQuery from 'lib/api/useApiQuery';
+import getCurrencyValue from 'lib/getCurrencyValue';
+import { thinsp } from 'lib/html-entities';
+import { HOMEPAGE_STATS } from 'stubs/stats';
+import { TXS_STATS } from 'stubs/tx';
+import StatsWidget from 'ui/shared/stats/StatsWidget';
+
+const TxsStats = () => {
+ const txsStatsQuery = useApiQuery('txs_stats', {
+ queryOptions: {
+ placeholderData: TXS_STATS,
+ },
+ });
+
+ const statsQuery = useApiQuery('stats', {
+ queryOptions: {
+ placeholderData: HOMEPAGE_STATS,
+ },
+ });
+
+ if (!txsStatsQuery.data) {
+ return null;
+ }
+
+ const txFeeAvg = getCurrencyValue({
+ value: txsStatsQuery.data.transaction_fees_avg_24h,
+ exchangeRate: statsQuery.data?.coin_price,
+ decimals: String(config.chain.currency.decimals),
+ accuracyUsd: 2,
+ });
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(TxsStats);
diff --git a/ui/txs/__screenshots__/TxsListItem.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/txs/__screenshots__/TxsListItem.pw.tsx_dark-color-mode_base-view-dark-mode-1.png
index 3e12af58d1..152eb5ef85 100644
Binary files a/ui/txs/__screenshots__/TxsListItem.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/txs/__screenshots__/TxsListItem.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ
diff --git a/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_base-view-dark-mode-1.png b/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_base-view-dark-mode-1.png
index 0cb72cb8d0..b81038e105 100644
Binary files a/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_base-view-dark-mode-1.png and b/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_base-view-dark-mode-1.png differ
diff --git a/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_with-base-address-1.png b/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_with-base-address-1.png
index 175908210a..21e4bd1616 100644
Binary files a/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_with-base-address-1.png and b/ui/txs/__screenshots__/TxsListItem.pw.tsx_default_with-base-address-1.png differ
diff --git a/ui/txs/__screenshots__/TxsStats.pw.tsx_default_base-view-mobile-1.png b/ui/txs/__screenshots__/TxsStats.pw.tsx_default_base-view-mobile-1.png
new file mode 100644
index 0000000000..0c97211555
Binary files /dev/null and b/ui/txs/__screenshots__/TxsStats.pw.tsx_default_base-view-mobile-1.png differ
diff --git a/ui/txs/__screenshots__/TxsStats.pw.tsx_mobile_base-view-mobile-1.png b/ui/txs/__screenshots__/TxsStats.pw.tsx_mobile_base-view-mobile-1.png
new file mode 100644
index 0000000000..4e6819c7ac
Binary files /dev/null and b/ui/txs/__screenshots__/TxsStats.pw.tsx_mobile_base-view-mobile-1.png differ
diff --git a/ui/txs/__screenshots__/TxsTable.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/txs/__screenshots__/TxsTable.pw.tsx_dark-color-mode_base-view-dark-mode-1.png
index f2715618aa..0418f67fdf 100644
Binary files a/ui/txs/__screenshots__/TxsTable.pw.tsx_dark-color-mode_base-view-dark-mode-1.png and b/ui/txs/__screenshots__/TxsTable.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ
diff --git a/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-dark-mode-1.png b/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-dark-mode-1.png
index 1f20725451..45373138cc 100644
Binary files a/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-dark-mode-1.png and b/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-dark-mode-1.png differ
diff --git a/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-screen-xl-1.png b/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-screen-xl-1.png
index 79573c1691..3df5a6aac3 100644
Binary files a/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-screen-xl-1.png and b/ui/txs/__screenshots__/TxsTable.pw.tsx_default_base-view-screen-xl-1.png differ
diff --git a/ui/verifiedContracts/VerifiedContractsCounters.tsx b/ui/verifiedContracts/VerifiedContractsCounters.tsx
index 4d7dcd562f..71e10fbe79 100644
--- a/ui/verifiedContracts/VerifiedContractsCounters.tsx
+++ b/ui/verifiedContracts/VerifiedContractsCounters.tsx
@@ -24,6 +24,7 @@ const VerifiedContractsCounters = () => {
diff={ countersQuery.data.new_smart_contracts_24h }
diffFormatted={ Number(countersQuery.data.new_smart_contracts_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
+ href={{ pathname: '/stats', query: { chartId: 'contractsGrowth' } }}
/>
{
diff={ countersQuery.data.new_verified_smart_contracts_24h }
diffFormatted={ Number(countersQuery.data.new_verified_smart_contracts_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
+ href={{ pathname: '/stats', query: { chartId: 'verifiedContractsGrowth' } }}
/>
);