Skip to content

Commit

Permalink
Show skeleton on Spacewalk transactions page (#551)
Browse files Browse the repository at this point in the history
* Show skeleton for transactions table

* Fix conditions for showing skeleton

* Fix table loading endlessly when no wallet connected

* Use react-query

* Change subscription of active block number to single fetch
This is to prevent rerenders of the table for each new active block
This prevents the timestamps of the entries in the table to change on each rerender
  • Loading branch information
ebma committed Sep 11, 2024
1 parent 6d823aa commit 375f530
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 87 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ dist-ssr
!.yarn/versions

vite.config.ts.timestamp-*
coverage/
coverage/
# Local Netlify folder
.netlify
40 changes: 26 additions & 14 deletions src/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export type TableProps<T> = {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const defaultData: any[] = [];
const loading = <>{repeat(<Skeleton className="h-8 mb-2" />, 6)}</>;

const Table = <T,>({
data = defaultData,
Expand All @@ -77,6 +76,20 @@ const Table = <T,>({
}: TableProps<T>): JSX.Element | null => {
const totalCount = data.length;

const showSkeleton = useMemo(() => {
return isLoading;
}, [isLoading]);

const tableData = useMemo(() => {
return showSkeleton ? Array(8).fill({}) : data;
}, [showSkeleton, data]);

const tableColumns = useMemo(() => {
return showSkeleton
? columns.map((column) => ({ ...column, cell: () => <Skeleton className="mb-2 h-8" /> }))
: columns;
}, [showSkeleton, columns]);

const initialSort = useMemo(() => {
return sortBy
? Object.keys(sortBy).map((columnName) => ({ id: columnName, desc: sortBy[columnName] === SortingOrder.DESC }))
Expand All @@ -85,8 +98,8 @@ const Table = <T,>({

const { getHeaderGroups, getRowModel, getPageCount, nextPage, previousPage, setGlobalFilter, getState } =
useReactTable({
columns,
data,
columns: tableColumns,
data: tableData,
initialState: {
pagination: {
pageSize: ps,
Expand All @@ -105,47 +118,46 @@ const Table = <T,>({
globalFilter,
} = getState();

if (isLoading) return loading;
return (
<>
{search ? (
<div className="flex flex-wrap flex-row gap-2 mb-2">
<div className="mb-2 flex flex-row flex-wrap gap-2">
<div className="ml-auto">
<GlobalFilter globalFilter={globalFilter} setGlobalFilter={setGlobalFilter} />
</div>
</div>
) : null}
<div
className={`table-container bg-base-200 table-border rounded-lg overflow-x-auto border border-base-300 ${
className={`table-container table-border overflow-x-auto rounded-lg border border-base-300 bg-base-200 ${
fontSize || 'text-sm'
} font-semibold ${className})`}
>
{title && <div className="bg-base-200 px-4 py-6 text-lg">{title}</div>}
<table className="table w-full">
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className="border-b table-border">
<tr key={headerGroup.id} className="table-border border-b">
{headerGroup.headers.map((header) => {
const isSortable = header.column.getCanSort();
return (
<th
key={header.id}
colSpan={header.colSpan}
className={`${isSortable ? ' cursor-pointer' : ''}`}
className={`${isSortable ? 'cursor-pointer' : ''}`}
onClick={header.column.getToggleSortingHandler()}
>
<div
className={`flex flex-row items-center font-normal ${
fontSize || 'text-sm'
} normal-case table-header ${header.column.columnDef.meta?.className || ''}`}
} table-header normal-case ${header.column.columnDef.meta?.className || ''}`}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{isSortable ? (
<div className={`sort ${header.column.getIsSorted()} ml-2 mb-0.5`}>
<div className={`sort ${header.column.getIsSorted()} mb-0.5 ml-2`}>
{header.column.getIsSorted() === 'desc' ? (
<ChevronDownIcon className="w-3 h-3" stroke-width="2" />
<ChevronDownIcon className="h-3 w-3" stroke-width="2" />
) : (
<ChevronUpIcon className="w-3 h-3" stroke-width="2" />
<ChevronUpIcon className="h-3 w-3" stroke-width="2" />
)}
</div>
) : null}
Expand All @@ -161,7 +173,7 @@ const Table = <T,>({
<tr
key={row.id}
onClick={rowCallback ? () => rowCallback(row, index) : undefined}
className={rowCallback && 'cursor-pointer highlighted-row'}
className={rowCallback && 'highlighted-row cursor-pointer'}
>
{row.getVisibleCells().map((cell) => {
return (
Expand All @@ -180,7 +192,7 @@ const Table = <T,>({
</tbody>
</table>
<Pagination
className="justify-end text-neutral-400 normal-case font-normal text-sm mt-2 mb-2"
className="mb-2 mt-2 justify-end text-sm font-normal normal-case text-neutral-400"
currentIndex={pageIndex}
pageSize={pageSize}
totalCount={totalCount}
Expand Down
153 changes: 81 additions & 72 deletions src/pages/spacewalk/transactions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
convertCurrencyToStellarAsset,
estimateRequestCreationTime,
} from '../../../helpers/spacewalk';
import { useIssuePallet } from '../../../hooks/spacewalk/useIssuePallet';
import { useRedeemPallet } from '../../../hooks/spacewalk/useRedeemPallet';
import { RichIssueRequest, useIssuePallet } from '../../../hooks/spacewalk/useIssuePallet';
import { RichRedeemRequest, useRedeemPallet } from '../../../hooks/spacewalk/useRedeemPallet';
import { useSecurityPallet } from '../../../hooks/spacewalk/useSecurityPallet';
import { nativeToDecimal } from '../../../shared/parseNumbers/metric';

Expand All @@ -32,84 +32,92 @@ import {
updatedColumn,
} from './TransactionsColumns';
import '../styles.css';
import { useQuery } from '@tanstack/react-query';
import { WalletAccount } from '@talismn/connect-wallets';

const fetchAllEntries = async (
walletAccount: WalletAccount | undefined,
activeBlockNumber: number | undefined,
getIssueRequests: () => Promise<RichIssueRequest[]>,
getRedeemRequests: () => Promise<RichRedeemRequest[]>,
) => {
const issueEntries = await getIssueRequests();
const redeemEntries = await getRedeemRequests();
const entries: TTransfer[] = [];

issueEntries.forEach((e) => {
if (!walletAccount || !e.request.requester.eq(walletAccount?.address)) {
return;
}

const deadline = calculateDeadline(
activeBlockNumber as number,
e.request.opentime.toNumber(),
e.request.period.toNumber(),
);

const timedOut = deadline < DateTime.now();
const pending = e.request.status.type === 'Pending';
entries.push({
updated: estimateRequestCreationTime(activeBlockNumber as number, e.request.opentime.toNumber()),
amount: nativeToDecimal(e.request.amount.toString()).toString(),
asset: convertCurrencyToStellarAsset(e.request.asset)?.code,
transactionId: e.id.toString(),
type: TransferType.issue,
status: timedOut && pending ? 'Cancelled' : e.request.status.type,
original: e.request,
});
});

redeemEntries.forEach((e) => {
if (!walletAccount || !e.request.redeemer.eq(walletAccount?.address)) {
return;
}

const deadline = calculateDeadline(
activeBlockNumber as number,
e.request.opentime.toNumber(),
e.request.period.toNumber(),
);

const timedOut = deadline < DateTime.now();
const pending = e.request.status.type === 'Pending';

entries.push({
updated: estimateRequestCreationTime(activeBlockNumber as number, e.request.opentime.toNumber()),
amount: nativeToDecimal(e.request.amount.toString()).toString(),
asset: convertCurrencyToStellarAsset(e.request.asset)?.code,
transactionId: e.id.toString(),
type: TransferType.redeem,
status: timedOut && pending ? 'Failed' : e.request.status.type,
original: e.request,
});
});

return entries;
};

function Transactions(): JSX.Element {
const { getIssueRequests } = useIssuePallet();
const { getRedeemRequests } = useRedeemPallet();
const { subscribeActiveBlockNumber } = useSecurityPallet();
const { getActiveBlockNumber } = useSecurityPallet();
const { tenantName, walletAccount } = useGlobalState();
const [currentTransfer, setCurrentTransfer] = useState<TTransfer | undefined>();
const [activeBlockNumber, setActiveBlockNumber] = useState<number>(0);
const [data, setData] = useState<TTransfer[] | undefined>(undefined);

useEffect(() => {
let unsub: VoidFn = () => undefined;
subscribeActiveBlockNumber((blockNumber) => {
getActiveBlockNumber().then((blockNumber) => {
setActiveBlockNumber(blockNumber);
}).then((u) => (unsub = u));

return unsub;
}, [subscribeActiveBlockNumber]);

useEffect(() => {
const fetchAllEntries = async () => {
const issueEntries = await getIssueRequests();
const redeemEntries = await getRedeemRequests();
const entries: TTransfer[] = [];

issueEntries.forEach((e) => {
if (!walletAccount || !e.request.requester.eq(walletAccount?.address)) {
return;
}

const deadline = calculateDeadline(
activeBlockNumber as number,
e.request.opentime.toNumber(),
e.request.period.toNumber(),
);

const timedOut = deadline < DateTime.now();
const pending = e.request.status.type === 'Pending';
entries.push({
updated: estimateRequestCreationTime(activeBlockNumber as number, e.request.opentime.toNumber()),
amount: nativeToDecimal(e.request.amount.toString()).toString(),
asset: convertCurrencyToStellarAsset(e.request.asset)?.code,
transactionId: e.id.toString(),
type: TransferType.issue,
status: timedOut && pending ? 'Cancelled' : e.request.status.type,
original: e.request,
});
});

redeemEntries.forEach((e) => {
if (!walletAccount || !e.request.redeemer.eq(walletAccount?.address)) {
return;
}

const deadline = calculateDeadline(
activeBlockNumber as number,
e.request.opentime.toNumber(),
e.request.period.toNumber(),
);

const timedOut = deadline < DateTime.now();
const pending = e.request.status.type === 'Pending';

entries.push({
updated: estimateRequestCreationTime(activeBlockNumber as number, e.request.opentime.toNumber()),
amount: nativeToDecimal(e.request.amount.toString()).toString(),
asset: convertCurrencyToStellarAsset(e.request.asset)?.code,
transactionId: e.id.toString(),
type: TransferType.redeem,
status: timedOut && pending ? 'Failed' : e.request.status.type,
original: e.request,
});
});

return entries;
};
fetchAllEntries().then((res) => setData(res));
}, [activeBlockNumber, walletAccount, getIssueRequests, getRedeemRequests]);
});
}, [getActiveBlockNumber]);

const { data, isInitialLoading } = useQuery<TTransfer[] | undefined>(
['fetchAllEntries', walletAccount, activeBlockNumber],
() => fetchAllEntries(walletAccount, activeBlockNumber, getIssueRequests, getRedeemRequests),
{
onError: console.error,
},
);

const columns = useMemo(() => {
const statusColumn = statusColumnCreator();
Expand All @@ -119,7 +127,7 @@ function Transactions(): JSX.Element {

const getDialog = (DialogComponent: ComponentType<{ transfer: TTransfer; visible: boolean; onClose: () => void }>) =>
currentTransfer ? (
<DialogComponent transfer={currentTransfer} visible onClose={() => setCurrentTransfer(undefined)} />
<DialogComponent transfer={currentTransfer} visible={true} onClose={() => setCurrentTransfer(undefined)} />
) : (
<></>
);
Expand All @@ -138,7 +146,7 @@ function Transactions(): JSX.Element {
<Table
data={data}
columns={columns}
isLoading={false}
isLoading={Boolean(walletAccount) && isInitialLoading}
search={false}
pageSize={8}
rowCallback={(row) => setCurrentTransfer(row.original)}
Expand All @@ -150,4 +158,5 @@ function Transactions(): JSX.Element {
</div>
);
}

export default Transactions;

0 comments on commit 375f530

Please sign in to comment.