Files
8f854ab8ad Clusters name sevices integration (#2994)
* adds clusters api configs

* adds clusters api configs

* adds api types

* adds props routes and titles for cluster pages

* adds query param enum for clusters api

* adds clusters svg

* adds dynamic page routes

* adds clusters entity ui in ui/shared/entities

* adds functions to detect input type; will be used to check whether a user is searching for a cluster name vs a evm address

* adds stubs for loading skeleton view

* adds pagination hook

* adds query param hooks for clusters trpc endpoints

* adds query param hooks for clusters trpc endpoints

* adds components that make up the /clusters ui

* adds components for /cluster ui

* adds clusters api endpoint to query by cluster id

* implements call to cluster id endpoint to get ac
tive chain count

* adds clusters search types and in api category

* creates clusters search hook and wraps useQuickSearchQuery hook

* replaces useQuickSearchQuery for useSearchWithClusters hook for both functionality

* add search suggestion ui

* adds optional chaining to handle react re-render when search type changes

* adds cluster ui to ui/pages/address on address details

* prevents clicking on cluster suggestion if address is non EVM

* adds util functions for testability along with tests, for action bar component logic

* refactor - extract clusters display logic wit
h tests

* refactor: extract page utilities from Clusters.tsx with tests

* adds jest testing infra for react components

* adds hook testing coverage

* adds component and integration tests

* adds detectInputType util tests

* removes generic parameters from clusters pagination types

* replaces any type with proper cluster config types

* adds cluster routes to metadata and analytics mappings

* updates order and stacks action bar items vertically when mobile view

* repositions clusters logo

* refactors search logic to render community clusters in search sugge
st

* adding clusters env vars

- add NEXT_PUBLIC_CLUSTERS_API_HOST and NEXT_PUBLIC_CLUSTERS_CDN_URL env vars
- configure clusters api integration in configs/app/apis.ts and features/clusters.ts
- adds clusters env vars to test env (.env.jest and .env.localhost)
- update CI deploy configs for both review envs
- add env vars validation schema
- document new vars in ENVS.md with usage examples

* centers directory/leaderboard toggle in mobile view

* resizes and sets proper color to logo in entity component

* hides clusters nav when clusters env is missing

* removes duplicate trailing slashes in cluster community names

* Bump `react` to v19 (#2782)

Co-authored-by: tom <[email protected]>

* change links to ton explorer

* fix internal links in ENVS.md file

* tx info: tac operation stage could be an array

* fix display of ad banner

* tac ui updates

* add more "button click" events

* update clock icon

* update screenshots

* fix age filter reset

* change tac operation status once again

* fix address icon tooltip content

* [skip ci] label released issues workflow: don't override release tag if it exists

* puzzle-15 (#2471)

* puzzle-15

* puzzle-review

* puzzle game

* puzzle fixes

* separate puzzle feature in readme

* fix testnet and rollup stage badges

* New styles for pagination and "Add / Remove" buttons (#2791)

* update styles for add / remove buttons

* update pagination styles

* change border color for filled inputs

* change row gap for alerts

* update icons and screenshots

* fix tests

* update buttons for contract method form

* update screenshot

* Gas tracker: bug fixes and improvements (#2792)

* Gas tracker page: hide `Gas price history` if chart is not enabled

Fixes #2781

* change default value for NEXT_PUBLIC_GAS_TRACKER_UNITS for testnets

* fix env for pw tests

* Public tags form improvements (#2796)

* adjust validation of comment field

* add icon url to public tag form

* update screenshots

* fix ts

* NFT marketplaces: make `collection_url` and `instance_url ` optional (#2797)

Fixes #2614

* Bump brace-expansion from 1.1.11 to 1.1.12 in /deploy/tools/affected-tests (#2801)

Bump brace-expansion in /deploy/tools/affected-tests

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>

* allow toaster to render custom description

* Encode the address hash for links to the TON chain (#2813)

Encode the address hash for links to the TON chain.

* Support redirects for known address discrepancies (#2799)

* Support redirects for known address discrepancies

Fixes #2793

* fix redirect to internal txs in the block

* add address user ops redirect

* Celo: support L2 epochs (#2784)

* epochs page

* epoch rewards distribution

* epoch election rewards

* address epoch rewards

* block epoch tag

* add some  tests

* adjust epoch details for L1 epochs

* support non-finalized epochs

* fixes after design review

* add truncation to block numbers on mobile

* update epochs table layout

* update timestamp hint

* fix bug with subheading of first epoch

* fixes of mobile view

* update screenshots

* Icons in footer links (#2809)

* Icons in footer links

Fixes #2539

* fix envs validator

* Metadata: send address to fetchers once visited (#2814)

* Metadata: send address to fetchers once visited

Fixes #2723

* fix ts

* replaces jest-dom matchers with standard matchers

* makes /clusters page default view the directory instead of leaderboard, including jest coverage

* updates columnn name from backing to total backing, in leaderboard display

* alphabetically reorders clusters export

* adds Cluster section to the Table of Cont
ents at top of page

* removes redundant Flex component wrapper around ClustersEntity

* uses standard toolkit values for border radius and color

* uses getFeaturePayload util instead of type coercion

* extends ImageProps interface to ClusterIconProps, and uses getFeaturePayload util instead of type coercion

* adds evm address regexp in regexp.ts as single source of truth

* updates imports for files that used form validator address regexp to use single source of truth

* removes unused args, unnecessary type coercions and memoizations. upper snake case constant values.

* sets correct action bar height using ACTION_BAR_HEIGHT_DESKTOP constant

* implements TimeFormatToggle to Joined column

* simplifies conditionals and removes unnecessary Box component wrapper

* displays standard 404 error screen on  erroneous ClusterDetails query

* standardizes details page to display ETH value and time in line with rest of app

* removes redundant debouncing

* moves useQueryParams hook to lib/router and updates imports and test to account

* moves config.features.cluster
s.isEnabled to top of the function and utilizes select function of useQuery in useAp
iQuery

* removes unnecessary 3rd column and isContractVerified since it is always false

* checks clusters enable for placeholder

* makes isClusterClickable condition more generic for future repurpose

* splits isEvmAddress out to general purpose utility in lib/address, update imports and test

* moves all cluster hooks and tests to lib/clusters folder

* wraps action bar with standard button wrapper

* makes nextjs-routes mock condition stricter

* adds mock clusters api data

* adds pw tests for cluster detail page

* adds pw tests for clusters directory page

* adds pw tests for clustersentity ui component

* adds mock envs for pw tests

* adds clusters api host and cdn url to env.pw

* fixes resolve react-hooks/rules-of-hooks violation in cluster search while keeping search optimization

* throws error instead of using app error component

* uses semantic token to reference color as per standard method in chakra ui

* removes empty or loading state pw tests

* pw tests - uses mockAssetResponse for icons, removes redundant tests, and updates vie
wport to narrow test case

* migrates all clusters pw tests from page.route to mockApiResponse

* updates docker generated pw screenshots

* removes object cloning in useSearchWithClusters hook

* uses standardized chakra name for backgroundColor

* resolves Clusters.pw.tsx docker screen generation issues and updates screenshots

* removes unneeded screenshots for Cluster.pw.tsx

* standardizes mobile testing approach with +@mobile tag by removing explicit viewport config, updates screenshots

* reverts expanded pw test coverage for ClustersEntity

* removes all clusters pw screenshots

* removes nested describe which was resulting in a failed test

* uploads latest docker generated screenshots for pw

* force trigger for conflict check

* removes repeating var from blockchainNavItems array

* fix screenshot merge conflicts - use upstream versions

* updates yarn.lock with upstream lib

* fix jest tests by mocking useQuery directly to resolve QueryClient context issues

* creates global mock object and proper typing to reduce duplication

* updates clusters pw screenshots with docker generation. update helia to 3.2.0 and pinned esbuild to 0.21.5. update docker dep script.

* updates yarn.lock

* removes unused imports from upstream/main

* add vite aliases for @libp2p/utils and @helia/verified-fetch to resolve playwright build failures

* updates pw screenshots for hero banner, home page and desktop header

* downgrades helia to v2.6.12

* revert changes in yarn.lock file related to helia package

* updates yarn.lock with helia downgrade

* revert changes in yarn.lock

* move clusters to the tab on the name service page

* fix tests

* small tweaks

* update screenshots

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Richard Oh <[email protected]>
Co-authored-by: Max Alekseenko <[email protected]>
Co-authored-by: isstuev <[email protected]>
Co-authored-by: Igor Stuev <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
Co-authored-by: Richard J Oh <[email protected]>
2025-09-08 19:21:15 +02:00

162 lines
5.0 KiB
TypeScript

import { ClustersOrderBy } from 'types/api/clusters';
import type { ClustersLeaderboardObject, ClustersDirectoryObject, ClustersByAddressObject } from 'types/api/clusters';
export type ViewMode = 'leaderboard' | 'directory';
export type InputType = 'address' | 'cluster_name';
export function getViewModeOrderBy(viewMode: ViewMode, hasSearchTerm: boolean): ClustersOrderBy {
if (viewMode === 'leaderboard') return ClustersOrderBy.RANK_ASC;
if (hasSearchTerm) return ClustersOrderBy.NAME_ASC;
return ClustersOrderBy.CREATED_AT_DESC;
}
export function shouldShowDirectoryView(viewMode: ViewMode, hasSearchTerm: boolean): boolean {
return viewMode === 'directory' || hasSearchTerm;
}
export function transformLeaderboardData(
data: unknown,
showDirectoryView: boolean,
): Array<ClustersLeaderboardObject> {
if (!data || showDirectoryView) return [];
if (data && typeof data === 'object' && 'result' in data) {
const result = (data as Record<string, unknown>).result;
if (result && typeof result === 'object' && 'data' in result && Array.isArray(result.data)) {
return result.data as Array<ClustersLeaderboardObject>;
}
}
return [];
}
export function transformAddressDataToDirectory(
addressData: Array<ClustersByAddressObject>,
clusterDetails: unknown,
): Array<ClustersDirectoryObject> {
const clusterDetailsData = clusterDetails &&
typeof clusterDetails === 'object' &&
'result' in clusterDetails &&
clusterDetails.result &&
typeof clusterDetails.result === 'object' &&
'data' in clusterDetails.result ? clusterDetails.result.data : null;
const allChainIds = clusterDetailsData &&
typeof clusterDetailsData === 'object' &&
'wallets' in clusterDetailsData &&
Array.isArray(clusterDetailsData.wallets) ?
clusterDetailsData.wallets.flatMap(
(wallet: unknown) => {
if (wallet && typeof wallet === 'object' && 'chainIds' in wallet && Array.isArray(wallet.chainIds)) {
return wallet.chainIds as Array<string>;
}
return [];
},
) : [];
const uniqueChainIds = [ ...new Set(allChainIds) ] as Array<string>;
return addressData.map((item) => ({
name: item.name,
isTestnet: item.isTestnet,
createdAt: item.createdAt,
owner: item.owner,
totalWeiAmount: item.totalWeiAmount,
updatedAt: item.updatedAt,
updatedBy: item.updatedBy,
chainIds: uniqueChainIds,
}));
}
export function transformFullDirectoryData(
data: unknown,
clusterDetails: unknown,
inputType: InputType,
showDirectoryView: boolean,
): Array<ClustersDirectoryObject> {
if (!showDirectoryView || !data) return [];
if (inputType === 'address') {
const addressData = data &&
typeof data === 'object' &&
'result' in data &&
data.result &&
typeof data.result === 'object' &&
'data' in data.result ? data.result.data as Array<ClustersByAddressObject> : null;
if (addressData && Array.isArray(addressData)) {
return transformAddressDataToDirectory(addressData, clusterDetails);
}
} else {
const apiData = data &&
typeof data === 'object' &&
'result' in data &&
data.result &&
typeof data.result === 'object' &&
'data' in data.result ? data.result.data : null;
if (apiData && typeof apiData === 'object' && 'items' in apiData && Array.isArray(apiData.items)) {
return apiData.items as Array<ClustersDirectoryObject>;
}
}
return [];
}
export function applyDirectoryPagination(
fullDirectoryData: Array<ClustersDirectoryObject>,
inputType: InputType,
page: number,
limit = 50,
): Array<ClustersDirectoryObject> {
if (inputType === 'address') {
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
return fullDirectoryData.slice(startIndex, endIndex);
}
return fullDirectoryData;
}
export function calculateHasNextPage(
data: unknown,
leaderboardDataLength: number,
fullDirectoryDataLength: number,
showDirectoryView: boolean,
inputType: InputType,
page: number,
hasSearchTerm: boolean,
limit = 50,
): boolean {
if (showDirectoryView) {
if (inputType === 'address') {
return page * limit < fullDirectoryDataLength;
} else {
if (hasSearchTerm) return false;
const apiData = data &&
typeof data === 'object' &&
'result' in data &&
data.result &&
typeof data.result === 'object' &&
'data' in data.result ? data.result.data : null;
if (apiData && typeof apiData === 'object' && 'total' in apiData && typeof apiData.total === 'number') {
return (page * limit) < apiData.total;
}
return false;
}
}
return leaderboardDataLength === limit;
}
export function isValidViewMode(value: string): value is ViewMode {
return value === 'leaderboard' || value === 'directory';
}
export function getDefaultViewMode(): ViewMode {
return 'directory';
}
export function getCurrentDataLength(
showDirectoryView: boolean,
directoryDataLength: number,
leaderboardDataLength: number,
): number {
return showDirectoryView ? directoryDataLength : leaderboardDataLength;
}