Commit 55bc66ea authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #2253 from blockscout/fe-2233

add blackfort validators
parents 3716c2f0 35b715cd
# Set of ENVs for BXN Testnet network explorer
# https://blackfort-testnet.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=blackfort_testnet"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=blackfort-testnet.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/blackfort-testnet.json
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/blackfort.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xcb4140e22cde3412eb5aecdedf2403032c7a251f5c96b11122aca5b1b88ed953
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(92deg, rgb(3, 150, 254) 0.24%, rgb(36, 209, 245) 98.31%)
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=TBXN
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=TBXN
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_ID=4777
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_NAME=BXN Testnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://testnet.blackfort.network/rpc
NEXT_PUBLIC_NETWORK_SHORT_NAME=BXN Testnet
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/blackfort.png
NEXT_PUBLIC_STATS_API_HOST=https://stats-blackfort-testnet.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=blackfort
\ No newline at end of file
...@@ -679,7 +679,7 @@ The feature enables the Validators page which provides detailed information abou ...@@ -679,7 +679,7 @@ The feature enables the Validators page which provides detailed information abou
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability'` | Chain type | Required | - | `'stability'` | v1.25.0+ | | NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability' \| 'blackfort'` | Chain type | Required | - | `'stability'` | v1.25.0+ |
   
......
...@@ -118,7 +118,15 @@ import type { TxInterpretationResponse } from 'types/api/txInterpretation'; ...@@ -118,7 +118,15 @@ import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters'; import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges'; import type { TxStateChanges } from 'types/api/txStateChanges';
import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps'; import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps';
import type { ValidatorsCountersResponse, ValidatorsFilters, ValidatorsResponse, ValidatorsSorting } from 'types/api/validators'; import type {
ValidatorsStabilityCountersResponse,
ValidatorsStabilityFilters,
ValidatorsStabilityResponse,
ValidatorsStabilitySorting,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
ValidatorsBlackfortSorting,
} from 'types/api/validators';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { import type {
...@@ -897,14 +905,19 @@ export const RESOURCES = { ...@@ -897,14 +905,19 @@ export const RESOURCES = {
}, },
// VALIDATORS // VALIDATORS
validators: { validators_stability: {
path: '/api/v2/validators/:chainType', path: '/api/v2/validators/stability',
pathParams: [ 'chainType' as const ],
filterFields: [ 'address_hash' as const, 'state_filter' as const ], filterFields: [ 'address_hash' as const, 'state_filter' as const ],
}, },
validators_counters: { validators_stability_counters: {
path: '/api/v2/validators/:chainType/counters', path: '/api/v2/validators/stability/counters',
pathParams: [ 'chainType' as const ], },
validators_blackfort: {
path: '/api/v2/validators/blackfort',
filterFields: [],
},
validators_blackfort_counters: {
path: '/api/v2/validators/blackfort/counters',
}, },
// BLOBS // BLOBS
...@@ -1002,7 +1015,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward ...@@ -1002,7 +1015,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' | 'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' | 'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' | 'watchlist' | 'private_tags_address' | 'private_tags_tx' |
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators' | 'noves_address_history'; 'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -1123,8 +1136,10 @@ Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse : ...@@ -1123,8 +1136,10 @@ Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob : Q extends 'blob' ? Blob :
Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> : Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview : Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
Q extends 'validators' ? ValidatorsResponse : Q extends 'validators_stability' ? ValidatorsStabilityResponse :
Q extends 'validators_counters' ? ValidatorsCountersResponse : Q extends 'validators_stability_counters' ? ValidatorsStabilityCountersResponse :
Q extends 'validators_blackfort' ? ValidatorsBlackfortResponse :
Q extends 'validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse :
Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse : Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse :
Q extends 'shibarium_deposits' ? ShibariumDepositsResponse : Q extends 'shibarium_deposits' ? ShibariumDepositsResponse :
Q extends 'shibarium_withdrawals_count' ? number : Q extends 'shibarium_withdrawals_count' ? number :
...@@ -1200,7 +1215,7 @@ Q extends 'verified_contracts' ? VerifiedContractsFilters : ...@@ -1200,7 +1215,7 @@ Q extends 'verified_contracts' ? VerifiedContractsFilters :
Q extends 'addresses_lookup' ? EnsAddressLookupFilters : Q extends 'addresses_lookup' ? EnsAddressLookupFilters :
Q extends 'domains_lookup' ? EnsDomainLookupFilters : Q extends 'domains_lookup' ? EnsDomainLookupFilters :
Q extends 'user_ops' ? UserOpsFilters : Q extends 'user_ops' ? UserOpsFilters :
Q extends 'validators' ? ValidatorsFilters : Q extends 'validators_stability' ? ValidatorsStabilityFilters :
Q extends 'address_mud_tables' ? AddressMudTablesFilter : Q extends 'address_mud_tables' ? AddressMudTablesFilter :
Q extends 'address_mud_records' ? AddressMudRecordsFilter : Q extends 'address_mud_records' ? AddressMudRecordsFilter :
never; never;
...@@ -1214,7 +1229,8 @@ Q extends 'verified_contracts' ? VerifiedContractsSorting : ...@@ -1214,7 +1229,8 @@ Q extends 'verified_contracts' ? VerifiedContractsSorting :
Q extends 'address_txs' ? TransactionsSorting : Q extends 'address_txs' ? TransactionsSorting :
Q extends 'addresses_lookup' ? EnsLookupSorting : Q extends 'addresses_lookup' ? EnsLookupSorting :
Q extends 'domains_lookup' ? EnsLookupSorting : Q extends 'domains_lookup' ? EnsLookupSorting :
Q extends 'validators' ? ValidatorsSorting : Q extends 'validators_stability' ? ValidatorsStabilitySorting :
Q extends 'validators_blackfort' ? ValidatorsBlackfortSorting :
Q extends 'address_mud_records' ? AddressMudRecordsSorting : Q extends 'address_mud_records' ? AddressMudRecordsSorting :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
import type {
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: ValidatorBlackfort = {
address: addressMock.withName,
name: 'testnet-3',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validator2: ValidatorBlackfort = {
address: addressMock.withEns,
name: 'GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG',
commission: 5000,
delegated_amount: '10000',
self_bonded_amount: '100',
};
export const validator3: ValidatorBlackfort = {
address: addressMock.withoutName,
name: 'testnet-1',
commission: 0,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validatorsResponse: ValidatorsBlackfortResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
import type { Validator, ValidatorsCountersResponse, ValidatorsResponse } from 'types/api/validators'; import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorsStabilityResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address'; import * as addressMock from '../address/address';
export const validator1: Validator = { export const validator1: ValidatorStability = {
address: addressMock.withName, address: addressMock.withName,
blocks_validated_count: 7334224, blocks_validated_count: 7334224,
state: 'active', state: 'active',
}; };
export const validator2: Validator = { export const validator2: ValidatorStability = {
address: addressMock.withEns, address: addressMock.withEns,
blocks_validated_count: 8937453, blocks_validated_count: 8937453,
state: 'probation', state: 'probation',
}; };
export const validator3: Validator = { export const validator3: ValidatorStability = {
address: addressMock.withoutName, address: addressMock.withoutName,
blocks_validated_count: 1234, blocks_validated_count: 1234,
state: 'inactive', state: 'inactive',
}; };
export const validatorsResponse: ValidatorsResponse = { export const validatorsResponse: ValidatorsStabilityResponse = {
items: [ validator1, validator2, validator3 ], items: [ validator1, validator2, validator3 ],
next_page_params: null, next_page_params: null,
}; };
export const validatorsCountersResponse: ValidatorsCountersResponse = { export const validatorsCountersResponse: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42', active_validators_counter: '42',
active_validators_percentage: 7.14, active_validators_percentage: 7.14,
new_validators_counter_24h: '11', new_validators_counter_24h: '11',
......
...@@ -4,7 +4,21 @@ import React from 'react'; ...@@ -4,7 +4,21 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
const Validators = dynamic(() => import('ui/pages/Validators'), { ssr: false }); import config from 'configs/app';
const validatorsFeature = config.features.validators;
const Validators = dynamic(() => {
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'stability') {
return import('ui/pages/ValidatorsStability');
}
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'blackfort') {
return import('ui/pages/ValidatorsBlackfort');
}
throw new Error('Validators feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
......
import type { Validator, ValidatorsCountersResponse } from 'types/api/validators'; import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
} from 'types/api/validators';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS } from './addressParams';
export const VALIDATOR: Validator = { export const VALIDATOR_STABILITY: ValidatorStability = {
address: ADDRESS_PARAMS, address: ADDRESS_PARAMS,
blocks_validated_count: 25987, blocks_validated_count: 25987,
state: 'active', state: 'active',
}; };
export const VALIDATORS_COUNTERS: ValidatorsCountersResponse = { export const VALIDATORS_STABILITY_COUNTERS: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42', active_validators_counter: '42',
active_validators_percentage: 7.14, active_validators_percentage: 7.14,
new_validators_counter_24h: '11', new_validators_counter_24h: '11',
validators_counter: '140', validators_counter: '140',
}; };
export const VALIDATOR_BLACKFORT: ValidatorBlackfort = {
address: ADDRESS_PARAMS,
name: 'testnet-1',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const VALIDATORS_BLACKFORT_COUNTERS: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
...@@ -5,6 +5,7 @@ import path from 'path'; ...@@ -5,6 +5,7 @@ import path from 'path';
const PRESETS = { const PRESETS = {
arbitrum: 'https://arbitrum.blockscout.com', arbitrum: 'https://arbitrum.blockscout.com',
base: 'https://base.blockscout.com', base: 'https://base.blockscout.com',
blackfort_testnet: 'https://blackfort-testnet.blockscout.com',
celo_alfajores: 'https://celo-alfajores.blockscout.com', celo_alfajores: 'https://celo-alfajores.blockscout.com',
eth: 'https://eth.blockscout.com', eth: 'https://eth.blockscout.com',
eth_goerli: 'https://eth-goerli.blockscout.com', eth_goerli: 'https://eth-goerli.blockscout.com',
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export interface Validator { export interface ValidatorStability {
address: AddressParam; address: AddressParam;
blocks_validated_count: number; blocks_validated_count: number;
state: 'active' | 'probation' | 'inactive'; state: 'active' | 'probation' | 'inactive';
} }
export interface ValidatorsResponse { export interface ValidatorsStabilityResponse {
items: Array<Validator>; items: Array<ValidatorStability>;
next_page_params: { next_page_params: {
'address_hash': string; 'address_hash': string;
'blocks_validated': string; 'blocks_validated': string;
'items_count': string; 'items_count': string;
'state': Validator['state']; 'state': ValidatorStability['state'];
} | null; } | null;
} }
export interface ValidatorsCountersResponse { export interface ValidatorsStabilityCountersResponse {
active_validators_counter: string; active_validators_counter: string;
active_validators_percentage: number; active_validators_percentage: number;
new_validators_counter_24h: string; new_validators_counter_24h: string;
validators_counter: string; validators_counter: string;
} }
export interface ValidatorsFilters { export interface ValidatorsStabilityFilters {
// address_hash: string | undefined; // right now API doesn't support filtering by address_hash // address_hash: string | undefined; // right now API doesn't support filtering by address_hash
state_filter: Validator['state'] | undefined; state_filter: ValidatorStability['state'] | undefined;
} }
export interface ValidatorsSorting { export interface ValidatorsStabilitySorting {
sort: 'state' | 'blocks_validated'; sort: 'state' | 'blocks_validated';
order: 'asc' | 'desc'; order: 'asc' | 'desc';
} }
export type ValidatorsSortingField = ValidatorsSorting['sort']; export type ValidatorsStabilitySortingField = ValidatorsStabilitySorting['sort'];
export type ValidatorsSortingValue = `${ ValidatorsSortingField }-${ ValidatorsSorting['order'] }`; export type ValidatorsStabilitySortingValue = `${ ValidatorsStabilitySortingField }-${ ValidatorsStabilitySorting['order'] }`;
export interface ValidatorBlackfort {
address: AddressParam;
name: string;
commission: number;
delegated_amount: string;
self_bonded_amount: string;
}
export interface ValidatorsBlackfortResponse {
items: Array<ValidatorBlackfort>;
next_page_params: {
'address_hash': string;
} | null;
}
export interface ValidatorsBlackfortCountersResponse {
new_validators_counter_24h: string;
validators_counter: string;
}
export interface ValidatorsBlackfortSorting {
sort: 'address_hash';
order: 'asc' | 'desc';
}
export type ValidatorsBlackfortSortingField = ValidatorsBlackfortSorting['sort'];
export type ValidatorsBlackfortSortingValue = `${ ValidatorsBlackfortSortingField }-${ ValidatorsBlackfortSorting['order'] }`;
...@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils'; ...@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils';
export const VALIDATORS_CHAIN_TYPE = [ export const VALIDATORS_CHAIN_TYPE = [
'stability', 'stability',
'blackfort',
] as const; ] as const;
export type ValidatorsChainType = ArrayElement<typeof VALIDATORS_CHAIN_TYPE>; export type ValidatorsChainType = ArrayElement<typeof VALIDATORS_CHAIN_TYPE>;
import React from 'react';
import * as validatorsMock from 'mocks/validators/blackfort';
import { test, expect } from 'playwright/lib';
import Validators from './ValidatorsBlackfort';
const chainType = 'blackfort';
test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]);
await mockApiResponse('validators_blackfort', validatorsMock.validatorsResponse);
await mockApiResponse('validators_blackfort_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd();
const component = await render(<Validators/>);
await expect(component).toHaveScreenshot();
});
import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type {
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import config from 'configs/app';
import { generateListStub } from 'stubs/utils';
import { VALIDATOR_BLACKFORT } from 'stubs/validators';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import { VALIDATORS_BLACKFORT_SORT_OPTIONS } from 'ui/validatorsBlackfort/utils';
import ValidatorsCounters from 'ui/validatorsBlackfort/ValidatorsCounters';
import ValidatorsList from 'ui/validatorsBlackfort/ValidatorsList';
import ValidatorsTable from 'ui/validatorsBlackfort/ValidatorsTable';
const ValidatorsBlackfort = () => {
const router = useRouter();
const [ sort, setSort ] =
React.useState<ValidatorsBlackfortSortingValue | undefined>(
getSortValueFromQuery<ValidatorsBlackfortSortingValue>(router.query, VALIDATORS_BLACKFORT_SORT_OPTIONS),
);
const { isError, isPlaceholderData, data, pagination, onSortingChange } = useQueryWithPages({
resourceName: 'validators_blackfort',
sorting: getSortParamsFromValue<ValidatorsBlackfortSortingValue, ValidatorsBlackfortSortingField, ValidatorsBlackfortSorting['order']>(sort),
options: {
enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators_blackfort'>(
VALIDATOR_BLACKFORT,
50,
{ next_page_params: null },
),
},
});
const handleSortChange = React.useCallback((value?: ValidatorsBlackfortSortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const sortButton = (
<Sort
name="validators_sorting"
defaultValue={ sort }
options={ VALIDATORS_BLACKFORT_SORT_OPTIONS }
onChange={ handleSortChange }
/>
);
const actionBar = (
<>
<HStack spacing={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
{ sortButton }
</HStack>
{ pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) }
</>
);
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
<ValidatorsList data={ data.items } isLoading={ isPlaceholderData }/>
</Show>
<Hide below="lg" ssr={ false }>
<ValidatorsTable
data={ data.items }
sort={ sort }
setSorting={ handleSortChange }
isLoading={ isPlaceholderData }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Hide>
</>
) : null;
return (
<Box>
<PageTitle title="Validators" withTextAd/>
<ValidatorsCounters/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no validators."
content={ content }
actionBar={ actionBar }
/>
</Box>
);
};
export default ValidatorsBlackfort;
import React from 'react'; import React from 'react';
import * as validatorsMock from 'mocks/validators/index'; import * as validatorsMock from 'mocks/validators/stability';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
import Validators from './Validators'; import Validators from './ValidatorsStability';
const chainType = 'stability'; const chainType = 'stability';
...@@ -11,8 +11,8 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd ...@@ -11,8 +11,8 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd
await mockEnvs([ await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ], [ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]); ]);
await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType } }); await mockApiResponse('validators_stability', validatorsMock.validatorsResponse);
await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType } }); await mockApiResponse('validators_stability_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd(); await mockTextAd();
const component = await render(<Validators/>); const component = await render(<Validators/>);
......
...@@ -2,8 +2,12 @@ import { Box, Hide, HStack, Show } from '@chakra-ui/react'; ...@@ -2,8 +2,12 @@ import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types'; import type {
import type { ValidatorsFilters, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators'; ValidatorsStabilityFilters,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import config from 'configs/app'; import config from 'configs/app';
// import useDebounce from 'lib/hooks/useDebounce'; // import useDebounce from 'lib/hooks/useDebounce';
...@@ -11,7 +15,7 @@ import useIsMobile from 'lib/hooks/useIsMobile'; ...@@ -11,7 +15,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { generateListStub } from 'stubs/utils'; import { generateListStub } from 'stubs/utils';
import { VALIDATOR } from 'stubs/validators'; import { VALIDATOR_STABILITY } from 'stubs/validators';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
// import FilterInput from 'ui/shared/filters/FilterInput'; // import FilterInput from 'ui/shared/filters/FilterInput';
...@@ -21,35 +25,36 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; ...@@ -21,35 +25,36 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort'; import Sort from 'ui/shared/sort/Sort';
import { SORT_OPTIONS } from 'ui/validators/utils'; import { VALIDATORS_STABILITY_SORT_OPTIONS } from 'ui/validatorsStability/utils';
import ValidatorsCounters from 'ui/validators/ValidatorsCounters'; import ValidatorsCounters from 'ui/validatorsStability/ValidatorsCounters';
import ValidatorsFilter from 'ui/validators/ValidatorsFilter'; import ValidatorsFilter from 'ui/validatorsStability/ValidatorsFilter';
import ValidatorsList from 'ui/validators/ValidatorsList'; import ValidatorsList from 'ui/validatorsStability/ValidatorsList';
import ValidatorsTable from 'ui/validators/ValidatorsTable'; import ValidatorsTable from 'ui/validatorsStability/ValidatorsTable';
const Validators = () => { const ValidatorsStability = () => {
const router = useRouter(); const router = useRouter();
// const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.address_hash) || undefined); // const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.address_hash) || undefined);
const [ statusFilter, setStatusFilter ] = React.useState(getQueryParamString(router.query.state_filter) as ValidatorsFilters['state_filter'] || undefined); const [ statusFilter, setStatusFilter ] =
const [ sort, setSort ] = React.useState(getQueryParamString(router.query.state_filter) as ValidatorsStabilityFilters['state_filter'] || undefined);
React.useState<ValidatorsSortingValue | undefined>(getSortValueFromQuery<ValidatorsSortingValue>(router.query, SORT_OPTIONS)); const [ sort, setSort ] = React.useState<ValidatorsStabilitySortingValue | undefined>(
getSortValueFromQuery<ValidatorsStabilitySortingValue>(router.query, VALIDATORS_STABILITY_SORT_OPTIONS),
);
// const debouncedSearchTerm = useDebounce(searchTerm || '', 300); // const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({ const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({
resourceName: 'validators', resourceName: 'validators_stability',
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
filters: { filters: {
// address_hash: debouncedSearchTerm, // address_hash: debouncedSearchTerm,
state_filter: statusFilter, state_filter: statusFilter,
}, },
sorting: getSortParamsFromValue<ValidatorsSortingValue, ValidatorsSortingField, ValidatorsSorting['order']>(sort), sorting: getSortParamsFromValue<ValidatorsStabilitySortingValue, ValidatorsStabilitySortingField, ValidatorsStabilitySorting['order']>(sort),
options: { options: {
enabled: config.features.validators.isEnabled, enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators'>( placeholderData: generateListStub<'validators_stability'>(
VALIDATOR, VALIDATOR_STABILITY,
50, 50,
{ next_page_params: null }, { next_page_params: null },
), ),
...@@ -69,7 +74,7 @@ const Validators = () => { ...@@ -69,7 +74,7 @@ const Validators = () => {
return; return;
} }
const state = value === 'all' ? undefined : value as ValidatorsFilters['state_filter']; const state = value === 'all' ? undefined : value as ValidatorsStabilityFilters['state_filter'];
onFilterChange({ onFilterChange({
// address_hash: debouncedSearchTerm, // address_hash: debouncedSearchTerm,
...@@ -78,12 +83,13 @@ const Validators = () => { ...@@ -78,12 +83,13 @@ const Validators = () => {
setStatusFilter(state); setStatusFilter(state);
}, [ onFilterChange ]); }, [ onFilterChange ]);
const handleSortChange = React.useCallback((value?: ValidatorsSortingValue) => { const handleSortChange = React.useCallback((value?: ValidatorsStabilitySortingValue) => {
setSort(value); setSort(value);
onSortingChange(getSortParamsFromValue(value)); onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]); }, [ onSortingChange ]);
const filterMenu = <ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>; const filterMenu =
<ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
// const filterInput = ( // const filterInput = (
// <FilterInput // <FilterInput
...@@ -99,7 +105,7 @@ const Validators = () => { ...@@ -99,7 +105,7 @@ const Validators = () => {
<Sort <Sort
name="validators_sorting" name="validators_sorting"
defaultValue={ sort } defaultValue={ sort }
options={ SORT_OPTIONS } options={ VALIDATORS_STABILITY_SORT_OPTIONS }
onChange={ handleSortChange } onChange={ handleSortChange }
/> />
); );
...@@ -141,7 +147,7 @@ const Validators = () => { ...@@ -141,7 +147,7 @@ const Validators = () => {
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } items={ data?.items }
emptyText="There are no verified contracts." emptyText="There are no validators."
filterProps={{ filterProps={{
emptyFilteredText: `Couldn${ apos }t find any validator that matches your query.`, emptyFilteredText: `Couldn${ apos }t find any validator that matches your query.`,
hasActiveFilters: Boolean( hasActiveFilters: Boolean(
...@@ -156,4 +162,4 @@ const Validators = () => { ...@@ -156,4 +162,4 @@ const Validators = () => {
); );
}; };
export default Validators; export default ValidatorsStability;
import React from 'react'; import React from 'react';
import type { Validator } from 'types/api/validators'; import type { ValidatorStability } from 'types/api/validators';
import StatusTag from './StatusTag'; import StatusTag from './StatusTag';
interface Props { interface Props {
state: Validator['state']; state: ValidatorStability['state'];
isLoading?: boolean; isLoading?: boolean;
} }
const ValidatorStatus = ({ state, isLoading }: Props) => { const ValidatorStabilityStatus = ({ state, isLoading }: Props) => {
switch (state) { switch (state) {
case 'active': case 'active':
return <StatusTag type="ok" text="Active" isLoading={ isLoading }/>; return <StatusTag type="ok" text="Active" isLoading={ isLoading }/>;
...@@ -20,4 +20,4 @@ const ValidatorStatus = ({ state, isLoading }: Props) => { ...@@ -20,4 +20,4 @@ const ValidatorStatus = ({ state, isLoading }: Props) => {
} }
}; };
export default React.memo(ValidatorStatus); export default React.memo(ValidatorStabilityStatus);
import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_BLACKFORT_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_blackfort_counters', {
queryOptions: {
enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_BLACKFORT_COUNTERS,
},
});
if (!countersQuery.data) {
return null;
}
return (
<Box columnGap={ 3 } rowGap={ 3 } mb={ 6 } display="grid" gridTemplateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }}>
<StatsWidget
label="Total validators"
value={ Number(countersQuery.data.validators_counter).toLocaleString() }
diff={ Number(countersQuery.data.new_validators_counter_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
/>
</Box>
);
};
export default React.memo(ValidatorsCounters);
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Validator } from 'types/api/validators'; import type { ValidatorBlackfort } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem'; import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<Validator>; isLoading: boolean }) => { const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorBlackfort>; isLoading: boolean }) => {
return ( return (
<Box> <Box>
{ data.map((item, index) => ( { data.map((item, index) => (
......
import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsListItem = ({ data, isLoading }: Props) => {
return (
<ListItemMobileGrid.Container gridTemplateColumns="130px auto">
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity
isLoading={ isLoading }
address={ data.address }
truncation="constant"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Name</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Flex><TruncatedValue value={ data.name } isLoading={ isLoading }/></Flex>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Commission</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Self bonded</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Delegated amount</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default React.memo(ValidatorsListItem);
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type {
ValidatorBlackfort,
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import { currencyUnits } from 'lib/units';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { VALIDATORS_BLACKFORT_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem';
interface Props {
data: Array<ValidatorBlackfort>;
sort: ValidatorsBlackfortSortingValue | undefined;
setSorting: (val: ValidatorsBlackfortSortingValue | undefined) => void;
isLoading?: boolean;
top: number;
}
const ValidatorsTable = ({ data, sort, setSorting, isLoading, top }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsBlackfortSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsBlackfortSortingField) => () => {
const value = getNextSortValue<ValidatorsBlackfortSortingField, ValidatorsBlackfortSortingValue>(VALIDATORS_BLACKFORT_SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th>
<Link
display="flex"
alignItems="center"
onClick={ isLoading ? undefined : onSortToggle('address_hash') }
columnGap={ 1 }
>
{ sort?.includes('address') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
Validator’s address
</Link>
</Th>
<Th>Name</Th>
<Th isNumeric>Commission</Th>
<Th isNumeric>{ `Self bonded ${ currencyUnits.ether }` }</Th>
<Th isNumeric>{ `Delegated amount ${ currencyUnits.ether }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => (
<ValidatorsTableItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
);
};
export default React.memo(ValidatorsTable);
import { Tr, Td, Skeleton, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsTableItem = ({ data, isLoading }: Props) => {
return (
<Tr>
<Td verticalAlign="middle">
<AddressEntity
address={ data.address }
isLoading={ isLoading }
truncation="constant"
/>
</Td>
<Td verticalAlign="middle">
<Flex>
<TruncatedValue value={ data.name } isLoading={ isLoading }/>
</Flex>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
</Tr>
);
};
export default React.memo(ValidatorsTableItem);
import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option';
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<TOption<ValidatorsBlackfortSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Address descending', id: 'address_hash-desc' },
{ title: 'Address ascending', id: 'address_hash-asc' },
];
export const VALIDATORS_BLACKFORT_SORT_SEQUENCE: Record<ValidatorsBlackfortSortingField, Array<ValidatorsBlackfortSortingValue | undefined>> = {
address_hash: [ 'address_hash-desc', 'address_hash-asc', undefined ],
};
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_COUNTERS } from 'stubs/validators'; import { VALIDATORS_STABILITY_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget'; import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => { const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_counters', { const countersQuery = useApiQuery('validators_stability_counters', {
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
queryOptions: { queryOptions: {
enabled: config.features.validators.isEnabled, enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_COUNTERS, placeholderData: VALIDATORS_STABILITY_COUNTERS,
}, },
}); });
......
import React from 'react'; import React from 'react';
import type { ValidatorsFilters } from 'types/api/validators'; import type { ValidatorsStabilityFilters } from 'types/api/validators';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio'; import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
...@@ -13,7 +13,7 @@ const OPTIONS = [ ...@@ -13,7 +13,7 @@ const OPTIONS = [
interface Props { interface Props {
hasActiveFilter: boolean; hasActiveFilter: boolean;
defaultValue: ValidatorsFilters['state_filter'] | undefined; defaultValue: ValidatorsStabilityFilters['state_filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void; onChange: (nextValue: string | Array<string>) => void;
} }
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { ValidatorStability } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorStability>; isLoading: boolean }) => {
return (
<Box>
{ data.map((item, index) => (
<ValidatorsListItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }
/>
)) }
</Box>
);
};
export default React.memo(ValidatorsList);
import { Skeleton } from '@chakra-ui/react'; import { Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Validator } from 'types/api/validators'; import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus'; import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props { interface Props {
data: Validator; data: ValidatorStability;
isLoading?: boolean; isLoading?: boolean;
} }
......
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Validator, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators'; import type {
ValidatorStability,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue'; import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import { SORT_SEQUENCE } from './utils'; import { VALIDATORS_STABILITY_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem'; import ValidatorsTableItem from './ValidatorsTableItem';
interface Props { interface Props {
data: Array<Validator>; data: Array<ValidatorStability>;
sort: ValidatorsSortingValue | undefined; sort: ValidatorsStabilitySortingValue | undefined;
setSorting: (val: ValidatorsSortingValue | undefined) => void; setSorting: (val: ValidatorsStabilitySortingValue | undefined) => void;
isLoading?: boolean; isLoading?: boolean;
} }
const ValidatorsTable = ({ data, sort, setSorting, isLoading }: Props) => { const ValidatorsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc' as ValidatorsStabilitySorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsSortingField) => () => { const onSortToggle = React.useCallback((field: ValidatorsStabilitySortingField) => () => {
const value = getNextSortValue<ValidatorsSortingField, ValidatorsSortingValue>(SORT_SEQUENCE, field)(sort); const value = getNextSortValue<ValidatorsStabilitySortingField, ValidatorsStabilitySortingValue>(VALIDATORS_STABILITY_SORT_SEQUENCE, field)(sort);
setSorting(value); setSorting(value);
}, [ sort, setSorting ]); }, [ sort, setSorting ]);
......
import { Tr, Td, Skeleton } from '@chakra-ui/react'; import { Tr, Td, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Validator } from 'types/api/validators'; import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus'; import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props { interface Props {
data: Validator; data: ValidatorStability;
isLoading?: boolean; isLoading?: boolean;
} }
......
import type { ValidatorsSortingValue, ValidatorsSortingField } from 'types/api/validators'; import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option'; import type { TOption } from 'ui/shared/sort/Option';
export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [ export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<TOption<ValidatorsStabilitySortingValue>> = [
{ title: 'Default', id: undefined }, { title: 'Default', id: undefined },
{ title: 'Status descending', id: 'state-desc' }, { title: 'Status descending', id: 'state-desc' },
{ title: 'Status ascending', id: 'state-asc' }, { title: 'Status ascending', id: 'state-asc' },
...@@ -10,7 +13,7 @@ export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [ ...@@ -10,7 +13,7 @@ export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [
{ title: 'Blocks validated ascending', id: 'blocks_validated-asc' }, { title: 'Blocks validated ascending', id: 'blocks_validated-asc' },
]; ];
export const SORT_SEQUENCE: Record<ValidatorsSortingField, Array<ValidatorsSortingValue | undefined>> = { export const VALIDATORS_STABILITY_SORT_SEQUENCE: Record<ValidatorsStabilitySortingField, Array<ValidatorsStabilitySortingValue | undefined>> = {
state: [ 'state-desc', 'state-asc', undefined ], state: [ 'state-desc', 'state-asc', undefined ],
blocks_validated: [ 'blocks_validated-desc', 'blocks_validated-asc', undefined ], blocks_validated: [ 'blocks_validated-desc', 'blocks_validated-asc', undefined ],
}; };
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