diff --git a/public/locales/en.json b/public/locales/en.json index f3ca78b..2e52d70 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -328,6 +328,8 @@ "cardRecentTransactionsTitle": "Recent transactions", "cardNamesTitle": "Names", + "transactionsError": "There was an error fetching the transactions. See the console for details.", + "resultInvalidTitle": "Invalid address", "resultInvalid": "That does not look like a valid Krist address.", "resultNotFoundTitle": "Address not found", diff --git a/src/pages/addresses/AddressPage.less b/src/pages/addresses/AddressPage.less index 51af0a0..07a72e3 100644 --- a/src/pages/addresses/AddressPage.less +++ b/src/pages/addresses/AddressPage.less @@ -26,5 +26,6 @@ .address-info-row { max-width: 768px; + margin-bottom: @margin-lg; } } diff --git a/src/pages/addresses/AddressPage.tsx b/src/pages/addresses/AddressPage.tsx index e077b4d..7297ba8 100644 --- a/src/pages/addresses/AddressPage.tsx +++ b/src/pages/addresses/AddressPage.tsx @@ -18,13 +18,15 @@ import * as api from "../../krist/api"; import { lookupAddress, KristAddressWithNames } from "../../krist/api/lookup"; +import { AddressTransactionsCard } from "./AddressTransactionsCard"; + import "./AddressPage.less"; interface ParamTypes { address: string; } -function Page({ address }: { address: KristAddressWithNames }): JSX.Element { +function PageContents({ address }: { address: KristAddressWithNames }): JSX.Element { const { t } = useTranslation(); return <> @@ -73,6 +75,15 @@ /> + + {/* Transaction and name row */} + + {/* Recent transactions */} + + + + + ; } @@ -115,7 +126,7 @@ {error ? : (kristAddress - ? + ? : )} ; } diff --git a/src/pages/addresses/AddressTransactionsCard.tsx b/src/pages/addresses/AddressTransactionsCard.tsx new file mode 100644 index 0000000..92f3217 --- /dev/null +++ b/src/pages/addresses/AddressTransactionsCard.tsx @@ -0,0 +1,69 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React, { useState, useEffect, useMemo } from "react"; +import classNames from "classnames"; +import { Card, Skeleton, Empty } from "antd"; + +import { useTranslation } from "react-i18next"; + +import { TransactionSummary } from "../../components/transactions/TransactionSummary"; +import { lookupTransactions, LookupTransactionsResponse } from "../../krist/api/lookup"; + +import { useSyncNode } from "../../krist/api"; + +import { SmallResult } from "../../components/SmallResult"; + +import Debug from "debug"; +const debug = Debug("kristweb:address-transactions-card"); + +async function fetchTransactions(address: string): Promise { + debug("fetching transactions"); + return lookupTransactions( + [address], + { includeMined: true, limit: 5, orderBy: "id", order: "DESC" } + ); +} + +export function AddressTransactionsCard({ address }: { address: string }): JSX.Element { + const { t } = useTranslation(); + const syncNode = useSyncNode(); + + const [res, setRes] = useState(); + const [error, setError] = useState(); + const [loading, setLoading] = useState(true); + + // Fetch transactions on page load or sync node reload + // TODO: set up something to temporarily subscribe to an address via the + // websocket service, so this can be updated in realtime + useEffect(() => { + if (!syncNode) return; + + fetchTransactions(address) + .then(setRes) + .catch(setError) + .finally(() => setLoading(false)); + }, [syncNode, address]); + + const isEmpty = !loading && (error || !res || res.count === 0); + const classes = classNames("kw-card", "address-card-transactions", { + "empty": isEmpty + }); + + return + + {error + ? + : (res && res.count > 0 + ? ( + + ) + : + )} + + ; +} diff --git a/src/pages/dashboard/WalletOverviewCard.tsx b/src/pages/dashboard/WalletOverviewCard.tsx index e009889..09d7254 100644 --- a/src/pages/dashboard/WalletOverviewCard.tsx +++ b/src/pages/dashboard/WalletOverviewCard.tsx @@ -19,21 +19,27 @@ const { wallets } = useWallets(); const { t } = useTranslation(); + // Turn the wallets into an array, to sort them later const clonedWallets = [...Object.values(wallets)]; + // Sum the balance and names of all wallets const balance = clonedWallets.filter(w => w.balance !== undefined) .reduce((acc, w) => acc + w.balance!, 0); const names = clonedWallets.filter(w => w.names !== undefined) .reduce((acc, w) => acc + w.names!, 0); + // Pick the top 4 wallets sorted by balance descending const topWallets = [...clonedWallets]; const sort = keyedNullSort("balance", undefined); topWallets.sort((a: Wallet, b: Wallet) => sort(a, b, "descend")); topWallets.reverse(); + const top4Wallets = topWallets.slice(0, 4); return + {/* Top row (summaries) */} + {/* Total balance */} + {/* Names */} + {/* Wallet list */} {top4Wallets.map(w => )} + {/* See more link */} + {/* Show an 'add wallets' button instead if there are no wallets yet */} {clonedWallets.length > 0 ? t("dashboard.walletOverviewSeeMore", { count: clonedWallets.length }) : }