diff --git a/apps/explorer/src/components/activity/Activity.tsx b/apps/explorer/src/components/activity/Activity.tsx index 5a5efea376f..ac0e58fd444 100644 --- a/apps/explorer/src/components/activity/Activity.tsx +++ b/apps/explorer/src/components/activity/Activity.tsx @@ -3,16 +3,39 @@ // SPDX-License-Identifier: Apache-2.0 import { useFeatureIsOn } from '@growthbook/growthbook-react'; -import { Heading } from '@iota/ui'; import { useState } from 'react'; import toast from 'react-hot-toast'; import { CheckpointsTable } from '../checkpoints/CheckpointsTable'; import { EpochsActivityTable } from './EpochsActivityTable'; import { TransactionsActivityTable } from './TransactionsActivityTable'; -import { PlayPause, Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui'; +import { PlayPause } from '~/components/ui'; +import { + ButtonSegment, + ButtonSegmentType, + SegmentedButton, + SegmentedButtonType, +} from '@iota/apps-ui-kit'; -const VALID_TABS = ['transactions', 'epochs', 'checkpoints']; +enum ActivityCategory { + Transactions = 'transactions', + Epochs = 'epochs', + Checkpoints = 'checkpoints', +} +const ACTIVITY_CATEGORIES = [ + { + label: 'Transactions', + value: ActivityCategory.Transactions, + }, + { + label: 'Epochs', + value: ActivityCategory.Epochs, + }, + { + label: 'Checkpoints', + value: ActivityCategory.Checkpoints, + }, +]; type ActivityProps = { initialTab?: string | null; @@ -24,16 +47,12 @@ const AUTO_REFRESH_ID = 'auto-refresh'; const REFETCH_INTERVAL_SECONDS = 10; const REFETCH_INTERVAL = REFETCH_INTERVAL_SECONDS * 1000; -export function Activity({ - initialTab, - initialLimit, - disablePagination, -}: ActivityProps): JSX.Element { +export function Activity({ initialLimit, disablePagination }: ActivityProps): JSX.Element { const pollingTxnTableEnabled = useFeatureIsOn('polling-txn-table'); const [paused, setPaused] = useState(false); - const [activeTab, setActiveTab] = useState(() => - initialTab && VALID_TABS.includes(initialTab) ? initialTab : 'transactions', + const [selectedCategory, setSelectedCategory] = useState( + ActivityCategory.Transactions, ); const handlePauseChange = () => { @@ -51,81 +70,82 @@ export function Activity({ const refetchInterval = paused || !pollingTxnTableEnabled ? undefined : REFETCH_INTERVAL; // TODO remove network check when querying transactions with TransactionKind filter is fixed on devnet and testnet /*const [network] = useNetwork(); - const isTransactionKindFilterEnabled = Network.MAINNET === network || Network.LOCAL === network; - const [showSystemTransactions, setShowSystemTransaction] = useState( - !isTransactionKindFilterEnabled, - ); - useEffect(() => { - if (!isTransactionKindFilterEnabled) { - setShowSystemTransaction(true); - } - }, [isTransactionKindFilterEnabled]);*/ + const isTransactionKindFilterEnabled = Network.MAINNET === network || Network.LOCAL === network; + const [showSystemTransactions, setShowSystemTransaction] = useState( + !isTransactionKindFilterEnabled, + ); + useEffect(() => { + if (!isTransactionKindFilterEnabled) { + setShowSystemTransaction(true); + } + }, [isTransactionKindFilterEnabled]);*/ return ( -
- -
- - - Transaction Blocks - - - Epochs - - - Checkpoints - - -
- {/* TODO re-enable this when index is stable */} - {/*activeTab === 'transactions' && isTransactionKindFilterEnabled ? ( - } - content={ - { - e.preventDefault(); - }} - onCheckedChange={() => { - setShowSystemTransaction((value) => !value); - }} - /> - } - modal={false} - align="end" - /> - ) : null */} - {/* todo: re-enable this when rpc is stable */} - {pollingTxnTableEnabled && activeTab === 'transactions' && ( + <> +
+ + {ACTIVITY_CATEGORIES.map(({ label, value }) => ( + setSelectedCategory(value)} + label={label} + selected={selectedCategory === value} + type={ButtonSegmentType.Underlined} + /> + ))} + +
+ {/* TODO re-enable this when index is stable */} + {/*activeTab === 'transactions' && isTransactionKindFilterEnabled ? ( + } + content={ + { + e.preventDefault(); + }} + onCheckedChange={() => { + setShowSystemTransaction((value) => !value); + }} + /> + } + modal={false} + align="end" + /> + ) : null */} + {/* todo: re-enable this when rpc is stable */} + {pollingTxnTableEnabled && + selectedCategory === ActivityCategory.Transactions && ( )} -
- +
+
+ {selectedCategory === ActivityCategory.Transactions && ( - - + )} + {selectedCategory === ActivityCategory.Epochs && ( - - + )} + {selectedCategory === ActivityCategory.Checkpoints && ( - - -
+ )} +
+ ); } diff --git a/apps/explorer/src/components/transactions/TxCardUtils.tsx b/apps/explorer/src/components/transactions/TxCardUtils.tsx index f0634847892..3264e85b7e1 100644 --- a/apps/explorer/src/components/transactions/TxCardUtils.tsx +++ b/apps/explorer/src/components/transactions/TxCardUtils.tsx @@ -3,21 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 import { getTotalGasUsed } from '@iota/core'; -import { X12, Dot12 } from '@iota/icons'; import { type IotaClient, type IotaTransactionBlockResponse } from '@iota/iota-sdk/client'; -import { IotaAmount } from '../table/IotaAmount'; -import { TxTimeType } from '../tx-time/TxTimeType'; -import { HighlightedTableCol } from '~/components'; -import { AddressLink, TransactionLink } from '~/components/ui'; -import { type ReactNode } from 'react'; +import { type TableCellProps, TableCellType } from '@iota/apps-ui-kit'; +import { addressToLink, transactionToLink } from '../ui'; interface TransactionData { - date: ReactNode; - digest: ReactNode; - txns: ReactNode; - gas: ReactNode; - sender: ReactNode; + date: TableCellProps; + digest: TableCellProps; + txns: TableCellProps; + gas: TableCellProps; + sender: TableCellProps; } interface TableColumn { @@ -33,47 +29,38 @@ export function genTableDataFromTxData(results: IotaTransactionBlockResponse[]): } { return { data: results.map((transaction) => { - const status = transaction.effects?.status.status; const sender = transaction.transaction?.data.sender; return { - date: ( - - - - ), - digest: ( - - - ) : ( - - ) - } - /> - - ), - txns: ( -
- {transaction.transaction?.data.transaction.kind === - 'ProgrammableTransaction' - ? transaction.transaction.data.transaction.transactions.length - : '--'} -
- ), - gas: ( - - ), - sender: ( - - {sender ? : '-'} - - ), + 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: [ diff --git a/apps/explorer/src/components/ui/InternalLink.tsx b/apps/explorer/src/components/ui/InternalLink.tsx index 51275a99c36..ac3ebb5b73e 100644 --- a/apps/explorer/src/components/ui/InternalLink.tsx +++ b/apps/explorer/src/components/ui/InternalLink.tsx @@ -50,3 +50,22 @@ 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'); diff --git a/apps/explorer/src/components/ui/Link.tsx b/apps/explorer/src/components/ui/Link.tsx index 44f6b8188b4..149439f89c2 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 font-medium text-hero-dark hover:text-hero-darkest break-all', + mono: 'font-mono text-body text-primary-30 hover:text-primary-20 break-all', textHeroDark: 'text-pBody font-medium text-hero-dark hover:text-hero-darkest', }, uppercase: { diff --git a/apps/explorer/src/components/ui/TableCard.tsx b/apps/explorer/src/components/ui/TableCard.tsx index 71c3af9766e..834d5997276 100644 --- a/apps/explorer/src/components/ui/TableCard.tsx +++ b/apps/explorer/src/components/ui/TableCard.tsx @@ -2,10 +2,18 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ArrowRight12 } from '@iota/icons'; +import { + Table, + TableBody, + TableBodyRow, + TableCell, + type TableCellProps, + TableHeader, + TableHeaderCell, + TableHeaderRow, +} from '@iota/apps-ui-kit'; import { type ColumnDef, - flexRender, getCoreRowModel, getSortedRowModel, type SortingState, @@ -22,15 +30,6 @@ export interface TableCardProps { defaultSorting?: SortingState; } -function AscDescIcon({ sorting }: { sorting: 'asc' | 'desc' }): JSX.Element { - return ( - - ); -} - export function TableCard({ refetching, data, @@ -76,61 +75,36 @@ export function TableCard({ refetching && 'opacity-50', )} > - - +
row.index)}> + {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map( - ({ id, colSpan, column, isPlaceholder, getContext }) => ( - - ), - )} - + + {headerGroup.headers.map(({ id, column }) => ( + + ))} + ))} - - + + {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map(({ column, id, getContext }) => ( - + + {row.getVisibleCells().map((cell) => ( + ()} /> ))} - + ))} - -
-
- {isPlaceholder - ? null - : flexRender(column.columnDef.header, getContext())} - - {column.getIsSorted() && ( - - )} -
-
- {flexRender(column.columnDef.cell, getContext())} -
+ +
); } diff --git a/apps/explorer/src/components/ui/Tabs.tsx b/apps/explorer/src/components/ui/Tabs.tsx index e08055edab4..38f3d4e3263 100644 --- a/apps/explorer/src/components/ui/Tabs.tsx +++ b/apps/explorer/src/components/ui/Tabs.tsx @@ -25,14 +25,14 @@ const TabSizeContext = createContext(null); const tabStyles = cva( [ 'flex items-center gap-1 border-b border-transparent -mb-px', - 'font-semibold text-steel-dark disabled:text-steel-dark disabled:pointer-events-none hover:text-steel-darker data-[state=active]:border-gray-65', + 'text-neutral-60 disabled:text-steel-dark disabled:pointer-events-none hover:text-neutral-10 data-[state=active]:border-primary-30', ], { variants: { size: { - lg: 'text-heading4 data-[state=active]:text-steel-darker pb-2', - md: 'text-body data-[state=active]:text-steel-darker pb-2', - sm: 'text-captionSmall font-medium pb-0.5 disabled:opacity-40 data-[state=active]:text-steel-darker', + lg: 'text-heading4 data-[state=active]:text-neutral-10 pb-4', + md: 'text-body data-[state=active]:text-neutral-10 pb-4', + sm: 'text-captionSmall font-medium pb-1 disabled:opacity-40 data-[state=active]:text-neutral-10', }, }, defaultVariants: { diff --git a/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx b/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx index cd26df37112..d8b2324f98b 100644 --- a/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx +++ b/apps/explorer/src/lib/ui/utils/generateTableDataFromCheckpointsData.tsx @@ -2,22 +2,19 @@ // 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 { Text } from '@iota/ui'; - -import { type ReactNode } from 'react'; -import { HighlightedTableCol, TxTimeType } from '~/components'; -import { CheckpointLink, CheckpointSequenceLink } from '~/components/ui'; +import { checkpointSequenceToLink, checkpointToLink } from '~/components'; interface CheckpointData { - digest: ReactNode; - time: ReactNode; - sequenceNumber: ReactNode; - transactionBlockCount: ReactNode; + digest: TableCellProps; + time: TableCellProps; + sequenceNumber: TableCellProps; + transactionBlockCount: TableCellProps; } interface TableColumn { - header: () => string; + header: string; accessorKey: keyof CheckpointData; } @@ -27,38 +24,41 @@ interface CheckpointTableData { } // Generate table data from the checkpoints data -export function generateTableDataFromCheckpointsData(data: CheckpointPage): CheckpointTableData { +export function generateTableDataFromCheckpointsData(results: CheckpointPage): CheckpointTableData { return { data: - data?.data.map((checkpoint) => ({ - digest: ( - - - - ), - time: , - sequenceNumber: , - transactionBlockCount: ( - - {checkpoint.transactions.length} - - ), + results.data.map((checkpoint) => ({ + digest: { + type: TableCellType.Link, + label: checkpoint.digest, + to: checkpointToLink({ digest: checkpoint.digest }), + }, + time: { type: TableCellType.Text, label: checkpoint.timestampMs }, + sequenceNumber: { + type: TableCellType.Link, + label: checkpoint.sequenceNumber, + to: checkpointSequenceToLink({ sequence: checkpoint.sequenceNumber }), + }, + transactionBlockCount: { + type: TableCellType.Text, + label: checkpoint.transactions.length.toString(), + }, })) ?? [], columns: [ { - header: () => 'Digest', + header: 'Digest', accessorKey: 'digest', }, { - header: () => 'Sequence Number', + header: 'Sequence Number', accessorKey: 'sequenceNumber', }, { - header: () => 'Time', + header: 'Time', accessorKey: 'time', }, { - header: () => 'Transaction Block Count', + 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 index bca907aa37b..6fdcd0b54e0 100644 --- a/apps/explorer/src/lib/ui/utils/generateTableDataFromEpochsData.tsx +++ b/apps/explorer/src/lib/ui/utils/generateTableDataFromEpochsData.tsx @@ -3,20 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 import { type EpochMetricsPage } from '@iota/iota-sdk/client'; -import { Text } from '@iota/ui'; - -import { IotaAmount, TxTimeType, HighlightedTableCol } from '~/components'; -import { CheckpointSequenceLink, EpochLink } from '~/components/ui'; import { getEpochStorageFundFlow } from '~/lib/utils'; -import { type ReactNode } from 'react'; +import { type TableCellProps, TableCellType } from '@iota/apps-ui-kit'; +import { checkpointSequenceToLink, epochToLink } from '~/components'; interface EpochData { - epoch: ReactNode; - transactions: ReactNode; - stakeRewards: ReactNode; - checkpointSet: ReactNode; - storageNetInflow: ReactNode; - time: ReactNode; + epoch: TableCellProps; + transactions: TableCellProps; + stakeRewards: TableCellProps; + checkpointSet: TableCellProps; + storageNetInflow: TableCellProps; + time: TableCellProps; } interface TableColumn { @@ -33,30 +30,29 @@ interface EpochTableData { export function generateTableDataFromEpochsData(results: EpochMetricsPage): EpochTableData { return { data: results?.data.map((epoch) => ({ - epoch: ( - - - - ), - transactions: {epoch.epochTotalTransactions}, - stakeRewards: ( - - ), - checkpointSet: ( -
- - {` - `} - -
- ), - storageNetInflow: ( -
- -
- ), - time: , + 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: [ {