Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement transaction table for account details page #220

Merged
merged 10 commits into from
Feb 24, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#220](https://github.com/alleslabs/celatone-frontend/pull/220) Add transactions table for account details page
- [#218](https://github.com/alleslabs/celatone-frontend/pull/218) Add instantiated and admin contracts of an account
- [#192](https://github.com/alleslabs/celatone-frontend/pull/192) Add alternative sidebar with only icons
- [#210](https://github.com/alleslabs/celatone-frontend/pull/210) New design for token card, currently support price
Expand Down
10 changes: 8 additions & 2 deletions src/lib/components/table/TableTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ interface TableTitleProps {
title: string;
count: number;
helperText?: string;
mb?: number | string;
}

export const TableTitle = ({ title, count, helperText }: TableTitleProps) => (
<Box mb={6}>
export const TableTitle = ({
title,
count,
helperText,
mb = "6",
}: TableTitleProps) => (
<Box mb={mb}>
<Flex gap={2} h="29px" alignItems="center">
<Heading as="h6" variant="h6">
{title}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
Box,
Flex,
Grid,
Icon,
Tag,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { MdCheck, MdClose, MdKeyboardArrowDown } from "react-icons/md";

import { RenderActionMessages } from "lib/components/action-msg/ActionMessages";
import { ExplorerLink } from "lib/components/ExplorerLink";
import { TableRow } from "lib/components/table";
import { AccordionTx } from "lib/components/table/AccordionTx";
import type { PastTransaction } from "lib/types";
import { dateFromNow, formatUTC } from "lib/utils";

interface TxsTableRowProps {
transaction: PastTransaction;
templateColumnsStyle: string;
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
}

export const TxsTableRow = ({
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
transaction,
templateColumnsStyle,
}: TxsTableRowProps) => {
const { isOpen, onToggle } = useDisclosure();
const [isAccordion, setIsAccordion] = useState(false);
const [showCopyButton, setShowCopyButton] = useState(false);
useEffect(() => {
if (transaction.messages.length > 1) setIsAccordion(true);
}, [transaction.messages]);
bkioshn marked this conversation as resolved.
Show resolved Hide resolved

return (
<Box w="full" minW="min-content">
<Grid
templateColumns={templateColumnsStyle}
onClick={isAccordion ? onToggle : undefined}
_hover={{ background: "pebble.900" }}
onMouseEnter={() => setShowCopyButton(true)}
onMouseLeave={() => setShowCopyButton(false)}
transition="all .25s ease-in-out"
cursor={isAccordion ? "pointer" : "default"}
>
<TableRow pl="16px">
<ExplorerLink
value={transaction.hash.toLocaleUpperCase()}
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
type="tx_hash"
canCopyWithHover
/>
</TableRow>
<TableRow>
<Icon
as={transaction.success ? MdCheck : MdClose}
fontSize="24px"
color={transaction.success ? "success.main" : "error.main"}
/>
</TableRow>
<TableRow>
<Flex gap={1} flexWrap="wrap">
<RenderActionMessages
transaction={transaction}
showCopyButton={showCopyButton}
/>
{transaction.isIbc && (
<Tag borderRadius="full" bg="honeydew.dark" color="pebble.900">
IBC
</Tag>
)}
</Flex>
</TableRow>
<TableRow>
<Flex direction="row" justify="space-between" align="center" w="full">
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
<Flex direction="column" gap={1}>
{transaction.created ? (
<>
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
<Text variant="body3">{formatUTC(transaction.created)}</Text>
<Text variant="body3" color="text.dark">
{`(${dateFromNow(transaction.created)})`}
</Text>
</>
) : (
<Text variant="body3">N/A</Text>
)}
</Flex>
</Flex>
</TableRow>
<TableRow>
{isAccordion && (
<Icon
as={MdKeyboardArrowDown}
transform={isOpen ? "rotate(180deg)" : "rotate(0deg)"}
boxSize="24px"
color="pebble.600"
/>
)}
</TableRow>
</Grid>
{isAccordion && (
<Grid w="full" py={2} bg="pebble.900" hidden={!isOpen}>
{transaction.messages.map((msg, index) => (
<AccordionTx
key={index.toString() + msg.type}
allowFurtherAction={false}
message={msg}
/>
))}
</Grid>
)}
</Box>
);
};
199 changes: 199 additions & 0 deletions src/lib/pages/account-details/components/tables/transactions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { Box, Flex, Grid } from "@chakra-ui/react";
import type { ChangeEvent } from "react";
import { useMemo, useState } from "react";

import { Loading } from "lib/components/Loading";
import { Pagination } from "lib/components/pagination";
import { usePaginator } from "lib/components/pagination/usePaginator";
import { EmptyState } from "lib/components/state/EmptyState";
import { TableContainer, TableHeader } from "lib/components/table";
import { TableTitle } from "lib/components/table/TableTitle";
import { ViewMore } from "lib/components/table/ViewMore";
import { FilterSelection } from "lib/pages/past-txs/components/FilterSelection";
import { useTxQuery } from "lib/pages/past-txs/query/useTxQuery";
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
import type { HumanAddr, Option, TxFilters } from "lib/types";

import { TxsTableRow } from "./TxsTableRow";

interface TransactionTableProps {
walletAddress: HumanAddr;
scrollComponentId: string;
totalData: Option<number>;
refetchCount: () => void;
onViewMore?: () => void;
}

interface TransactionTableBodyProps extends TransactionTableProps {
filters: TxFilters;
filterSelected: string[];
}

const TransactionTableBody = ({
walletAddress,
scrollComponentId,
totalData,
refetchCount,
onViewMore,
filters,
filterSelected,
}: TransactionTableBodyProps) => {
const {
pagesQuantity,
currentPage,
setCurrentPage,
pageSize,
setPageSize,
offset,
} = usePaginator({
total: totalData,
initialState: {
pageSize: 10,
currentPage: 1,
isDisabled: false,
},
});

const { data: transactions, isLoading } = useTxQuery(
walletAddress,
"",
filters,
onViewMore ? 5 : pageSize,
offset
);

const onPageChange = (nextPage: number) => {
refetchCount();
setCurrentPage(nextPage);
};

const onPageSizeChange = (e: ChangeEvent<HTMLSelectElement>) => {
const size = Number(e.target.value);
refetchCount();
setPageSize(size);
setCurrentPage(1);
};

const templateColumnsStyle =
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
"180px 70px minmax(360px, 1fr) max(250px) max(70px)";

if (isLoading) return <Loading />;

if (!transactions?.length && filterSelected.length > 0) {
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
return (
<Flex
mt="20px"
py="64px"
direction="column"
borderY="1px solid"
borderColor="pebble.700"
>
<EmptyState
image="https://assets.alleslabs.dev/illustration/search-empty.svg"
message={`
Transactions involving with Wasm module
such as Instantiate, Execute, or Upload Wasm file will display here.
`}
/>
</Flex>
);
}

if (!transactions?.length)
return (
<Flex
mt="20px"
py="64px"
direction="column"
borderY="1px solid"
borderColor="pebble.700"
>
<EmptyState message="This account did not submit any transactions before." />
</Flex>
);

return (
<>
<TableContainer>
<Grid templateColumns={templateColumnsStyle}>
<TableHeader>Transaction Hash</TableHeader>
<TableHeader />
<TableHeader>Messages</TableHeader>
<TableHeader>Timestamp</TableHeader>
<TableHeader />
</Grid>
{transactions.map((transaction) => (
<TxsTableRow
key={transaction.hash}
transaction={transaction}
templateColumnsStyle={templateColumnsStyle}
/>
))}
</TableContainer>
{totalData &&
(onViewMore
? totalData > 5 && <ViewMore onClick={onViewMore} />
: totalData > 10 && (
<Pagination
currentPage={currentPage}
pagesQuantity={pagesQuantity}
offset={offset}
totalData={totalData}
scrollComponentId={scrollComponentId}
pageSize={pageSize}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
))}
</>
);
};

export const TransactionTable = (
transactionTableProps: TransactionTableProps
) => {
const [filters, setFilters] = useState<TxFilters>({
isExecute: false,
isInstantiate: false,
isUpload: false,
isIbc: false,
isSend: false,
isMigrate: false,
isUpdateAdmin: false,
isClearAdmin: false,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
});

const handleSetFilters = (filter: string, bool: boolean) => {
setFilters({ ...filters, [filter]: bool });
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
};

const filterSelected = useMemo(() => {
return Object.keys(filters).reduce((acc: string[], key: string) => {
if (filters[key as keyof typeof filters]) {
acc.push(key);
}
return acc;
}, []);
}, [filters]);
bkioshn marked this conversation as resolved.
Show resolved Hide resolved

const { totalData, onViewMore } = transactionTableProps;
return (
<Box mt={12} mb={4}>
<Flex direction="row" justify="space-between" alignItems="center">
<TableTitle title="Transactions" count={totalData ?? 0} mb={0} />
{!onViewMore && (
<FilterSelection
result={filterSelected}
setResult={handleSetFilters}
boxWidth="400px"
placeholder="All"
/>
)}
</Flex>
<TransactionTableBody
{...transactionTableProps}
filters={filters}
filterSelected={filterSelected}
/>
</Box>
);
};
20 changes: 15 additions & 5 deletions src/lib/pages/account-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
InstantiatedContractTable,
AdminContractTable,
} from "./components/tables/contracts";
import { TransactionTable } from "./components/tables/transactions";

enum TabIndex {
Overview,
Expand All @@ -51,7 +52,7 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => {
// refetchCodes,
refetchContractsAdminCount,
refetchContractsCount,
// refetchCountTxs,
refetchCountTxs,
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
// refetchProposalCount,
} = useAccountDetailsTableCounts(accountAddress);

Expand Down Expand Up @@ -134,8 +135,13 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => {
<Text>Delegations</Text>
{/* TODO: replace with the truncated Assets table */}
<Text>Assets</Text>
{/* TODO: replace with the truncated Transactions table */}
<Text>Transactions</Text>
<TransactionTable
walletAddress={accountAddress}
scrollComponentId={tableHeaderId}
totalData={tableCounts.countTxs}
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
refetchCount={refetchCountTxs}
onViewMore={() => setTabIndex(TabIndex.Txs)}
/>
{/* TODO: replace with the truncated Codes table */}
<Text>Stored Codes</Text>
<InstantiatedContractTable
Expand Down Expand Up @@ -165,8 +171,12 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => {
<Text>Assets</Text>
</TabPanel>
<TabPanel p={0}>
{/* TODO: replace with the full Transactions table */}
<Text>Transactions</Text>
<TransactionTable
walletAddress={accountAddress}
scrollComponentId={tableHeaderId}
totalData={tableCounts.countTxs}
bkioshn marked this conversation as resolved.
Show resolved Hide resolved
refetchCount={refetchCountTxs}
/>
</TabPanel>
<TabPanel p={0}>
{/* TODO: replace with the full Codes table */}
Expand Down
Loading