From f74dc2602aa7416808675f7f2280b2fc96bd6163 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Tue, 28 Nov 2023 16:00:48 -0500 Subject: [PATCH] fix: hostd paginate transactions, entitylist states --- .changeset/lucky-walls-remember.md | 5 + .changeset/twelve-glasses-approve.md | 5 + apps/explorer/components/Address/index.tsx | 4 +- .../components/AddressSkeleton/index.tsx | 4 +- apps/explorer/components/Block/index.tsx | 2 +- .../components/BlockSkeleton/index.tsx | 2 +- apps/explorer/components/Contract/index.tsx | 34 ++--- .../components/ContractSkeleton/index.tsx | 8 +- apps/explorer/components/Home/index.tsx | 2 +- .../components/HomeSkeleton/index.tsx | 4 +- .../explorer/components/Transaction/index.tsx | 9 +- .../components/TransactionSkeleton/index.tsx | 10 +- apps/hostd/components/Node/index.tsx | 1 + .../components/Wallet/WalletFilterBar.tsx | 18 +++ apps/hostd/components/Wallet/index.tsx | 103 +++---------- apps/hostd/config/providers.tsx | 23 +-- apps/hostd/contexts/transactions/index.tsx | 139 ++++++++++++++++++ apps/renterd/components/Node/index.tsx | 1 + apps/renterd/components/Wallet/index.tsx | 3 +- apps/renterd/contexts/transactions/index.tsx | 12 +- apps/walletd/components/Node/index.tsx | 5 +- libs/design-system/src/app/PeerList.tsx | 6 +- libs/design-system/src/app/TxPoolList.tsx | 6 +- .../src/components/BlockList.tsx | 102 +++++++------ .../src/components/EntityList.tsx | 40 +++-- .../src/components/PaginatorUnknownTotal.tsx | 2 +- 26 files changed, 347 insertions(+), 203 deletions(-) create mode 100644 .changeset/lucky-walls-remember.md create mode 100644 .changeset/twelve-glasses-approve.md create mode 100644 apps/hostd/components/Wallet/WalletFilterBar.tsx create mode 100644 apps/hostd/contexts/transactions/index.tsx diff --git a/.changeset/lucky-walls-remember.md b/.changeset/lucky-walls-remember.md new file mode 100644 index 000000000..5c462e47a --- /dev/null +++ b/.changeset/lucky-walls-remember.md @@ -0,0 +1,5 @@ +--- +'hostd': minor +--- + +Wallet transactions are now paginated. diff --git a/.changeset/twelve-glasses-approve.md b/.changeset/twelve-glasses-approve.md new file mode 100644 index 000000000..d3456fcbe --- /dev/null +++ b/.changeset/twelve-glasses-approve.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +EntityList props refactored. diff --git a/apps/explorer/components/Address/index.tsx b/apps/explorer/components/Address/index.tsx index b750cf052..fbc552b16 100644 --- a/apps/explorer/components/Address/index.tsx +++ b/apps/explorer/components/Address/index.tsx @@ -152,10 +152,10 @@ export function Address({ id, address }: Props) { Unspent outputs - + - + diff --git a/apps/explorer/components/AddressSkeleton/index.tsx b/apps/explorer/components/AddressSkeleton/index.tsx index 928a7c3e6..a77ff9654 100644 --- a/apps/explorer/components/AddressSkeleton/index.tsx +++ b/apps/explorer/components/AddressSkeleton/index.tsx @@ -30,10 +30,10 @@ export function AddressSkeleton() { Unspent outputs - + - + diff --git a/apps/explorer/components/Block/index.tsx b/apps/explorer/components/Block/index.tsx index b28558585..c60a83041 100644 --- a/apps/explorer/components/Block/index.tsx +++ b/apps/explorer/components/Block/index.tsx @@ -63,7 +63,7 @@ export function Block({ block }: Props) { > ({ + dataset={block.transactions?.map((tx) => ({ hash: tx.id, label: 'transaction', initials: 'T', diff --git a/apps/explorer/components/BlockSkeleton/index.tsx b/apps/explorer/components/BlockSkeleton/index.tsx index cc1ad04fb..d72efbb86 100644 --- a/apps/explorer/components/BlockSkeleton/index.tsx +++ b/apps/explorer/components/BlockSkeleton/index.tsx @@ -25,7 +25,7 @@ export function BlockSkeleton() { } > - + ) } diff --git a/apps/explorer/components/Contract/index.tsx b/apps/explorer/components/Contract/index.tsx index f3a5fde76..82bc4773a 100644 --- a/apps/explorer/components/Contract/index.tsx +++ b/apps/explorer/components/Contract/index.tsx @@ -65,9 +65,9 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { value: contract.negotiation_timestamp !== '0001-01-01T00:00:00Z' ? humanDate(contract.negotiation_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) + dateStyle: 'medium', + timeStyle: 'short', + }) : '-', }, { @@ -81,9 +81,9 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { value: contract.expiration_timestamp !== '0001-01-01T00:00:00Z' ? humanDate(contract.expiration_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) + dateStyle: 'medium', + timeStyle: 'short', + }) : '-', }, { @@ -99,9 +99,9 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { value: contract.proof_timestamp !== '0001-01-01T00:00:00Z' ? humanDate(contract.proof_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) + dateStyle: 'medium', + timeStyle: 'short', + }) : '-', }, { @@ -115,9 +115,9 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { value: contract.proof_deadline_timestamp !== '0001-01-01T00:00:00Z' ? humanDate(contract.proof_deadline_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) + dateStyle: 'medium', + timeStyle: 'short', + }) : '-', }, { @@ -131,9 +131,9 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { value: contract.payout_timestamp !== '0001-01-01T00:00:00Z' ? humanDate(contract.payout_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) + dateStyle: 'medium', + timeStyle: 'short', + }) : '-', }, { @@ -202,13 +202,13 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) {
diff --git a/apps/explorer/components/ContractSkeleton/index.tsx b/apps/explorer/components/ContractSkeleton/index.tsx index f7142696a..97cf6b21e 100644 --- a/apps/explorer/components/ContractSkeleton/index.tsx +++ b/apps/explorer/components/ContractSkeleton/index.tsx @@ -28,16 +28,12 @@ export function ContractSkeleton() {
- +
diff --git a/apps/explorer/components/Home/index.tsx b/apps/explorer/components/Home/index.tsx index 3b7383624..08ea01465 100644 --- a/apps/explorer/components/Home/index.tsx +++ b/apps/explorer/components/Home/index.tsx @@ -212,7 +212,7 @@ export function Home({
({ + dataset={reverse(sortBy(blocks, 'timestamp')).map((block) => ({ height: block.height, timestamp: block.timestamp, href: routes.block.view.replace(':id', String(block.height)), diff --git a/apps/explorer/components/HomeSkeleton/index.tsx b/apps/explorer/components/HomeSkeleton/index.tsx index c989db229..1c7d41cce 100644 --- a/apps/explorer/components/HomeSkeleton/index.tsx +++ b/apps/explorer/components/HomeSkeleton/index.tsx @@ -16,8 +16,8 @@ export function HomeSkeleton() { } >
- - + +
) diff --git a/apps/explorer/components/Transaction/index.tsx b/apps/explorer/components/Transaction/index.tsx index bd10b8536..444621be5 100644 --- a/apps/explorer/components/Transaction/index.tsx +++ b/apps/explorer/components/Transaction/index.tsx @@ -130,17 +130,14 @@ export function Transaction({ title, transaction }: Props) {
- + {inputs?.map((i) => ( ))}
- + {outputs?.map((o) => ( ))} @@ -150,7 +147,7 @@ export function Transaction({ title, transaction }: Props) { {!!operations?.length && ( )}
diff --git a/apps/explorer/components/TransactionSkeleton/index.tsx b/apps/explorer/components/TransactionSkeleton/index.tsx index da035a9ea..079f56e78 100644 --- a/apps/explorer/components/TransactionSkeleton/index.tsx +++ b/apps/explorer/components/TransactionSkeleton/index.tsx @@ -16,19 +16,15 @@ export function TransactionSkeleton() {
- +
- +
{/* */}
diff --git a/apps/hostd/components/Node/index.tsx b/apps/hostd/components/Node/index.tsx index d96025b19..b05642320 100644 --- a/apps/hostd/components/Node/index.tsx +++ b/apps/hostd/components/Node/index.tsx @@ -46,6 +46,7 @@ export function Node() {
openDialog('connectPeer')} />
diff --git a/apps/hostd/components/Wallet/WalletFilterBar.tsx b/apps/hostd/components/Wallet/WalletFilterBar.tsx new file mode 100644 index 000000000..8a2b6f3c2 --- /dev/null +++ b/apps/hostd/components/Wallet/WalletFilterBar.tsx @@ -0,0 +1,18 @@ +import { WalletSyncWarning } from '@siafoundation/design-system' +import { useSyncStatus } from '../../hooks/useSyncStatus' + +export function WalletFilterBar() { + const { isSynced, syncPercent, isWalletSynced, walletScanPercent } = + useSyncStatus() + return ( +
+ +
+
+ ) +} diff --git a/apps/hostd/components/Wallet/index.tsx b/apps/hostd/components/Wallet/index.tsx index cada57bc0..1ab128f38 100644 --- a/apps/hostd/components/Wallet/index.tsx +++ b/apps/hostd/components/Wallet/index.tsx @@ -1,19 +1,10 @@ import { EntityList, - EntityListItemProps, WalletLayoutActions, - getTransactionType, BalanceEvolution, - daysInMilliseconds, - WalletSyncWarning, + PaginatorUnknownTotal, } from '@siafoundation/design-system' -import { - useMetricsPeriod, - useWallet, - useWalletPending, - useWalletTransactions, -} from '@siafoundation/react-hostd' -import { useMemo } from 'react' +import { useWallet } from '@siafoundation/react-hostd' import { useDialog } from '../../contexts/dialog' import { routes } from '../../config/routes' import BigNumber from 'bignumber.js' @@ -21,78 +12,18 @@ import { HostdSidenav } from '../HostdSidenav' import { HostdAuthedLayout } from '../HostdAuthedLayout' import { useSyncStatus } from '../../hooks/useSyncStatus' import { EmptyState } from './EmptyState' +import { useTransactions } from '../../contexts/transactions' +import { WalletFilterBar } from './WalletFilterBar' export function Wallet() { - const transactions = useWalletTransactions({ - params: { - limit: 50, - offset: 0, - }, - }) - const pending = useWalletPending() - const { openDialog } = useDialog() - const wallet = useWallet() - - const entities: EntityListItemProps[] = useMemo( - () => [ - ...(pending.data || []).map((t): EntityListItemProps => { - return { - type: 'transaction', - txType: getTransactionType(t.transaction, t.source), - hash: t.ID, - timestamp: new Date(t.timestamp).getTime(), - sc: new BigNumber(t.inflow).minus(t.outflow), - unconfirmed: true, - } - }), - ...(transactions.data || []) - .map((t): EntityListItemProps => { - return { - type: 'transaction', - txType: getTransactionType(t.transaction, t.source), - hash: t.ID, - timestamp: new Date(t.timestamp).getTime(), - onClick: () => openDialog('transactionDetails', t.ID), - sc: new BigNumber(t.inflow).minus(t.outflow), - } - }) - .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)), - ], - [pending, transactions, openDialog] - ) - - const dayPeriods = 30 - const start = useMemo(() => { - const today = new Date().getTime() - const periodsInMs = daysInMilliseconds(dayPeriods) - const periodsAgo = today - periodsInMs - return new Date(periodsAgo).toISOString() - }, []) - - const metrics = useMetricsPeriod({ - params: { - interval: 'daily', - start, - }, - }) - const balances = useMemo( - () => - (metrics.data || []) - .map((t) => { - return { - sc: Number(t.balance), - timestamp: new Date(t.timestamp).getTime(), - } - }) - .sort((a, b) => (a.timestamp >= b.timestamp ? 1 : -1)), - [metrics.data] - ) - const { isSynced, isWalletSynced, syncPercent, walletScanPercent } = useSyncStatus() + const { dataset, balances, metrics, offset, limit, dataState, pageCount } = + useTransactions() + return ( openDialog('sendSiacoin')} /> } - stats={ - - } + stats={} >
{balances?.length && balances.find((b) => b.sc) ? ( @@ -136,8 +60,17 @@ export function Wallet() { ) : null} } + actions={ + + } />
diff --git a/apps/hostd/config/providers.tsx b/apps/hostd/config/providers.tsx index c70861a91..fe1fd8086 100644 --- a/apps/hostd/config/providers.tsx +++ b/apps/hostd/config/providers.tsx @@ -4,6 +4,7 @@ import { DialogProvider, Dialogs } from '../contexts/dialog' import { VolumesProvider } from '../contexts/volumes' import { ConfigProvider } from '../contexts/config' import { OnboardingBar } from '../components/OnboardingBar' +import { TransactionsProvider } from '../contexts/transactions' type Props = { children: React.ReactNode @@ -13,17 +14,19 @@ export function Providers({ children }: Props) { return ( - - - - {/* this is here so that dialogs can use all the other providers, + + + + + {/* this is here so that dialogs can use all the other providers, and the other providers can trigger dialogs */} - - - {children} - - - + + + {children} + + + + ) diff --git a/apps/hostd/contexts/transactions/index.tsx b/apps/hostd/contexts/transactions/index.tsx new file mode 100644 index 000000000..80d60f80b --- /dev/null +++ b/apps/hostd/contexts/transactions/index.tsx @@ -0,0 +1,139 @@ +import { + EntityListItemProps, + daysInMilliseconds, + getTransactionType, + secondsInMilliseconds, + useDatasetEmptyState, +} from '@siafoundation/design-system' +import { + useMetricsPeriod, + useWalletPending, + useWalletTransactions, +} from '@siafoundation/react-hostd' +import { createContext, useContext, useMemo } from 'react' +import { useDialog } from '../dialog' +import BigNumber from 'bignumber.js' +import { useRouter } from 'next/router' + +const defaultLimit = 50 +const filters = [] + +function useTransactionsMain() { + const router = useRouter() + const limit = Number(router.query.limit || defaultLimit) + const offset = Number(router.query.offset || 0) + const transactions = useWalletTransactions({ + params: { + limit, + offset, + }, + config: { + swr: { + refreshInterval: secondsInMilliseconds(60), + }, + }, + }) + const pending = useWalletPending({ + config: { + swr: { + refreshInterval: secondsInMilliseconds(60), + }, + }, + }) + + const { openDialog } = useDialog() + + const dataset: EntityListItemProps[] | null = useMemo(() => { + if (!pending.data || !transactions.data) { + return null + } + return [ + ...(pending.data || []).map((t): EntityListItemProps => { + return { + type: 'transaction', + txType: getTransactionType(t.transaction, t.source), + hash: t.ID, + timestamp: new Date(t.timestamp).getTime(), + sc: new BigNumber(t.inflow).minus(t.outflow), + unconfirmed: true, + } + }), + ...(transactions.data || []) + .map((t): EntityListItemProps => { + return { + type: 'transaction', + txType: getTransactionType(t.transaction, t.source), + hash: t.ID, + timestamp: new Date(t.timestamp).getTime(), + onClick: () => openDialog('transactionDetails', t.ID), + sc: new BigNumber(t.inflow).minus(t.outflow), + } + }) + .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)), + ] + }, [pending, transactions, openDialog]) + + const dayPeriods = 30 + const start = useMemo(() => { + const today = new Date().getTime() + const periodsInMs = daysInMilliseconds(dayPeriods) + const periodsAgo = today - periodsInMs + return new Date(periodsAgo).toISOString() + }, []) + + const metrics = useMetricsPeriod({ + params: { + interval: 'daily', + start, + }, + }) + const balances = useMemo( + () => + (metrics.data || []) + .map((t) => { + return { + sc: Number(t.balance), + timestamp: new Date(t.timestamp).getTime(), + } + }) + .sort((a, b) => (a.timestamp >= b.timestamp ? 1 : -1)), + [metrics.data] + ) + + const error = transactions.error + const dataState = useDatasetEmptyState( + dataset, + transactions.isValidating, + error, + filters + ) + + return { + balances, + metrics, + dataset, + error, + dataState, + offset, + limit, + pageCount: dataset?.length || 0, + } +} + +type State = ReturnType + +const TransactionsContext = createContext({} as State) +export const useTransactions = () => useContext(TransactionsContext) + +type Props = { + children: React.ReactNode +} + +export function TransactionsProvider({ children }: Props) { + const state = useTransactionsMain() + return ( + + {children} + + ) +} diff --git a/apps/renterd/components/Node/index.tsx b/apps/renterd/components/Node/index.tsx index 61ca60115..f1cc5208a 100644 --- a/apps/renterd/components/Node/index.tsx +++ b/apps/renterd/components/Node/index.tsx @@ -50,6 +50,7 @@ export function Node() {
openDialog('connectPeer')} />
diff --git a/apps/renterd/components/Wallet/index.tsx b/apps/renterd/components/Wallet/index.tsx index efe51e8aa..4d5fa3a4d 100644 --- a/apps/renterd/components/Wallet/index.tsx +++ b/apps/renterd/components/Wallet/index.tsx @@ -59,7 +59,8 @@ export function Wallet() { ) : null} */} } actions={
p.addr)} connectPeer={() => openDialog('connectPeer')} />
{/*
- +
*/} diff --git a/libs/design-system/src/app/PeerList.tsx b/libs/design-system/src/app/PeerList.tsx index f8a363539..23d57a474 100644 --- a/libs/design-system/src/app/PeerList.tsx +++ b/libs/design-system/src/app/PeerList.tsx @@ -3,15 +3,17 @@ import { EntityList } from '../components/EntityList' type Props = { peers?: string[] + isLoading?: boolean connectPeer: () => void } -export function PeerList({ peers, connectPeer }: Props) { +export function PeerList({ peers, isLoading, connectPeer }: Props) { return ( Connect} - entities={ + isLoading={isLoading} + dataset={ peers?.map((netAddress) => ({ type: 'ip', hash: netAddress, diff --git a/libs/design-system/src/app/TxPoolList.tsx b/libs/design-system/src/app/TxPoolList.tsx index 231ba1f8a..5893e8b67 100644 --- a/libs/design-system/src/app/TxPoolList.tsx +++ b/libs/design-system/src/app/TxPoolList.tsx @@ -3,15 +3,17 @@ import { getTransactionTotals, getTransactionType } from '../lib/entityTypes' import { Transaction } from '@siafoundation/react-core' type Props = { + isLoading?: boolean transactions?: Transaction[] } -export function TxPoolList({ transactions }: Props) { +export function TxPoolList({ transactions, isLoading }: Props) { return ( ({ + isLoading={isLoading} + dataset={transactions?.map((t) => ({ type: 'transaction', txType: getTransactionType(t), sc: getTransactionTotals(t).sc, diff --git a/libs/design-system/src/components/BlockList.tsx b/libs/design-system/src/components/BlockList.tsx index 55e2ad89c..ff5461e2c 100644 --- a/libs/design-system/src/components/BlockList.tsx +++ b/libs/design-system/src/components/BlockList.tsx @@ -18,11 +18,27 @@ type BlockListItemProps = { type Props = { title: string - blocks?: BlockListItemProps[] + isLoading?: boolean + dataset?: BlockListItemProps[] skeletonCount?: number } -export function BlockList({ title, blocks, skeletonCount = 10 }: Props) { +export function BlockList({ + title, + dataset, + isLoading, + skeletonCount = 10, +}: Props) { + let show = 'emptyState' + + if (isLoading && !dataset?.length) { + show = 'skeleton' + } + + if (dataset?.length) { + show = 'currentData' + } + return (
@@ -33,7 +49,7 @@ export function BlockList({ title, blocks, skeletonCount = 10 }: Props) {
- {blocks?.length === 0 && ( + {show === 'emptyState' && (
)} - {blocks?.map((block, i) => ( -
- -
- - - - {humanNumber(block.height)} - + {show === 'currentData' && + dataset?.map((block, i) => ( +
+ +
+ + + + {humanNumber(block.height)} + + + {block.miningPool + ? ' mined by ' + : i < dataset.length - 1 + ? ' mined ' + : ''} + {block.miningPool} + {block.miningPool ? ' ' : ''} + {i < dataset.length - 1 + ? `in ${formatDistance( + new Date(block.timestamp), + new Date(dataset[i + 1].timestamp) + )}` + : ''} + + + {formatDistance(new Date(block.timestamp), new Date(), { + addSuffix: true, + })} - {block.miningPool - ? ' mined by ' - : i < blocks.length - 1 - ? ' mined ' - : ''} - {block.miningPool} - {block.miningPool ? ' ' : ''} - {i < blocks.length - 1 - ? `in ${formatDistance( - new Date(block.timestamp), - new Date(blocks[i + 1].timestamp) - )}` - : ''} - - - {formatDistance(new Date(block.timestamp), new Date(), { - addSuffix: true, - })} - +
-
- )) || } + ))} + {show === 'skeleton' && ( + + )}
diff --git a/libs/design-system/src/components/EntityList.tsx b/libs/design-system/src/components/EntityList.tsx index 09d8bd554..0fad58b3c 100644 --- a/libs/design-system/src/components/EntityList.tsx +++ b/libs/design-system/src/components/EntityList.tsx @@ -11,8 +11,9 @@ import { EntityListItem, EntityListItemProps } from './EntityListItem' type Props = { title?: string actions?: React.ReactNode - entities?: EntityListItemProps[] + dataset?: EntityListItemProps[] children?: React.ReactNode + isLoading?: boolean emptyState?: React.ReactNode emptyMessage?: string skeletonCount?: number @@ -21,13 +22,24 @@ type Props = { export function EntityList({ title, actions, - entities, + dataset, + isLoading, emptyState, emptyMessage, skeletonCount = 10, children, }: Props) { const showHeading = title || actions + let show = 'emptyState' + + if (isLoading && !dataset?.length && !children) { + show = 'skeleton' + } + + if (dataset?.length || children) { + show = 'currentData' + } + return (
@@ -43,7 +55,7 @@ export function EntityList({
)}
- {entities?.length === 0 && + {show === 'emptyState' && (emptyState || (
))} - {children || - entities?.map((entity, i) => { - return ( - - ) - }) || } + {show === 'currentData' && + (children || + dataset?.map((entity, i) => { + return ( + + ) + }))} + {show === 'skeleton' && ( + + )}
diff --git a/libs/design-system/src/components/PaginatorUnknownTotal.tsx b/libs/design-system/src/components/PaginatorUnknownTotal.tsx index 89f0af2a2..dde436ba3 100644 --- a/libs/design-system/src/components/PaginatorUnknownTotal.tsx +++ b/libs/design-system/src/components/PaginatorUnknownTotal.tsx @@ -24,7 +24,7 @@ export function PaginatorUnknownTotal({ isLoading, }: Props) { const router = useRouter() - const isMore = pageTotal === limit + const isMore = pageTotal >= limit const from = offset + 1 const to = Math.min(offset + limit, offset + pageTotal) return (