Commit 46e795fe authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #908 from blockscout/test/hook-for-paginated-queries

test: pagination refactoring and test
parents b2c51065 400424a0
......@@ -199,7 +199,7 @@ module.exports = {
groups: [
'module',
'/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^stubs/', '/^theme/', '/^ui/' ],
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^jest/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^stubs/', '/^theme/', '/^ui/' ],
[ 'parent', 'sibling', 'index' ],
],
alphabetize: { order: 'asc', ignoreCase: true },
......@@ -289,7 +289,7 @@ module.exports = {
},
},
{
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts' ],
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts', 'playwright/**/*.ts' ],
rules: {
// for configs allow to consume env variables from process.env directly
'no-restricted-properties': [ 0 ],
......
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest: watch current file",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": [
"${fileBasename}",
"--runInBand",
"--verbose",
"-i",
"--no-cache",
"--watchAll",
"--testTimeout=1000000000",
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
\ No newline at end of file
# app config
NEXT_PUBLIC_APP_HOST=blockscout.com
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_INSTANCE=jest
NEXT_PUBLIC_APP_ENV=testing
# ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_GIT_TAG=v1.0.11
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap']
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=
NEXT_PUBLIC_FEATURED_NETWORKS=
NEXT_PUBLIC_FOOTER_LINKS=
NEXT_PUBLIC_NETWORK_LOGO=
NEXT_PUBLIC_NETWORK_LOGO_DARK=
NEXT_PUBLIC_NETWORK_ICON=
NEXT_PUBLIC_NETWORK_ICON_DARK=
NEXT_PUBLIC_NETWORK_RPC_URL=https://localhost:1111
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json
NEXT_PUBLIC_IS_L2_NETWORK=false
# network config
NEXT_PUBLIC_NETWORK_NAME=Blockscout
NEXT_PUBLIC_NETWORK_SHORT_NAME=Blockscout
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=
NEXT_PUBLIC_NETWORK_ID=1
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_HOST=localhost
NEXT_PUBLIC_API_PORT=3003
NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_API_BASE_PATH=/
......@@ -38,7 +38,8 @@ NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
# api config
NEXT_PUBLIC_API_HOST=https://localhost:3003
NEXT_PUBLIC_API_HOST=localhost
NEXT_PUBLIC_API_PORT=3003
NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
......
......@@ -7,198 +7,35 @@ import type { JestConfigWithTsJest } from 'ts-jest';
*/
const config: JestConfigWithTsJest = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/3v/ry82j1y52sqgz3yvbcbt1krw0000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
globalSetup: '<rootDir>/configs/jest/globalSetup.ts',
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
globalSetup: '<rootDir>/jest/global-setup.ts',
moduleDirectories: [
'node_modules',
__dirname,
],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
moduleNameMapper: {
'^jest/(.*)': '<rootDir>/jest/$1',
},
modulePathIgnorePatterns: [
'node_modules_linux',
],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
reporters: [ 'default', 'github-actions' ],
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: undefined,
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
setupFiles: [
'<rootDir>/jest/setup.ts',
],
testEnvironment: 'jsdom',
// testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: 'tsconfig.jest.json',
},
],
},
};
export default config;
......@@ -2,5 +2,4 @@ import dotenv from 'dotenv';
export default async function globalSetup() {
dotenv.config({ path: './configs/envs/.env.jest' });
dotenv.config({ path: './configs/envs/.env.poa_core' });
}
import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { RenderOptions } from '@testing-library/react';
import { render } from '@testing-library/react';
import React from 'react';
import { AppContextProvider } from 'lib/contexts/app';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { SocketProvider } from 'lib/socket/context';
import theme from 'theme';
import 'lib/setLocale';
const PAGE_PROPS = {
cookies: '',
referrer: '',
};
const TestApp = ({ children }: {children: React.ReactNode}) => {
const [ queryClient ] = React.useState(() => new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: 0,
},
},
}));
return (
<ChakraProvider theme={ theme }>
<QueryClientProvider client={ queryClient }>
<AppContextProvider pageProps={ PAGE_PROPS }>
<ScrollDirectionProvider>
<SocketProvider>
{ children }
</SocketProvider>
</ScrollDirectionProvider>
</AppContextProvider>
</QueryClientProvider>
</ChakraProvider>
);
};
const customRender = (
ui: React.ReactElement,
options?: Omit<RenderOptions, 'wrapper'>,
) => render(ui, { wrapper: TestApp, ...options });
export * from '@testing-library/react';
export { customRender as render };
export { TestApp as wrapper };
import type { NextRouter } from 'next/router';
export const router = {
query: {},
push: jest.fn(() => Promise.resolve()),
};
export const useRouter = jest.fn<unknown, Array<Partial<NextRouter>>>(() => (router));
export const mockUseRouter = (params?: Partial<NextRouter>) => {
return {
useRouter: jest.fn(() => ({
...router,
...params,
})),
};
};
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
// eslint-disable-next-line no-console
const consoleError = console.error;
global.console = {
...console,
error: (...args) => {
// silence some irrelevant errors
if (args.some((arg) => typeof arg === 'string' && arg.includes('Using kebab-case for css properties'))) {
return;
}
consoleError(...args);
},
};
const scheduler = typeof setImmediate === 'function' ? setImmediate : setTimeout;
export default function flushPromises() {
return new Promise(function(resolve) {
scheduler(resolve);
});
}
import {
ChakraProvider,
ChakraProvider as ChakraProviderDefault,
cookieStorageManagerSSR,
localStorageManager,
} from '@chakra-ui/react';
......@@ -10,15 +10,15 @@ interface Props extends ChakraProviderProps {
cookies?: string;
}
export function Chakra({ cookies, theme, children }: Props) {
export function ChakraProvider({ cookies, theme, children }: Props) {
const colorModeManager =
typeof cookies === 'string' ?
cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) :
localStorageManager;
return (
<ChakraProvider colorModeManager={ colorModeManager } theme={ theme }>
<ChakraProviderDefault colorModeManager={ colorModeManager } theme={ theme }>
{ children }
</ChakraProvider>
</ChakraProviderDefault>
);
}
import { useEffect } from 'react';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import isBrowser from 'lib/isBrowser';
......
import appConfig from 'configs/app/config';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
export default function useHasAccount() {
......
import React from 'react';
export default function useIsInitialLoading(isLoading: boolean | undefined) {
const [ isInitialLoading, setIsInitialLoading ] = React.useState(Boolean(isLoading));
React.useEffect(() => {
if (!isLoading) {
setIsInitialLoading(false);
}
}, [ isLoading ]);
return isInitialLoading;
}
......@@ -25,7 +25,7 @@
"lint:tsc": "./node_modules/.bin/tsc -p ./tsconfig.json",
"prepare": "husky install",
"format-svg": "./node_modules/.bin/svgo -r ./icons",
"test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" playwright test -c playwright-ct.config.ts",
"test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" ./node_modules/.bin/dotenv -e ./configs/envs/.env.pw -- playwright test -c playwright-ct.config.ts",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && rm -rf ./playwright/.cache && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.32.0-focal ./playwright/run-tests.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
......@@ -88,7 +88,7 @@
"devDependencies": {
"@playwright/experimental-ct-react": "1.32.3",
"@svgr/webpack": "^6.5.1",
"@testing-library/react": "^13.4.0",
"@testing-library/react": "^14.0.0",
"@total-typescript/ts-reset": "^0.3.7",
"@types/crypto-js": "^4.1.1",
"@types/csp-dev": "^1.0.0",
......@@ -120,6 +120,7 @@
"husky": "^8.0.0",
"jest": "^29.2.1",
"jest-environment-jsdom": "^29.2.1",
"jest-fetch-mock": "^3.0.3",
"lint-staged": ">=10",
"mockdate": "^3.0.5",
"next-transpile-modules": "^10.0.0",
......
......@@ -6,8 +6,8 @@ import React, { useState } from 'react';
import appConfig from 'configs/app/config';
import type { ResourceError } from 'lib/api/resources';
import { AppContextProvider } from 'lib/appContext';
import { Chakra } from 'lib/Chakra';
import { AppContextProvider } from 'lib/contexts/app';
import { ChakraProvider } from 'lib/contexts/chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import useConfigSentry from 'lib/hooks/useConfigSentry';
......@@ -63,7 +63,7 @@ function MyApp({ Component, pageProps }: AppProps) {
}, []);
return (
<Chakra theme={ theme } cookies={ pageProps.cookies }>
<ChakraProvider theme={ theme } cookies={ pageProps.cookies }>
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
<AppContextProvider pageProps={ pageProps }>
<QueryClientProvider client={ queryClient }>
......@@ -77,7 +77,7 @@ function MyApp({ Component, pageProps }: AppProps) {
</QueryClientProvider>
</AppContextProvider>
</ErrorBoundary>
</Chakra>
</ChakraProvider>
);
}
......
......@@ -5,10 +5,10 @@ import React from 'react';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { AppContextProvider } from 'lib/appContext';
import { AppContextProvider } from 'lib/contexts/app';
import type { Props as PageProps } from 'lib/next/getServerSideProps';
import { SocketProvider } from 'lib/socket/context';
import { PORT } from 'playwright/fixtures/socketServer';
import * as app from 'playwright/utils/app';
import theme from 'theme';
type Props = {
......@@ -54,7 +54,7 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props
return (
<ChakraProvider theme={ theme }>
<QueryClientProvider client={ queryClient }>
<SocketProvider url={ withSocket ? `ws://localhost:${ PORT }` : undefined }>
<SocketProvider url={ withSocket ? `ws://${ app.domain }:${ app.socketPort }` : undefined }>
<AppContextProvider { ...appContext }>
<WagmiConfig config={ wagmiConfig }>
{ children }
......
import type { BrowserContext } from '@playwright/test';
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: 'localhost', path: '/' } ]);
context.addCookies([ { name: cookies.NAMES.API_TOKEN, value: 'foo', domain, path: '/' } ]);
}
import type { test } from '@playwright/experimental-ct-react';
import type { Browser } from '@playwright/test';
import * as app from 'playwright/utils/app';
interface Env {
name: string;
value: string;
......@@ -20,7 +22,7 @@ export async function createContextWithEnvs(browser: Browser, envs: Array<Env>)
return browser.newContext({
storageState: {
origins: [
{ origin: 'http://localhost:3100', localStorage: envs },
{ origin: app.url, localStorage: envs },
],
cookies: [],
},
......
......@@ -8,6 +8,8 @@ import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import type { Transaction } from 'types/api/transaction';
import * as app from 'playwright/utils/app';
type ReturnType = () => Promise<WebSocket>;
type Channel = [string, string, string];
......@@ -16,11 +18,9 @@ export interface SocketServerFixture {
createSocket: ReturnType;
}
export const PORT = 3200;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const createSocket: TestFixture<ReturnType, { page: Page}> = async({ page }, use) => {
const socketServer = new WebSocketServer({ port: PORT });
const socketServer = new WebSocketServer({ port: app.socketPort });
const connectionPromise = new Promise<WebSocket>((resolve) => {
socketServer.on('connection', (socket: WebSocket) => {
......
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;
......@@ -5,5 +5,7 @@ import { RESOURCES } from 'lib/api/resources';
export default function buildApiUrl<R extends ResourceName>(resourceName: R, pathParams?: ResourcePathParams<R>) {
const resource = RESOURCES[resourceName];
return compile('/node-api/proxy' + resource.path)(pathParams);
const defaultApi = 'https://' + process.env.NEXT_PUBLIC_API_HOST + ':' + process.env.NEXT_PUBLIC_API_PORT;
const origin = 'endpoint' in resource ? resource.endpoint + resource.basePath : defaultApi;
return origin + compile(resource.path)(pathParams);
}
{
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react",
"incremental": true,
"baseUrl": ".",
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "decs.d.ts", "global.d.ts"],
"exclude": ["node_modules", "node_modules_linux"],
}
......@@ -8,14 +8,14 @@ import type { AddressBlocksValidatedResponse } from 'types/api/address';
import appConfig from 'configs/app/config';
import { getResourceKey } from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { BLOCK } from 'stubs/block';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import SocketAlert from 'ui/shared/SocketAlert';
import { default as Thead } from 'ui/shared/TheadSticky';
......@@ -88,7 +88,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
{ socketAlert && <SocketAlert mb={ 6 }/> }
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Thead top={ query.isPaginationVisible ? 80 : 0 }>
<Thead top={ query.pagination.isVisible ? 80 : 0 }>
<Tr>
<Th width="17%">Block</Th>
<Th width="17%">Age</Th>
......@@ -122,7 +122,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
</>
) : null;
const actionBar = query.isPaginationVisible ? (
const actionBar = query.pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
......
......@@ -6,12 +6,12 @@ import type { SocketMessage } from 'lib/socket/types';
import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ADDRESS_COIN_BALANCE } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import SocketAlert from 'ui/shared/SocketAlert';
import AddressCoinBalanceChart from './coinBalance/AddressCoinBalanceChart';
......
......@@ -6,6 +6,7 @@ import type { CsvExportType } from 'types/client/address';
import appConfig from 'configs/app/config';
import svgFileIcon from 'icons/files/csv.svg';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import useIsMobile from 'lib/hooks/useIsMobile';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -18,12 +19,13 @@ interface Props {
const AddressCsvExportLink = ({ className, address, type, isLoading }: Props) => {
const isMobile = useIsMobile();
const isInitialLoading = useIsInitialLoading(isLoading);
if (!appConfig.reCaptcha.siteKey) {
return null;
}
if (isLoading) {
if (isInitialLoading) {
return (
<Flex className={ className } flexShrink={ 0 } alignItems="center">
<Skeleton boxSize={{ base: '32px', lg: 6 }} borderRadius="base"/>
......
......@@ -6,7 +6,6 @@ import type { AddressFromToFilter } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { INTERNAL_TX } from 'stubs/internalTx';
......@@ -14,7 +13,8 @@ import { generateListStub } from 'stubs/utils';
import AddressIntTxsTable from 'ui/address/internals/AddressIntTxsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink';
import AddressTxsFilter from './AddressTxsFilter';
......@@ -28,7 +28,7 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
const hash = getQueryParamString(router.query.hash);
const { data, isPlaceholderData, isError, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'address_internal_txs',
pathParams: { hash },
filters: { filter: filterValue },
......@@ -75,7 +75,7 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
isLoading={ pagination.isLoading }
/>
<AddressCsvExportLink address={ hash } isLoading={ pagination.isLoading } type="internal-transactions" ml={{ base: 2, lg: 'auto' }}/>
{ isPaginationVisible && <Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/> }
<Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/>
</ActionBar>
);
......
import { useRouter } from 'next/router';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { LOG } from 'stubs/log';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import LogItem from 'ui/shared/logs/LogItem';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink';
......
......@@ -14,7 +14,6 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
......@@ -24,7 +23,8 @@ import { getTokenTransfersStub } from 'stubs/token';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import HashStringShorten from 'ui/shared/HashStringShorten';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TokenLogo from 'ui/shared/TokenLogo';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
......@@ -88,7 +88,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
},
);
const { isError, isPlaceholderData, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'address_token_transfers',
pathParams: { hash: currentAddress },
filters: tokenFilter ? { token: tokenFilter } : filters,
......@@ -277,7 +277,7 @@ const AddressTokenTransfers = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Pr
isLoading={ isPlaceholderData }
/>
) }
{ isPaginationVisible && <Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/> }
<Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/>
</ActionBar>
) }
</>
......
......@@ -3,14 +3,14 @@ import { useRouter } from 'next/router';
import React from 'react';
import type { TokenType } from 'types/api/token';
import type { PaginationParams } from 'ui/shared/pagination/types';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { ADDRESS_TOKEN_BALANCE_ERC_1155, ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_TOKEN_BALANCE_ERC_721 } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import { tokenTabsByType } from 'ui/pages/Address';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import ERC1155Tokens from './tokens/ERC1155Tokens';
......@@ -81,17 +81,13 @@ const AddressTokens = () => {
{ id: tokenTabsByType['ERC-1155'], title: 'ERC-1155', component: <ERC1155Tokens tokensQuery={ erc1155Query }/> },
];
let isPaginationVisible;
let pagination: PaginationProps | undefined;
let pagination: PaginationParams | undefined;
if (tab === tokenTabsByType['ERC-1155']) {
isPaginationVisible = erc1155Query.isPaginationVisible;
pagination = erc1155Query.pagination;
} else if (tab === tokenTabsByType['ERC-721']) {
isPaginationVisible = erc721Query.isPaginationVisible;
pagination = erc721Query.pagination;
} else {
isPaginationVisible = erc20Query.isPaginationVisible;
pagination = erc20Query.pagination;
}
......@@ -106,7 +102,7 @@ const AddressTokens = () => {
colorScheme="gray"
size="sm"
tabListProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS }
rightSlot={ isPaginationVisible && !isMobile ? <Pagination { ...pagination }/> : null }
rightSlot={ pagination.isVisible && !isMobile ? <Pagination { ...pagination }/> : null }
stickyEnabled={ !isMobile }
/>
</>
......
......@@ -10,14 +10,14 @@ import type { Transaction } from 'types/api/transaction';
import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent';
import AddressCsvExportLink from './AddressCsvExportLink';
......@@ -172,7 +172,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
isLoading={ addressTxsQuery.pagination.isLoading }
/>
) }
{ addressTxsQuery.isPaginationVisible && <Pagination { ...addressTxsQuery.pagination } ml={ 8 }/> }
<Pagination { ...addressTxsQuery.pagination } ml={ 8 }/>
</ActionBar>
) }
<TxsContent
......
......@@ -10,6 +10,7 @@ import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import FilterButton from 'ui/shared/filters/FilterButton';
interface Props {
......@@ -21,13 +22,14 @@ interface Props {
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => {
const { isOpen, onToggle } = useDisclosure();
const isInitialLoading = useIsInitialLoading(isLoading);
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
isLoading={ isLoading }
isLoading={ isInitialLoading }
onClick={ onToggle }
as="div"
/>
......
......@@ -2,13 +2,13 @@ import { Show, Hide } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { generateListStub } from 'stubs/utils';
import { WITHDRAWAL } from 'stubs/withdrawals';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable';
......@@ -17,7 +17,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
const hash = getQueryParamString(router.query.hash);
const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'address_withdrawals',
pathParams: { hash },
scrollRef,
......@@ -41,12 +41,12 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
)) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="address" top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
<WithdrawalsTable items={ data.items } view="address" top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null ;
const actionBar = isPaginationVisible ? (
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -3,12 +3,12 @@ import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import type { PaginationParams } from 'ui/shared/pagination/types';
import appConfig from 'configs/app/config';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressCoinBalanceListItem from './AddressCoinBalanceListItem';
......@@ -16,8 +16,7 @@ import AddressCoinBalanceTableItem from './AddressCoinBalanceTableItem';
interface Props {
query: UseQueryResult<AddressCoinBalanceHistoryResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
pagination: PaginationParams;
};
}
......@@ -27,7 +26,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
<>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm">
<Thead top={ query.isPaginationVisible ? 80 : 0 }>
<Thead top={ query.pagination.isVisible ? 80 : 0 }>
<Tr>
<Th width="20%">Block</Th>
<Th width="20%">Txn</Th>
......@@ -61,7 +60,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
</>
) : null;
const actionBar = query.isPaginationVisible ? (
const actionBar = query.pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
......
import { Grid } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { AddressTokensResponse } from 'types/api/address';
import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import NFTItem from './NFTItem';
type Props = {
tokensQuery: UseQueryResult<AddressTokensResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
tokensQuery: QueryWithPagesResult<'address_tokens'>;
}
const ERC1155Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const { isError, isPlaceholderData, data, pagination } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && (
const actionBar = isMobile && pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
import { Show, Hide } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { AddressTokensResponse } from 'types/api/address';
import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import ERC20TokensListItem from './ERC20TokensListItem';
import ERC20TokensTable from './ERC20TokensTable';
type Props = {
tokensQuery: UseQueryResult<AddressTokensResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
tokensQuery: QueryWithPagesResult<'address_tokens'>;
}
const ERC20Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const { isError, isPlaceholderData, data, pagination } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && (
const actionBar = isMobile && pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......@@ -33,7 +27,7 @@ const ERC20Tokens = ({ tokensQuery }: Props) => {
const content = data?.items ? (
<>
<Hide below="lg" ssr={ false }><ERC20TokensTable data={ data.items } top={ isPaginationVisible ? 72 : 0 } isLoading={ isPlaceholderData }/></Hide>
<Hide below="lg" ssr={ false }><ERC20TokensTable data={ data.items } top={ pagination.isVisible ? 72 : 0 } isLoading={ isPlaceholderData }/></Hide>
<Show below="lg" ssr={ false }>{ data.items.map((item, index) => (
<ERC20TokensListItem
key={ item.token.address + (isPlaceholderData ? index : '') }
......
import { Show, Hide } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { AddressTokensResponse } from 'types/api/address';
import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import ERC721TokensListItem from './ERC721TokensListItem';
import ERC721TokensTable from './ERC721TokensTable';
type Props = {
tokensQuery: UseQueryResult<AddressTokensResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
tokensQuery: QueryWithPagesResult<'address_tokens'>;
}
const ERC721Tokens = ({ tokensQuery }: Props) => {
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = tokensQuery;
const { isError, isPlaceholderData, data, pagination } = tokensQuery;
const actionBar = isMobile && isPaginationVisible && (
const actionBar = isMobile && pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......@@ -33,7 +27,7 @@ const ERC721Tokens = ({ tokensQuery }: Props) => {
const content = data?.items ? (
<>
<Hide below="lg" ssr={ false }><ERC721TokensTable data={ data.items } isLoading={ isPlaceholderData } top={ isPaginationVisible ? 72 : 0 }/></Hide>
<Hide below="lg" ssr={ false }><ERC721TokensTable data={ data.items } isLoading={ isPlaceholderData } top={ pagination.isVisible ? 72 : 0 }/></Hide>
<Show below="lg" ssr={ false }>{ data.items.map((item, index) => (
<ERC721TokensListItem
key={ item.token.address + (isPlaceholderData ? index : '') }
......
import { Show, Hide } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { BlockWithdrawalsResponse } from 'types/api/block';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable';
type QueryResult = UseQueryResult<BlockWithdrawalsResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
type Props = {
blockWithdrawalsQuery: QueryResult;
blockWithdrawalsQuery: QueryWithPagesResult<'block_withdrawals'>;
}
const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => {
......@@ -35,7 +27,7 @@ const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => {
<WithdrawalsTable
items={ blockWithdrawalsQuery.data.items }
isLoading={ blockWithdrawalsQuery.isPlaceholderData }
top={ blockWithdrawalsQuery.isPaginationVisible ? 80 : 0 }
top={ blockWithdrawalsQuery.pagination.isVisible ? 80 : 0 }
view="block"
/>
</Hide>
......
import { Alert, Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
......@@ -14,17 +13,12 @@ import BlocksList from 'ui/blocks/BlocksList';
import BlocksTable from 'ui/blocks/BlocksTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
type QueryResult = UseQueryResult<BlocksResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
import Pagination from 'ui/shared/pagination/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
interface Props {
type?: BlockType;
query: QueryResult;
query: QueryWithPagesResult<'blocks'>;
}
const BlocksContent = ({ type, query }: Props) => {
......@@ -81,12 +75,17 @@ const BlocksContent = ({ type, query }: Props) => {
<BlocksList data={ query.data.items } isLoading={ query.isPlaceholderData } page={ query.pagination.page }/>
</Box>
<Box display={{ base: 'none', lg: 'block' }}>
<BlocksTable data={ query.data.items } top={ query.isPaginationVisible ? 80 : 0 } page={ query.pagination.page } isLoading={ query.isPlaceholderData }/>
<BlocksTable
data={ query.data.items }
top={ query.pagination.isVisible ? 80 : 0 }
page={ query.pagination.page }
isLoading={ query.isPlaceholderData }
/>
</Box>
</>
) : null;
const actionBar = isMobile && query.isPaginationVisible ? (
const actionBar = isMobile && query.pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
......
import { Flex, Box, Text, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types';
import useApiQuery from 'lib/api/useApiQuery';
import { nbsp } from 'lib/html-entities';
import { HOMEPAGE_STATS } from 'stubs/stats';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
interface Props {
pagination: PaginationProps;
isPaginationVisible: boolean;
pagination: PaginationParams;
}
const BlocksTabSlot = ({ pagination, isPaginationVisible }: Props) => {
const BlocksTabSlot = ({ pagination }: Props) => {
const statsQuery = useApiQuery('homepage_stats', {
queryOptions: {
placeholderData: HOMEPAGE_STATS,
......@@ -31,7 +31,7 @@ const BlocksTabSlot = ({ pagination, isPaginationVisible }: Props) => {
</Skeleton>
</Box>
) }
{ isPaginationVisible && <Pagination my={ 1 } { ...pagination }/> }
<Pagination my={ 1 } { ...pagination }/>
</Flex>
);
};
......
......@@ -6,7 +6,7 @@ import type { SocketMessage } from 'lib/socket/types';
import type { IndexingStatus } from 'types/api/indexingStatus';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp, ndash } from 'lib/html-entities';
......
import { Hide, Show } from '@chakra-ui/react';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { TOP_ADDRESS } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import AddressesListItem from 'ui/addresses/AddressesListItem';
......@@ -9,12 +8,13 @@ import AddressesTable from 'ui/addresses/AddressesTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
const PAGE_SIZE = 50;
const Accounts = () => {
const { isError, isPlaceholderData, data, isPaginationVisible, pagination } = useQueryWithPages({
const { isError, isPlaceholderData, data, pagination } = useQueryWithPages({
resourceName: 'addresses',
options: {
placeholderData: generateListStub<'addresses'>(
......@@ -32,7 +32,7 @@ const Accounts = () => {
},
});
const actionBar = isPaginationVisible && (
const actionBar = pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......@@ -43,7 +43,7 @@ const Accounts = () => {
<>
<Hide below="lg" ssr={ false }>
<AddressesTable
top={ isPaginationVisible ? 80 : 0 }
top={ pagination.isVisible ? 80 : 0 }
items={ data.items }
totalSupply={ data.total_supply }
pageStartIndex={ pageStartIndex }
......
......@@ -8,7 +8,7 @@ import type { RoutedTab } from 'ui/shared/Tabs/types';
import appConfig from 'configs/app/config';
import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
......
import { useRouter } from 'next/router';
import React from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import appConfig from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { BLOCK } from 'stubs/block';
import { TX } from 'stubs/tx';
......@@ -18,8 +18,8 @@ import BlockWithdrawals from 'ui/block/BlockWithdrawals';
import TextAd from 'ui/shared/ad/TextAd';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TxsContent from 'ui/txs/TxsContent';
......@@ -87,8 +87,8 @@ const BlockPageContent = () => {
].filter(Boolean)), [ blockQuery, blockTxsQuery, blockWithdrawalsQuery ]);
const hasPagination = !isMobile && (
(tab === 'txs' && blockTxsQuery.isPaginationVisible) ||
(tab === 'withdrawals' && blockWithdrawalsQuery.isPaginationVisible)
(tab === 'txs' && blockTxsQuery.pagination.isVisible) ||
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible)
);
let pagination;
......@@ -124,7 +124,7 @@ const BlockPageContent = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ hasPagination ? <Pagination { ...(pagination as PaginationProps) }/> : null }
rightSlot={ hasPagination ? <Pagination { ...(pagination as PaginationParams) }/> : null }
stickyEnabled={ hasPagination }
/>
) }
......
......@@ -4,13 +4,13 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { BLOCK } from 'stubs/block';
import { generateListStub } from 'stubs/utils';
import BlocksContent from 'ui/blocks/BlocksContent';
import BlocksTabSlot from 'ui/blocks/BlocksTabSlot';
import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
const TAB_LIST_PROPS = {
......@@ -58,23 +58,14 @@ const BlocksPageContent = () => {
},
});
const { pagination, isPaginationVisible } = (() => {
const pagination = (() => {
if (tab === 'reorgs') {
return {
pagination: reorgsQuery.pagination,
isPaginationVisible: reorgsQuery.isPaginationVisible,
};
return reorgsQuery.pagination;
}
if (tab === 'uncles') {
return {
pagination: unclesQuery.pagination,
isPaginationVisible: unclesQuery.isPaginationVisible,
};
return unclesQuery.pagination;
}
return {
pagination: blocksQuery.pagination,
isPaginationVisible: blocksQuery.isPaginationVisible,
};
return blocksQuery.pagination;
})();
const tabs: Array<RoutedTab> = [
......@@ -89,7 +80,7 @@ const BlocksPageContent = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ <BlocksTabSlot pagination={ pagination } isPaginationVisible={ isPaginationVisible }/> }
rightSlot={ <BlocksTabSlot pagination={ pagination }/> }
stickyEnabled={ !isMobile }
/>
</>
......
......@@ -5,7 +5,7 @@ import React from 'react';
import type { SmartContractVerificationMethod } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import { isValidVerificationMethod, sortVerificationMethods } from 'ui/contractVerification/utils';
......
......@@ -6,7 +6,7 @@ import type { CsvExportType } from 'types/client/address';
import type { ResourceName } from 'lib/api/resources';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import CsvExportForm from 'ui/csvExport/CsvExportForm';
import Address from 'ui/shared/address/Address';
......
......@@ -2,7 +2,6 @@ import { Box, Hide, Show, Skeleton } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { rightLineArrow, nbsp } from 'lib/html-entities';
import { L2_DEPOSIT_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
......@@ -11,10 +10,11 @@ import DepositsTable from 'ui/l2Deposits/DepositsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
const L2Deposits = () => {
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'l2_deposits',
options: {
placeholderData: generateListStub<'l2_deposits'>(
......@@ -49,7 +49,7 @@ const L2Deposits = () => {
))) }
</Show>
<Hide below="lg" ssr={ false }>
<DepositsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
<DepositsTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......@@ -78,7 +78,7 @@ const L2Deposits = () => {
<Box display={{ base: 'none', lg: 'block' }}>
{ text }
</Box>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
{ pagination.isVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
......
......@@ -2,7 +2,6 @@ import { Box, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { L2_OUTPUT_ROOTS_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
import OutputRootsListItem from 'ui/l2OutputRoots/OutputRootsListItem';
......@@ -10,10 +9,11 @@ import OutputRootsTable from 'ui/l2OutputRoots/OutputRootsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
const L2OutputRoots = () => {
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'l2_output_roots',
options: {
placeholderData: generateListStub<'l2_output_roots'>(
......@@ -47,7 +47,7 @@ const L2OutputRoots = () => {
))) }
</Show>
<Hide below="lg" ssr={ false }>
<OutputRootsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
<OutputRootsTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......@@ -76,7 +76,7 @@ const L2OutputRoots = () => {
<Box display={{ base: 'none', lg: 'block' }}>
{ text }
</Box>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
{ pagination.isVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
......
......@@ -2,7 +2,6 @@ import { Box, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { nbsp } from 'lib/html-entities';
import { L2_TXN_BATCHES_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
......@@ -11,10 +10,11 @@ import TxnBatchesTable from 'ui/l2TxnBatches/TxnBatchesTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
const L2TxnBatches = () => {
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'l2_txn_batches',
options: {
placeholderData: generateListStub<'l2_txn_batches'>(
......@@ -47,7 +47,7 @@ const L2TxnBatches = () => {
/>
))) }
</Show>
<Hide below="lg" ssr={ false }><TxnBatchesTable items={ data.items } top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/></Hide>
<Hide below="lg" ssr={ false }><TxnBatchesTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/></Hide>
</>
) : null;
......@@ -75,7 +75,7 @@ const L2TxnBatches = () => {
<Box display={{ base: 'none', lg: 'block' }}>
{ text }
</Box>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
{ pagination.isVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
......
......@@ -2,7 +2,6 @@ import { Box, Hide, Show, Skeleton } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { rightLineArrow, nbsp } from 'lib/html-entities';
import { L2_WITHDRAWAL_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
......@@ -11,10 +10,11 @@ import WithdrawalsTable from 'ui/l2Withdrawals/WithdrawalsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
const L2Withdrawals = () => {
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'l2_withdrawals',
options: {
placeholderData: generateListStub<'l2_withdrawals'>(
......@@ -46,7 +46,7 @@ const L2Withdrawals = () => {
/>
))) }</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
<WithdrawalsTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......@@ -75,7 +75,7 @@ const L2Withdrawals = () => {
<Box display={{ base: 'none', lg: 'block' }}>
{ text }
</Box>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
{ pagination.isVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
......
......@@ -10,7 +10,7 @@ import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import Thead from 'ui/shared/TheadSticky';
import Header from 'ui/snippets/header/Header';
import SearchBarInput from 'ui/snippets/searchBar/SearchBarInput';
......@@ -19,7 +19,7 @@ import useSearchQuery from 'ui/snippets/searchBar/useSearchQuery';
const SearchResultsPageContent = () => {
const router = useRouter();
const { query, redirectCheckQuery, searchTerm, debouncedSearchTerm, handleSearchTermChange } = useSearchQuery(true);
const { data, isError, isPlaceholderData, pagination, isPaginationVisible } = query;
const { data, isError, isPlaceholderData, pagination } = query;
const [ showContent, setShowContent ] = React.useState(false);
React.useEffect(() => {
......@@ -70,7 +70,7 @@ const SearchResultsPageContent = () => {
</Show>
<Hide below="lg" ssr={ false }>
<Table variant="simple" size="md" fontWeight={ 500 }>
<Thead top={ isPaginationVisible ? 80 : 0 }>
<Thead top={ pagination.isVisible ? 80 : 0 }>
<Tr>
<Th width="50%">Search Result</Th>
<Th width="50%"/>
......@@ -99,10 +99,10 @@ const SearchResultsPageContent = () => {
}
const text = isPlaceholderData && pagination.page === 1 ? (
<Skeleton h={ 6 } w="280px" borderRadius="full" mb={ isPaginationVisible ? 0 : 6 }/>
<Skeleton h={ 6 } w="280px" borderRadius="full" mb={ pagination.isVisible ? 0 : 6 }/>
) : (
(
<Box mb={ isPaginationVisible ? 0 : 6 } lineHeight="32px">
<Box mb={ pagination.isVisible ? 0 : 6 } lineHeight="32px">
<span>Found </span>
<chakra.span fontWeight={ 700 }>
{ pagination.page > 1 ? 50 : data?.items.length }{ data?.next_page_params || pagination.page > 1 ? '+' : '' }
......@@ -113,7 +113,7 @@ const SearchResultsPageContent = () => {
)
);
if (!isPaginationVisible) {
if (!pagination.isVisible) {
return text;
}
......
......@@ -2,7 +2,7 @@ import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......
......@@ -5,15 +5,15 @@ import React, { useEffect } from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/token';
import type { PaginationParams } from 'ui/shared/pagination/types';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import appConfig from 'configs/app/config';
import iconSuccess from 'icons/status/success.svg';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -26,8 +26,8 @@ import TextAd from 'ui/shared/ad/TextAd';
import EntityTags from 'ui/shared/EntityTags';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TokenLogo from 'ui/shared/TokenLogo';
......@@ -183,21 +183,17 @@ const TokenPageContent = () => {
} : undefined,
].filter(Boolean);
let hasPagination;
let pagination: PaginationProps | undefined;
let pagination: PaginationParams | undefined;
if (!router.query.tab || router.query.tab === 'token_transfers') {
hasPagination = transfersQuery.isPaginationVisible;
pagination = transfersQuery.pagination;
}
if (router.query.tab === 'holders') {
hasPagination = holdersQuery.isPaginationVisible;
pagination = holdersQuery.pagination;
}
if (router.query.tab === 'inventory') {
hasPagination = inventoryQuery.isPaginationVisible;
pagination = inventoryQuery.pagination;
}
......@@ -280,7 +276,7 @@ const TokenPageContent = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ tabListProps }
rightSlot={ !isMobile && hasPagination && pagination ? <Pagination { ...pagination }/> : null }
rightSlot={ !isMobile && pagination?.isVisible ? <Pagination { ...pagination }/> : null }
stickyEnabled={ !isMobile }
/>
) }
......
......@@ -2,13 +2,13 @@ import { Box, Icon, Skeleton } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { PaginationParams } from 'ui/shared/pagination/types';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import nftIcon from 'icons/nft_shield.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { TOKEN_INSTANCE } from 'stubs/token';
import * as tokenStubs from 'stubs/token';
import { generateListStub } from 'stubs/utils';
......@@ -17,8 +17,8 @@ import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import Tag from 'ui/shared/chakra/Tag';
import LinkExternal from 'ui/shared/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TokenHolders from 'ui/token/TokenHolders/TokenHolders';
......@@ -144,15 +144,12 @@ const TokenInstanceContent = () => {
}
})();
let pagination: PaginationProps | undefined;
let isPaginationVisible;
let pagination: PaginationParams | undefined;
if (tab === 'token_transfers') {
pagination = transfersQuery.pagination;
isPaginationVisible = transfersQuery.isPaginationVisible;
} else if (tab === 'holders') {
pagination = holdersQuery.pagination;
isPaginationVisible = holdersQuery.isPaginationVisible;
}
return (
......@@ -179,7 +176,7 @@ const TokenInstanceContent = () => {
<RoutedTabs
tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
rightSlot={ !isMobile && isPaginationVisible && pagination ? <Pagination { ...pagination }/> : null }
rightSlot={ !isMobile && pagination?.isVisible ? <Pagination { ...pagination }/> : null }
stickyEnabled={ !isMobile }
/>
) }
......
......@@ -4,7 +4,7 @@ import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
import { TX } from 'stubs/tx';
......
......@@ -7,13 +7,13 @@ import appConfig from 'configs/app/config';
import useHasAccount from 'lib/hooks/useHasAccount';
import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { TX } from 'stubs/tx';
import { generateListStub } from 'stubs/utils';
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 TxsContent from 'ui/txs/TxsContent';
import TxsTabSlot from 'ui/txs/TxsTabSlot';
import TxsWatchlist from 'ui/txs/TxsWatchlist';
const TAB_LIST_PROPS = {
......@@ -81,6 +81,8 @@ const Transactions = () => {
} : undefined,
].filter(Boolean);
const pagination = router.query.tab === 'watchlist' ? txsWatchlistQuery.pagination : txsQuery.pagination;
return (
<>
<PageTitle title="Transactions" withTextAd/>
......@@ -88,10 +90,7 @@ const Transactions = () => {
tabs={ tabs }
tabListProps={ isMobile ? undefined : TAB_LIST_PROPS }
rightSlot={ (
<TxsTabSlot
pagination={ router.query.tab === 'watchlist' ? txsWatchlistQuery.pagination : txsQuery.pagination }
isPaginationVisible={ txsQuery.isPaginationVisible && !isMobile }
/>
pagination.isVisible && !isMobile ? <Pagination my={ 1 } { ...pagination }/> : null
) }
stickyEnabled={ !isMobile }
/>
......
......@@ -6,7 +6,6 @@ import type { VerifiedContractsFilters } from 'types/api/contracts';
import useDebounce from 'lib/hooks/useDebounce';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { VERIFIED_CONTRACT_INFO } from 'stubs/contract';
......@@ -15,7 +14,8 @@ import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import FilterInput from 'ui/shared/filters/FilterInput';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import Sort from 'ui/shared/sort/Sort';
import type { SortField, Sort as TSort } from 'ui/verifiedContracts/utils';
import { SORT_OPTIONS, sortFn, getNextSortValue } from 'ui/verifiedContracts/utils';
......@@ -34,7 +34,7 @@ const VerifiedContracts = () => {
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'verified_contracts',
filters: { q: debouncedSearchTerm, filter: type },
options: {
......@@ -100,13 +100,13 @@ const VerifiedContracts = () => {
{ sortButton }
{ filterInput }
</HStack>
{ (!isMobile || isPaginationVisible) && (
{ (!isMobile || pagination.isVisible) && (
<ActionBar mt={ -6 }>
<HStack spacing={ 3 } display={{ base: 'none', lg: 'flex' }}>
{ typeFilter }
{ filterInput }
</HStack>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) }
</>
......
......@@ -5,20 +5,20 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import getCurrencyValue from 'lib/getCurrencyValue';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { generateListStub } from 'stubs/utils';
import { WITHDRAWAL } from 'stubs/withdrawals';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable';
const Withdrawals = () => {
const isMobile = useIsMobile();
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'withdrawals',
options: {
placeholderData: generateListStub<'withdrawals'>(WITHDRAWAL, 50, { next_page_params: {
......@@ -43,7 +43,7 @@ const Withdrawals = () => {
))) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="list" top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
<WithdrawalsTable items={ data.items } view="list" top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......@@ -54,8 +54,8 @@ const Withdrawals = () => {
<Skeleton
w={{ base: '100%', lg: '320px' }}
h="24px"
mb={{ base: 6, lg: isPaginationVisible ? 0 : 7 }}
mt={{ base: 0, lg: isPaginationVisible ? 0 : 7 }}
mb={{ base: 6, lg: pagination.isVisible ? 0 : 7 }}
mt={{ base: 0, lg: pagination.isVisible ? 0 : 7 }}
/>
);
}
......@@ -66,7 +66,7 @@ const Withdrawals = () => {
const { valueStr } = getCurrencyValue({ value: countersQuery.data.withdrawal_sum });
return (
<Text mb={{ base: 6, lg: isPaginationVisible ? 0 : 6 }} lineHeight={{ base: '24px', lg: '32px' }}>
<Text mb={{ base: 6, lg: pagination.isVisible ? 0 : 6 }} lineHeight={{ base: '24px', lg: '32px' }}>
{ BigNumber(countersQuery.data.withdrawal_count).toFormat() } withdrawals processed and { valueStr } ETH withdrawn
</Text>
);
......@@ -74,8 +74,8 @@ const Withdrawals = () => {
const actionBar = (
<>
{ (isMobile || !isPaginationVisible) && text }
{ isPaginationVisible && (
{ (isMobile || !pagination.isVisible) && text }
{ pagination.isVisible && (
<ActionBar mt={ -6 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ !isMobile && text }
......
......@@ -9,6 +9,7 @@ import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
import type { TokenType } from 'types/api/token';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
......@@ -31,9 +32,10 @@ const TokenTransferFilter = ({
defaultAddressFilter,
isLoading,
}: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading);
return (
<PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '200px' }} isLoading={ isLoading }>
<PopoverFilter appliedFiltersNum={ appliedFiltersNum } contentProps={{ w: '200px' }} isLoading={ isInitialLoading }>
{ withAddressFilter && (
<>
<Text variant="secondary" fontWeight={ 600 }>Address</Text>
......
......@@ -2,7 +2,7 @@ import { useColorModeValue, useToken, SkeletonCircle, Image, Box } from '@chakra
import React from 'react';
import Identicon from 'react-identicons';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
......
......@@ -2,7 +2,7 @@ import { chakra, Skeleton } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import isSelfHosted from 'lib/isSelfHosted';
......
import { Box, Image, Link, Text, chakra, Skeleton } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import { ndash } from 'lib/html-entities';
import isBrowser from 'lib/isBrowser';
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { PaginationParams } from './types';
import TestApp from 'playwright/TestApp';
import Pagination from './Pagination';
test.use({ viewport: { width: 250, height: 50 } });
test('default view', async({ mount }) => {
const props: PaginationParams = {
page: 2,
isVisible: true,
isLoading: false,
hasNextPage: true,
hasPages: true,
canGoBackwards: false,
onNextPageClick: () => {},
onPrevPageClick: () => {},
resetPage: () => {},
};
const component = await mount(
<TestApp>
<Pagination { ...props } w="fit-content"/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Button, Skeleton, Flex, Icon, IconButton, chakra } from '@chakra-ui/react';
import React from 'react';
import type { PaginationParams } from './types';
import arrowIcon from 'icons/arrows/east-mini.svg';
export type Props = {
page: number;
onNextPageClick: () => void;
onPrevPageClick: () => void;
resetPage: () => void;
hasNextPage: boolean;
interface Props extends PaginationParams {
className?: string;
canGoBackwards: boolean;
isLoading?: boolean;
}
const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasNextPage, className, canGoBackwards, isLoading }: Props) => {
const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasPages, hasNextPage, className, canGoBackwards, isLoading, isVisible }: Props) => {
if (!isVisible) {
return null;
}
const showSkeleton = page === 1 && !hasPages && isLoading;
return (
<Flex
......@@ -22,28 +23,28 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasNext
fontSize="sm"
alignItems="center"
>
<Skeleton isLoaded={ !isLoading } display="inline-block" mr={ 4 } borderRadius="base">
<Skeleton isLoaded={ !showSkeleton } display="inline-block" mr={ 4 } borderRadius="base">
<Button
variant="outline"
size="sm"
onClick={ resetPage }
isDisabled={ page === 1 }
isDisabled={ page === 1 || isLoading }
>
First
</Button>
</Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" mr={ 3 } borderRadius="base">
<Skeleton isLoaded={ !showSkeleton } display="inline-block" mr={ 3 } borderRadius="base">
<IconButton
variant="outline"
onClick={ onPrevPageClick }
size="sm"
aria-label="Next page"
aria-label="Prev page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
isDisabled={ !canGoBackwards || page === 1 }
isDisabled={ !canGoBackwards || page === 1 || isLoading }
/>
</Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" borderRadius="base">
<Skeleton isLoaded={ !showSkeleton } display="inline-block" borderRadius="base">
<Button
variant="outline"
size="sm"
......@@ -51,12 +52,13 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasNext
borderWidth="1px"
fontWeight={ 400 }
h={ 8 }
minW="36px"
cursor="unset"
>
{ page }
</Button>
</Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" ml={ 3 } borderRadius="base">
<Skeleton isLoaded={ !showSkeleton } display="inline-block" ml={ 3 } borderRadius="base">
<IconButton
variant="outline"
onClick={ onNextPageClick }
......@@ -64,7 +66,7 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasNext
aria-label="Next page"
w="36px"
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
isDisabled={ !hasNextPage }
isDisabled={ !hasNextPage || isLoading }
/>
</Skeleton>
{ /* not implemented yet */ }
......
export interface PaginationParams {
page: number;
onNextPageClick: () => void;
onPrevPageClick: () => void;
resetPage: () => void;
hasPages: boolean;
hasNextPage: boolean;
canGoBackwards: boolean;
isLoading: boolean;
isVisible: boolean;
}
This diff is collapsed.
import type { UseQueryResult } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
import omit from 'lodash/omit';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
import type { PaginatedResources, PaginationFilters } from 'lib/api/resources';
import type { PaginationParams } from './types';
import type { PaginatedResources, PaginationFilters, ResourceError, ResourcePayload } from 'lib/api/resources';
import { RESOURCES } from 'lib/api/resources';
import type { Params as UseApiQueryParams } from 'lib/api/useApiQuery';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
interface Params<Resource extends PaginatedResources> {
export interface Params<Resource extends PaginatedResources> {
resourceName: Resource;
options?: UseApiQueryParams<Resource>['queryOptions'];
pathParams?: UseApiQueryParams<Resource>['pathParams'];
......@@ -30,13 +33,20 @@ function getPaginationParamsFromQuery(queryString: string | Array<string> | unde
return {};
}
export type QueryWithPagesResult<Resource extends PaginatedResources> =
UseQueryResult<ResourcePayload<Resource>, ResourceError<unknown>> &
{
onFilterChange: (filters: PaginationFilters<Resource>) => void;
pagination: PaginationParams;
}
export default function useQueryWithPages<Resource extends PaginatedResources>({
resourceName,
filters,
options,
pathParams,
scrollRef,
}: Params<Resource>) {
}: Params<Resource>): QueryWithPagesResult<Resource> {
const resource = RESOURCES[resourceName];
const queryClient = useQueryClient();
const router = useRouter();
......@@ -45,7 +55,7 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
const [ pageParams, setPageParams ] = React.useState<Record<number, NextPageParams>>({
[page]: getPaginationParamsFromQuery(router.query.next_page_params),
});
const [ hasPagination, setHasPagination ] = React.useState(page > 1);
const [ hasPages, setHasPages ] = React.useState(page > 1);
const isMounted = React.useRef(false);
const canGoBackwards = React.useRef(!router.query.page);
......@@ -83,7 +93,7 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
next_page_params: encodeURIComponent(JSON.stringify(data.next_page_params)),
};
setHasPagination(true);
setHasPages(true);
scrollToTop();
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
}, [ data?.next_page_params, page, router, scrollToTop ]);
......@@ -106,7 +116,6 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
setPage(prev => prev - 1);
page === 2 && queryClient.removeQueries({ queryKey: [ resourceName ] });
});
setHasPagination(true);
}, [ router, page, pageParams, scrollToTop, queryClient, resourceName ]);
const resetPage = useCallback(() => {
......@@ -125,8 +134,6 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
queryClient.removeQueries({ queryKey: [ resourceName ], type: 'inactive' });
}, 100);
});
setHasPagination(true);
}, [ queryClient, resourceName, router, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<Resource> | undefined) => {
......@@ -138,7 +145,6 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
}
});
}
setHasPagination(false);
scrollToTop();
router.push(
{
......@@ -148,25 +154,27 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
undefined,
{ shallow: true },
).then(() => {
setHasPages(false);
setPage(1);
setPageParams({});
});
}, [ router, resource.filterFields, scrollToTop ]);
const nextPageParams = data?.next_page_params;
const hasNextPage = nextPageParams ? Object.keys(nextPageParams).length > 0 : false;
const pagination = {
page,
onNextPageClick,
onPrevPageClick,
resetPage,
hasNextPage: nextPageParams ? Object.keys(nextPageParams).length > 0 : false,
hasPages,
hasNextPage,
canGoBackwards: canGoBackwards.current,
isLoading: queryResult.isPlaceholderData && !hasPagination,
isLoading: queryResult.isPlaceholderData,
isVisible: hasPages || hasNextPage,
};
const isPaginationVisible = hasPagination || (!queryResult.isLoading && !queryResult.isError && pagination.hasNextPage);
React.useEffect(() => {
if (page !== 1 && isMounted.current) {
queryClient.cancelQueries({ queryKey: [ resourceName ] });
......@@ -182,5 +190,5 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
}, 0);
}, []);
return { ...queryResult, pagination, onFilterChange, isPaginationVisible };
return { ...queryResult, pagination, onFilterChange };
}
......@@ -7,6 +7,7 @@ import * as cookies from 'lib/cookies';
import authFixture from 'playwright/fixtures/auth';
import contextWithEnvs, { createContextWithEnvs } from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app';
import * as configs from 'playwright/utils/configs';
import NavigationDesktop from './NavigationDesktop';
......@@ -150,7 +151,7 @@ base.describe('cookie set to false', () => {
const context = await createContextWithEnvs(browser, [
{ name: 'NEXT_PUBLIC_FEATURED_NETWORKS', value: FEATURED_NETWORKS_URL },
]);
context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'false', domain: 'localhost', path: '/' } ]);
context.addCookies([ { name: cookies.NAMES.NAV_BAR_COLLAPSED, value: 'false', domain: app.domain, path: '/' } ]);
use(context);
},
});
......
......@@ -4,7 +4,7 @@ import React from 'react';
import appConfig from 'configs/app/config';
import chevronIcon from 'icons/arrows/east-mini.svg';
import testnetIcon from 'icons/testnet.svg';
import { useAppContext } from 'lib/appContext';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import useHasAccount from 'lib/hooks/useHasAccount';
import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems';
......
......@@ -4,6 +4,7 @@ import React from 'react';
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 ProfileMenuDesktop from './ProfileMenuDesktop';
......@@ -23,7 +24,7 @@ test('no auth', async({ mount, page }) => {
);
await component.locator('.identicon').click();
expect(page.url()).toBe('http://localhost:3100/auth/auth0?path=%2F');
expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`);
});
test.describe('auth', () => {
......
......@@ -4,6 +4,7 @@ import React from 'react';
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 ProfileMenuMobile from './ProfileMenuMobile';
......@@ -23,7 +24,7 @@ test('no auth', async({ mount, page }) => {
);
await component.locator('.identicon').click();
expect(page.url()).toBe('http://localhost:3100/auth/auth0?path=%2F');
expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`);
});
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
......
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