Commit bcc6c448 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into common-skeleton

parents 52421f23 3fda7486
...@@ -57,7 +57,7 @@ jobs: ...@@ -57,7 +57,7 @@ jobs:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true push: true
cache-from: type=registry,ref=ghcr.io/blockscout/frontend::buildcache cache-from: type=gha
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.GITHUB_REF_NAME_SLUG }} tags: ghcr.io/blockscout/frontend:prerelease-${{ env.GITHUB_REF_NAME_SLUG }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
......
...@@ -44,8 +44,8 @@ jobs: ...@@ -44,8 +44,8 @@ jobs:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true push: true
cache-from: type=registry,ref=ghcr.io/blockscout/frontend::buildcache cache-from: type=gha
cache-to: type=registry,ref=ghcr.io/blockscout/frontend::buildcache,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"detail": "start local dev server", "detail": "start local dev server",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
"detail": "start local dev server for POA network", "detail": "start local dev server for POA network",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
"detail": "start local dev server for Goerli network", "detail": "start local dev server for Goerli network",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
}, },
"presentation": { "presentation": {
"reveal": "never", "reveal": "never",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
"detail": "run eslint", "detail": "run eslint",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
"icon": { "icon": {
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
"detail": "run visual components tests for current file", "detail": "run visual components tests for current file",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"focus": true, "focus": true,
}, },
"icon": { "icon": {
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
"detail": "run visual components tests for current file", "detail": "run visual components tests for current file",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"focus": true, "focus": true,
}, },
"icon": { "icon": {
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
"detail": "run visual components tests", "detail": "run visual components tests",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"focus": true, "focus": true,
}, },
"icon": { "icon": {
...@@ -171,7 +171,7 @@ ...@@ -171,7 +171,7 @@
"detail": "run jest tests", "detail": "run jest tests",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"focus": true, "focus": true,
}, },
"icon": { "icon": {
...@@ -190,7 +190,7 @@ ...@@ -190,7 +190,7 @@
"detail": "run jest tests in watch mode", "detail": "run jest tests in watch mode",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"focus": true, "focus": true,
}, },
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
"detail": "run jest tests in watch mode for current file", "detail": "run jest tests in watch mode for current file",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"focus": true, "focus": true,
}, },
"icon": { "icon": {
...@@ -230,7 +230,7 @@ ...@@ -230,7 +230,7 @@
"detail": "build docker image", "detail": "build docker image",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
"focus": true, "focus": true,
...@@ -251,7 +251,7 @@ ...@@ -251,7 +251,7 @@
"detail": "run docker container for POA network", "detail": "run docker container for POA network",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -271,7 +271,7 @@ ...@@ -271,7 +271,7 @@
"detail": "format svg files with svgo", "detail": "format svg files with svgo",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "dedicated", "panel": "shared",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
......
...@@ -11,6 +11,8 @@ NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout ...@@ -11,6 +11,8 @@ NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_NETWORK_LOGO=
NEXT_PUBLIC_NETWORK_SMALL_LOGO=
# api config # api config
NEXT_PUBLIC_API_HOST=blockscout.com NEXT_PUBLIC_API_HOST=blockscout.com
......
...@@ -22,8 +22,12 @@ blockscout: ...@@ -22,8 +22,12 @@ blockscout:
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
annotations: {} annotations:
# - 'nginx.ingress.kubernetes.io/rewrite-target: /$2' # - 'nginx.ingress.kubernetes.io/rewrite-target: /$2'
- 'nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.blockscout-main.test.aws-k8s.blockscout.com, https://*.test.aws-k8s.blockscout.com, http://localhost:3000"'
- 'nginx.ingress.kubernetes.io/cors-allow-credentials: "true"'
- 'nginx.ingress.kubernetes.io/cors-allow-methods: PUT, GET, POST, OPTIONS, DELETE, PATCH'
- 'nginx.ingress.kubernetes.io/enable-cors: "true"'
host: host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com _default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
......
import type { Types } from 'typescript-cookie'; import Cookies from 'js-cookie';
import { Cookies } from 'typescript-cookie';
import isBrowser from './isBrowser'; import isBrowser from './isBrowser';
...@@ -14,10 +13,13 @@ export function get(name?: NAMES | undefined | null, serverCookie?: string) { ...@@ -14,10 +13,13 @@ export function get(name?: NAMES | undefined | null, serverCookie?: string) {
if (!isBrowser()) { if (!isBrowser()) {
return serverCookie ? getFromCookieString(serverCookie, name) : undefined; return serverCookie ? getFromCookieString(serverCookie, name) : undefined;
} }
return Cookies.get(name);
if (name) {
return Cookies.get(name);
}
} }
export function set(name: string, value: string, attributes: Types.CookieAttributes = {}) { export function set(name: string, value: string, attributes: Cookies.CookieAttributes = {}) {
attributes.path = '/'; attributes.path = '/';
return Cookies.set(name, value, attributes); return Cookies.set(name, value, attributes);
......
import type { FeaturedNetwork } from 'types/networks';
const FEATURED_NETWORKS: Array<FeaturedNetwork> = [
{ title: 'Gnosis Chain', url: 'https://blockscout.com/xdai/mainnet', group: 'mainnets', type: 'xdai_mainnet' },
{ title: 'Optimism on Gnosis Chain', url: 'https://blockscout.com/xdai/optimism', group: 'mainnets', type: 'xdai_optimism' },
{ title: 'Arbitrum on xDai', url: 'https://blockscout.com/xdai/aox', group: 'mainnets' },
{ title: 'Ethereum', url: 'https://blockscout.com/eth/mainnet', group: 'mainnets', type: 'eth_mainnet' },
{ title: 'Ethereum Classic', url: 'https://blockscout.com/etx/mainnet', group: 'mainnets', type: 'etc_mainnet', icon: 'https://example.com/my-logo.png' },
{ title: 'POA', url: 'https://blockscout.com/poa/core', group: 'mainnets', type: 'poa_core' },
{ title: 'RSK', url: 'https://blockscout.com/rsk/mainnet', group: 'mainnets', type: 'rsk_mainnet' },
{ title: 'Gnosis Chain Testnet', url: 'https://blockscout.com/xdai/testnet', group: 'testnets', type: 'xdai_testnet' },
{ title: 'POA Sokol', url: 'https://blockscout.com/poa/sokol', group: 'testnets', type: 'poa_sokol' },
{ title: 'ARTIS Σ1', url: 'https://blockscout.com/artis/sigma1', group: 'other', type: 'artis_sigma1' },
{ title: 'LUKSO L14', url: 'https://blockscout.com/lukso/l14', group: 'other', type: 'lukso_l14' },
{ title: 'Astar', url: 'https://blockscout.com/astar', group: 'other', type: 'astar' },
];
export const FEATURED_NETWORKS_MOCK = JSON.stringify(FEATURED_NETWORKS).replaceAll('"', '\'');
export const base = {
avatar: 'https://avatars.githubusercontent.com/u/22130104',
email: 'tom@ohhhh.me',
name: 'tom goriunov',
nickname: 'tom2drum',
};
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"ethers": "^5.7.1", "ethers": "^5.7.1",
"framer-motion": "^6.5.1", "framer-motion": "^6.5.1",
"js-cookie": "^3.0.1",
"lodash": "^4.0.0", "lodash": "^4.0.0",
"next": "12.2.5", "next": "12.2.5",
"node-fetch": "^3.2.9", "node-fetch": "^3.2.9",
...@@ -56,7 +57,6 @@ ...@@ -56,7 +57,6 @@
"react-identicons": "^1.2.5", "react-identicons": "^1.2.5",
"react-jazzicon": "^1.0.4", "react-jazzicon": "^1.0.4",
"react-scroll": "^1.8.7", "react-scroll": "^1.8.7",
"typescript-cookie": "^1.0.4",
"use-font-face-observer": "^1.2.1" "use-font-face-observer": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"@types/jest": "^29.2.0", "@types/jest": "^29.2.0",
"@types/js-cookie": "^3.0.2",
"@types/node": "17.0.36", "@types/node": "17.0.36",
"@types/phoenix": "^1.5.4", "@types/phoenix": "^1.5.4",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.0",
......
...@@ -24,7 +24,7 @@ const watchlistWithTokensHandler = async(_req: NextApiRequest, res: NextApiRespo ...@@ -24,7 +24,7 @@ const watchlistWithTokensHandler = async(_req: NextApiRequest, res: NextApiRespo
} }
const data = await Promise.all(watchlistData.map(async item => { const data = await Promise.all(watchlistData.map(async item => {
const tokens = await fetch(`?module=account&action=tokenlist&address=${ item.address_hash }`); const tokens = await fetch(`/api/?module=account&action=tokenlist&address=${ item.address_hash }`);
const tokensData = await tokens.json() as Tokenlist; const tokensData = await tokens.json() as Tokenlist;
return ({ ...item, tokens_count: Array.isArray(tokensData.result) ? tokensData.result.length : 0 }); return ({ ...item, tokens_count: Array.isArray(tokensData.result) ? tokensData.result.length : 0 });
......
import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const searchParamsStr = getSearchParams(req);
return `/v2/addresses/${ req.query.id }/internal-transactions${ searchParamsStr ? '?' + searchParamsStr : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
...@@ -96,6 +96,15 @@ const config: PlaywrightTestConfig = { ...@@ -96,6 +96,15 @@ const config: PlaywrightTestConfig = {
colorScheme: 'dark', colorScheme: 'dark',
}, },
}, },
{
name: 'dark color mode desktop xl',
grep: /\+@dark-mode-xl/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1600, height: 1000 },
colorScheme: 'dark',
},
},
], ],
}; };
......
...@@ -2,6 +2,8 @@ import { ChakraProvider } from '@chakra-ui/react'; ...@@ -2,6 +2,8 @@ import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import { AppContextProvider } from 'lib/appContext';
import type { Props as PageProps } from 'lib/next/getServerSideProps';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import { PORT } from 'playwright/fixtures/socketServer'; import { PORT } from 'playwright/fixtures/socketServer';
import theme from 'theme'; import theme from 'theme';
...@@ -9,9 +11,19 @@ import theme from 'theme'; ...@@ -9,9 +11,19 @@ import theme from 'theme';
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
withSocket?: boolean; withSocket?: boolean;
appContext?: {
pageProps: PageProps;
};
} }
const TestApp = ({ children, withSocket }: Props) => { const defaultAppContext = {
pageProps: {
cookies: '',
referrer: '',
},
};
const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => {
const [ queryClient ] = React.useState(() => new QueryClient({ const [ queryClient ] = React.useState(() => new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
...@@ -25,7 +37,9 @@ const TestApp = ({ children, withSocket }: Props) => { ...@@ -25,7 +37,9 @@ const TestApp = ({ children, withSocket }: Props) => {
<ChakraProvider theme={ theme }> <ChakraProvider theme={ theme }>
<QueryClientProvider client={ queryClient }> <QueryClientProvider client={ queryClient }>
<SocketProvider url={ withSocket ? `ws://localhost:${ PORT }` : undefined }> <SocketProvider url={ withSocket ? `ws://localhost:${ PORT }` : undefined }>
{ children } <AppContextProvider { ...appContext }>
{ children }
</AppContextProvider>
</SocketProvider> </SocketProvider>
</QueryClientProvider> </QueryClientProvider>
</ChakraProvider> </ChakraProvider>
......
import type { BrowserContext } from '@playwright/test';
import * as cookies from 'lib/cookies';
export default function authFixture(context: BrowserContext) {
context.addCookies([ { name: cookies.NAMES.API_TOKEN, value: 'foo', domain: 'localhost', path: '/' } ]);
}
...@@ -25,10 +25,8 @@ do ...@@ -25,10 +25,8 @@ do
configValue="$(cut -d'=' -f2- <<<"$line")"; configValue="$(cut -d'=' -f2- <<<"$line")";
# if there is a value, escape it and add line to target file # if there is a value, escape it and add line to target file
if [ -n "$configValue" ]; then escapedConfigValue=$(echo $configValue | sed s/\'/\"/g);
escapedConfigValue=$(echo $configValue | sed s/\'/\"/g); echo "window.process.env.${configName} = localStorage.getItem('${configName}') || '${escapedConfigValue}';" >> $targetFile;
echo "window.process.env.${configName} = localStorage.getItem('${configName}') || '${escapedConfigValue}';" >> $targetFile;
fi
done < $envFile done < $envFile
done done
......
import { Button } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
[
{ variant: 'solid' },
{ variant: 'solid', colorScheme: 'gray', withDarkMode: true },
{ variant: 'outline', colorScheme: 'gray', withDarkMode: true },
{ variant: 'outline', colorScheme: 'gray-dark', withDarkMode: true },
{ variant: 'outline', colorScheme: 'blue', withDarkMode: true },
{ variant: 'simple', withDarkMode: true },
{ variant: 'ghost', withDarkMode: true },
{ variant: 'subtle' },
{ variant: 'subtle', colorScheme: 'gray', withDarkMode: true },
].forEach(({ variant, colorScheme, withDarkMode }) => {
test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => {
test('base', async({ mount }) => {
const component = await mount(
<TestApp>
<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>
</TestApp>,
);
await expect(component.locator('button')).toHaveScreenshot();
});
test('disabled', async({ mount }) => {
const component = await mount(
<TestApp>
<Button variant={ variant } colorScheme={ colorScheme } disabled>Click me</Button>
</TestApp>,
);
await expect(component.locator('button')).toHaveScreenshot();
});
test('hovered', async({ mount }) => {
const component = await mount(
<TestApp>
<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>
</TestApp>,
);
await component.getByText(/click/i).hover();
await expect(component.locator('button')).toHaveScreenshot();
});
test('active', async({ mount }) => {
const component = await mount(
<TestApp>
<Button variant={ variant } colorScheme={ colorScheme } isActive>Click me</Button>
</TestApp>,
);
await expect(component.locator('button')).toHaveScreenshot();
});
});
});
import { FormControl, Input, FormLabel } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
test.use({ viewport: { width: 500, height: 300 } });
test.describe('floating label size md +@dark-mode', () => {
test('empty', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="md">
<Input required value=""/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.locator('input').focus();
await expect(component).toHaveScreenshot();
});
test('empty error', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="md">
<Input required value="" isInvalid/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.locator('input').focus();
await expect(component).toHaveScreenshot();
});
test('filled', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="md">
<Input required value="foo"/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('filled disabled', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="md">
<Input required value="foo" isDisabled/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test('filled error', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="md">
<Input required value="foo" isInvalid/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
});
test.describe('floating label size lg +@dark-mode', () => {
test('empty', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="lg">
<Input required value=""/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.locator('input').focus();
await expect(component).toHaveScreenshot();
});
test('filled', async({ mount }) => {
const component = await mount(
<TestApp>
<FormControl variant="floating" id="name" isRequired size="lg">
<Input required value="foo"/>
<FormLabel>Smart contract / Address (0x...)</FormLabel>
</FormControl>
</TestApp>,
);
await expect(component).toHaveScreenshot();
await component.locator('input').focus();
await expect(component).toHaveScreenshot();
});
});
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment