diff --git a/.github/workflows/_external_rust_lints.yml b/.github/workflows/_external_rust_lints.yml
index d848357dd01..53fb0f33149 100644
--- a/.github/workflows/_external_rust_lints.yml
+++ b/.github/workflows/_external_rust_lints.yml
@@ -23,8 +23,7 @@ env:
jobs:
rustfmt:
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
runs-on: [self-hosted]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1
@@ -35,8 +34,7 @@ jobs:
cargo +nightly ci-fmt-external
cargo-deny:
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
name: cargo-deny (advisories, licenses, bans, ...)
runs-on: [self-hosted]
steps:
@@ -48,8 +46,7 @@ jobs:
use-git-cli: true
clippy:
- if: |
- !cancelled() && success('rustfmt', 'cargo-deny') && inputs.isRust
+ if: (!cancelled() && !failure() && inputs.isRust)
needs:
- rustfmt
- cargo-deny
diff --git a/.github/workflows/_external_rust_tests.yml b/.github/workflows/_external_rust_tests.yml
index f3512091306..a1056ca3910 100644
--- a/.github/workflows/_external_rust_tests.yml
+++ b/.github/workflows/_external_rust_tests.yml
@@ -32,8 +32,7 @@ jobs:
test:
name: Test external crates
needs: changes
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
env:
# Tests written with #[sim_test] are often flaky if run as #[tokio::test] - this var
# causes #[sim_test] to only run under the deterministic `simtest` job, and not the
@@ -74,8 +73,7 @@ jobs:
check-unused-deps:
name: Check Unused Dependencies (${{ matrix.flags }})
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
strategy:
matrix:
flags: ["--all-features", "--no-default-features"]
diff --git a/.github/workflows/_rust_lints.yml b/.github/workflows/_rust_lints.yml
index e7c15960d83..92e5b271fbf 100644
--- a/.github/workflows/_rust_lints.yml
+++ b/.github/workflows/_rust_lints.yml
@@ -23,8 +23,7 @@ env:
jobs:
rustfmt:
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
runs-on: [self-hosted]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1
@@ -34,8 +33,7 @@ jobs:
run: cargo +nightly ci-fmt
cargo-deny:
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
name: cargo-deny (advisories, licenses, bans, ...)
runs-on: [self-hosted]
steps:
@@ -47,8 +45,7 @@ jobs:
use-git-cli: true
clippy:
- if: |
- !cancelled() && success('rustfmt', 'cargo-deny') && inputs.isRust
+ if: (!cancelled() && !failure() && inputs.isRust)
needs:
- rustfmt
- cargo-deny
diff --git a/.github/workflows/_rust_tests.yml b/.github/workflows/_rust_tests.yml
index c83984cdada..ed7b01a8ffa 100644
--- a/.github/workflows/_rust_tests.yml
+++ b/.github/workflows/_rust_tests.yml
@@ -33,8 +33,7 @@ jobs:
test:
name: Test rust crates
needs: changes
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
env:
# Tests written with #[sim_test] are often flaky if run as #[tokio::test] - this var
# causes #[sim_test] to only run under the deterministic `simtest` job, and not the
@@ -68,8 +67,7 @@ jobs:
check-unused-deps:
name: Check Unused Dependencies (${{ matrix.flags }})
- if: |
- !cancelled() && inputs.isRust
+ if: (!cancelled() && inputs.isRust)
strategy:
matrix:
flags: ["--all-features", "--no-default-features"]
diff --git a/.github/workflows/hierarchy.yml b/.github/workflows/hierarchy.yml
index fc12c62ba2a..729cbeb88e3 100644
--- a/.github/workflows/hierarchy.yml
+++ b/.github/workflows/hierarchy.yml
@@ -54,8 +54,7 @@ jobs:
group: license-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
needs: diff
- if: |
- !cancelled() && needs.diff.outputs.isRust == 'true'
+ if: (!cancelled() && needs.diff.outputs.isRust == 'true')
runs-on: [self-hosted]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1
@@ -64,14 +63,12 @@ jobs:
docusaurus:
needs: diff
- if: |
- !cancelled() && needs.diff.outputs.isDoc == 'true' && github.event.pull_request.draft == false
+ if: (!cancelled() && needs.diff.outputs.isDoc == 'true' && !github.event.pull_request.draft)
uses: ./.github/workflows/_docusaurus.yml
docs-lint:
needs: diff
- if: |
- !cancelled() && needs.diff.outputs.isDoc == 'true'
+ if: (!cancelled() && needs.diff.outputs.isDoc == 'true')
uses: ./.github/workflows/_docs_lint.yml
release-notes-description-check:
@@ -80,8 +77,7 @@ jobs:
group: release-notes-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
needs: diff
- if: |
- !cancelled() && needs.diff.outputs.isReleaseNotesEligible == 'true'
+ if: (!cancelled() && needs.diff.outputs.isReleaseNotesEligible == 'true')
runs-on: [self-hosted]
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1
@@ -115,8 +111,7 @@ jobs:
- license-check
- typos
if: |
- !cancelled() &&
- success('dprint-format', 'license-check', 'typos') &&
+ !cancelled() && !failure() &&
needs.diff.outputs.isRust == 'false' &&
needs.diff.outputs.isMove == 'true' &&
github.event.pull_request.draft == false
@@ -128,15 +123,14 @@ jobs:
- dprint-format
- license-check
- typos
- if: |
- !cancelled() && success('dprint-format', 'license-check', 'typos')
+ if: (!cancelled() && !failure())
uses: ./.github/workflows/_rust.yml
with:
isRust: ${{ needs.diff.outputs.isRust == 'true' }}
secrets: inherit
e2e:
- if: github.event.pull_request.draft == false
+ if: (!cancelled() && !failure() && !github.event.pull_request.draft)
needs:
- diff
- dprint-format
diff --git a/apps/explorer/src/components/activity/EpochsActivityTable.tsx b/apps/explorer/src/components/activity/EpochsActivityTable.tsx
index 3ac88030c7d..13ebd6083f7 100644
--- a/apps/explorer/src/components/activity/EpochsActivityTable.tsx
+++ b/apps/explorer/src/components/activity/EpochsActivityTable.tsx
@@ -8,7 +8,7 @@ import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { PlaceholderTable, TableCard, useCursorPagination } from '~/components/ui';
-import { generateTableDataFromEpochsData } from '~/lib/ui';
+import { generateEpochsTableColumns } from '~/lib/ui';
import { numberSuffix } from '~/lib/utils';
const DEFAULT_EPOCHS_LIMIT = 20;
@@ -39,7 +39,7 @@ export function EpochsActivityTable({
const { data, isFetching, pagination, isPending, isError } =
useCursorPagination(epochMetricsQuery);
- const cardData = data ? generateTableDataFromEpochsData(data) : undefined;
+ const tableColumns = generateEpochsTableColumns();
return (
@@ -48,7 +48,7 @@ export function EpochsActivityTable({
Failed to load Epochs
)}
- {isPending || isFetching || !cardData ? (
+ {isPending || isFetching || !data?.data ? (
) : (
{
goToFirstPageRef.current();
@@ -58,7 +58,7 @@ export function TransactionsActivityTable({
)}
- {isPending || isFetching || !cardData ? (
+ {isPending || isFetching || !data?.data ? (
) : (
@@ -54,7 +54,7 @@ export function CheckpointsTable({
Failed to load Checkpoints
)}
- {isPending || isFetching || !cardData ? (
+ {isPending || isFetching || !data?.data ? (
) : (
> = {
- success: 'positive',
- error: 'error',
-};
+import { Toaster } from '../toaster';
export function Layout(): JSX.Element {
const [network, setNetwork] = useNetwork();
@@ -47,25 +40,7 @@ export function Layout(): JSX.Element {
-
- {(toast) => (
-
- {resolveValue(toast.message, toast)}
-
- )}
-
+
diff --git a/apps/explorer/src/components/object/ObjectFieldsCard.tsx b/apps/explorer/src/components/object/ObjectFieldsCard.tsx
index c368f18794a..a83c19f32b5 100644
--- a/apps/explorer/src/components/object/ObjectFieldsCard.tsx
+++ b/apps/explorer/src/components/object/ObjectFieldsCard.tsx
@@ -156,9 +156,7 @@ export function ObjectFieldsCard({
>
diff --git a/apps/explorer/src/components/owned-coins/CoinItem.tsx b/apps/explorer/src/components/owned-coins/CoinItem.tsx
index abe73c73824..a79d7cdf12f 100644
--- a/apps/explorer/src/components/owned-coins/CoinItem.tsx
+++ b/apps/explorer/src/components/owned-coins/CoinItem.tsx
@@ -6,6 +6,7 @@ import { KeyValueInfo } from '@iota/apps-ui-kit';
import { useFormatCoin } from '@iota/core';
import { type CoinStruct } from '@iota/iota-sdk/client';
import { formatAddress } from '@iota/iota-sdk/utils';
+import { ObjectLink } from '../ui';
interface CoinItemProps {
coin: CoinStruct;
@@ -16,8 +17,9 @@ export default function CoinItem({ coin }: CoinItemProps): JSX.Element {
return (
+ }
fullwidth
/>
);
diff --git a/apps/explorer/src/components/toaster/Toaster.tsx b/apps/explorer/src/components/toaster/Toaster.tsx
new file mode 100644
index 00000000000..14e4fce7d27
--- /dev/null
+++ b/apps/explorer/src/components/toaster/Toaster.tsx
@@ -0,0 +1,40 @@
+// Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import toast, { Toaster as ToasterLib, type ToastType, resolveValue } from 'react-hot-toast';
+import { Snackbar, SnackbarType } from '@iota/apps-ui-kit';
+
+export type ToasterProps = {
+ bottomNavEnabled?: boolean;
+};
+
+export function Toaster() {
+ function getSnackbarType(type: ToastType): SnackbarType {
+ switch (type) {
+ case 'success':
+ return SnackbarType.Default;
+ case 'error':
+ return SnackbarType.Error;
+ case 'loading':
+ return SnackbarType.Default;
+ default:
+ return SnackbarType.Default;
+ }
+ }
+
+ return (
+
+ {(t) => (
+
+ toast.dismiss(t.id)}
+ text={resolveValue(t.message, t)}
+ type={getSnackbarType(t.type)}
+ showClose
+ duration={t.duration}
+ />
+
+ )}
+
+ );
+}
diff --git a/apps/explorer/src/components/toaster/index.ts b/apps/explorer/src/components/toaster/index.ts
new file mode 100644
index 00000000000..90ef62a3438
--- /dev/null
+++ b/apps/explorer/src/components/toaster/index.ts
@@ -0,0 +1,4 @@
+// Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+export * from './Toaster';
diff --git a/apps/explorer/src/components/top-packages/TopPackagesTable.tsx b/apps/explorer/src/components/top-packages/TopPackagesTable.tsx
index 05096aecd2d..8aa84ddbf9c 100644
--- a/apps/explorer/src/components/top-packages/TopPackagesTable.tsx
+++ b/apps/explorer/src/components/top-packages/TopPackagesTable.tsx
@@ -2,62 +2,73 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
-import { TableCellType } from '@iota/apps-ui-kit';
+import { TableCellText, TableCellBase } from '@iota/apps-ui-kit';
import { type MoveCallMetric } from '@iota/iota-sdk/client';
-import { useMemo } from 'react';
+import { type ColumnDef } from '@tanstack/react-table';
-import { createLinkTo, objectToLink, PlaceholderTable, TableCard } from '~/components/ui';
+import { ObjectLink, PlaceholderTable, TableCard } from '~/components/ui';
interface TopPackagesTableProps {
data: MoveCallMetric[];
isLoading: boolean;
}
-export function TopPackagesTable({ data, isLoading }: TopPackagesTableProps) {
- const tableData = useMemo(
- () => ({
- data: data?.map(([item, count]) => ({
- module: {
- type: TableCellType.Link,
- label: item.module,
- to: createLinkTo(item.package, 'module')({ module: item.module }),
- },
- function: {
- type: TableCellType.Text,
- label: item.function,
- },
- package: {
- type: TableCellType.Link,
- label: item.package,
- to: objectToLink({ objectId: item.package }),
- },
- count: {
- type: TableCellType.Text,
- label: Number(count).toLocaleString(),
- },
- })),
- columns: [
- {
- header: 'Package ID',
- accessorKey: 'package',
- },
- {
- header: 'Module',
- accessorKey: 'module',
- },
- {
- header: 'Function',
- accessorKey: 'function',
- },
- {
- header: 'Transactions',
- accessorKey: 'count',
- },
- ],
- }),
- [data],
- );
+const tableColumns: ColumnDef[] = [
+ {
+ header: 'Module',
+ id: 'module',
+ cell({ row: { original: metric } }) {
+ const item = metric[0];
+ return (
+
+
+ {item.module}
+
+
+ );
+ },
+ },
+ {
+ header: 'Function',
+ id: 'function',
+ cell({ row: { original: metric } }) {
+ const item = metric[0];
+ return (
+
+ {item.function}
+
+ );
+ },
+ },
+ {
+ header: 'Function',
+ id: 'function',
+ cell({ row: { original: metric } }) {
+ const item = metric[0].package;
+ return (
+
+
+ {item}
+
+
+ );
+ },
+ },
+ {
+ header: 'Count',
+ id: 'count',
+ cell({ row: { original: metric } }) {
+ const item = metric[1];
+ return (
+
+ {item}
+
+ );
+ },
+ },
+];
+export function TopPackagesTable({ data, isLoading }: TopPackagesTableProps) {
if (isLoading) {
return (
;
+ return ;
}
diff --git a/apps/explorer/src/components/top-validators-card/TopValidatorsCard.tsx b/apps/explorer/src/components/top-validators-card/TopValidatorsCard.tsx
index ee08d4967db..543b13f3077 100644
--- a/apps/explorer/src/components/top-validators-card/TopValidatorsCard.tsx
+++ b/apps/explorer/src/components/top-validators-card/TopValidatorsCard.tsx
@@ -5,28 +5,12 @@
import { useIotaClientQuery } from '@iota/dapp-kit';
import { ArrowRight12 } from '@iota/icons';
import { Text } from '@iota/ui';
-import { useMemo } from 'react';
import { Banner, Link, PlaceholderTable, TableCard } from '~/components/ui';
-import { generateValidatorsTableData, type ValidatorTableColumn } from '~/lib/ui';
+import { generateValidatorsTableColumns } from '~/lib/ui';
const NUMBER_OF_VALIDATORS = 10;
-const VALIDATOR_COLUMNS: ValidatorTableColumn[] = [
- {
- header: 'Name',
- accessorKey: 'name',
- },
- {
- header: 'Address',
- accessorKey: 'address',
- },
- {
- header: 'Stake',
- accessorKey: 'stake',
- },
-];
-
type TopValidatorsCardProps = {
limit?: number;
showIcon?: boolean;
@@ -35,23 +19,16 @@ type TopValidatorsCardProps = {
export function TopValidatorsCard({ limit, showIcon }: TopValidatorsCardProps): JSX.Element {
const { data, isPending, isSuccess, isError } = useIotaClientQuery('getLatestIotaSystemState');
- const tableData = useMemo(
- () =>
- data
- ? generateValidatorsTableData({
- validators: [...data.activeValidators].sort(() => 0.5 - Math.random()),
- atRiskValidators: [],
- validatorEvents: [],
- rollingAverageApys: null,
- limit,
- showValidatorIcon: showIcon,
- columns: VALIDATOR_COLUMNS,
- })
- : null,
- [data, limit, showIcon],
- );
+ const tableColumns = generateValidatorsTableColumns({
+ atRiskValidators: [],
+ validatorEvents: [],
+ rollingAverageApys: null,
+ limit,
+ showValidatorIcon: showIcon,
+ includeColumns: ['Name', 'Address', 'Stake'],
+ });
- if (isError || (!isPending && !tableData?.data.length)) {
+ if (isError || (!isPending && !data.activeValidators.length)) {
return (
Validator data could not be loaded
@@ -69,9 +46,9 @@ export function TopValidatorsCard({ limit, showIcon }: TopValidatorsCardProps):
/>
)}
- {isSuccess && tableData && (
+ {isSuccess && (
<>
-
+
diff --git a/apps/explorer/src/components/transaction-blocks-for-address/TransactionBlocksForAddress.tsx b/apps/explorer/src/components/transaction-blocks-for-address/TransactionBlocksForAddress.tsx
index 9cec5614b64..790b986dd94 100644
--- a/apps/explorer/src/components/transaction-blocks-for-address/TransactionBlocksForAddress.tsx
+++ b/apps/explorer/src/components/transaction-blocks-for-address/TransactionBlocksForAddress.tsx
@@ -10,7 +10,6 @@ import {
useGetTransactionBlocks,
} from '~/hooks/useGetTransactionBlocks';
import { ObjectFilterValue } from '~/lib/enums';
-import { genTableDataFromTxData } from '../transactions/TxCardUtils';
import {
ButtonSegment,
ButtonSegmentType,
@@ -19,6 +18,7 @@ import {
SegmentedButtonType,
Title,
} from '@iota/apps-ui-kit';
+import { generateTransactionsTableColumns } from '~/lib/ui';
type TransactionBlocksForAddressProps = {
address: string;
@@ -110,10 +110,7 @@ export function TransactionBlocksForAddress({
} as TransactionFilter);
const currentPage = currentPageState[filterValue];
- const cardData =
- data && data.pages[currentPage]
- ? genTableDataFromTxData(data.pages[currentPage].data)
- : undefined;
+ const tableColumns = generateTransactionsTableColumns();
return (
@@ -125,7 +122,10 @@ export function TransactionBlocksForAddress({
- {isPending || isFetching || isFetchingNextPage || !cardData ? (
+ {isPending ||
+ isFetching ||
+ isFetchingNextPage ||
+ !data?.pages[currentPage].data ? (
) : (
)}
diff --git a/apps/explorer/src/components/transactions/TransactionsForAddress.tsx b/apps/explorer/src/components/transactions/TransactionsForAddress.tsx
index 5095ef24a89..3a50378542d 100644
--- a/apps/explorer/src/components/transactions/TransactionsForAddress.tsx
+++ b/apps/explorer/src/components/transactions/TransactionsForAddress.tsx
@@ -8,7 +8,7 @@ import { LoadingIndicator, Text } from '@iota/ui';
import { useQuery } from '@tanstack/react-query';
import { Banner, TableCard } from '~/components/ui';
-import { genTableDataFromTxData } from './TxCardUtils';
+import { generateTransactionsTableColumns } from '~/lib/ui';
interface TransactionsForAddressProps {
address: string;
@@ -44,7 +44,7 @@ export function TransactionsForAddressTable({
);
}
- const tableData = genTableDataFromTxData(data);
+ const tableColumns = generateTransactionsTableColumns();
const hasTxns = data?.length > 0;
if (!hasTxns) {
@@ -57,7 +57,7 @@ export function TransactionsForAddressTable({
);
}
- return
;
+ return
;
}
export function TransactionsForAddress({
diff --git a/apps/explorer/src/components/transactions/TxCardUtils.tsx b/apps/explorer/src/components/transactions/TxCardUtils.tsx
deleted file mode 100644
index 3264e85b7e1..00000000000
--- a/apps/explorer/src/components/transactions/TxCardUtils.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// Modifications Copyright (c) 2024 IOTA Stiftung
-// SPDX-License-Identifier: Apache-2.0
-
-import { getTotalGasUsed } from '@iota/core';
-import { type IotaClient, type IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
-
-import { type TableCellProps, TableCellType } from '@iota/apps-ui-kit';
-import { addressToLink, transactionToLink } from '../ui';
-
-interface TransactionData {
- date: TableCellProps;
- digest: TableCellProps;
- txns: TableCellProps;
- gas: TableCellProps;
- sender: TableCellProps;
-}
-
-interface TableColumn {
- header: string;
- accessorKey: keyof TransactionData;
-}
-
-// Generate table data from the transaction data
-
-export function genTableDataFromTxData(results: IotaTransactionBlockResponse[]): {
- data: TransactionData[];
- columns: TableColumn[];
-} {
- return {
- data: results.map((transaction) => {
- const sender = transaction.transaction?.data.sender;
-
- return {
- date: { type: TableCellType.Text, label: transaction.timestampMs?.toString() },
- digest: {
- type: TableCellType.Link,
- label: transaction.digest,
- to: transactionToLink({ digest: transaction.digest }),
- },
- txns: {
- type: TableCellType.Text,
- label:
- transaction.transaction?.data.transaction.kind === 'ProgrammableTransaction'
- ? transaction.transaction.data.transaction.transactions.length.toString()
- : '--',
- },
- gas: {
- type: TableCellType.Text,
- label: transaction.effects
- ? getTotalGasUsed(transaction.effects)?.toString()
- : '0',
- },
- sender: sender
- ? {
- type: TableCellType.Link,
- label: sender,
- to: addressToLink({ address: sender }),
- }
- : {
- type: TableCellType.Text,
- label: '--',
- },
- };
- }),
- columns: [
- {
- header: 'Digest',
- accessorKey: 'digest',
- },
- {
- header: 'Sender',
- accessorKey: 'sender',
- },
- {
- header: 'Txns',
- accessorKey: 'txns',
- },
- {
- header: 'Gas',
- accessorKey: 'gas',
- },
- {
- header: 'Time',
- accessorKey: 'date',
- },
- ],
- };
-}
-
-const dedupe = (arr: string[]) => Array.from(new Set(arr));
-
-export function getDataOnTxDigests(
- client: IotaClient,
- transactions: string[],
-): Promise
{
- return client
- .multiGetTransactionBlocks({
- digests: dedupe(transactions),
- options: {
- showInput: true,
- showEffects: true,
- showEvents: true,
- },
- })
- .then((transactions) =>
- // Remove failed transactions
- transactions.filter((item) => item),
- );
-}
diff --git a/apps/explorer/src/components/transactions/index.ts b/apps/explorer/src/components/transactions/index.ts
index 968ecec6a40..1a1cc9b6bea 100644
--- a/apps/explorer/src/components/transactions/index.ts
+++ b/apps/explorer/src/components/transactions/index.ts
@@ -3,4 +3,3 @@
export * from './ProgTxnBlockCard';
export * from './TransactionsForAddress';
-export * from './TxCardUtils';
diff --git a/apps/explorer/src/components/ui/InternalLink.tsx b/apps/explorer/src/components/ui/InternalLink.tsx
index 8628e7f8fff..22dfe4d4ee5 100644
--- a/apps/explorer/src/components/ui/InternalLink.tsx
+++ b/apps/explorer/src/components/ui/InternalLink.tsx
@@ -51,23 +51,3 @@ export const AddressLink = createInternalLink('address', 'address', (addressOrNs
export const ObjectLink = createInternalLink('object', 'objectId', formatAddress);
export const TransactionLink = createInternalLink('txblock', 'digest', formatDigest);
export const ValidatorLink = createInternalLink('validator', 'address', formatAddress);
-
-// This will ultimately replace createInternalLink.
-export function createLinkTo(
- base: string,
- propName: T,
-): (args: { queryStrings?: Record } & Record) => string {
- return ({ [propName]: id, queryStrings = {} }) => {
- const queryString = new URLSearchParams(queryStrings).toString();
- const queryStringPrefix = queryString ? `?${queryString}` : '';
-
- return `/${base}/${encodeURI(id)}${queryStringPrefix}`;
- };
-}
-
-export const transactionToLink = createLinkTo('txblock', 'digest');
-export const checkpointToLink = createLinkTo('checkpoint', 'digest');
-export const epochToLink = createLinkTo('epoch', 'epoch');
-export const addressToLink = createLinkTo('address', 'address');
-export const checkpointSequenceToLink = createLinkTo('checkpoint', 'sequence');
-export const objectToLink = createLinkTo('object', 'objectId');
diff --git a/apps/explorer/src/components/ui/Link.tsx b/apps/explorer/src/components/ui/Link.tsx
index 149439f89c2..db60da72395 100644
--- a/apps/explorer/src/components/ui/Link.tsx
+++ b/apps/explorer/src/components/ui/Link.tsx
@@ -11,7 +11,7 @@ const linkStyles = cva([], {
variants: {
variant: {
text: 'text-body font-semibold text-steel-dark hover:text-steel-darker active:text-steel disabled:text-gray-60',
- mono: 'font-mono text-body text-primary-30 hover:text-primary-20 break-all',
+ mono: 'text-body text-primary-30 hover:text-primary-20',
textHeroDark: 'text-pBody font-medium text-hero-dark hover:text-hero-darkest',
},
uppercase: {
diff --git a/apps/explorer/src/components/ui/PlaceholderTable.tsx b/apps/explorer/src/components/ui/PlaceholderTable.tsx
index deb3f0194ec..6c5f3b07007 100644
--- a/apps/explorer/src/components/ui/PlaceholderTable.tsx
+++ b/apps/explorer/src/components/ui/PlaceholderTable.tsx
@@ -5,7 +5,7 @@
import { useMemo } from 'react';
import { TableCard } from './TableCard';
-import { TableCellType } from '@iota/apps-ui-kit';
+import { TableCellBase, TableCellPlaceholder } from '@iota/apps-ui-kit';
export interface PlaceholderTableProps {
rowCount: number;
@@ -13,30 +13,30 @@ export interface PlaceholderTableProps {
colHeadings: string[];
}
+function PlaceholderCell() {
+ return (
+
+
+
+ );
+}
+
export function PlaceholderTable({
rowCount,
rowHeight,
colHeadings,
}: PlaceholderTableProps): JSX.Element {
const rowEntry = useMemo(
- () =>
- Object.fromEntries(
- colHeadings.map((header, index) => [
- `a${index}`,
- {
- type: TableCellType.Placeholder,
- },
- ]),
- ),
+ () => Object.fromEntries(colHeadings.map((index) => [`a${index}`, null])),
[colHeadings, rowHeight],
);
const loadingTable = useMemo(
() => ({
data: new Array(rowCount).fill(rowEntry),
- columns: colHeadings.map((header, index) => ({
- header: header,
- accessorKey: `a${index}`,
+ columns: colHeadings.map((header) => ({
+ header,
+ cell: PlaceholderCell,
})),
}),
[rowCount, rowEntry, colHeadings],
diff --git a/apps/explorer/src/components/ui/TableCard.tsx b/apps/explorer/src/components/ui/TableCard.tsx
index 98572fbcd6f..372ecc0cb36 100644
--- a/apps/explorer/src/components/ui/TableCard.tsx
+++ b/apps/explorer/src/components/ui/TableCard.tsx
@@ -5,16 +5,15 @@
import {
Table,
TableBody,
- TableBodyRow,
- TableCell,
- type TableCellProps,
TableHeader,
TableHeaderCell,
- TableHeaderRow,
+ TableRow,
+ TableActionButton,
type TablePaginationOptions,
} from '@iota/apps-ui-kit';
import {
type ColumnDef,
+ flexRender,
getCoreRowModel,
getSortedRowModel,
type RowData,
@@ -22,8 +21,8 @@ import {
useReactTable,
} from '@tanstack/react-table';
import clsx from 'clsx';
-import { useState } from 'react';
-import { useNavigateWithQuery } from './LinkWithQuery';
+import { Fragment, useState } from 'react';
+import { Link } from './Link';
export interface TableCardProps {
refetching?: boolean;
@@ -48,7 +47,6 @@ export function TableCard({
totalLabel,
viewAll,
}: TableCardProps): JSX.Element {
- const navigate = useNavigateWithQuery();
const [sorting, setSorting] = useState(defaultSorting || []);
const table = useReactTable({
@@ -72,19 +70,18 @@ export function TableCard({
row.index)}
paginationOptions={paginationOptions}
- actionLabel={viewAll ? 'View All' : undefined}
supportingLabel={totalLabel}
- onActionClick={
- viewAll
- ? () => {
- navigate(viewAll, {});
- }
- : undefined
+ action={
+ viewAll ? (
+
+
+
+ ) : undefined
}
>
{table.getHeaderGroups().map((headerGroup) => (
-
+
{headerGroup.headers.map(({ id, column }) => (
({
isContentCentered={areHeadersCentered}
/>
))}
-
+
))}
{table.getRowModel().rows.map((row) => (
-
+
{row.getVisibleCells().map((cell) => (
- ()} />
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
))}
-
+
))}
diff --git a/apps/explorer/src/components/validator/ValidatorMeta.tsx b/apps/explorer/src/components/validator/ValidatorMeta.tsx
index a426ff23a7b..cc7dd6a0a04 100644
--- a/apps/explorer/src/components/validator/ValidatorMeta.tsx
+++ b/apps/explorer/src/components/validator/ValidatorMeta.tsx
@@ -6,7 +6,7 @@ import { Badge, BadgeType, KeyValueInfo, Panel } from '@iota/apps-ui-kit';
import { type IotaValidatorSummary } from '@iota/iota-sdk/client';
import toast from 'react-hot-toast';
import { ArrowTopRight } from '@iota/ui-icons';
-import { ImageIcon } from '~/components/ui';
+import { AddressLink, ImageIcon } from '~/components/ui';
type ValidatorMetaProps = {
validatorData: IotaValidatorSummary;
@@ -60,21 +60,29 @@ export function ValidatorMeta({ validatorData }: ValidatorMetaProps): JSX.Elemen
-
+
+ }
+ copyText={validatorData.iotaAddress}
onCopySuccess={handleOnCopy}
/>
-
+
diff --git a/apps/explorer/src/lib/enums/objectFilterValue.enum.ts b/apps/explorer/src/lib/enums/objectFilterValue.enum.ts
index d75e6d5869c..2bbfe28109a 100644
--- a/apps/explorer/src/lib/enums/objectFilterValue.enum.ts
+++ b/apps/explorer/src/lib/enums/objectFilterValue.enum.ts
@@ -2,6 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
export enum ObjectFilterValue {
- Input = 'inputObject',
- Changed = 'changedObject',
+ Input = 'InputObject',
+ Changed = 'ChangedObject',
}
diff --git a/apps/explorer/src/lib/ui/utils/generateCheckpointsTableColumns.tsx b/apps/explorer/src/lib/ui/utils/generateCheckpointsTableColumns.tsx
new file mode 100644
index 00000000000..571257fe9bf
--- /dev/null
+++ b/apps/explorer/src/lib/ui/utils/generateCheckpointsTableColumns.tsx
@@ -0,0 +1,83 @@
+// Copyright (c) Mysten Labs, Inc.
+// Modifications Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import { TableCellBase, TableCellText } from '@iota/apps-ui-kit';
+import type { Checkpoint } from '@iota/iota-sdk/client';
+import type { ColumnDef } from '@tanstack/react-table';
+import { CheckpointSequenceLink, CheckpointLink } from '~/components';
+
+/**
+ * Generate table columns renderers for the checkpoints data.
+ */
+export function generateCheckpointsTableColumns(): ColumnDef[] {
+ return [
+ {
+ header: 'Digest',
+ accessorKey: 'digest',
+ cell: ({ getValue }) => {
+ const digest = getValue();
+ return (
+
+ {digest}}
+ />
+
+ );
+ },
+ },
+ {
+ header: 'Sequence Number',
+ accessorKey: 'sequenceNumber',
+ cell: ({ getValue }) => {
+ const sequenceNumber = getValue();
+ return (
+
+
+
+ {sequenceNumber}
+
+
+
+ );
+ },
+ },
+ {
+ header: 'Transactions',
+ accessorKey: 'networkTotalTransactions',
+ cell: ({ getValue }) => {
+ const networkTotalTransactions = getValue();
+ return (
+
+ {networkTotalTransactions}
+
+ );
+ },
+ },
+ {
+ header: 'Time',
+ accessorKey: 'timestampMs',
+ cell: ({ getValue }) => {
+ const timestampMs = getValue();
+ return (
+
+ {timestampMs}
+
+ );
+ },
+ },
+ {
+ header: 'Transaction Block Count',
+ accessorKey: 'transactions',
+ cell: ({ getValue }) => {
+ const transactions = getValue();
+ return (
+
+ {transactions.length}
+
+ );
+ },
+ },
+ ];
+}
diff --git a/apps/explorer/src/lib/ui/utils/generateEpochsTableColumns.tsx b/apps/explorer/src/lib/ui/utils/generateEpochsTableColumns.tsx
new file mode 100644
index 00000000000..e7ee623facb
--- /dev/null
+++ b/apps/explorer/src/lib/ui/utils/generateEpochsTableColumns.tsx
@@ -0,0 +1,99 @@
+// Copyright (c) Mysten Labs, Inc.
+// Modifications Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import type { EpochMetrics } from '@iota/iota-sdk/client';
+import type { ColumnDef } from '@tanstack/react-table';
+import { TableCellBase, TableCellText } from '@iota/apps-ui-kit';
+import { CheckpointSequenceLink, EpochLink } from '~/components';
+import { getEpochStorageFundFlow } from '~/lib/utils';
+
+/**
+ * Generate table columns renderers for the epochs data.
+ */
+export function generateEpochsTableColumns(): ColumnDef[] {
+ return [
+ {
+ header: 'Epoch',
+ accessorKey: 'epoch',
+ cell: ({ getValue }) => {
+ const epoch = getValue();
+ return (
+
+
+ {epoch}
+
+
+ );
+ },
+ },
+ {
+ header: 'Transaction Blocks',
+ accessorKey: 'epochTotalTransactions',
+ cell: ({ getValue }) => {
+ const epochTotalTransactions = getValue();
+ return (
+
+ {epochTotalTransactions}
+
+ );
+ },
+ },
+ {
+ header: 'Stake Rewards',
+ id: 'stakeRewards',
+ accessorKey: 'endOfEpochInfo.totalStakeRewardsDistributed',
+ cell: ({ row: { original: epochMetrics } }) => {
+ const totalStakeRewardsDistributed =
+ epochMetrics.endOfEpochInfo?.totalStakeRewardsDistributed;
+ return (
+
+ {totalStakeRewardsDistributed ?? '0'}
+
+ );
+ },
+ },
+ {
+ header: 'Checkpoint Set',
+ accessorKey: 'firstCheckpointId',
+ cell: ({ getValue }) => {
+ const firstCheckpointId = getValue();
+ return (
+
+
+
+ {firstCheckpointId}
+
+
+
+ );
+ },
+ },
+ {
+ header: 'Storage Net Inflow',
+ accessorKey: 'endOfEpochInfo',
+ cell: ({ getValue }) => {
+ const endOfEpochInfo = getValue();
+ const storageNetInflow =
+ getEpochStorageFundFlow(endOfEpochInfo).netInflow?.toString() ?? '--';
+ return (
+
+ {storageNetInflow}
+
+ );
+ },
+ },
+ {
+ header: 'Epoch End',
+ id: 'epochEndTimestamp',
+ cell: ({ row: { original: epochMetrics } }) => {
+ const epochEndTimestamp = epochMetrics.endOfEpochInfo?.epochEndTimestamp;
+ return (
+
+ {epochEndTimestamp || '--'}
+
+ );
+ },
+ },
+ ];
+}
diff --git a/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx b/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx
deleted file mode 100644
index 2df2bf21c01..00000000000
--- a/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// Modifications Copyright (c) 2024 IOTA Stiftung
-// SPDX-License-Identifier: Apache-2.0
-
-import { type TableCellProps, TableCellType } from '@iota/apps-ui-kit';
-import { type CheckpointPage } from '@iota/iota-sdk/client';
-import { checkpointSequenceToLink, checkpointToLink } from '~/components';
-
-interface CheckpointData {
- digest: TableCellProps;
- time: TableCellProps;
- transactions: TableCellProps;
- sequenceNumber: TableCellProps;
- transactionBlockCount: TableCellProps;
-}
-
-interface TableColumn {
- header: string;
- accessorKey: keyof CheckpointData;
-}
-
-interface CheckpointTableData {
- data: CheckpointData[];
- columns: TableColumn[];
-}
-
-// Generate table data from the checkpoints data
-export function generateTableDataFromCheckpointsData(results: CheckpointPage): CheckpointTableData {
- return {
- data:
- results.data.map((checkpoint) => ({
- digest: {
- type: TableCellType.Link,
- label: checkpoint.digest,
- to: checkpointToLink({ digest: checkpoint.digest }),
- },
- time: { type: TableCellType.Text, label: checkpoint.timestampMs },
- transactions: {
- type: TableCellType.Text,
- label: checkpoint.networkTotalTransactions,
- },
- sequenceNumber: {
- type: TableCellType.Link,
- label: checkpoint.sequenceNumber,
- to: checkpointSequenceToLink({ sequence: checkpoint.sequenceNumber }),
- },
- transactionBlockCount: {
- type: TableCellType.Text,
- label: checkpoint.transactions.length.toString(),
- },
- })) ?? [],
- columns: [
- {
- header: 'Digest',
- accessorKey: 'digest',
- },
- {
- header: 'Sequence Number',
- accessorKey: 'sequenceNumber',
- },
- {
- header: 'Transactions',
- accessorKey: 'transactions',
- },
- {
- header: 'Time',
- accessorKey: 'time',
- },
- {
- header: 'Transaction Block Count',
- accessorKey: 'transactionBlockCount',
- },
- ],
- };
-}
diff --git a/apps/explorer/src/lib/ui/utils/generateTableDataFromEpochsData.tsx b/apps/explorer/src/lib/ui/utils/generateTableDataFromEpochsData.tsx
deleted file mode 100644
index 6fdcd0b54e0..00000000000
--- a/apps/explorer/src/lib/ui/utils/generateTableDataFromEpochsData.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) Mysten Labs, Inc.
-// Modifications Copyright (c) 2024 IOTA Stiftung
-// SPDX-License-Identifier: Apache-2.0
-
-import { type EpochMetricsPage } from '@iota/iota-sdk/client';
-import { getEpochStorageFundFlow } from '~/lib/utils';
-import { type TableCellProps, TableCellType } from '@iota/apps-ui-kit';
-import { checkpointSequenceToLink, epochToLink } from '~/components';
-
-interface EpochData {
- epoch: TableCellProps;
- transactions: TableCellProps;
- stakeRewards: TableCellProps;
- checkpointSet: TableCellProps;
- storageNetInflow: TableCellProps;
- time: TableCellProps;
-}
-
-interface TableColumn {
- header: string;
- accessorKey: keyof EpochData;
-}
-
-interface EpochTableData {
- data: EpochData[];
- columns: TableColumn[];
-}
-
-// Generate table data from the epochs data
-export function generateTableDataFromEpochsData(results: EpochMetricsPage): EpochTableData {
- return {
- data: results?.data.map((epoch) => ({
- epoch: {
- type: TableCellType.Link,
- label: epoch.epoch,
- to: epochToLink({ epoch: epoch.epoch }),
- },
- transactions: { type: TableCellType.Text, label: epoch.epochTotalTransactions },
- stakeRewards: {
- type: TableCellType.Text,
- label: epoch.endOfEpochInfo?.totalStakeRewardsDistributed ?? '0',
- },
- checkpointSet: {
- type: TableCellType.Link,
- label: epoch.firstCheckpointId,
- to: checkpointSequenceToLink({ sequence: epoch.firstCheckpointId }),
- },
- storageNetInflow: {
- type: TableCellType.Text,
- label: getEpochStorageFundFlow(epoch.endOfEpochInfo).netInflow?.toString() ?? '--',
- },
- time: {
- type: TableCellType.Text,
- label: epoch.endOfEpochInfo?.epochEndTimestamp ?? '--',
- },
- })),
- columns: [
- {
- header: 'Epoch',
- accessorKey: 'epoch',
- },
- {
- header: 'Transaction Blocks',
- accessorKey: 'transactions',
- },
- {
- header: 'Stake Rewards',
- accessorKey: 'stakeRewards',
- },
- {
- header: 'Checkpoint Set',
- accessorKey: 'checkpointSet',
- },
- {
- header: 'Storage Net Inflow',
- accessorKey: 'storageNetInflow',
- },
- {
- header: 'Epoch End',
- accessorKey: 'time',
- },
- ],
- };
-}
diff --git a/apps/explorer/src/lib/ui/utils/generateTransactionsTableColumns.tsx b/apps/explorer/src/lib/ui/utils/generateTransactionsTableColumns.tsx
new file mode 100644
index 00000000000..ac57ab6e666
--- /dev/null
+++ b/apps/explorer/src/lib/ui/utils/generateTransactionsTableColumns.tsx
@@ -0,0 +1,94 @@
+// Copyright (c) Mysten Labs, Inc.
+// Modifications Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import { getTotalGasUsed } from '@iota/core';
+import type { IotaTransactionBlockKind, IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
+
+import { TableCellBase, TableCellText } from '@iota/apps-ui-kit';
+import type { ColumnDef } from '@tanstack/react-table';
+import { AddressLink, TransactionLink } from '../../../components/ui';
+import { formatAddress } from '@iota/iota-sdk/utils';
+import { getElapsedTime } from '~/pages/epochs/utils';
+
+/**
+ * Generate table columns renderers for the transactions data.
+ */
+export function generateTransactionsTableColumns(): ColumnDef[] {
+ return [
+ {
+ header: 'Digest',
+ accessorKey: 'digest',
+ cell: ({ getValue }) => {
+ const digest = getValue();
+ return (
+
+ {formatAddress(digest)}}
+ />
+
+ );
+ },
+ },
+ {
+ header: 'Sender',
+ accessorKey: 'transaction.data.sender',
+ cell: ({ getValue }) => {
+ const address = getValue();
+ return (
+
+ {formatAddress(address)}}
+ />
+
+ );
+ },
+ },
+ {
+ header: 'Txns',
+ accessorKey: 'transaction.data.transaction',
+ cell: ({ getValue }) => {
+ const transaction = getValue();
+ const txns =
+ transaction.kind === 'ProgrammableTransaction'
+ ? transaction.transactions.length.toString()
+ : '--';
+ return (
+
+ {txns}
+
+ );
+ },
+ },
+ {
+ header: 'Gas',
+ accessorKey: 'effects',
+ cell: ({ getValue }) => {
+ const effects = getValue();
+ return (
+
+
+ {effects ? getTotalGasUsed(effects)?.toString() : '0'}
+
+
+ );
+ },
+ },
+ {
+ header: 'Time',
+ accessorKey: 'timestampMs',
+ cell: ({ getValue }) => {
+ const timestampMs = getValue();
+ return (
+
+
+ {getElapsedTime(Number(timestampMs), Date.now()) || '--'}
+
+
+ );
+ },
+ },
+ ];
+}
diff --git a/apps/explorer/src/lib/ui/utils/generateValidatorsTableColumns.tsx b/apps/explorer/src/lib/ui/utils/generateValidatorsTableColumns.tsx
new file mode 100644
index 00000000000..6875e772638
--- /dev/null
+++ b/apps/explorer/src/lib/ui/utils/generateValidatorsTableColumns.tsx
@@ -0,0 +1,196 @@
+// Copyright (c) 2024 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import { Badge, BadgeType, TableCellBase, TableCellText } from '@iota/apps-ui-kit';
+import type { ColumnDef } from '@tanstack/react-table';
+import { type ApyByValidator, formatPercentageDisplay } from '@iota/core';
+
+import { ampli, getValidatorMoveEvent, VALIDATOR_LOW_STAKE_GRACE_PERIOD } from '~/lib';
+import { StakeColumn, ValidatorLink, ImageIcon } from '~/components';
+import type { IotaEvent, IotaValidatorSummary } from '@iota/iota-sdk/dist/cjs/client';
+
+interface generateValidatorsTableColumnsArgs {
+ atRiskValidators: [string, string][];
+ validatorEvents: IotaEvent[];
+ rollingAverageApys: ApyByValidator | null;
+ limit?: number;
+ showValidatorIcon?: boolean;
+ includeColumns?: string[];
+}
+
+function ValidatorWithImage({ validator }: { validator: IotaValidatorSummary }) {
+ return (
+
+ ampli.clickedValidatorRow({
+ sourceFlow: 'Epoch details',
+ validatorAddress: validator.iotaAddress,
+ validatorName: validator.name,
+ })
+ }
+ label={
+
+
+ {validator.name}
+
+ }
+ />
+ );
+}
+
+export function generateValidatorsTableColumns({
+ atRiskValidators = [],
+ validatorEvents = [],
+ rollingAverageApys = null,
+ showValidatorIcon = true,
+ includeColumns,
+}: generateValidatorsTableColumnsArgs): ColumnDef[] {
+ let columns: ColumnDef[] = [
+ {
+ header: 'Name',
+ id: 'name',
+ cell({ row: { original: validator } }) {
+ return (
+
+ {showValidatorIcon ? (
+
+ ) : (
+ {validator.name}
+ )}
+
+ );
+ },
+ },
+
+ {
+ header: 'Stake',
+ accessorKey: 'stakingPoolIotaBalance',
+ cell({ getValue }) {
+ const stakingPoolIotaBalance = getValue();
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ header: 'Proposed next Epoch gas price',
+ accessorKey: 'nextEpochGasPrice',
+ cell({ getValue }) {
+ const nextEpochGasPrice = getValue();
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ header: 'APY',
+ accessorKey: 'iotaAddress',
+ cell({ getValue }) {
+ const iotaAddress = getValue();
+ const { apy, isApyApproxZero } = rollingAverageApys?.[iotaAddress] ?? {
+ apy: null,
+ };
+ return (
+
+
+ {formatPercentageDisplay(apy, '--', isApyApproxZero)}
+
+
+ );
+ },
+ },
+ {
+ header: 'Comission',
+ accessorKey: 'commissionRate',
+ cell({ getValue }) {
+ return (
+
+ {`${Number(getValue()) / 100}%`}
+
+ );
+ },
+ },
+ {
+ header: 'Last Epoch Rewards',
+ id: 'lastReward',
+ cell({ row: { original: validator } }) {
+ const event = getValidatorMoveEvent(validatorEvents, validator.iotaAddress) as {
+ pool_staking_reward?: string;
+ };
+ const lastReward = event?.pool_staking_reward ?? null;
+ return (
+
+
+ {lastReward !== null ? (
+
+ ) : (
+ '--'
+ )}
+
+
+ );
+ },
+ },
+ {
+ header: 'Voting Power',
+ accessorKey: 'votingPower',
+ cell({ getValue }) {
+ const votingPower = getValue();
+ return (
+
+
+ {votingPower ? Number(votingPower) / 100 + '%' : '--'}
+
+
+ );
+ },
+ },
+
+ {
+ header: 'Status',
+ id: 'atRisk',
+ cell({ row: { original: validator } }) {
+ const atRiskValidator = atRiskValidators.find(
+ ([address]) => address === validator.iotaAddress,
+ );
+ const isAtRisk = !!atRiskValidator;
+ const atRisk = isAtRisk
+ ? VALIDATOR_LOW_STAKE_GRACE_PERIOD - Number(atRiskValidator[1])
+ : null;
+
+ if (atRisk === null) {
+ return (
+
+
+
+ );
+ }
+
+ const atRiskText = atRisk > 1 ? `in ${atRisk} epochs` : 'next epoch';
+ return (
+
+
+
+ );
+ },
+ },
+ ];
+
+ if (includeColumns) {
+ columns = columns.filter((col) =>
+ includeColumns.includes(col.header?.toString() as string),
+ );
+ }
+
+ return columns;
+}
diff --git a/apps/explorer/src/lib/ui/utils/generateValidatorsTableData.tsx b/apps/explorer/src/lib/ui/utils/generateValidatorsTableData.tsx
deleted file mode 100644
index 3eabb06a9af..00000000000
--- a/apps/explorer/src/lib/ui/utils/generateValidatorsTableData.tsx
+++ /dev/null
@@ -1,302 +0,0 @@
-// Copyright (c) 2024 IOTA Stiftung
-// SPDX-License-Identifier: Apache-2.0
-
-import React from 'react';
-import {
- BadgeType,
- type TableCellProps,
- TableCellType,
- TableCellTextColor,
-} from '@iota/apps-ui-kit';
-import type { ColumnDef } from '@tanstack/react-table';
-import { type ApyByValidator, formatPercentageDisplay } from '@iota/core';
-
-import { ampli, getValidatorMoveEvent, VALIDATOR_LOW_STAKE_GRACE_PERIOD } from '~/lib';
-import { Link, StakeColumn, AddressLink, ImageIcon } from '~/components';
-import type { IotaEvent, IotaValidatorSummary } from '@iota/iota-sdk/dist/cjs/client';
-
-interface ValidatorTableRow {
- name: TableCellProps;
- stake: TableCellProps;
- apy: TableCellProps;
- nextEpochGasPrice: TableCellProps;
- commission: TableCellProps;
- address: TableCellProps;
- lastReward: TableCellProps;
- votingPower: TableCellProps;
- atRisk: TableCellProps;
-}
-
-interface GenerateValidatorsTableDataArgs {
- validators: IotaValidatorSummary[];
- atRiskValidators: [string, string][];
- validatorEvents: IotaEvent[];
- rollingAverageApys: ApyByValidator | null;
- limit?: number;
- showValidatorIcon?: boolean;
- columns?: ColumnDef