Commit 79fa0642 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #662 from blockscout/chors

chores: better types and eslint rules
parents a0857b28 9a99b613
...@@ -30,6 +30,7 @@ module.exports = { ...@@ -30,6 +30,7 @@ module.exports = {
'jsx-a11y', 'jsx-a11y',
'eslint-plugin-import-helpers', 'eslint-plugin-import-helpers',
'jest', 'jest',
'eslint-plugin-no-cyrillic-string',
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
...@@ -117,7 +118,7 @@ module.exports = { ...@@ -117,7 +118,7 @@ module.exports = {
'@typescript-eslint/type-annotation-spacing': 'error', '@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ], '@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ],
// отключены в пользу @typescript-eslint // disabled in favor of @typescript-eslint
'brace-style': 'off', 'brace-style': 'off',
camelcase: 'off', camelcase: 'off',
indent: 'off', indent: 'off',
...@@ -269,7 +270,7 @@ module.exports = { ...@@ -269,7 +270,7 @@ module.exports = {
'regexp/no-empty-capturing-group': 'error', 'regexp/no-empty-capturing-group': 'error',
'regexp/no-lazy-ends': 'error', 'regexp/no-lazy-ends': 'error',
'regexp/no-obscure-range': [ 'error', { 'regexp/no-obscure-range': [ 'error', {
allowed: [ 'alphanumeric', 'А-Я', 'а-я' ], allowed: [ 'alphanumeric' ],
} ], } ],
'regexp/no-optional-assertion': 'error', 'regexp/no-optional-assertion': 'error',
'regexp/no-unused-capturing-group': [ 'error', { 'regexp/no-unused-capturing-group': [ 'error', {
...@@ -277,6 +278,8 @@ module.exports = { ...@@ -277,6 +278,8 @@ module.exports = {
} ], } ],
'regexp/no-useless-character-class': 'error', 'regexp/no-useless-character-class': 'error',
'regexp/no-useless-dollar-replacements': 'error', 'regexp/no-useless-dollar-replacements': 'error',
'no-cyrillic-string/no-cyrillic-string': 'error',
}, },
overrides: [ overrides: [
{ {
......
...@@ -6,7 +6,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types'; ...@@ -6,7 +6,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"'); const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => { const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
try { try {
return JSON.parse(env || 'null'); return JSON.parse(env || 'null') as DataType | null;
} catch (error) { } catch (error) {
return null; return null;
} }
......
import type { MetaMaskInpageProvider } from '@metamask/providers'; import type { MetaMaskInpageProvider } from '@metamask/providers';
type CPreferences = {
zone: string;
width: string;
height: string;
}
declare global { declare global {
interface Window { interface Window {
ethereum: MetaMaskInpageProvider; ethereum: MetaMaskInpageProvider;
coinzilla_display: Array<CPreferences>;
} }
} }
...@@ -6,7 +6,7 @@ import type { Params as ApiFetchParams } from './useApiFetch'; ...@@ -6,7 +6,7 @@ import type { Params as ApiFetchParams } from './useApiFetch';
import useApiFetch from './useApiFetch'; import useApiFetch from './useApiFetch';
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> { export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>; queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
} }
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) { export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
...@@ -23,9 +23,12 @@ export default function useApiQuery<R extends ResourceName, E = unknown>( ...@@ -23,9 +23,12 @@ export default function useApiQuery<R extends ResourceName, E = unknown>(
) { ) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError<E>, ResourcePayload<R>>( return useQuery<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>(
getResourceKey(resource, { pathParams, queryParams }), getResourceKey(resource, { pathParams, queryParams }),
async() => { async() => {
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams }); // all errors and error typing is handled by react-query
// so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, fetchParams }) as Promise<ResourcePayload<R>>;
}, queryOptions); }, queryOptions);
} }
export default function getErrorStatusCode(error: Error | undefined): number | undefined {
return (
error && 'cause' in error &&
typeof error.cause === 'object' && error.cause !== null &&
'status' in error.cause && typeof error.cause.status === 'number' &&
error.cause.status
) ||
undefined;
}
export default function formatNumberToMetricPrefix(number: number) {
return Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 3,
}).format(number);
}
export function shortenNumberWithLetter(
x: number,
params?: {
unitSeparator: string;
},
_options?: Intl.NumberFormatOptions,
) {
const options = _options || { maximumFractionDigits: 2 };
const unitSeparator = params?.unitSeparator || '';
if (x > 1_000_000_000) {
return (x / 1_000_000_000).toLocaleString('en', options) + unitSeparator + 'B';
}
if (x > 1_000_000) {
return (x / 1_000_000).toLocaleString('en', options) + unitSeparator + 'M';
}
if (x > 1_000) {
return (x / 1_000).toLocaleString('en', options) + unitSeparator + 'K';
}
return x.toLocaleString('en', options);
}
export default function getFilterValue<FilterType>(filterValues: ReadonlyArray<FilterType>, val: string | Array<string> | undefined) { export default function getFilterValue<FilterType>(filterValues: ReadonlyArray<FilterType>, val: string | Array<string> | undefined): FilterType | undefined {
if (typeof val === 'string' && filterValues.includes(val as unknown as FilterType)) { if (typeof val === 'string' && filterValues.includes(val as FilterType)) {
return val as unknown as FilterType; return val as FilterType;
} }
} }
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import type { Unit } from 'types/unit';
import { WEI, GWEI } from 'lib/consts'; import { WEI, GWEI } from 'lib/consts';
export default function getValueWithUnit(value: string | number, unit: 'wei' | 'gwei' | 'ether' = 'wei') { export default function getValueWithUnit(value: string | number, unit: Unit = 'wei') {
let unitBn: BigNumber.Value; let unitBn: BigNumber.Value;
switch (unit) { switch (unit) {
case 'wei': case 'wei':
......
...@@ -2,7 +2,6 @@ import React from 'react'; ...@@ -2,7 +2,6 @@ import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import notEmpty from 'lib/notEmpty';
import ContractCode from 'ui/address/contract/ContractCode'; import ContractCode from 'ui/address/contract/ContractCode';
import ContractRead from 'ui/address/contract/ContractRead'; import ContractRead from 'ui/address/contract/ContractRead';
import ContractWrite from 'ui/address/contract/ContractWrite'; import ContractWrite from 'ui/address/contract/ContractWrite';
...@@ -33,6 +32,6 @@ export default function useContractTabs(data: Address | undefined) { ...@@ -33,6 +32,6 @@ export default function useContractTabs(data: Address | undefined) {
data?.has_custom_methods_write ? data?.has_custom_methods_write ?
{ id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } : { id: 'write_custom_methods', title: 'Write custom', component: <ContractWrite addressHash={ data?.hash } isCustomAbi/> } :
undefined, undefined,
].filter(notEmpty); ].filter(Boolean);
}, [ data ]); }, [ data ]);
} }
...@@ -23,7 +23,6 @@ import transactionsIcon from 'icons/transactions.svg'; ...@@ -23,7 +23,6 @@ import transactionsIcon from 'icons/transactions.svg';
import verifiedIcon from 'icons/verified.svg'; import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
// import { rightLineArrow } from 'lib/html-entities'; // import { rightLineArrow } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
export interface NavItem { export interface NavItem {
text: string; text: string;
...@@ -106,7 +105,7 @@ export default function useNavItems(): ReturnType { ...@@ -106,7 +105,7 @@ export default function useNavItems(): ReturnType {
} : null, } : null,
// FIXME: need icon for this item // FIXME: need icon for this item
{ text: 'GraphQL', nextRoute: { pathname: '/graphiql' as const }, icon: topAccountsIcon, isActive: false, isNewUi: false }, { text: 'GraphQL', nextRoute: { pathname: '/graphiql' as const }, icon: topAccountsIcon, isActive: false, isNewUi: false },
].filter(notEmpty); ].filter(Boolean);
const mainNavItems = [ const mainNavItems = [
{ {
...@@ -125,7 +124,7 @@ export default function useNavItems(): ReturnType { ...@@ -125,7 +124,7 @@ export default function useNavItems(): ReturnType {
// at this stage custom menu items is under development, we will implement it later // at this stage custom menu items is under development, we will implement it later
otherNavItems.length > 0 ? otherNavItems.length > 0 ?
{ text: 'Other', icon: gearIcon, isActive: otherNavItems.some(item => item.isActive), subItems: otherNavItems } : null, { text: 'Other', icon: gearIcon, isActive: otherNavItems.some(item => item.isActive), subItems: otherNavItems } : null,
].filter(notEmpty) as Array<NavItem | NavGroupItem>; ].filter(Boolean);
const accountNavItems = [ const accountNavItems = [
{ {
......
// https://unicode-table.com // https://unicode-table.com
export const asymp = String.fromCharCode(8776); // приблизительно export const asymp = String.fromCharCode(8776); // ~
export const hellip = String.fromCharCode(8230); // многоточие export const hellip = String.fromCharCode(8230); //
export const nbsp = String.fromCharCode(160); // неразрывный пробел export const nbsp = String.fromCharCode(160); // no-break Space
export const thinsp = String.fromCharCode(8201); // короткий пробел export const thinsp = String.fromCharCode(8201); // thin Space
export const space = String.fromCharCode(32); // обычный пробел export const space = String.fromCharCode(32); // space
export const nbdash = String.fromCharCode(8209); // неразрывное тире export const nbdash = String.fromCharCode(8209); // non-breaking hyphen
export const mdash = String.fromCharCode(8212); // длинное тире export const mdash = String.fromCharCode(8212); // em dash
export const ndash = String.fromCharCode(8211); // среднее тире export const ndash = String.fromCharCode(8211); // en dash
export const laquo = String.fromCharCode(171); // кавычки-ёлочки (левые) export const laquo = String.fromCharCode(171); // «
export const raquo = String.fromCharCode(187); // кавычки-ёлочки (правые) export const raquo = String.fromCharCode(187); // »
export const middot = String.fromCharCode(183); // точка по центру строки (в вертикальном смысле) export const middot = String.fromCharCode(183); // ·
export const blackCircle = String.fromCharCode(9679); // жирная точка по центру строки (в вертикальном смысле) export const blackCircle = String.fromCharCode(9679); //
export const blackRightwardsArrowhead = String.fromCharCode(10148); // ➤ export const blackRightwardsArrowhead = String.fromCharCode(10148); // ➤
export const degree = String.fromCharCode(176); // градус ° export const degree = String.fromCharCode(176); // °
export const times = String.fromCharCode(215); // мультипликатор × export const times = String.fromCharCode(215); // ×
export const disk = String.fromCharCode(8226); // диск export const disk = String.fromCharCode(8226); // •
export const minus = String.fromCharCode(8722); // минус export const minus = String.fromCharCode(8722); // −
export const leftLineArrow = String.fromCharCode(8592); // стрелка export const leftLineArrow = String.fromCharCode(8592); // ←
export const rightLineArrow = String.fromCharCode(8594); // стрелка export const rightLineArrow = String.fromCharCode(8594); // →
export const apos = String.fromCharCode(39); // апостроф ' export const apos = String.fromCharCode(39); // apostrophe '
export default function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}
import type { Channel } from 'phoenix'; import type { Channel } from 'phoenix';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import notEmpty from 'lib/notEmpty';
import { useSocket } from './context'; import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, Channel> = {}; const CHANNEL_REGISTRY: Record<string, Channel> = {};
...@@ -27,7 +25,7 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on ...@@ -27,7 +25,7 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
useEffect(() => { useEffect(() => {
const cleanUpRefs = () => { const cleanUpRefs = () => {
const refs = [ onCloseRef.current, onErrorRef.current ].filter(notEmpty); const refs = [ onCloseRef.current, onErrorRef.current ].filter(Boolean);
refs.length > 0 && socket?.off(refs); refs.length > 0 && socket?.off(refs);
}; };
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
"@playwright/experimental-ct-react": "1.31.0", "@playwright/experimental-ct-react": "1.31.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@total-typescript/ts-reset": "^0.3.7",
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"@types/dom-to-image": "^2.6.4", "@types/dom-to-image": "^2.6.4",
"@types/jest": "^29.2.0", "@types/jest": "^29.2.0",
...@@ -87,16 +88,17 @@ ...@@ -87,16 +88,17 @@
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.0",
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-dom": "18.0.5", "@types/react-dom": "18.0.5",
"@types/swagger-ui-react": "^4.11.0",
"@types/react-google-recaptcha": "^2.1.5", "@types/react-google-recaptcha": "^2.1.5",
"@types/swagger-ui-react": "^4.11.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/eslint-plugin": "^5.53.0",
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",
"eslint-config-next": "^12.3.0", "eslint-config-next": "^12.3.0",
"eslint-plugin-es5": "^1.5.0", "eslint-plugin-es5": "^1.5.0",
"eslint-plugin-import-helpers": "^1.2.1", "eslint-plugin-import-helpers": "^1.2.1",
"eslint-plugin-jest": "^27.1.6", "eslint-plugin-jest": "^27.1.6",
"eslint-plugin-no-cyrillic-string": "^1.0.5",
"eslint-plugin-playwright": "^0.11.2", "eslint-plugin-playwright": "^0.11.2",
"eslint-plugin-regexp": "^1.7.0", "eslint-plugin-regexp": "^1.7.0",
"husky": "^8.0.0", "husky": "^8.0.0",
...@@ -109,7 +111,7 @@ ...@@ -109,7 +111,7 @@
"svgo": "^2.8.0", "svgo": "^2.8.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "4.8.2", "typescript": "4.9.5",
"vite-plugin-svgr": "^2.2.2", "vite-plugin-svgr": "^2.2.2",
"vite-tsconfig-paths": "^3.5.2", "vite-tsconfig-paths": "^3.5.2",
"ws": "^8.11.0" "ws": "^8.11.0"
......
...@@ -9,6 +9,7 @@ import type { ResourceError } from 'lib/api/resources'; ...@@ -9,6 +9,7 @@ import type { ResourceError } from 'lib/api/resources';
import { AppContextProvider } from 'lib/appContext'; import { AppContextProvider } from 'lib/appContext';
import { Chakra } from 'lib/Chakra'; import { Chakra } from 'lib/Chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import getErrorStatusCode from 'lib/errors/getErrorStatusCode';
import useConfigSentry from 'lib/hooks/useConfigSentry'; import useConfigSentry from 'lib/hooks/useConfigSentry';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import theme from 'theme'; import theme from 'theme';
...@@ -36,12 +37,11 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -36,12 +37,11 @@ function MyApp({ Component, pageProps }: AppProps) {
})); }));
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const statusCode = getErrorStatusCode(error);
const statusCode = (error?.cause as any)?.status || 500;
return ( return (
<AppError <AppError
statusCode={ statusCode } statusCode={ statusCode || 500 }
height="100vh" height="100vh"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
......
...@@ -35,7 +35,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => { ...@@ -35,7 +35,7 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => {
return new Promise<[string, string, string]>((resolve, reject) => { return new Promise<[string, string, string]>((resolve, reject) => {
socket.on('message', (msg) => { socket.on('message', (msg) => {
try { try {
const payload: Array<string> = JSON.parse(msg.toString()); const payload = JSON.parse(msg.toString()) as Array<string>;
if (channelName === payload[2] && payload[3] === 'phx_join') { if (channelName === payload[2] && payload[3] === 'phx_join') {
socket.send(JSON.stringify([ socket.send(JSON.stringify([
......
import '@total-typescript/ts-reset';
import type { AddressParam } from './addressParams';
export type TransactionReward = {
types: Array<string>;
emission_reward: string;
block_hash: string;
from: AddressParam;
to: AddressParam;
}
...@@ -7,7 +7,6 @@ import type { AppItemPreview } from 'types/client/apps'; ...@@ -7,7 +7,6 @@ import type { AppItemPreview } from 'types/client/apps';
import northEastIcon from 'icons/arrows/north-east.svg'; import northEastIcon from 'icons/arrows/north-east.svg';
import starFilledIcon from 'icons/star_filled.svg'; import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import notEmpty from 'lib/notEmpty';
import AppCardLink from './AppCardLink'; import AppCardLink from './AppCardLink';
import { APP_CATEGORIES } from './constants'; import { APP_CATEGORIES } from './constants';
...@@ -30,7 +29,7 @@ const AppCard = ({ ...@@ -30,7 +29,7 @@ const AppCard = ({
isFavorite, isFavorite,
onFavoriteClick, onFavoriteClick,
}: Props) => { }: Props) => {
const categoriesLabel = categories.map(c => APP_CATEGORIES[c]).filter(notEmpty).join(', '); const categoriesLabel = categories.map(c => APP_CATEGORIES[c]).filter(Boolean).join(', ');
const handleInfoClick = useCallback((event: MouseEvent) => { const handleInfoClick = useCallback((event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
......
...@@ -15,7 +15,6 @@ import starFilledIcon from 'icons/star_filled.svg'; ...@@ -15,7 +15,6 @@ import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg'; import starOutlineIcon from 'icons/star_outline.svg';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
import AppModalLink from './AppModalLink'; import AppModalLink from './AppModalLink';
import { APP_CATEGORIES } from './constants'; import { APP_CATEGORIES } from './constants';
...@@ -60,7 +59,7 @@ const AppModal = ({ ...@@ -60,7 +59,7 @@ const AppModal = ({
icon: ghIcon, icon: ghIcon,
url: github, url: github,
} : null, } : null,
].filter(notEmpty); ].filter(Boolean);
const handleFavoriteClick = useCallback(() => { const handleFavoriteClick = useCallback(() => {
onFavoriteClick(id, isFavorite); onFavoriteClick(id, isFavorite);
......
...@@ -9,7 +9,7 @@ const favoriteAppsLocalStorageKey = 'favoriteApps'; ...@@ -9,7 +9,7 @@ const favoriteAppsLocalStorageKey = 'favoriteApps';
function getFavoriteApps() { function getFavoriteApps() {
try { try {
return JSON.parse(localStorage.getItem(favoriteAppsLocalStorageKey) || '[]'); return JSON.parse(localStorage.getItem(favoriteAppsLocalStorageKey) || '[]') as Array<string>;
} catch (e) { } catch (e) {
return []; return [];
} }
......
...@@ -101,7 +101,7 @@ export const DEFAULT_VALUES = { ...@@ -101,7 +101,7 @@ export const DEFAULT_VALUES = {
}; };
export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod { export function isValidVerificationMethod(method?: string): method is SmartContractVerificationMethod {
return method && SUPPORTED_VERIFICATION_METHODS.includes(method as SmartContractVerificationMethod) ? true : false; return method && SUPPORTED_VERIFICATION_METHODS.includes(method) ? true : false;
} }
export function sortVerificationMethods(methodA: SmartContractVerificationMethod, methodB: SmartContractVerificationMethod) { export function sortVerificationMethods(methodA: SmartContractVerificationMethod, methodB: SmartContractVerificationMethod) {
......
import type { TooltipProps } from '@chakra-ui/react'; import type { SystemStyleObject, TooltipProps } from '@chakra-ui/react';
import { Flex, Icon, Text, useColorModeValue, chakra, LightMode } from '@chakra-ui/react'; import { Flex, Icon, Text, useColorModeValue, chakra, LightMode } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -25,13 +25,13 @@ const TOOLTIP_PROPS: Partial<TooltipProps> = { ...@@ -25,13 +25,13 @@ const TOOLTIP_PROPS: Partial<TooltipProps> = {
}; };
const StatsItem = ({ icon, title, value, className, tooltipLabel, url }: Props) => { const StatsItem = ({ icon, title, value, className, tooltipLabel, url }: Props) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const sxContainer: SystemStyleObject = {
const sxContainer = {} as any; [`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { flexDirection: 'column' },
sxContainer[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { flexDirection: 'column' }; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any const sxText: SystemStyleObject = {
const sxText = {} as any; [`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`]: { alignItems: 'center' },
sxText[`@media screen and (min-width: ${ breakpoints.lg }) and (max-width: ${ LARGEST_BREAKPOINT })`] = { alignItems: 'center' }; };
const infoColor = useColorModeValue('gray.600', 'gray.400'); const infoColor = useColorModeValue('gray.600', 'gray.400');
......
...@@ -6,14 +6,13 @@ import type { TChainIndicator } from '../types'; ...@@ -6,14 +6,13 @@ import type { TChainIndicator } from '../types';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import globeIcon from 'icons/globe.svg'; import globeIcon from 'icons/globe.svg';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import { shortenNumberWithLetter } from 'lib/formatters';
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts'; import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = { const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = {
id: 'daily_txs', id: 'daily_txs',
title: 'Daily transactions', title: 'Daily transactions',
value: (stats) => shortenNumberWithLetter(Number(stats.transactions_today), undefined, { maximumFractionDigits: 2 }), value: (stats) => Number(stats.transactions_today).toLocaleString('en', { maximumFractionDigits: 2, notation: 'compact' }),
icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>, icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>,
hint: `The total daily number of transactions on the blockchain for the last month.`, hint: `The total daily number of transactions on the blockchain for the last month.`,
api: { api: {
...@@ -23,7 +22,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = { ...@@ -23,7 +22,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = {
.map((item) => ({ date: new Date(item.date), value: item.tx_count })) .map((item) => ({ date: new Date(item.date), value: item.tx_count }))
.sort(sortByDateDesc), .sort(sortByDateDesc),
name: 'Tx/day', name: 'Tx/day',
valueFormatter: (x: number) => shortenNumberWithLetter(x, undefined, { maximumFractionDigits: 2 }), valueFormatter: (x: number) => x.toLocaleString('en', { maximumFractionDigits: 2, notation: 'compact' }),
} ]), } ]),
}, },
}; };
...@@ -49,7 +48,7 @@ const coinPriceIndicator: TChainIndicator<'homepage_chart_market'> = { ...@@ -49,7 +48,7 @@ const coinPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = { const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
id: 'market_cup', id: 'market_cup',
title: 'Market cap', title: 'Market cap',
value: (stats) => '$' + shortenNumberWithLetter(Number(stats.market_cap), undefined, { maximumFractionDigits: 0 }), value: (stats) => '$' + Number(stats.market_cap).toLocaleString('en', { maximumFractionDigits: 0, notation: 'compact' }),
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>, icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.', hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.',
...@@ -60,7 +59,7 @@ const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = { ...@@ -60,7 +59,7 @@ const marketPriceIndicator: TChainIndicator<'homepage_chart_market'> = {
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) })) .map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) }))
.sort(sortByDateDesc), .sort(sortByDateDesc),
name: 'Market cap', name: 'Market cap',
valueFormatter: (x: number) => '$' + shortenNumberWithLetter(x, undefined, { maximumFractionDigits: 0 }), valueFormatter: (x: number) => '$' + x.toLocaleString('en', { maximumFractionDigits: 0 }),
} ]), } ]),
}, },
}; };
......
...@@ -9,7 +9,6 @@ import iconSuccess from 'icons/status/success.svg'; ...@@ -9,7 +9,6 @@ import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import notEmpty from 'lib/notEmpty';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated'; import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance'; import AddressCoinBalance from 'ui/address/AddressCoinBalance';
...@@ -57,7 +56,7 @@ const AddressPageContent = () => { ...@@ -57,7 +56,7 @@ const AddressPageContent = () => {
...(addressQuery.data?.public_tags || []), ...(addressQuery.data?.public_tags || []),
...(addressQuery.data?.watchlist_names || []), ...(addressQuery.data?.watchlist_names || []),
] ]
.filter(notEmpty) .filter(Boolean)
.map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>); .map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const contractTabs = useContractTabs(addressQuery.data); const contractTabs = useContractTabs(addressQuery.data);
...@@ -92,7 +91,7 @@ const AddressPageContent = () => { ...@@ -92,7 +91,7 @@ const AddressPageContent = () => {
component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>, component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(notEmpty); ].filter(Boolean);
}, [ addressQuery.data, contractTabs, hash ]); }, [ addressQuery.data, contractTabs, hash ]);
const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null; const tagsNode = tags.length > 0 ? <Flex columnGap={ 2 }>{ tags }</Flex> : null;
......
...@@ -2,7 +2,7 @@ import { Text } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { Text } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SmartContractVerificationConfigRaw, SmartContractVerificationMethod } from 'types/api/contract'; import type { SmartContractVerificationMethod } from 'types/api/contract';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
...@@ -39,11 +39,10 @@ const ContractVerification = () => { ...@@ -39,11 +39,10 @@ const ContractVerification = () => {
const configQuery = useApiQuery('contract_verification_config', { const configQuery = useApiQuery('contract_verification_config', {
queryOptions: { queryOptions: {
select: (data: unknown) => { select: (data) => {
const _data = data as SmartContractVerificationConfigRaw;
return { return {
..._data, ...data,
verification_options: _data.verification_options.filter(isValidVerificationMethod).sort(sortVerificationMethods), verification_options: data.verification_options.filter(isValidVerificationMethod).sort(sortVerificationMethods),
}; };
}, },
enabled: Boolean(hash), enabled: Boolean(hash),
......
...@@ -10,7 +10,6 @@ import { useAppContext } from 'lib/appContext'; ...@@ -10,7 +10,6 @@ import { useAppContext } from 'lib/appContext';
import useContractTabs from 'lib/hooks/useContractTabs'; import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import notEmpty from 'lib/notEmpty';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressContract from 'ui/address/AddressContract'; import AddressContract from 'ui/address/AddressContract';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
...@@ -118,7 +117,7 @@ const TokenPageContent = () => { ...@@ -118,7 +117,7 @@ const TokenPageContent = () => {
component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>, component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(notEmpty); ].filter(Boolean);
let hasPagination; let hasPagination;
let pagination; let pagination;
......
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getErrorStatusCode from 'lib/errors/getErrorStatusCode';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
...@@ -26,8 +27,7 @@ const Page = ({ ...@@ -26,8 +27,7 @@ const Page = ({
useGetCsrfToken(); useGetCsrfToken();
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const statusCode = getErrorStatusCode(error) || 500;
const statusCode = (error?.cause as any)?.status || 500;
const isInvalidTxHash = error?.message.includes('Invalid tx hash'); const isInvalidTxHash = error?.message.includes('Invalid tx hash');
if (wrapChildren) { if (wrapChildren) {
......
...@@ -4,29 +4,17 @@ import React from 'react'; ...@@ -4,29 +4,17 @@ import React from 'react';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coinzilla_display: any;
}
}
type CPreferences = {
zone: string;
width: string;
height: string;
}
const CoinzillaBanner = ({ className }: { className?: string }) => { const CoinzillaBanner = ({ className }: { className?: string }) => {
const isInBrowser = isBrowser(); const isInBrowser = isBrowser();
React.useEffect(() => { React.useEffect(() => {
if (isInBrowser) { if (isInBrowser) {
window.coinzilla_display = window.coinzilla_display || []; window.coinzilla_display = window.coinzilla_display || [];
const cDisplayPreferences = {} as CPreferences; const cDisplayPreferences = {
cDisplayPreferences.zone = '26660bf627543e46851'; zone: '26660bf627543e46851',
cDisplayPreferences.width = '728'; width: '728',
cDisplayPreferences.height = '90'; height: '90',
};
window.coinzilla_display.push(cDisplayPreferences); window.coinzilla_display.push(cDisplayPreferences);
} }
}, [ isInBrowser ]); }, [ isInBrowser ]);
......
...@@ -31,7 +31,8 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => { ...@@ -31,7 +31,8 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => {
useEffect(() => { useEffect(() => {
fetch('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242') fetch('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242')
.then(res => res.status === 200 ? res.json() : null) .then(res => res.status === 200 ? res.json() : null)
.then((data) => { .then((_data) => {
const data = _data as AdData;
setAdData(data); setAdData(data);
if (data?.ad?.impressionUrl) { if (data?.ad?.impressionUrl) {
fetch(data.ad.impressionUrl); fetch(data.ad.impressionUrl);
......
...@@ -27,7 +27,7 @@ const AddressContractIcon = ({ className }: Props) => { ...@@ -27,7 +27,7 @@ const AddressContractIcon = ({ className }: Props) => {
transitionDuration="normal" transitionDuration="normal"
transitionTimingFunction="ease" transitionTimingFunction="ease"
> >
С C
</Box> </Box>
</Tooltip> </Tooltip>
); );
......
...@@ -4,7 +4,6 @@ import { useMemo } from 'react'; ...@@ -4,7 +4,6 @@ import { useMemo } from 'react';
import type { TimeChartData } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
import { WEEK, MONTH, YEAR } from 'lib/consts'; import { WEEK, MONTH, YEAR } from 'lib/consts';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
interface Props { interface Props {
data: TimeChartData; data: TimeChartData;
...@@ -73,7 +72,7 @@ export default function useTimeChartController({ data, width, height }: Props) { ...@@ -73,7 +72,7 @@ export default function useTimeChartController({ data, width, height }: Props) {
return format(d as Date); return format(d as Date);
}; };
const yTickFormat = () => (d: d3.AxisDomain) => formatNumberToMetricPrefix(Number(d)); const yTickFormat = () => (d: d3.AxisDomain) => Number(d).toLocaleString('en', { maximumFractionDigits: 3, notation: 'compact' });
return { return {
xTickFormat, xTickFormat,
......
...@@ -7,7 +7,6 @@ import placeholderIcon from 'icons/files/placeholder.svg'; ...@@ -7,7 +7,6 @@ import placeholderIcon from 'icons/files/placeholder.svg';
import solIcon from 'icons/files/sol.svg'; import solIcon from 'icons/files/sol.svg';
import yulIcon from 'icons/files/yul.svg'; import yulIcon from 'icons/files/yul.svg';
import infoIcon from 'icons/info.svg'; import infoIcon from 'icons/info.svg';
import { shortenNumberWithLetter } from 'lib/formatters';
const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = { const FILE_ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'.json': jsonIcon, '.json': jsonIcon,
...@@ -101,7 +100,9 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr ...@@ -101,7 +100,9 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr
alignSelf="flex-start" alignSelf="flex-start"
/> />
</Flex> </Flex>
<Text variant="secondary" mt={ 1 }>{ shortenNumberWithLetter(file.size) }B</Text> <Text variant="secondary" mt={ 1 }>
{ file.size.toLocaleString('en', { notation: 'compact', maximumFractionDigits: 2, unit: 'byte', unitDisplay: 'narrow', style: 'unit' }) }
</Text>
</Box> </Box>
</Flex> </Flex>
); );
......
...@@ -6,7 +6,6 @@ import type { Log } from 'types/api/log'; ...@@ -6,7 +6,6 @@ import type { Log } from 'types/api/log';
// import searchIcon from 'icons/search.svg'; // import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import notEmpty from 'lib/notEmpty';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -83,7 +82,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash ...@@ -83,7 +82,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
) } ) }
<RowHeader>Topics</RowHeader> <RowHeader>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.filter(notEmpty).map((item, index) => ( { topics.filter(Boolean).map((item, index) => (
<LogTopic <LogTopic
key={ index } key={ index }
hex={ item } hex={ item }
......
...@@ -70,8 +70,9 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr ...@@ -70,8 +70,9 @@ const NavLinkGroupDesktop = ({ text, subItems, icon, isCollapsed, isActive }: Pr
{ text } { text }
</Text> </Text>
<VStack spacing={ 1 } alignItems="start"> <VStack spacing={ 1 } alignItems="start">
{ subItems.map(item => Array.isArray(item) ? ( { subItems.map((item, index) => Array.isArray(item) ? (
<Box <Box
key={ index }
w="100%" w="100%"
as="ul" as="ul"
_notLast={{ _notLast={{
......
...@@ -93,8 +93,9 @@ const NavigationMobile = () => { ...@@ -93,8 +93,9 @@ const NavigationMobile = () => {
as="ul" as="ul"
> >
{ isGroupItem(openedItem) && openedItem.subItems?.map( { isGroupItem(openedItem) && openedItem.subItems?.map(
item => Array.isArray(item) ? ( (item, index) => Array.isArray(item) ? (
<Box <Box
key={ index }
w="100%" w="100%"
as="ul" as="ul"
_notLast={{ _notLast={{
......
...@@ -2,7 +2,6 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,7 +2,6 @@ import { Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
import DataFetchAlert from '../shared/DataFetchAlert'; import DataFetchAlert from '../shared/DataFetchAlert';
import NumberWidget from './NumberWidget'; import NumberWidget from './NumberWidget';
...@@ -32,7 +31,7 @@ const NumberWidgetsList = () => { ...@@ -32,7 +31,7 @@ const NumberWidgetsList = () => {
<NumberWidget <NumberWidget
key={ id } key={ id }
label={ title } label={ title }
value={ `${ formatNumberToMetricPrefix(Number(value)) } ${ units ? units : '' }` } value={ `${ Number(value).toLocaleString('en', { maximumFractionDigits: 3, notation: 'compact' }) } ${ units ? units : '' }` }
/> />
); );
}) } }) }
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment