diff --git a/public/locales/en.json b/public/locales/en.json index cf043eb..132cc36 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -369,6 +369,8 @@ "transactions": { "title": "Network Transactions", "myTransactionsTitle": "My Transactions", + "nameHistoryTitle": "Name History", + "nameTransactionsTitle": "Name Transactions", "columnID": "ID", "columnType": "Type", diff --git a/src/global/AppRouter.tsx b/src/global/AppRouter.tsx index 1211ee0..4d20979 100644 --- a/src/global/AppRouter.tsx +++ b/src/global/AppRouter.tsx @@ -43,6 +43,16 @@ name: "transactions", component: }, + { + path: "/network/names/:name/history", + name: "nameHistory", + component: + }, + { + path: "/network/names/:name/transactions", + name: "nameTransactions", + component: + }, { path: "/settings", name: "settings", component: }, { path: "/settings/debug", name: "settingsDebug" }, diff --git a/src/krist/api/lookup.ts b/src/krist/api/lookup.ts index 70f6d29..8536e42 100644 --- a/src/krist/api/lookup.ts +++ b/src/krist/api/lookup.ts @@ -54,12 +54,20 @@ // ============================================================================= export type SortableTransactionFields = "id" | "from" | "to" | "value" | "time" | "sent_name" | "sent_metaname"; + +export enum LookupTransactionType { + TRANSACTIONS, + NAME_HISTORY, + NAME_TRANSACTIONS +} + export interface LookupTransactionsOptions { includeMined?: boolean; limit?: number; offset?: number; orderBy?: SortableTransactionFields; order?: "ASC" | "DESC"; + type?: LookupTransactionType; } export interface LookupTransactionsResponse { @@ -76,12 +84,24 @@ if (opts.orderBy) qs.append("orderBy", opts.orderBy); if (opts.order) qs.append("order", opts.order); + // Map the lookup type to the appropriate route + // TODO: this is kinda wack + const type = opts.type || LookupTransactionType.TRANSACTIONS; + const route = type === LookupTransactionType.TRANSACTIONS + ? "transactions" : "names"; + const routeExtra = type !== LookupTransactionType.TRANSACTIONS + ? (type === LookupTransactionType.NAME_HISTORY + ? "/history" + : "/transactions") + : ""; + return await api.get( - "lookup/transactions/" + `lookup/${route}/` + (addresses && addresses.length > 0 ? encodeURIComponent(addresses.join(",")) : "") - + "?" + qs + + routeExtra + + `?${qs}` ); } diff --git a/src/pages/transactions/TransactionsPage.tsx b/src/pages/transactions/TransactionsPage.tsx index 268424c..ca23866 100644 --- a/src/pages/transactions/TransactionsPage.tsx +++ b/src/pages/transactions/TransactionsPage.tsx @@ -12,6 +12,7 @@ import { TransactionsTable } from "./TransactionsTable"; import { useWallets } from "../../krist/wallets/Wallet"; +import { KristNameLink } from "../../components/KristNameLink"; /** The type of transaction listing to search by. */ export enum ListingType { @@ -21,11 +22,27 @@ /** Transactions across the whole network */ NETWORK_ALL, /** Network transactions filtered to a particular address */ - NETWORK_ADDRESS + NETWORK_ADDRESS, + + /** Name history transactions */ + NAME_HISTORY, + /** Transactions sent to a particular name */ + NAME_SENT, } +const LISTING_TYPE_TITLES: Record = { + [ListingType.WALLETS]: "transactions.myTransactionsTitle", + + [ListingType.NETWORK_ALL]: "transactions.title", + [ListingType.NETWORK_ADDRESS]: "transactions.title", + + [ListingType.NAME_HISTORY]: "transactions.nameHistoryTitle", + [ListingType.NAME_SENT]: "transactions.nameTransactionsTitle" +}; + interface ParamTypes { address?: string; + name?: string; } interface Props { @@ -34,30 +51,32 @@ export function TransactionsPage({ listingType }: Props): JSX.Element { const { t } = useTranslation(); - const { address } = useParams(); + const { address, name } = useParams(); + const [includeMined, setIncludeMined] = useState(false); // If there is an error (e.g. the lookup rejected the address list due to an // invalid address), the table will bubble it up to here const [error, setError] = useState(); - const [includeMined, setIncludeMined] = useState(false); + const subTitle = name + ? + : (listingType === ListingType.NETWORK_ADDRESS + ? address + : undefined); return + extra={!name && <> @@ -117,7 +140,10 @@ const table = useMemo(() => ( diff --git a/src/pages/transactions/TransactionsTable.tsx b/src/pages/transactions/TransactionsTable.tsx index 4f1f21c..9ab41c4 100644 --- a/src/pages/transactions/TransactionsTable.tsx +++ b/src/pages/transactions/TransactionsTable.tsx @@ -7,7 +7,9 @@ import { useTranslation } from "react-i18next"; import { KristTransaction } from "../../krist/api/types"; -import { convertSorterOrder, lookupTransactions, LookupTransactionsOptions, LookupTransactionsResponse, SortableTransactionFields } from "../../krist/api/lookup"; +import { convertSorterOrder, lookupTransactions, LookupTransactionsOptions, LookupTransactionsResponse, LookupTransactionType, SortableTransactionFields } from "../../krist/api/lookup"; + +import { ListingType } from "./TransactionsPage"; import { TransactionType } from "../../components/transactions/TransactionType"; import { ContextualAddress } from "../../components/ContextualAddress"; @@ -19,13 +21,27 @@ import Debug from "debug"; const debug = Debug("kristweb:transactions-table"); +// Received 'Cannot access LookupTransactionType before initialization' here, +// this is a crude workaround +const LISTING_TYPE_MAP: Record = { + [0]: LookupTransactionType.TRANSACTIONS, + [1]: LookupTransactionType.TRANSACTIONS, + [2]: LookupTransactionType.TRANSACTIONS, + [3]: LookupTransactionType.NAME_HISTORY, + [4]: LookupTransactionType.NAME_TRANSACTIONS +}; + interface Props { + listingType: ListingType; + addresses?: string[]; + name?: string; + includeMined?: boolean; setError?: Dispatch>; } -export function TransactionsTable({ addresses, includeMined, setError }: Props): JSX.Element { +export function TransactionsTable({ listingType, addresses, name, includeMined, setError }: Props): JSX.Element { const { t } = useTranslation(); const [loading, setLoading] = useState(true); @@ -33,20 +49,24 @@ const [options, setOptions] = useState({ limit: 20, offset: 0, - orderBy: "time", + orderBy: "time", // Equivalent to sorting by ID order: "DESC" }); // Fetch the transactions from the API, mapping the table options useEffect(() => { - debug("looking up transactions for %s", addresses ? addresses.join(",") : "network"); + debug("looking up transactions for %s", name || (addresses ? addresses.join(",") : "network")); setLoading(true); - lookupTransactions(addresses, { ...options, includeMined }) + lookupTransactions(name ? [name] : addresses, { + ...options, + includeMined, + type: LISTING_TYPE_MAP[listingType] + }) .then(setRes) .catch(setError) .finally(() => setLoading(false)); - }, [addresses, setError, options, includeMined ]); + }, [listingType, addresses, name, setError, options, includeMined]); debug("results? %b res.transactions.length: %d res.count: %d res.total: %d", !!res, res?.transactions?.length, res?.count, res?.total); @@ -58,8 +78,15 @@ dataSource={res?.transactions || []} rowKey="id" + // Triggered whenever the filter, sorting, or pagination changes onChange={(pagination, _, sorter) => { + // While the pagination should never be undefined, it's important to + // ensure that the default pageSize here is equal to the pagination's + // default pageSize, otherwise ant-design will print a warning when the + // data is first populated. const pageSize = (pagination?.pageSize) || 20; + + // This will trigger a data re-fetch setOptions({ ...options, @@ -89,7 +116,10 @@ dataIndex: "id", key: "id", render: id => <>{id.toLocaleString()}, - width: 100, + width: 100 + + // Don't allow sorting by ID to save a bit of width in the columns; + // it's equivalent to sorting by time anyway }, // Type {