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;
}
import { animateScroll } from 'react-scroll';
import fetch from 'jest-fetch-mock';
import { renderHook, wrapper, act } from 'jest/lib';
import { useRouter, router } from 'jest/mocks/next-router';
import flushPromises from 'jest/utils/flushPromises';
import * as addressMock from 'mocks/address/address';
jest.mock('next/router', () => ({ useRouter }));
jest.mock('react-scroll', () => ({ animateScroll: { scrollToTop: jest.fn() } }));
import type { Params, QueryWithPagesResult } from './useQueryWithPages';
import useQueryWithPages from './useQueryWithPages';
const responses = {
page_empty: {
items: [],
next_page_params: null,
},
page_1: {
items: [ { hash: '11' }, { hash: '12' } ],
next_page_params: {
block_number: 11,
index: 12,
items_count: 13,
},
},
page_2: {
items: [ { hash: '21' }, { hash: '22' } ],
next_page_params: {
block_number: 21,
index: 22,
items_count: 23,
},
},
page_3: {
items: [ { hash: '31' }, { hash: '32' } ],
next_page_params: null,
},
page_filtered: {
items: [ { hash: '41' }, { hash: '42' } ],
next_page_params: {
block_number: 41,
index: 42,
items_count: 43,
},
},
};
beforeEach(() => {
fetch.resetMocks();
});
it('returns correct data if there is only one page', async() => {
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
fetch.mockResponse(JSON.stringify(responses.page_empty));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_empty);
expect(result.current.pagination).toMatchObject({
page: 1,
canGoBackwards: true,
hasNextPage: false,
isLoading: false,
isVisible: false,
hasPages: false,
});
});
describe('if there are multiple pages', () => {
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
it('return correct data for the first page', async() => {
fetch.mockResponse(JSON.stringify(responses.page_1));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_1);
expect(result.current.pagination).toMatchObject({
page: 1,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
});
});
describe('correctly navigates forward and backward', () => {
const routerPush = jest.fn(() => Promise.resolve());
let result: {
current: QueryWithPagesResult<'address_txs'>;
};
beforeEach(async() => {
routerPush.mockClear();
useRouter.mockReturnValue({ ...router, pathname: '/current-route', push: routerPush });
fetch.once(JSON.stringify(responses.page_1));
fetch.once(JSON.stringify(responses.page_2));
fetch.once(JSON.stringify(responses.page_3));
fetch.once(JSON.stringify(responses.page_1));
// INITIAL LOAD
const { result: r } = renderHook(() => useQueryWithPages(params), { wrapper });
result = r;
await waitForApiResponse();
});
it('from page 1 to page 2', async() => {
await act(() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_2);
expect(result.current.pagination).toMatchObject({
page: 2,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(1);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {
next_page_params: encodeURIComponent(JSON.stringify(responses.page_1.next_page_params)),
page: '2',
},
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(1);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
it('from page 2 to page 3', async() => {
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_3);
expect(result.current.pagination).toMatchObject({
page: 3,
canGoBackwards: true,
hasNextPage: false,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(2);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {
next_page_params: encodeURIComponent(JSON.stringify(responses.page_2.next_page_params)),
page: '3',
},
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(2);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
it('from page 3 to page 2', async() => {
await act(() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(() => {
result.current.pagination.onPrevPageClick();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_2);
expect(result.current.pagination).toMatchObject({
page: 2,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(3);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {
next_page_params: encodeURIComponent(JSON.stringify(responses.page_1.next_page_params)),
page: '2',
},
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(3);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
it('from page 2 to page 1', async() => {
await act(() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(() => {
result.current.pagination.onPrevPageClick();
});
await waitForApiResponse();
await act(() => {
result.current.pagination.onPrevPageClick();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_1);
expect(result.current.pagination).toMatchObject({
page: 1,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(4);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {},
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(4);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
});
it('correctly resets the page', async() => {
const routerPush = jest.fn(() => Promise.resolve());
useRouter.mockReturnValue({ ...router, pathname: '/current-route', push: routerPush });
fetch.once(JSON.stringify(responses.page_1));
fetch.once(JSON.stringify(responses.page_2));
fetch.once(JSON.stringify(responses.page_3));
fetch.once(JSON.stringify(responses.page_1));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(async() => {
result.current.pagination.resetPage();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_1);
expect(result.current.pagination).toMatchObject({
page: 1,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(3);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {},
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(3);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
it('when navigates between pages can scroll to custom element', async() => {
const scrollRef = {
current: {
scrollIntoView: jest.fn(),
},
};
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
scrollRef: scrollRef as unknown as React.RefObject<HTMLDivElement>,
};
fetch.once(JSON.stringify(responses.page_1));
fetch.once(JSON.stringify(responses.page_2));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
expect(scrollRef.current.scrollIntoView).toHaveBeenCalledTimes(1);
expect(scrollRef.current.scrollIntoView).toHaveBeenCalledWith(true);
});
});
describe('if there is page query param in URL', () => {
it('sets this param as the page number', async() => {
useRouter.mockReturnValueOnce({ ...router, query: { page: '3' } });
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
fetch.mockResponse(JSON.stringify(responses.page_empty));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_empty);
expect(result.current.pagination).toMatchObject({
page: 3,
canGoBackwards: false,
hasNextPage: false,
isLoading: false,
isVisible: true,
hasPages: true,
});
});
it('correctly navigates to the following pages', async() => {
const routerPush = jest.fn(() => Promise.resolve());
useRouter.mockReturnValue({ ...router, pathname: '/current-route', push: routerPush, query: { page: '2' } });
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
fetch.once(JSON.stringify(responses.page_2));
fetch.once(JSON.stringify(responses.page_3));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_3);
expect(result.current.pagination).toMatchObject({
page: 3,
canGoBackwards: false,
hasNextPage: false,
isLoading: false,
isVisible: true,
hasPages: true,
});
expect(routerPush).toHaveBeenCalledTimes(1);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {
next_page_params: encodeURIComponent(JSON.stringify(responses.page_2.next_page_params)),
page: '3',
},
},
undefined,
{ shallow: true },
);
});
});
describe('queries with filters', () => {
it('reset page when filter is changed', async() => {
const routerPush = jest.fn(() => Promise.resolve());
useRouter.mockReturnValue({ ...router, pathname: '/current-route', push: routerPush, query: { foo: 'bar' } });
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
fetch.once(JSON.stringify(responses.page_1));
fetch.once(JSON.stringify(responses.page_2));
fetch.once(JSON.stringify(responses.page_filtered));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
await act(async() => {
result.current.onFilterChange({ filter: 'from' });
});
await waitForApiResponse();
expect(result.current.data).toEqual(responses.page_filtered);
expect(result.current.pagination).toMatchObject({
page: 1,
canGoBackwards: true,
hasNextPage: true,
isLoading: false,
isVisible: true,
hasPages: false,
});
expect(routerPush).toHaveBeenCalledTimes(2);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: { filter: 'from', foo: 'bar' },
},
undefined,
{ shallow: true },
);
expect(animateScroll.scrollToTop).toHaveBeenCalledTimes(2);
expect(animateScroll.scrollToTop).toHaveBeenLastCalledWith({ duration: 0 });
});
it('saves filter params in query when navigating between pages', async() => {
const routerPush = jest.fn(() => Promise.resolve());
useRouter.mockReturnValue({ ...router, pathname: '/current-route', push: routerPush, query: { filter: 'from', foo: 'bar' } });
const params: Params<'address_txs'> = {
resourceName: 'address_txs',
pathParams: { hash: addressMock.hash },
};
fetch.once(JSON.stringify(responses.page_1));
fetch.once(JSON.stringify(responses.page_2));
const { result } = renderHook(() => useQueryWithPages(params), { wrapper });
await waitForApiResponse();
await act(async() => {
result.current.pagination.onNextPageClick();
});
await waitForApiResponse();
expect(routerPush).toHaveBeenCalledTimes(1);
expect(routerPush).toHaveBeenLastCalledWith(
{
pathname: '/current-route',
query: {
filter: 'from',
foo: 'bar',
next_page_params: encodeURIComponent(JSON.stringify(responses.page_1.next_page_params)),
page: '2',
},
},
undefined,
{ shallow: true },
);
});
});
async function waitForApiResponse() {
await flushPromises();
await act(flushPromises);
}
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 });
......
......@@ -3,12 +3,12 @@ import type { UseQueryResult } from '@tanstack/react-query';
import _uniqBy from 'lodash/uniqBy';
import React from 'react';
import type { SearchRedirectResult, SearchResult, SearchResultItem } from 'types/api/search';
import type { SearchRedirectResult, SearchResultItem } from 'types/api/search';
import useIsMobile from 'lib/hooks/useIsMobile';
import TextAd from 'ui/shared/ad/TextAd';
import ContentLoader from 'ui/shared/ContentLoader';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import SearchBarSuggestItem from './SearchBarSuggestItem';
......@@ -31,9 +31,7 @@ const getUniqueIdentifier = (item: SearchResultItem) => {
};
interface Props {
query: UseQueryResult<SearchResult> & {
pagination: PaginationProps;
};
query: QueryWithPagesResult<'search'>;
redirectCheckQuery: UseQueryResult<SearchRedirectResult>;
searchTerm: string;
onItemClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
......
......@@ -3,11 +3,11 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useDebounce from 'lib/hooks/useDebounce';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import useUpdateValueEffect from 'lib/hooks/useUpdateValueEffect';
import getQueryParamString from 'lib/router/getQueryParamString';
import { SEARCH_RESULT_ITEM, SEARCH_RESULT_NEXT_PAGE_PARAMS } from 'stubs/search';
import { generateListStub } from 'stubs/utils';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
export default function useSearchQuery(isSearchPage = false) {
const router = useRouter();
......
import { Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TokenHolders, TokenInfo } from 'types/api/token';
import type { TokenInfo } from 'types/api/token';
import useIsMobile from 'lib/hooks/useIsMobile';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
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 TokenHoldersList from './TokenHoldersList';
import TokenHoldersTable from './TokenHoldersTable';
type Props = {
token?: TokenInfo;
holdersQuery: UseQueryResult<TokenHolders> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
holdersQuery: QueryWithPagesResult<'token_holders'>;
}
const TokenHoldersContent = ({ holdersQuery, token }: Props) => {
......@@ -29,7 +25,7 @@ const TokenHoldersContent = ({ holdersQuery, token }: Props) => {
return <DataFetchAlert/>;
}
const actionBar = isMobile && holdersQuery.isPaginationVisible && (
const actionBar = isMobile && holdersQuery.pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...holdersQuery.pagination }/>
</ActionBar>
......@@ -43,7 +39,7 @@ const TokenHoldersContent = ({ holdersQuery, token }: Props) => {
<TokenHoldersTable
data={ items }
token={ token }
top={ holdersQuery.isPaginationVisible ? 80 : 0 }
top={ holdersQuery.pagination.isVisible ? 80 : 0 }
isLoading={ holdersQuery.isPlaceholderData }
/>
</Box>
......
......@@ -19,10 +19,9 @@ test('base view +@mobile', async({ mount }) => {
items: [ tokenInstanse, tokenInstanse, tokenInstanse ],
next_page_params: { unique_token: 1 },
},
isPaginationVisible: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
pagination: { page: 1 },
pagination: { page: 1, isVisible: true },
}}
/>
</TestApp>,
......
import { Grid } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TokenInventoryResponse } from 'types/api/token';
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 TokenInventoryItem from './TokenInventoryItem';
type Props = {
inventoryQuery: UseQueryResult<TokenInventoryResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
inventoryQuery: QueryWithPagesResult<'token_inventory'>;
}
const TokenInventory = ({ inventoryQuery }: Props) => {
const isMobile = useIsMobile();
const actionBar = isMobile && inventoryQuery.isPaginationVisible && (
const actionBar = isMobile && inventoryQuery.pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...inventoryQuery.pagination }/>
</ActionBar>
......
......@@ -19,10 +19,9 @@ test('erc20 +@mobile', async({ mount }) => {
items: [ tokenTransferMock.erc20 ],
next_page_params: null,
},
isPaginationVisible: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
pagination: { page: 1 },
pagination: { page: 1, isVisible: true },
}}
/>
</TestApp>,
......@@ -43,10 +42,9 @@ test('erc721 +@mobile', async({ mount }) => {
items: [ tokenTransferMock.erc721 ],
next_page_params: null,
},
isPaginationVisible: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
pagination: { page: 1 },
pagination: { page: 1, isVisible: true },
}}
/>
</TestApp>,
......@@ -72,10 +70,9 @@ test('erc1155 +@mobile', async({ mount }) => {
],
next_page_params: null,
},
isPaginationVisible: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
pagination: { page: 1 },
pagination: { page: 1, isVisible: true },
}}
/>
</TestApp>,
......
import { Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/token';
import type { TokenTransferResponse } from 'types/api/tokenTransfer';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import useIsMobile from 'lib/hooks/useIsMobile';
......@@ -13,17 +11,14 @@ import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
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 * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable';
type Props = {
transfersQuery: UseQueryResult<TokenTransferResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
transfersQuery: QueryWithPagesResult<'token_transfers'> | QueryWithPagesResult<'token_instance_transfers'>;
tokenId?: string;
token?: TokenInfo;
}
......@@ -31,7 +26,7 @@ type Props = {
const TokenTransfer = ({ transfersQuery, tokenId, token }: Props) => {
const isMobile = useIsMobile();
const router = useRouter();
const { isError, isPlaceholderData, data, pagination, isPaginationVisible } = transfersQuery;
const { isError, isPlaceholderData, data, pagination } = transfersQuery;
const [ newItemsCount, setNewItemsCount ] = useGradualIncrement(0);
const [ socketAlert, setSocketAlert ] = React.useState('');
......@@ -66,7 +61,7 @@ const TokenTransfer = ({ transfersQuery, tokenId, token }: Props) => {
<Box display={{ base: 'none', lg: 'block' }}>
<TokenTransferTable
data={ data?.items }
top={ isPaginationVisible ? 80 : 0 }
top={ pagination.isVisible ? 80 : 0 }
showSocketInfo={ pagination.page === 1 }
socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount }
......@@ -90,7 +85,7 @@ const TokenTransfer = ({ transfersQuery, tokenId, token }: Props) => {
</>
) : null;
const actionBar = isMobile && isPaginationVisible ? (
const actionBar = isMobile && pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -6,7 +6,6 @@ import type { TokenType } from 'types/api/token';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useDebounce from 'lib/hooks/useDebounce';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import TOKEN_TYPE from 'lib/token/tokenTypes';
import { TOKEN_INFO_ERC_20 } from 'stubs/token';
......@@ -17,7 +16,8 @@ import DataListDisplay from 'ui/shared/DataListDisplay';
import FilterInput from 'ui/shared/filters/FilterInput';
import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TokensListItem from './TokensListItem';
import TokensTable from './TokensTable';
......@@ -32,7 +32,7 @@ const Tokens = () => {
const debouncedFilter = useDebounce(filter, 300);
const { isError, isPlaceholderData, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'tokens',
filters: { q: debouncedFilter, type },
options: {
......@@ -92,7 +92,7 @@ const Tokens = () => {
{ typeFilter }
{ filterInput }
</HStack>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
</>
);
......
......@@ -4,7 +4,6 @@ import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
import { SECOND } from 'lib/consts';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
// import { apos } from 'lib/html-entities';
import { INTERNAL_TX } from 'stubs/internalTx';
import { generateListStub } from 'stubs/utils';
......@@ -12,7 +11,8 @@ import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
// import FilterInput from 'ui/shared/filters/FilterInput';
// import TxInternalsFilter from 'ui/tx/internals/TxInternalsFilter';
import Pagination from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxInternalsList from 'ui/tx/internals/TxInternalsList';
import TxInternalsTable from 'ui/tx/internals/TxInternalsTable';
import type { Sort, SortField } from 'ui/tx/internals/utils';
......@@ -72,7 +72,7 @@ const TxInternals = () => {
// const [ searchTerm, setSearchTerm ] = React.useState<string>('');
const [ sort, setSort ] = React.useState<Sort>();
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'tx_internal_txs',
pathParams: { hash: txInfo.data?.hash },
options: {
......@@ -112,14 +112,14 @@ const TxInternals = () => {
data={ filteredData }
sort={ sort }
onSortToggle={ handleSortToggle }
top={ isPaginationVisible ? 80 : 0 }
top={ pagination.isVisible ? 80 : 0 }
isLoading={ isPlaceholderData }
/>
</Hide>
</>
) : null;
const actionBar = isPaginationVisible ? (
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }>
{ /* <TxInternalsFilter onFilterChange={ handleFilterChange } defaultFilters={ filters } appliedFiltersNum={ filters.length }/> */ }
{ /* <FilterInput onChange={ setSearchTerm } maxW="360px" ml={ 3 } size="xs" placeholder="Search by addresses, hash, method..."/> */ }
......
......@@ -2,20 +2,20 @@ import { Box, Text } from '@chakra-ui/react';
import React from 'react';
import { SECOND } from 'lib/consts';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { LOG } from 'stubs/log';
import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
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 TxPendingAlert from 'ui/tx/TxPendingAlert';
import TxSocketAlert from 'ui/tx/TxSocketAlert';
import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxLogs = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'tx_logs',
pathParams: { hash: txInfo.data?.hash },
options: {
......@@ -38,7 +38,7 @@ const TxLogs = () => {
return (
<Box>
{ isPaginationVisible && (
{ pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -2,11 +2,11 @@ import { Accordion, Hide, Show, Text } from '@chakra-ui/react';
import React from 'react';
import { SECOND } from 'lib/consts';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { TX_STATE_CHANGES } from 'stubs/txStateChanges';
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 TxStateList from 'ui/tx/state/TxStateList';
import TxStateTable from 'ui/tx/state/TxStateTable';
import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
......@@ -16,7 +16,7 @@ import TxSocketAlert from './TxSocketAlert';
const TxState = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isPlaceholderData, isError, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
resourceName: 'tx_state_changes',
pathParams: { hash: txInfo.data?.hash },
options: {
......@@ -38,7 +38,7 @@ const TxState = () => {
const content = data ? (
<Accordion allowMultiple defaultIndex={ [] }>
<Hide below="lg" ssr={ false }>
<TxStateTable data={ data.items } isLoading={ isPlaceholderData } top={ isPaginationVisible ? 80 : 0 }/>
<TxStateTable data={ data.items } isLoading={ isPlaceholderData } top={ pagination.isVisible ? 80 : 0 }/>
</Hide>
<Show below="lg" ssr={ false }>
<TxStateList data={ data.items } isLoading={ isPlaceholderData }/>
......@@ -46,7 +46,7 @@ const TxState = () => {
</Accordion>
) : null;
const actionBar = isPaginationVisible ? (
const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 } showShadow>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
......
......@@ -6,14 +6,14 @@ import type { TokenType } from 'types/api/token';
import { SECOND } from 'lib/consts';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import TOKEN_TYPE from 'lib/token/tokenTypes';
import { getTokenTransfersStub } from 'stubs/token';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
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 TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable';
......@@ -77,7 +77,7 @@ const TxTokenTransfer = () => {
appliedFiltersNum={ numActiveFilters }
isLoading={ tokenTransferQuery.isPlaceholderData }
/>
{ tokenTransferQuery.isPaginationVisible && <Pagination ml="auto" { ...tokenTransferQuery.pagination }/> }
<Pagination ml="auto" { ...tokenTransferQuery.pagination }/>
</ActionBar>
) : null;
......
import { Box, Show, Hide } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TxsResponse } from 'types/api/transaction';
import useIsMobile from 'lib/hooks/useIsMobile';
import AddressCsvExportLink from 'ui/address/AddressCsvExportLink';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import TxsHeaderMobile from './TxsHeaderMobile';
......@@ -15,13 +12,8 @@ import TxsListItem from './TxsListItem';
import TxsTable from './TxsTable';
import useTxsSort from './useTxsSort';
type QueryResult = UseQueryResult<TxsResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
type Props = {
query: QueryResult;
query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'>;
showBlockInfo?: boolean;
showSocketInfo?: boolean;
socketInfoAlert?: string;
......@@ -79,7 +71,7 @@ const TxsContent = ({
showSocketInfo={ showSocketInfo }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
top={ top || query.isPaginationVisible ? 80 : 0 }
top={ top || query.pagination.isVisible ? 80 : 0 }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
isLoading={ isPlaceholderData }
......@@ -94,7 +86,7 @@ const TxsContent = ({
sorting={ sorting }
setSorting={ setSortByValue }
paginationProps={ query.pagination }
showPagination={ query.isPaginationVisible }
showPagination={ query.pagination.isVisible }
filterComponent={ filter }
linkSlot={ currentAddress ?
<AddressCsvExportLink address={ currentAddress } type="transactions" ml={ 2 } isLoading={ query.pagination.isLoading }/> : null
......
......@@ -2,11 +2,12 @@ import { HStack, chakra } from '@chakra-ui/react';
import React from 'react';
import type { Sort as TSort } from 'types/client/txs-sort';
import type { PaginationParams } from 'ui/shared/pagination/types';
// import FilterInput from 'ui/shared/filters/FilterInput';
import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/Pagination';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/pagination/Pagination';
import type { Option } from 'ui/shared/sort/Sort';
import Sort from 'ui/shared/sort/Sort';
......@@ -23,7 +24,7 @@ const SORT_OPTIONS: Array<Option<TSort>> = [
type Props = {
sorting: TSort;
setSorting: (val: TSort | undefined) => void;
paginationProps: PaginationProps;
paginationProps: PaginationParams;
className?: string;
showPagination?: boolean;
filterComponent?: React.ReactNode;
......
......@@ -101,7 +101,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
</Box>
) }
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address maxWidth={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<Address w={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon address={ tx.from } isLoading={ isLoading }/>
<AddressLink
type="address"
......@@ -126,7 +126,7 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
</Box>
) }
{ dataTo ? (
<Address maxWidth={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<Address w={ `calc((100% - ${ currentAddress ? TAG_WIDTH + 16 : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon address={ dataTo } isLoading={ isLoading }/>
<AddressLink
type="address"
......
import React from 'react';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import Pagination from 'ui/shared/Pagination';
interface Props {
pagination: PaginationProps;
isPaginationVisible: boolean;
}
const TxsTabSlot = ({ pagination, isPaginationVisible }: Props) => {
if (!isPaginationVisible) {
return null;
}
return <Pagination my={ 1 } { ...pagination }/>;
};
export default TxsTabSlot;
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { TxsResponse } from 'types/api/transaction';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import type { Props as PaginationProps } from 'ui/shared/Pagination';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent';
type QueryResult = UseQueryResult<TxsResponse> & {
pagination: PaginationProps;
isPaginationVisible: boolean;
};
type Props = {
query: QueryResult;
query: QueryWithPagesResult<'txs_watchlist'>;
}
const TxsWatchlist = ({ query }: Props) => {
......
......@@ -4567,27 +4567,27 @@
"@tanstack/query-core" "4.29.11"
use-sync-external-store "^1.2.0"
"@testing-library/dom@^8.5.0":
version "8.19.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f"
integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A==
"@testing-library/dom@^9.0.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.0.tgz#ed8ce10aa5e05eb6eaf0635b5b8975d889f66075"
integrity sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
"@types/aria-query" "^5.0.1"
aria-query "^5.0.0"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.4.4"
lz-string "^1.5.0"
pretty-format "^27.0.2"
"@testing-library/react@^13.4.0":
version "13.4.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.4.0.tgz#6a31e3bf5951615593ad984e96b9e5e2d9380966"
integrity sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==
"@testing-library/react@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c"
integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.5.0"
"@testing-library/dom" "^9.0.0"
"@types/react-dom" "^18.0.0"
"@tootallnate/once@2":
......@@ -4625,10 +4625,10 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@types/aria-query@^4.2.0":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
"@types/aria-query@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==
"@types/babel__core@^7.1.14":
version "7.1.20"
......@@ -6844,6 +6844,13 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-fetch@^3.0.4:
version "3.1.6"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.6.tgz#bae05aa31a4da760969756318feeee6e70f15d6c"
integrity sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==
dependencies:
node-fetch "^2.6.11"
cross-fetch@^3.1.4, cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
......@@ -9555,6 +9562,14 @@ jest-environment-node@^29.3.1:
jest-mock "^29.3.1"
jest-util "^29.3.1"
jest-fetch-mock@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
dependencies:
cross-fetch "^3.0.4"
promise-polyfill "^8.1.3"
jest-get-type@^29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408"
......@@ -10210,10 +10225,10 @@ lru_map@^0.3.3:
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
magic-string@^0.27.0:
version "0.27.0"
......@@ -10528,6 +10543,13 @@ node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.11:
version "2.6.11"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25"
integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==
dependencies:
whatwg-url "^5.0.0"
node-fetch@^3.2.9:
version "3.2.10"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8"
......@@ -11240,6 +11262,11 @@ progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-polyfill@^8.1.3:
version "8.3.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
prompts@^2.0.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
......
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