diff --git a/eslint.config.mjs b/eslint.config.mjs index 23651fb5b8..c5c6cacca7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -30,14 +30,9 @@ const RESTRICTED_MODULES = { importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast', 'Skeleton' ], message: 'Please use corresponding component or hook from ui/shared/chakra component instead', }, - { - name: 'lodash', - message: 'Please use `import [package] from \'lodash/[package]\'` instead.', - }, ], patterns: [ 'icons/*', - '!lodash/*', ], }; diff --git a/icons/clock-light.svg b/icons/clock-light.svg index 9b18072460..110cd4b797 100644 --- a/icons/clock-light.svg +++ b/icons/clock-light.svg @@ -1,3 +1,3 @@ - + diff --git a/lib/api/useApiFetch.tsx b/lib/api/useApiFetch.tsx index cda519c1c1..a0658417d9 100644 --- a/lib/api/useApiFetch.tsx +++ b/lib/api/useApiFetch.tsx @@ -1,6 +1,5 @@ import { useQueryClient } from '@tanstack/react-query'; -import _omit from 'lodash/omit'; -import _pickBy from 'lodash/pickBy'; +import { omit, pickBy } from 'es-toolkit'; import React from 'react'; import type { CsrfData } from 'types/client/account'; @@ -38,7 +37,7 @@ export default function useApiFetch() { const resource: ApiResource = RESOURCES[resourceName]; const url = buildUrl(resourceName, pathParams, queryParams); const withBody = isBodyAllowed(fetchParams?.method); - const headers = _pickBy({ + const headers = pickBy({ 'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined, Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined, 'x-csrf-token': withBody && csrfToken ? csrfToken : undefined, @@ -55,7 +54,7 @@ export default function useApiFetch() { // change condition here if something is changed credentials: config.features.account.isEnabled ? 'include' : 'same-origin', headers, - ..._omit(fetchParams, 'headers'), + ...(fetchParams ? omit(fetchParams, [ 'headers' ]) : {}), }, { resource: resource.path, diff --git a/lib/contexts/scrollDirection.tsx b/lib/contexts/scrollDirection.tsx index 154b5438b4..1c4f5503bd 100644 --- a/lib/contexts/scrollDirection.tsx +++ b/lib/contexts/scrollDirection.tsx @@ -1,5 +1,4 @@ -import clamp from 'lodash/clamp'; -import throttle from 'lodash/throttle'; +import { throttle, clamp } from 'es-toolkit'; import React from 'react'; const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null); diff --git a/lib/hooks/useClientRect.tsx b/lib/hooks/useClientRect.tsx index 05aa278f5b..44033e30cc 100644 --- a/lib/hooks/useClientRect.tsx +++ b/lib/hooks/useClientRect.tsx @@ -1,4 +1,4 @@ -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import type { LegacyRef } from 'react'; import React from 'react'; @@ -19,7 +19,7 @@ export default function useClientRect(): [ DOMRect | null, Le return; } - const resizeHandler = _debounce(() => { + const resizeHandler = debounce(() => { setRect(nodeRef.current?.getBoundingClientRect() ?? null); }, 100); diff --git a/lib/hooks/useIsSticky.tsx b/lib/hooks/useIsSticky.tsx index cc9c5c4b6d..2dfa3ff574 100644 --- a/lib/hooks/useIsSticky.tsx +++ b/lib/hooks/useIsSticky.tsx @@ -1,4 +1,4 @@ -import throttle from 'lodash/throttle'; +import { throttle } from 'es-toolkit'; import React from 'react'; export default function useIsSticky(ref: React.RefObject, offset = 0, isEnabled = true) { diff --git a/lib/hooks/useLazyRenderedList.tsx b/lib/hooks/useLazyRenderedList.tsx index 3f2d828fe0..4ebd630b18 100644 --- a/lib/hooks/useLazyRenderedList.tsx +++ b/lib/hooks/useLazyRenderedList.tsx @@ -1,4 +1,4 @@ -import _clamp from 'lodash/clamp'; +import { clamp } from 'es-toolkit'; import React from 'react'; import { useInView } from 'react-intersection-observer'; @@ -15,7 +15,7 @@ export default function useLazyRenderedList(list: Array, isEnabled: boo React.useEffect(() => { if (inView) { - setRenderedItemsNum((prev) => _clamp(prev + STEP, 0, list.length)); + setRenderedItemsNum((prev) => clamp(prev + STEP, 0, list.length)); } }, [ inView, list.length ]); diff --git a/lib/mixpanel/getTabName.ts b/lib/mixpanel/getTabName.ts index 3ca3b8cae8..99f742a887 100644 --- a/lib/mixpanel/getTabName.ts +++ b/lib/mixpanel/getTabName.ts @@ -1,5 +1,5 @@ -import _capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; export default function getTabName(tab: string) { - return tab !== '' ? _capitalize(tab.replaceAll('_', ' ')) : 'Default'; + return tab !== '' ? capitalize(tab.replaceAll('_', ' ')) : 'Default'; } diff --git a/lib/mixpanel/useInit.tsx b/lib/mixpanel/useInit.tsx index 23f02332f8..9e67acc858 100644 --- a/lib/mixpanel/useInit.tsx +++ b/lib/mixpanel/useInit.tsx @@ -1,4 +1,4 @@ -import _capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import type { Config } from 'mixpanel-browser'; import mixpanel from 'mixpanel-browser'; import { useRouter } from 'next/router'; @@ -40,12 +40,12 @@ export default function useMixpanelInit() { 'Viewport width': window.innerWidth, 'Viewport height': window.innerHeight, Language: window.navigator.language, - 'Device type': _capitalize(deviceType), + 'Device type': capitalize(deviceType), 'User id': userId, }); mixpanel.identify(userId); userProfile.set({ - 'Device Type': _capitalize(deviceType), + 'Device Type': capitalize(deviceType), ...(isAuth ? { 'With Account': true } : {}), }); userProfile.setOnce({ diff --git a/lib/networks/networkExplorers.ts b/lib/networks/networkExplorers.ts index cfcc828115..b5a4648d41 100644 --- a/lib/networks/networkExplorers.ts +++ b/lib/networks/networkExplorers.ts @@ -1,5 +1,4 @@ -import _compose from 'lodash/fp/compose'; -import _mapValues from 'lodash/mapValues'; +import { mapValues } from 'es-toolkit'; import type { NetworkExplorer } from 'types/networks'; @@ -32,7 +31,7 @@ const networkExplorers: Array = (() => { return config.UI.explorers.items.map((explorer) => ({ ...explorer, baseUrl: stripTrailingSlash(explorer.baseUrl), - paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)), + paths: mapValues(explorer.paths, (value) => value ? stripTrailingSlash(addLeadingSlash(value)) : value), })); })(); diff --git a/lib/recentSearchKeywords.ts b/lib/recentSearchKeywords.ts index 5fe5a6bd71..3c0b31d6fe 100644 --- a/lib/recentSearchKeywords.ts +++ b/lib/recentSearchKeywords.ts @@ -1,4 +1,4 @@ -import _uniq from 'lodash/uniq'; +import { uniq } from 'es-toolkit'; import isBrowser from './isBrowser'; @@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) { } const keywordsArr = getRecentSearchKeywords(); - const result = _uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1); + const result = uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1); window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result)); } diff --git a/lib/token/metadata/attributesParser.ts b/lib/token/metadata/attributesParser.ts index 63e572fe72..2167464d96 100644 --- a/lib/token/metadata/attributesParser.ts +++ b/lib/token/metadata/attributesParser.ts @@ -1,4 +1,4 @@ -import _upperFirst from 'lodash/upperFirst'; +import { upperFirst } from 'es-toolkit'; import type { Metadata, MetadataAttributes } from 'types/client/token'; @@ -72,7 +72,7 @@ export default function attributesParser(attributes: Array): Metadata[' return { ...formatValue(value, display, trait), - trait_type: _upperFirst(trait || 'property'), + trait_type: upperFirst(trait || 'property'), }; }) .filter((item) => item?.value) diff --git a/lib/web3/useAddOrSwitchChain.tsx b/lib/web3/useAddOrSwitchChain.tsx index adba7a6d4e..b4e533571b 100644 --- a/lib/web3/useAddOrSwitchChain.tsx +++ b/lib/web3/useAddOrSwitchChain.tsx @@ -1,4 +1,4 @@ -import _get from 'lodash/get'; +import { get } from 'es-toolkit/compat'; import React from 'react'; import config from 'configs/app'; @@ -25,7 +25,7 @@ export default function useAddOrSwitchChain() { const errorObj = getErrorObj(error); const code = errorObj && 'code' in errorObj ? errorObj.code : undefined; - const originalErrorCode = _get(errorObj, 'data.originalError.code'); + const originalErrorCode = get(errorObj, 'data.originalError.code'); // This error code indicates that the chain has not been added to Wallet. if (code === 4902 || originalErrorCode === 4902) { diff --git a/mocks/blocks/epoch.ts b/mocks/blocks/epoch.ts index 58f614fbe6..165660c7c1 100644 --- a/mocks/blocks/epoch.ts +++ b/mocks/blocks/epoch.ts @@ -1,4 +1,4 @@ -import _padStart from 'lodash/padStart'; +import { padStart } from 'es-toolkit/compat'; import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block'; @@ -42,11 +42,11 @@ function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails { amount: `${ 100 - index }210001063118670575`, account: { ...addressMock.withoutName, - hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`, + hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`, }, associated_account: { ...addressMock.withoutName, - hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`, + hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`, }, }; } diff --git a/mocks/stats/index.ts b/mocks/stats/index.ts index f4bc3f53e3..725831d2d3 100644 --- a/mocks/stats/index.ts +++ b/mocks/stats/index.ts @@ -1,4 +1,4 @@ -import _mapValues from 'lodash/mapValues'; +import { mapValues } from 'es-toolkit'; import type { HomeStats } from 'types/api/stats'; @@ -51,17 +51,17 @@ export const withBtcLocked: HomeStats = { export const withoutFiatPrices: HomeStats = { ...base, - gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null), + gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null) : null, }; export const withoutGweiPrices: HomeStats = { ...base, - gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null), + gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null) : null, }; export const withoutBothPrices: HomeStats = { ...base, - gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null), + gas_prices: base.gas_prices ? mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null) : null, }; export const withoutGasInfo: HomeStats = { diff --git a/nextjs/csp/utils.ts b/nextjs/csp/utils.ts index cdc9a0fdd7..6c5dc55797 100644 --- a/nextjs/csp/utils.ts +++ b/nextjs/csp/utils.ts @@ -1,4 +1,5 @@ import type CspDev from 'csp-dev'; +import { uniq } from 'es-toolkit'; export const KEY_WORDS = { BLOB: 'blob:', @@ -11,17 +12,6 @@ export const KEY_WORDS = { UNSAFE_EVAL: '\'unsafe-eval\'', }; -// we cannot use lodash/uniq and lodash/mergeWith in middleware code since it calls new Set() and it'is causing an error in Next.js -// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime" -export function unique(array: Array) { - const set: Record = {}; - for (const item of array) { - item && (set[item] = true); - } - - return Object.keys(set); -} - export function mergeDescriptors(...descriptors: Array) { return descriptors.reduce((result, item) => { for (const _key in item) { @@ -50,7 +40,7 @@ export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) { return; } - const uniqueValues = unique(value); + const uniqueValues = uniq(value); return [ key, uniqueValues.join(' ') ].join(' '); }) .filter(Boolean) diff --git a/nextjs/utils/fetchProxy.ts b/nextjs/utils/fetchProxy.ts index a8f0c032f2..0fd5b19b57 100644 --- a/nextjs/utils/fetchProxy.ts +++ b/nextjs/utils/fetchProxy.ts @@ -1,5 +1,5 @@ +import { pick } from 'es-toolkit'; import type { IncomingMessage } from 'http'; -import _pick from 'lodash/pick'; import type { NextApiRequest } from 'next'; import type { NextApiRequestCookies } from 'next/dist/server/api-utils'; import type { RequestInit, Response } from 'node-fetch'; @@ -21,7 +21,7 @@ export default function fetchFactory( accept: _req.headers['accept'] || 'application/json', 'content-type': _req.headers['content-type'] || 'application/json', cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '', - ..._pick(_req.headers, [ + ...pick(_req.headers, [ 'x-csrf-token', 'Authorization', // the old value, just in case 'authorization', // Node.js automatically lowercases headers diff --git a/package.json b/package.json index 43de857133..d9212fb6ed 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "dappscout-iframe": "0.2.5", "dayjs": "^1.11.5", "dom-to-image": "^2.6.0", + "es-toolkit": "1.31.0", "focus-visible": "^5.2.0", "framer-motion": "^6.5.1", "getit-sdk": "^1.0.4", @@ -88,7 +89,6 @@ "graphql": "^16.8.1", "graphql-ws": "^5.11.3", "js-cookie": "^3.0.1", - "lodash": "^4.0.0", "magic-bytes.js": "1.8.0", "mixpanel-browser": "^2.47.0", "monaco-editor": "^0.34.1", diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts index 78d8e152ba..ebb653f73b 100644 --- a/pages/api/proxy.ts +++ b/pages/api/proxy.ts @@ -1,5 +1,4 @@ -import _pick from 'lodash/pick'; -import _pickBy from 'lodash/pickBy'; +import { pick, pickBy } from 'es-toolkit'; import type { NextApiRequest, NextApiResponse } from 'next'; import fetchFactory from 'nextjs/utils/fetchProxy'; @@ -18,7 +17,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => { ); const apiRes = await fetchFactory(nextReq)( url.toString(), - _pickBy(_pick(nextReq, [ 'body', 'method' ]), Boolean), + pickBy(pick(nextReq, [ 'body', 'method' ]), Boolean), ); // proxy some headers from API diff --git a/playwright/fixtures/mockContractReadResponse.ts b/playwright/fixtures/mockContractReadResponse.ts index 1245724c69..9a9325d960 100644 --- a/playwright/fixtures/mockContractReadResponse.ts +++ b/playwright/fixtures/mockContractReadResponse.ts @@ -1,5 +1,5 @@ import type { TestFixture, Page } from '@playwright/test'; -import _isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import { encodeFunctionData, encodeFunctionResult, type AbiFunction } from 'viem'; import { getEnvValue } from 'configs/app/utils'; @@ -43,7 +43,7 @@ const fixture: TestFixture = as value: params?.value, }; - if (_isEqual(params, callParams) && id) { + if (isEqual(params, callParams) && id) { return route.fulfill({ status: 200, json: { diff --git a/playwright/fixtures/mockRpcResponse.ts b/playwright/fixtures/mockRpcResponse.ts index b257599fe3..ceacf111b8 100644 --- a/playwright/fixtures/mockRpcResponse.ts +++ b/playwright/fixtures/mockRpcResponse.ts @@ -1,5 +1,5 @@ import type { TestFixture, Page } from '@playwright/test'; -import _isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import type { PublicRpcSchema } from 'viem'; import { getEnvValue } from 'configs/app/utils'; @@ -34,7 +34,7 @@ const fixture: TestFixture = async({ pag ...(rpcMock.Parameters ? { params: rpcMock.Parameters } : {}), }; - if (_isEqual(json, payload) && id !== undefined) { + if (isEqual(json, payload) && id !== undefined) { return route.fulfill({ status: 200, json: { diff --git a/playwright/index.ts b/playwright/index.ts index 96c506e9fc..31ab5f53de 100644 --- a/playwright/index.ts +++ b/playwright/index.ts @@ -1,7 +1,6 @@ import './fonts.css'; import './index.css'; import { beforeMount } from '@playwright/experimental-ct-react/hooks'; -import _defaultsDeep from 'lodash/defaultsDeep'; import MockDate from 'mockdate'; import * as router from 'next/router'; @@ -12,12 +11,15 @@ const NEXT_ROUTER_MOCK = { replace: () => Promise.resolve(), }; -beforeMount(async({ hooksConfig }) => { +beforeMount(async({ hooksConfig }: { hooksConfig?: { router: typeof router } }) => { // Before mount, redefine useRouter to return mock value from test. // @ts-ignore: I really want to redefine this property :) // eslint-disable-next-line no-import-assign - router.useRouter = () => _defaultsDeep(hooksConfig?.router, NEXT_ROUTER_MOCK); + router.useRouter = () => ({ + ...NEXT_ROUTER_MOCK, + ...hooksConfig?.router, + }); // set current date MockDate.set('2022-11-11T12:00:00Z'); diff --git a/public/static/badges.svg b/public/static/badges.svg index 741e81b6e5..a1f43d51c2 100644 --- a/public/static/badges.svg +++ b/public/static/badges.svg @@ -17,15 +17,14 @@ - + - + - - - + + @@ -36,18 +35,18 @@ - - - + + + - + - + - + @@ -81,7 +80,7 @@ - + @@ -96,15 +95,15 @@ - - + + - + - + @@ -113,22 +112,22 @@ - + - - + + - + - + - + @@ -144,10 +143,10 @@ - - - - + + + + @@ -160,7 +159,7 @@ - + @@ -171,13 +170,12 @@ - - + + - - - - + + + @@ -192,27 +190,27 @@ - + - - - + + + - + - - + + - + @@ -221,7 +219,7 @@ - + @@ -231,16 +229,16 @@ - + - - + + - + @@ -277,27 +275,27 @@ - + - + - + - + - + - + diff --git a/ui/address/contract/audits/ContractSubmitAuditForm.pw.tsx b/ui/address/contract/audits/ContractSubmitAuditForm.pw.tsx index a9053cad83..b95829f290 100644 --- a/ui/address/contract/audits/ContractSubmitAuditForm.pw.tsx +++ b/ui/address/contract/audits/ContractSubmitAuditForm.pw.tsx @@ -1,4 +1,4 @@ -import noop from 'lodash/noop'; +import { noop } from 'es-toolkit'; import React from 'react'; import { test, expect } from 'playwright/lib'; diff --git a/ui/address/contract/methods/ContractAbi.tsx b/ui/address/contract/methods/ContractAbi.tsx index 72f7fe6295..660cc99eff 100644 --- a/ui/address/contract/methods/ContractAbi.tsx +++ b/ui/address/contract/methods/ContractAbi.tsx @@ -1,5 +1,5 @@ import { Accordion, Box, Flex, Link } from '@chakra-ui/react'; -import _range from 'lodash/range'; +import { range } from 'es-toolkit'; import React from 'react'; import type { SmartContractMethod } from './types'; @@ -39,7 +39,7 @@ const ContractAbi = ({ abi, addressHash, sourceAddress, tab, visibleItems }: Pro } if (expandedSections.length < abi.length) { - setExpandedSections(_range(0, abi.length)); + setExpandedSections(range(0, abi.length)); } else { setExpandedSections([]); } diff --git a/ui/address/contract/methods/form/utils.ts b/ui/address/contract/methods/form/utils.ts index e067f0f74f..1c1c197cbc 100644 --- a/ui/address/contract/methods/form/utils.ts +++ b/ui/address/contract/methods/form/utils.ts @@ -1,4 +1,4 @@ -import _set from 'lodash/set'; +import { set } from 'es-toolkit/compat'; import type { ContractAbiItemInput } from '../types'; @@ -78,7 +78,7 @@ export function transformFormDataToMethodArgs(formData: ContractMethodFormFields for (const field in formData) { const value = formData[field]; - _set(result, field.replaceAll(':', '.'), value); + set(result, field.replaceAll(':', '.'), value); } const filteredResult = filterOutEmptyItems(result); diff --git a/ui/address/contract/methods/useMethodsFilters.ts b/ui/address/contract/methods/useMethodsFilters.ts index 5cda37075f..78e5709713 100644 --- a/ui/address/contract/methods/useMethodsFilters.ts +++ b/ui/address/contract/methods/useMethodsFilters.ts @@ -1,4 +1,4 @@ -import _pickBy from 'lodash/pickBy'; +import { pickBy } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; @@ -63,7 +63,7 @@ export default function useMethodsFilters({ abi }: Params) { return; } - const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`)); + const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`)); router.push( { pathname: router.pathname, query: { ...queryForPathname, tab: nextTab } }, undefined, diff --git a/ui/address/details/AddressMultichainButton.tsx b/ui/address/details/AddressMultichainButton.tsx index eee6e63c92..a8dc73382a 100644 --- a/ui/address/details/AddressMultichainButton.tsx +++ b/ui/address/details/AddressMultichainButton.tsx @@ -1,5 +1,5 @@ import { Image, Tooltip } from '@chakra-ui/react'; -import _capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import React from 'react'; import type { MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig'; @@ -25,10 +25,10 @@ const AddressMultichainButton = ({ item, addressHash, onClick, hasSingleProvider const buttonContent = hasSingleProvider ? ( <> { buttonIcon } - { _capitalize(item.name) } + { capitalize(item.name) } ) : ( - { buttonIcon } + { buttonIcon } ); const linkProps = { diff --git a/ui/address/ensDomains/AddressEnsDomains.tsx b/ui/address/ensDomains/AddressEnsDomains.tsx index 6349d3d431..0f488cfa84 100644 --- a/ui/address/ensDomains/AddressEnsDomains.tsx +++ b/ui/address/ensDomains/AddressEnsDomains.tsx @@ -12,7 +12,7 @@ import { chakra, } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; -import _clamp from 'lodash/clamp'; +import { clamp } from 'es-toolkit'; import React from 'react'; import type * as bens from '@blockscout/bens-types'; @@ -37,7 +37,7 @@ interface Props { const DomainsGrid = ({ data }: { data: Array }) => { return ( { ); } - const hasTokens = _sumBy(Object.values(data), ({ items }) => items.length) > 0; + const hasTokens = sumBy(Object.values(data), ({ items }) => items.length) > 0; if (isError || !hasTokens) { return 0; } diff --git a/ui/address/tokenSelect/TokenSelectMenu.tsx b/ui/address/tokenSelect/TokenSelectMenu.tsx index 41c8129d11..bb25a39f76 100644 --- a/ui/address/tokenSelect/TokenSelectMenu.tsx +++ b/ui/address/tokenSelect/TokenSelectMenu.tsx @@ -1,5 +1,5 @@ import { Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react'; -import _sumBy from 'lodash/sumBy'; +import { sumBy } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; @@ -26,7 +26,7 @@ interface Props { const TokenSelectMenu = ({ erc20sort, erc1155sort, erc404sort, filteredData, onInputChange, onSortClick, searchTerm }: Props) => { const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); - const hasFilteredResult = _sumBy(Object.values(filteredData), ({ items }) => items.length) > 0; + const hasFilteredResult = sumBy(Object.values(filteredData), ({ items }) => items.length) > 0; return ( <> diff --git a/ui/address/tokenSelect/useTokenSelect.ts b/ui/address/tokenSelect/useTokenSelect.ts index 95346e25c3..35ecc004d1 100644 --- a/ui/address/tokenSelect/useTokenSelect.ts +++ b/ui/address/tokenSelect/useTokenSelect.ts @@ -1,4 +1,4 @@ -import _mapValues from 'lodash/mapValues'; +import { mapValues } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; @@ -31,7 +31,7 @@ export default function useTokenSelect(data: FormattedData) { }, []); const filteredData = React.useMemo(() => { - return _mapValues(data, ({ items, isOverflow }) => ({ + return mapValues(data, ({ items, isOverflow }) => ({ isOverflow, items: items.filter(filterTokens(searchTerm.toLowerCase())), })); diff --git a/ui/address/utils/tokenUtils.ts b/ui/address/utils/tokenUtils.ts index a4aaa37364..293f245ed0 100644 --- a/ui/address/utils/tokenUtils.ts +++ b/ui/address/utils/tokenUtils.ts @@ -1,5 +1,4 @@ import BigNumber from 'bignumber.js'; -import fpAdd from 'lodash/fp/add'; import type { AddressTokenBalance } from 'types/api/address'; import type { TokenType } from 'types/api/token'; @@ -100,7 +99,7 @@ export const getTokensTotalInfo = (data: TokenSelectData) => { const num = Object.values(data) .map(({ items }) => items.length) - .reduce(fpAdd, 0); + .reduce((result, item) => result + item, 0); const isOverflow = Object.values(data).some(({ isOverflow }) => isOverflow); diff --git a/ui/advancedFilter/filters/AddressFilter.tsx b/ui/advancedFilter/filters/AddressFilter.tsx index 9bb197a099..7620c5bcb9 100644 --- a/ui/advancedFilter/filters/AddressFilter.tsx +++ b/ui/advancedFilter/filters/AddressFilter.tsx @@ -1,5 +1,5 @@ import { Flex, Select, Input, InputGroup, InputRightElement, VStack, IconButton } from '@chakra-ui/react'; -import isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; diff --git a/ui/advancedFilter/filters/AgeFilter.tsx b/ui/advancedFilter/filters/AgeFilter.tsx index 370407e518..d9cb00be1c 100644 --- a/ui/advancedFilter/filters/AgeFilter.tsx +++ b/ui/advancedFilter/filters/AgeFilter.tsx @@ -1,5 +1,5 @@ import { Flex, Input, Text } from '@chakra-ui/react'; -import isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; diff --git a/ui/advancedFilter/filters/AmountFilter.tsx b/ui/advancedFilter/filters/AmountFilter.tsx index df64eb08e6..68abad8a2a 100644 --- a/ui/advancedFilter/filters/AmountFilter.tsx +++ b/ui/advancedFilter/filters/AmountFilter.tsx @@ -1,5 +1,5 @@ import { Flex, Input, Tag, Text } from '@chakra-ui/react'; -import isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; diff --git a/ui/advancedFilter/filters/AssetFilter.tsx b/ui/advancedFilter/filters/AssetFilter.tsx index 048e8aeddb..619fe7bcff 100644 --- a/ui/advancedFilter/filters/AssetFilter.tsx +++ b/ui/advancedFilter/filters/AssetFilter.tsx @@ -1,5 +1,5 @@ import { Flex, Checkbox, CheckboxGroup, Text, Spinner, Select } from '@chakra-ui/react'; -import isEqual from 'lodash/isEqual'; +import { isEqual } from 'es-toolkit'; import React from 'react'; import type { AdvancedFilterParams } from 'types/api/advancedFilter'; diff --git a/ui/advancedFilter/filters/MethodFilter.tsx b/ui/advancedFilter/filters/MethodFilter.tsx index 93bc65e45d..d61622ae92 100644 --- a/ui/advancedFilter/filters/MethodFilter.tsx +++ b/ui/advancedFilter/filters/MethodFilter.tsx @@ -1,6 +1,5 @@ import { Flex, Checkbox, CheckboxGroup, Spinner, chakra } from '@chakra-ui/react'; -import differenceBy from 'lodash/differenceBy'; -import isEqual from 'lodash/isEqual'; +import { isEqual, differenceBy } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; diff --git a/ui/advancedFilter/filters/TypeFilter.tsx b/ui/advancedFilter/filters/TypeFilter.tsx index f3a61c2562..c6cd46f8be 100644 --- a/ui/advancedFilter/filters/TypeFilter.tsx +++ b/ui/advancedFilter/filters/TypeFilter.tsx @@ -1,6 +1,5 @@ import { Flex, Checkbox, CheckboxGroup } from '@chakra-ui/react'; -import isEqual from 'lodash/isEqual'; -import without from 'lodash/without'; +import { isEqual, without } from 'es-toolkit'; import type { ChangeEvent } from 'react'; import React from 'react'; diff --git a/ui/advancedFilter/lib.ts b/ui/advancedFilter/lib.ts index 3b8380fde2..b65cc08f01 100644 --- a/ui/advancedFilter/lib.ts +++ b/ui/advancedFilter/lib.ts @@ -1,4 +1,4 @@ -import castArray from 'lodash/castArray'; +import { castArray } from 'es-toolkit/compat'; import type { AdvancedFilterAge, AdvancedFilterParams } from 'types/api/advancedFilter'; diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index 5e8d95bb13..f97d84d250 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -1,6 +1,6 @@ import { Grid, GridItem, Text, Link, Box, Tooltip } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; -import capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; import { scroller, Element } from 'react-scroll'; diff --git a/ui/blockCountdown/splitSecondsInPeriods.ts b/ui/blockCountdown/splitSecondsInPeriods.ts index b187ba5e0e..9c915a82d6 100644 --- a/ui/blockCountdown/splitSecondsInPeriods.ts +++ b/ui/blockCountdown/splitSecondsInPeriods.ts @@ -1,4 +1,4 @@ -import _padStart from 'lodash/padStart'; +import { padStart } from 'es-toolkit/compat'; export default function splitSecondsInPeriods(value: number) { const seconds = value % 60; @@ -7,9 +7,9 @@ export default function splitSecondsInPeriods(value: number) { const days = (value - seconds - minutes * 60 - hours * 60 * 60) / (60 * 60 * 24); return { - seconds: _padStart(String(seconds), 2, '0'), - minutes: _padStart(String(minutes), 2, '0'), - hours: _padStart(String(hours), 2, '0'), - days: _padStart(String(days), 2, '0'), + seconds: padStart(String(seconds), 2, '0'), + minutes: padStart(String(minutes), 2, '0'), + hours: padStart(String(hours), 2, '0'), + days: padStart(String(days), 2, '0'), }; } diff --git a/ui/blocks/BlocksListItem.tsx b/ui/blocks/BlocksListItem.tsx index 8c33ef8afc..be78336f86 100644 --- a/ui/blocks/BlocksListItem.tsx +++ b/ui/blocks/BlocksListItem.tsx @@ -1,6 +1,6 @@ import { Flex, Text, Box, Tooltip } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; -import capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import React from 'react'; import type { Block } from 'types/api/block'; diff --git a/ui/blocks/BlocksTable.tsx b/ui/blocks/BlocksTable.tsx index 45a1fd0cbd..e563da20ea 100644 --- a/ui/blocks/BlocksTable.tsx +++ b/ui/blocks/BlocksTable.tsx @@ -1,6 +1,6 @@ import { Table, Tbody, Tr, Th } from '@chakra-ui/react'; +import { capitalize } from 'es-toolkit'; import { AnimatePresence } from 'framer-motion'; -import capitalize from 'lodash/capitalize'; import React from 'react'; import type { Block } from 'types/api/block'; diff --git a/ui/contractVerification/fields/ContractVerificationFieldGitHubRepo.tsx b/ui/contractVerification/fields/ContractVerificationFieldGitHubRepo.tsx index 68e378b868..f05750fbff 100644 --- a/ui/contractVerification/fields/ContractVerificationFieldGitHubRepo.tsx +++ b/ui/contractVerification/fields/ContractVerificationFieldGitHubRepo.tsx @@ -1,4 +1,4 @@ -import _get from 'lodash/get'; +import { get } from 'es-toolkit/compat'; import React from 'react'; import { useFormContext } from 'react-hook-form'; @@ -43,7 +43,7 @@ const ContractVerificationFieldGitHubRepo = ({ onCommitHashChange }: Props) => { const response = await fetch(`https://api.github.com/repos/${ gitHubData.owner }/${ gitHubData.repo }/commits?per_page=1`); repoErrorRef.current = undefined; trigger('repository_url'); - onCommitHashChange(_get(response, '[0].sha')); + onCommitHashChange(get(response, '[0].sha')); return; } catch (error) { repoErrorRef.current = 'GitHub repository not found'; diff --git a/ui/csvExport/CsvExportFormField.tsx b/ui/csvExport/CsvExportFormField.tsx index 9c556e2a1e..c64eef9b38 100644 --- a/ui/csvExport/CsvExportFormField.tsx +++ b/ui/csvExport/CsvExportFormField.tsx @@ -1,4 +1,4 @@ -import _capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import React from 'react'; import type { UseFormReturn } from 'react-hook-form'; @@ -40,7 +40,7 @@ const CsvExportFormField = ({ formApi, name }: Props) => { name={ name } type="date" max={ dayjs().format('YYYY-MM-DD') } - placeholder={ _capitalize(name) } + placeholder={ capitalize(name) } isRequired rules={{ validate }} size={{ base: 'md', lg: 'lg' }} diff --git a/ui/pages/AdvancedFilter.tsx b/ui/pages/AdvancedFilter.tsx index 9fc6a5a9f1..e37733babf 100644 --- a/ui/pages/AdvancedFilter.tsx +++ b/ui/pages/AdvancedFilter.tsx @@ -15,7 +15,7 @@ import { HStack, Link, } from '@chakra-ui/react'; -import omit from 'lodash/omit'; +import { omit } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index 9bea7844d9..5a45762d54 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -1,5 +1,5 @@ import { chakra } from '@chakra-ui/react'; -import capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx index c3b5146b38..fbf07a8846 100644 --- a/ui/pages/Transactions.tsx +++ b/ui/pages/Transactions.tsx @@ -1,5 +1,5 @@ import { Flex } from '@chakra-ui/react'; -import capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; diff --git a/ui/pages/UserOp.tsx b/ui/pages/UserOp.tsx index 92eebad927..18bfcb1f46 100644 --- a/ui/pages/UserOp.tsx +++ b/ui/pages/UserOp.tsx @@ -1,4 +1,4 @@ -import _inRange from 'lodash/inRange'; +import { inRange } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; @@ -43,7 +43,7 @@ const UserOp = () => { if (!userOpQuery.data) { return true; } else { - if (_inRange( + if (inRange( Number(tt.log_index), userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count, @@ -58,7 +58,7 @@ const UserOp = () => { if (!userOpQuery.data) { return true; } else { - if (_inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) { + if (inRange(log.index, userOpQuery.data?.user_logs_start_index, userOpQuery.data?.user_logs_start_index + userOpQuery.data?.user_logs_count)) { return true; } return false; diff --git a/ui/publicTags/submit/PublicTagsSubmitResult.tsx b/ui/publicTags/submit/PublicTagsSubmitResult.tsx index f1fd62eb74..1ce36ad7e7 100644 --- a/ui/publicTags/submit/PublicTagsSubmitResult.tsx +++ b/ui/publicTags/submit/PublicTagsSubmitResult.tsx @@ -1,5 +1,5 @@ import { Alert, Box, Button, Flex, Grid, GridItem } from '@chakra-ui/react'; -import _pickBy from 'lodash/pickBy'; +import { pickBy } from 'es-toolkit'; import React from 'react'; import type { FormSubmitResult } from './types'; @@ -26,7 +26,7 @@ const PublicTagsSubmitResult = ({ data }: Props) => { const hasErrors = groupedData.items.some((item) => item.error !== null); const companyWebsite = makePrettyLink(groupedData.companyWebsite); - const startOverButtonQuery = hasErrors ? _pickBy({ + const startOverButtonQuery = hasErrors ? pickBy({ requesterName: groupedData.requesterName, requesterEmail: groupedData.requesterEmail, companyName: groupedData.companyName, diff --git a/ui/publicTags/submit/fields/PublicTagsSubmitFieldTagType.tsx b/ui/publicTags/submit/fields/PublicTagsSubmitFieldTagType.tsx index 23d0c2ec68..df5c3782a0 100644 --- a/ui/publicTags/submit/fields/PublicTagsSubmitFieldTagType.tsx +++ b/ui/publicTags/submit/fields/PublicTagsSubmitFieldTagType.tsx @@ -1,7 +1,7 @@ import { chakra, Flex } from '@chakra-ui/react'; import type { GroupBase, SelectComponentsConfig, SingleValueProps } from 'chakra-react-select'; import { chakraComponents } from 'chakra-react-select'; -import _capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import React from 'react'; import { useFormContext } from 'react-hook-form'; @@ -22,7 +22,7 @@ const PublicTagsSubmitFieldTagType = ({ index, tagTypes }: Props) => { const typeOptions = React.useMemo(() => tagTypes?.map((type) => ({ value: type.type, - label: _capitalize(type.type), + label: capitalize(type.type), })) ?? [], [ tagTypes ]); const fieldValue = watch(`tags.${ index }.type`).value; diff --git a/ui/publicTags/submit/result/PublicTagsSubmitResultWithErrors.tsx b/ui/publicTags/submit/result/PublicTagsSubmitResultWithErrors.tsx index e38492462f..92df2f1e6c 100644 --- a/ui/publicTags/submit/result/PublicTagsSubmitResultWithErrors.tsx +++ b/ui/publicTags/submit/result/PublicTagsSubmitResultWithErrors.tsx @@ -1,5 +1,5 @@ import { Box, Button, Flex, Grid, GridItem, useColorModeValue } from '@chakra-ui/react'; -import _pickBy from 'lodash/pickBy'; +import { pickBy } from 'es-toolkit'; import React from 'react'; import type { FormSubmitResultGrouped } from '../types'; @@ -23,7 +23,7 @@ const PublicTagsSubmitResultWithErrors = ({ data }: Props) => { { data.items.map((item, index) => { - const startOverButtonQuery = _pickBy({ + const startOverButtonQuery = pickBy({ addresses: item.addresses, requesterName: data.requesterName, requesterEmail: data.requesterEmail, diff --git a/ui/publicTags/submit/utils.ts b/ui/publicTags/submit/utils.ts index 6ee6df8c8a..1e45d41d3a 100644 --- a/ui/publicTags/submit/utils.ts +++ b/ui/publicTags/submit/utils.ts @@ -1,5 +1,4 @@ -import _isEqual from 'lodash/isEqual'; -import _pickBy from 'lodash/pickBy'; +import { pickBy, isEqual } from 'es-toolkit'; import type { FormFieldTag, FormFields, FormSubmitResult, FormSubmitResultGrouped, FormSubmitResultItemGrouped, SubmitRequestBody } from './types'; import type { UserInfo } from 'types/api/account'; @@ -22,7 +21,7 @@ export function convertFormDataToRequestsBody(data: FormFields): Array error === item.error && _isEqual(tags, item.tags)); + const existingItem = items.find(({ error, tags }) => error === item.error && isEqual(tags, item.tags)); if (existingItem) { existingItem.addresses.push(...item.addresses); continue; diff --git a/ui/searchResults/SearchResultsInput.tsx b/ui/searchResults/SearchResultsInput.tsx index 10cc920adb..925ca952a8 100644 --- a/ui/searchResults/SearchResultsInput.tsx +++ b/ui/searchResults/SearchResultsInput.tsx @@ -1,5 +1,5 @@ import { PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import type { FormEvent, FocusEvent } from 'react'; import React from 'react'; @@ -59,7 +59,7 @@ const SearchResultsInput = ({ searchTerm, handleSubmit, handleSearchTermChange } } calculateMenuWidth(); - const resizeHandler = _debounce(calculateMenuWidth, 200); + const resizeHandler = debounce(calculateMenuWidth, 200); const resizeObserver = new ResizeObserver(resizeHandler); resizeObserver.observe(inputRef.current); diff --git a/ui/shared/AccountPageDescription.tsx b/ui/shared/AccountPageDescription.tsx index dc19772a87..3364c34cfc 100644 --- a/ui/shared/AccountPageDescription.tsx +++ b/ui/shared/AccountPageDescription.tsx @@ -1,5 +1,5 @@ import { Box, useColorModeValue } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import React, { useRef, useEffect, useState, useCallback } from 'react'; const CUT_HEIGHT = 144; @@ -25,7 +25,7 @@ const AccountPageDescription = ({ children, allowCut = true }: { children: React } calculateCut(); - const resizeHandler = _debounce(calculateCut, 300); + const resizeHandler = debounce(calculateCut, 300); window.addEventListener('resize', resizeHandler); return function cleanup() { window.removeEventListener('resize', resizeHandler); diff --git a/ui/shared/HashStringShortenDynamic.tsx b/ui/shared/HashStringShortenDynamic.tsx index cb1130662d..343df0f6ab 100644 --- a/ui/shared/HashStringShortenDynamic.tsx +++ b/ui/shared/HashStringShortenDynamic.tsx @@ -10,7 +10,7 @@ import type { As } from '@chakra-ui/react'; import { Tooltip, chakra } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import React, { useCallback, useEffect, useRef } from 'react'; import type { FontFace } from 'use-font-face-observer'; import useFontFaceObserver from 'use-font-face-observer'; @@ -81,7 +81,7 @@ const HashStringShortenDynamic = ({ hash, fontWeight = '400', isTooltipDisabled, }, [ calculateString, isFontFaceLoaded ]); useEffect(() => { - const resizeHandler = _debounce(calculateString, 100); + const resizeHandler = debounce(calculateString, 100); const resizeObserver = new ResizeObserver(resizeHandler); resizeObserver.observe(document.body); diff --git a/ui/shared/Page/PageTitle.tsx b/ui/shared/Page/PageTitle.tsx index 9e199f5136..95b3e838f3 100644 --- a/ui/shared/Page/PageTitle.tsx +++ b/ui/shared/Page/PageTitle.tsx @@ -1,5 +1,5 @@ import { Heading, Flex, Tooltip, Link, chakra, useDisclosure } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import React from 'react'; import useIsMobile from 'lib/hooks/useIsMobile'; @@ -94,7 +94,7 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa }, [ isLoading, updatedTruncateState ]); React.useEffect(() => { - const handleResize = _debounce(updatedTruncateState, 1000); + const handleResize = debounce(updatedTruncateState, 1000); window.addEventListener('resize', handleResize); return function cleanup() { diff --git a/ui/shared/Tabs/RoutedTabs.tsx b/ui/shared/Tabs/RoutedTabs.tsx index 227dc81108..950a45a323 100644 --- a/ui/shared/Tabs/RoutedTabs.tsx +++ b/ui/shared/Tabs/RoutedTabs.tsx @@ -1,6 +1,6 @@ import type { ChakraProps, ThemingProps } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _pickBy from 'lodash/pickBy'; +import { pickBy } from 'es-toolkit'; import { useRouter } from 'next/router'; import React, { useEffect, useRef } from 'react'; @@ -42,7 +42,7 @@ const RoutedTabs = ({ const handleTabChange = React.useCallback((index: number) => { const nextTab = tabs[index]; - const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`)); + const queryForPathname = pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`)); const tabId = Array.isArray(nextTab.id) ? nextTab.id[0] : nextTab.id; router.push( { pathname: router.pathname, query: { ...queryForPathname, tab: tabId } }, diff --git a/ui/shared/Tabs/TabsWithScroll.tsx b/ui/shared/Tabs/TabsWithScroll.tsx index 3b4503e0bb..97dfc6a1f0 100644 --- a/ui/shared/Tabs/TabsWithScroll.tsx +++ b/ui/shared/Tabs/TabsWithScroll.tsx @@ -6,7 +6,7 @@ import { TabPanels, chakra, } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import React, { useEffect, useRef, useState } from 'react'; import type { TabItem } from './types'; @@ -69,7 +69,7 @@ const TabsWithScroll = ({ }, [ defaultTabIndex ]); React.useEffect(() => { - const resizeHandler = _debounce(() => { + const resizeHandler = debounce(() => { setScreenWidth(window.innerWidth); }, 100); const resizeObserver = new ResizeObserver(resizeHandler); diff --git a/ui/shared/TheadSticky.tsx b/ui/shared/TheadSticky.tsx index 966e870681..e25af0fbe7 100644 --- a/ui/shared/TheadSticky.tsx +++ b/ui/shared/TheadSticky.tsx @@ -1,6 +1,6 @@ import { Thead, useColorModeValue } from '@chakra-ui/react'; import type { TableHeadProps, PositionProps } from '@chakra-ui/react'; -import throttle from 'lodash/throttle'; +import { throttle } from 'es-toolkit'; import React from 'react'; interface Props extends TableHeadProps { diff --git a/ui/shared/TruncatedTextTooltip.tsx b/ui/shared/TruncatedTextTooltip.tsx index eaf7dfdd66..454e37e01c 100644 --- a/ui/shared/TruncatedTextTooltip.tsx +++ b/ui/shared/TruncatedTextTooltip.tsx @@ -1,6 +1,6 @@ import type { PlacementWithLogical } from '@chakra-ui/react'; import { Tooltip, useDisclosure } from '@chakra-ui/react'; -import debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import React from 'react'; import useFontFaceObserver from 'use-font-face-observer'; diff --git a/ui/shared/Utilization/Utilization.tsx b/ui/shared/Utilization/Utilization.tsx index bcd0d9f3f3..82353b1f3e 100644 --- a/ui/shared/Utilization/Utilization.tsx +++ b/ui/shared/Utilization/Utilization.tsx @@ -1,5 +1,5 @@ import { Box, Flex, chakra, useColorModeValue } from '@chakra-ui/react'; -import clamp from 'lodash/clamp'; +import { clamp } from 'es-toolkit'; import React from 'react'; import Skeleton from 'ui/shared/chakra/Skeleton'; diff --git a/ui/shared/chart/tooltip/ChartTooltipContent.tsx b/ui/shared/chart/tooltip/ChartTooltipContent.tsx index da1a62a5d6..a5d325838c 100644 --- a/ui/shared/chart/tooltip/ChartTooltipContent.tsx +++ b/ui/shared/chart/tooltip/ChartTooltipContent.tsx @@ -1,5 +1,5 @@ import * as d3 from 'd3'; -import _clamp from 'lodash/clamp'; +import { clamp } from 'es-toolkit'; import React from 'react'; import { POINT_SIZE } from './utils'; @@ -69,33 +69,33 @@ function calculatePosition({ pointX, pointY, canvasWidth, canvasHeight, nodeWidt // right if (pointX + offset + nodeWidth <= canvasWidth) { const x = pointX + offset; - const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); + const y = clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); return [ x, y ]; } // left if (nodeWidth + offset <= pointX) { const x = pointX - offset - nodeWidth; - const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); + const y = clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); return [ x, y ]; } // top if (nodeHeight + offset <= pointY) { - const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); + const x = clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); const y = pointY - offset - nodeHeight; return [ x, y ]; } // bottom if (pointY + offset + nodeHeight <= canvasHeight) { - const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); + const x = clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); const y = pointY + offset; return [ x, y ]; } - const x = _clamp(pointX / 2, 0, canvasWidth - nodeWidth); - const y = _clamp(pointY / 2, 0, canvasHeight - nodeHeight); + const x = clamp(pointX / 2, 0, canvasWidth - nodeWidth); + const y = clamp(pointY / 2, 0, canvasHeight - nodeHeight); return [ x, y ]; } diff --git a/ui/shared/chart/useChartLegend.tsx b/ui/shared/chart/useChartLegend.tsx index 2a0987f8a0..56beded875 100644 --- a/ui/shared/chart/useChartLegend.tsx +++ b/ui/shared/chart/useChartLegend.tsx @@ -1,8 +1,8 @@ -import _range from 'lodash/range'; +import { range } from 'es-toolkit'; import React from 'react'; export default function useChartLegend(dataLength: number) { - const [ selectedLines, setSelectedLines ] = React.useState>(_range(dataLength)); + const [ selectedLines, setSelectedLines ] = React.useState>(range(dataLength)); const handleLegendItemClick = React.useCallback((index: number) => { const nextSelectedLines = selectedLines.includes(index) ? selectedLines.filter((item) => item !== index) : [ ...selectedLines, index ]; diff --git a/ui/shared/chart/utils/timeChartAxis.ts b/ui/shared/chart/utils/timeChartAxis.ts index b57253fe39..95ab77fab1 100644 --- a/ui/shared/chart/utils/timeChartAxis.ts +++ b/ui/shared/chart/utils/timeChartAxis.ts @@ -1,6 +1,5 @@ import * as d3 from 'd3'; -import _maxBy from 'lodash/maxBy'; -import _unique from 'lodash/uniq'; +import { maxBy, uniq } from 'es-toolkit'; import type { AxesConfig, AxisConfig, TimeChartData } from '../types'; @@ -88,8 +87,8 @@ function getYLabelFormatParams(ticks: Array, maximumSignificantDigits = notation: 'compact' as const, }; - const uniqTicksStr = _unique(ticks.map((tick) => tick.toLocaleString(undefined, params))); - const maxLabelLength = _maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH; + const uniqTicksStr = uniq(ticks.map((tick) => tick.toLocaleString(undefined, params))); + const maxLabelLength = maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH; if (uniqTicksStr.length === ticks.length || maximumSignificantDigits === MAXIMUM_SIGNIFICANT_DIGITS_LIMIT) { return { ...params, maxLabelLength }; diff --git a/ui/shared/forms/inputs/select/FancySelect.pw.tsx b/ui/shared/forms/inputs/select/FancySelect.pw.tsx index 1c6ebcfcbd..de15975b29 100644 --- a/ui/shared/forms/inputs/select/FancySelect.pw.tsx +++ b/ui/shared/forms/inputs/select/FancySelect.pw.tsx @@ -1,4 +1,4 @@ -import _noop from 'lodash/noop'; +import { noop } from 'es-toolkit'; import React from 'react'; import { test, expect } from 'playwright/lib'; @@ -18,7 +18,7 @@ const defaultProps = { isRequired: true, placeholder: 'Compiler', name: 'compiler', - onChange: _noop, + onChange: noop, }; [ 'md' as const, 'lg' as const ].forEach((size) => { diff --git a/ui/shared/monaco/CodeEditorSideBar.tsx b/ui/shared/monaco/CodeEditorSideBar.tsx index eccf855d42..c7e9af280d 100644 --- a/ui/shared/monaco/CodeEditorSideBar.tsx +++ b/ui/shared/monaco/CodeEditorSideBar.tsx @@ -1,6 +1,6 @@ import type { HTMLChakraProps } from '@chakra-ui/react'; import { Box, Tab, TabList, TabPanel, TabPanels, Tabs, useBoolean } from '@chakra-ui/react'; -import _throttle from 'lodash/throttle'; +import { throttle } from 'es-toolkit'; import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import React from 'react'; @@ -47,7 +47,7 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco, editor, selectedFile, m letterSpacing: 0.3, }; - const handleScrollThrottled = React.useRef(_throttle((event: React.SyntheticEvent) => { + const handleScrollThrottled = React.useRef(throttle((event: React.SyntheticEvent) => { setIsStuck((event.target as HTMLDivElement).scrollTop > 0); }, 100)); diff --git a/ui/shared/pagination/useQueryWithPages.ts b/ui/shared/pagination/useQueryWithPages.ts index df554bb91b..d6e80c3084 100644 --- a/ui/shared/pagination/useQueryWithPages.ts +++ b/ui/shared/pagination/useQueryWithPages.ts @@ -1,6 +1,6 @@ import type { UseQueryResult } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query'; -import omit from 'lodash/omit'; +import { omit } from 'es-toolkit'; import { useRouter } from 'next/router'; import React, { useCallback } from 'react'; import { animateScroll } from 'react-scroll'; @@ -154,7 +154,14 @@ export default function useQueryWithPages({ }, [ queryClient, resourceName, router, scrollToTop ]); const onFilterChange = useCallback((newFilters: PaginationFilters | undefined) => { - const newQuery = omit(router.query, 'next_page_params', 'page', 'filterFields' in resource ? resource.filterFields : []); + const newQuery: typeof router.query = omit( + router.query, + [ + 'next_page_params', + 'page', + ...('filterFields' in resource ? resource.filterFields : []), + ], + ); if (newFilters) { Object.entries(newFilters).forEach(([ key, value ]) => { const isValidValue = typeof value === 'boolean' || (value && value.length); @@ -179,8 +186,8 @@ export default function useQueryWithPages({ }, [ router, resource, scrollToTop ]); const onSortingChange = useCallback((newSorting: PaginationSorting | undefined) => { - const newQuery = { - ...omit(router.query, 'next_page_params', 'page', SORTING_FIELDS), + const newQuery: typeof router.query = { + ...omit(router.query, [ 'next_page_params', 'page', ...SORTING_FIELDS ]), ...newSorting, }; scrollToTop(); diff --git a/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx b/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx index 4ccc12110c..82b8426912 100644 --- a/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx +++ b/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx @@ -1,4 +1,4 @@ -import _noop from 'lodash/noop'; +import { noop } from 'es-toolkit'; import React from 'react'; import { test, expect } from 'playwright/lib'; @@ -12,7 +12,7 @@ test('base view +@dark-mode', async({ render }) => { , ); diff --git a/ui/snippets/networkMenu/NetworkMenuContentMobile.tsx b/ui/snippets/networkMenu/NetworkMenuContentMobile.tsx index 4b48882365..98f1f2b142 100644 --- a/ui/snippets/networkMenu/NetworkMenuContentMobile.tsx +++ b/ui/snippets/networkMenu/NetworkMenuContentMobile.tsx @@ -1,5 +1,5 @@ import { Box, Select, VStack, Flex } from '@chakra-ui/react'; -import capitalize from 'lodash/capitalize'; +import { capitalize } from 'es-toolkit'; import React from 'react'; import type { NetworkGroup, FeaturedNetwork } from 'types/networks'; diff --git a/ui/snippets/searchBar/SearchBar.tsx b/ui/snippets/searchBar/SearchBar.tsx index 07de33d020..806e63c627 100644 --- a/ui/snippets/searchBar/SearchBar.tsx +++ b/ui/snippets/searchBar/SearchBar.tsx @@ -8,7 +8,7 @@ import { useDisclosure, useOutsideClick, } from '@chakra-ui/react'; -import _debounce from 'lodash/debounce'; +import { debounce } from 'es-toolkit'; import { useRouter } from 'next/router'; import type { FormEvent } from 'react'; import React from 'react'; @@ -115,7 +115,7 @@ const SearchBar = ({ isHomepage }: Props) => { } calculateMenuWidth(); - const resizeHandler = _debounce(calculateMenuWidth, 200); + const resizeHandler = debounce(calculateMenuWidth, 200); const resizeObserver = new ResizeObserver(resizeHandler); resizeObserver.observe(inputRef.current); diff --git a/ui/snippets/searchBar/SearchBarInput.tsx b/ui/snippets/searchBar/SearchBarInput.tsx index 4a8a0e8952..62f1fde327 100644 --- a/ui/snippets/searchBar/SearchBarInput.tsx +++ b/ui/snippets/searchBar/SearchBarInput.tsx @@ -1,5 +1,5 @@ import { InputGroup, Input, InputLeftElement, chakra, useColorModeValue, forwardRef, InputRightElement, Center } from '@chakra-ui/react'; -import throttle from 'lodash/throttle'; +import { throttle } from 'es-toolkit'; import React from 'react'; import type { ChangeEvent, FormEvent, FocusEvent } from 'react'; diff --git a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggest.tsx b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggest.tsx index a3a01f0a50..e8f392d441 100644 --- a/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggest.tsx +++ b/ui/snippets/searchBar/SearchBarSuggest/SearchBarSuggest.tsx @@ -1,6 +1,6 @@ import { Box, Tab, TabList, Tabs, Text, useColorModeValue } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query'; -import throttle from 'lodash/throttle'; +import { throttle } from 'es-toolkit'; import React from 'react'; import { scroller, Element } from 'react-scroll'; diff --git a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png index ca47f6fdd9..266acbdea6 100644 Binary files a/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png and b/ui/snippets/searchBar/__screenshots__/SearchBar.pw.tsx_default_scroll-suggest-to-category-1.png differ diff --git a/ui/tokenInstance/metadata/utils.ts b/ui/tokenInstance/metadata/utils.ts index d99db798e9..9442b19714 100644 --- a/ui/tokenInstance/metadata/utils.ts +++ b/ui/tokenInstance/metadata/utils.ts @@ -1,11 +1,11 @@ -import _upperFirst from 'lodash/upperFirst'; +import { upperFirst } from 'es-toolkit'; export function formatName(_name: string) { const name = _name .replaceAll('_', ' ') .replaceAll(/\burl|nft|id\b/gi, (str) => str.toUpperCase()); - return _upperFirst(name.trim()); + return upperFirst(name.trim()); } const PINNED_FIELDS = [ 'name', 'description' ]; diff --git a/ui/tx/TxAssetFlows.tsx b/ui/tx/TxAssetFlows.tsx index c4d0dac409..97ec6a3603 100644 --- a/ui/tx/TxAssetFlows.tsx +++ b/ui/tx/TxAssetFlows.tsx @@ -1,5 +1,5 @@ import { Table, Tbody, Tr, Th, Box, Text, Show, Hide } from '@chakra-ui/react'; -import _chunk from 'lodash/chunk'; +import { chunk } from 'es-toolkit'; import React, { useMemo, useState } from 'react'; import type { PaginationParams } from 'ui/shared/pagination/types'; @@ -34,7 +34,7 @@ export default function TxAssetFlows(props: FlowViewProps) { const [ page, setPage ] = useState(1); const ViewData = useMemo(() => (queryData ? generateFlowViewData(queryData) : []), [ queryData ]); - const chunkedViewData = _chunk(ViewData, 50); + const chunkedViewData = chunk(ViewData, 50); const paginationProps: PaginationParams = useMemo(() => ({ onNextPageClick: () => setPage(page + 1), diff --git a/ui/tx/assetFlows/utils/generateFlowViewData.ts b/ui/tx/assetFlows/utils/generateFlowViewData.ts index e4adb0d348..5024942104 100644 --- a/ui/tx/assetFlows/utils/generateFlowViewData.ts +++ b/ui/tx/assetFlows/utils/generateFlowViewData.ts @@ -1,5 +1,3 @@ -import _findIndex from 'lodash/findIndex'; - import type { NovesNft, NovesResponseData, NovesSentReceived, NovesToken } from 'types/api/noves'; export interface NovesAction { @@ -27,7 +25,7 @@ export function generateFlowViewData(data: NovesResponseData): Array item.action === 'paidGas'); + const paidGasIndex = txItems.findIndex((item) => item.action === 'paidGas'); if (paidGasIndex >= 0) { const element = txItems.splice(paidGasIndex, 1)[0]; element.to.name = 'Validators'; diff --git a/ui/tx/assetFlows/utils/getTokensData.ts b/ui/tx/assetFlows/utils/getTokensData.ts index 159dcca11a..d4a1d51a22 100644 --- a/ui/tx/assetFlows/utils/getTokensData.ts +++ b/ui/tx/assetFlows/utils/getTokensData.ts @@ -1,6 +1,4 @@ -import _groupBy from 'lodash/groupBy'; -import _keysIn from 'lodash/keysIn'; -import _mapValues from 'lodash/mapValues'; +import { groupBy, mapValues } from 'es-toolkit'; import type { NovesResponseData } from 'types/api/noves'; import type { TokenInfo } from 'types/api/token'; @@ -49,28 +47,28 @@ export function getTokensData(data: NovesResponseData): TokensData { }); // Group tokens by property into arrays - const tokensGroupByname = _groupBy(tokens, 'name'); - const tokensGroupBySymbol = _groupBy(tokens, 'symbol'); - const tokensGroupById = _groupBy(tokens, 'id'); + const tokensGroupByName = groupBy(tokens, (item) => item.name || 'null'); + const tokensGroupBySymbol = groupBy(tokens, (item) => item.symbol || 'null'); + const tokensGroupById = groupBy(tokens, (item) => item.id || 'null'); // Map properties to an object and remove duplicates - const mappedNames = _mapValues(tokensGroupByname, (i) => { + const mappedNames = mapValues(tokensGroupByName, (i) => { return i[0]; }); - const mappedSymbols = _mapValues(tokensGroupBySymbol, (i) => { + const mappedSymbols = mapValues(tokensGroupBySymbol, (i) => { return i[0]; }); - const mappedIds = _mapValues(tokensGroupById, (i) => { + const mappedIds = mapValues(tokensGroupById, (i) => { return i[0]; }); const filters = [ 'undefined', 'null' ]; // Array of keys to match in string - const nameList = _keysIn(mappedNames).filter(i => !filters.includes(i)); - const symbolList = _keysIn(mappedSymbols).filter(i => !filters.includes(i)); - const idList = _keysIn(mappedIds).filter(i => !filters.includes(i)); + const nameList = Object.keys(mappedNames).filter(i => !filters.includes(i)); + const symbolList = Object.keys(mappedSymbols).filter(i => !filters.includes(i)); + const idList = Object.keys(mappedIds).filter(i => !filters.includes(i)); return { nameList, diff --git a/ui/txs/noves/useDescribeTxs.tsx b/ui/txs/noves/useDescribeTxs.tsx index 1d2f611da4..888ec91c04 100644 --- a/ui/txs/noves/useDescribeTxs.tsx +++ b/ui/txs/noves/useDescribeTxs.tsx @@ -1,6 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import _chunk from 'lodash/chunk'; -import _uniq from 'lodash/uniq'; +import { uniq, chunk } from 'es-toolkit'; import React from 'react'; import type { NovesDescribeTxsResponse } from 'types/api/noves'; @@ -16,8 +15,8 @@ const translateEnabled = feature.isEnabled && feature.provider === 'noves'; export default function useDescribeTxs(items: Array | undefined, viewAsAccountAddress: string | undefined, isPlaceholderData: boolean) { const apiFetch = useApiFetch(); - const txsHash = _uniq(items?.map(i => i.hash)); - const txChunks = _chunk(txsHash, 10); + const txsHash = items ? uniq(items.map(i => i.hash)) : []; + const txChunks = chunk(txsHash, 10); const queryKey = { viewAsAccountAddress, diff --git a/yarn.lock b/yarn.lock index cb671a6f9d..eedbade03b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10376,6 +10376,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-toolkit@1.31.0: + version "1.31.0" + resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.31.0.tgz#f4fc1382aea09cb239afa38f3c724a5658ff3163" + integrity sha512-vwS0lv/tzjM2/t4aZZRAgN9I9TP0MSkWuvt6By+hEXfG/uLs8yg2S1/ayRXH/x3pinbLgVJYT+eppueg3cM6tg== + esbuild@^0.21.3: version "0.21.5" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" @@ -13573,7 +13578,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.21: +lodash@^4.15.0, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==