152 lines
4.3 KiB
TypeScript
152 lines
4.3 KiB
TypeScript
import React from "react";
|
|
import { PublicKey } from "@solana/web3.js";
|
|
import { FetchStatus } from "providers/cache";
|
|
import { useAccountInfo, useAccountHistory } from "providers/accounts";
|
|
import { useFetchAccountHistory } from "providers/accounts/history";
|
|
import { Signature } from "components/common/Signature";
|
|
import { ErrorCard } from "components/common/ErrorCard";
|
|
import { LoadingCard } from "components/common/LoadingCard";
|
|
import { Slot } from "components/common/Slot";
|
|
|
|
export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) {
|
|
const address = pubkey.toBase58();
|
|
const info = useAccountInfo(address);
|
|
const history = useAccountHistory(address);
|
|
const fetchAccountHistory = useFetchAccountHistory();
|
|
const refresh = () => fetchAccountHistory(pubkey, true);
|
|
const loadMore = () => fetchAccountHistory(pubkey);
|
|
|
|
React.useEffect(() => {
|
|
if (!history) refresh();
|
|
}, [address]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
if (!history || info?.data === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (history?.data === undefined) {
|
|
if (history.status === FetchStatus.Fetching) {
|
|
return <LoadingCard message="Loading history" />;
|
|
}
|
|
|
|
return (
|
|
<ErrorCard retry={refresh} text="Failed to fetch transaction history" />
|
|
);
|
|
}
|
|
|
|
const transactions = history.data.fetched;
|
|
if (transactions.length === 0) {
|
|
if (history.status === FetchStatus.Fetching) {
|
|
return <LoadingCard message="Loading history" />;
|
|
}
|
|
return (
|
|
<ErrorCard
|
|
retry={loadMore}
|
|
retryText="Try again"
|
|
text="No transaction history found"
|
|
/>
|
|
);
|
|
}
|
|
|
|
const detailsList: React.ReactNode[] = [];
|
|
for (var i = 0; i < transactions.length; i++) {
|
|
const slot = transactions[i].slot;
|
|
const slotTransactions = [transactions[i]];
|
|
while (i + 1 < transactions.length) {
|
|
const nextSlot = transactions[i + 1].slot;
|
|
if (nextSlot !== slot) break;
|
|
slotTransactions.push(transactions[++i]);
|
|
}
|
|
|
|
slotTransactions.forEach(({ signature, err }) => {
|
|
let statusText;
|
|
let statusClass;
|
|
if (err) {
|
|
statusClass = "warning";
|
|
statusText = "Failed";
|
|
} else {
|
|
statusClass = "success";
|
|
statusText = "Success";
|
|
}
|
|
|
|
detailsList.push(
|
|
<tr key={signature}>
|
|
<td className="w-1">
|
|
<Slot slot={slot} />
|
|
</td>
|
|
|
|
<td>
|
|
<span className={`badge badge-soft-${statusClass}`}>
|
|
{statusText}
|
|
</span>
|
|
</td>
|
|
|
|
<td>
|
|
<Signature signature={signature} link />
|
|
</td>
|
|
</tr>
|
|
);
|
|
});
|
|
}
|
|
|
|
const fetching = history.status === FetchStatus.Fetching;
|
|
return (
|
|
<div className="card">
|
|
<div className="card-header align-items-center">
|
|
<h3 className="card-header-title">Transaction History</h3>
|
|
<button
|
|
className="btn btn-white btn-sm"
|
|
disabled={fetching}
|
|
onClick={refresh}
|
|
>
|
|
{fetching ? (
|
|
<>
|
|
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
|
Loading
|
|
</>
|
|
) : (
|
|
<>
|
|
<span className="fe fe-refresh-cw mr-2"></span>
|
|
Refresh
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<div className="table-responsive mb-0">
|
|
<table className="table table-sm table-nowrap card-table">
|
|
<thead>
|
|
<tr>
|
|
<th className="text-muted w-1">Slot</th>
|
|
<th className="text-muted">Result</th>
|
|
<th className="text-muted">Transaction Signature</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="list">{detailsList}</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div className="card-footer">
|
|
{history.data.foundOldest ? (
|
|
<div className="text-muted text-center">Fetched full history</div>
|
|
) : (
|
|
<button
|
|
className="btn btn-primary w-100"
|
|
onClick={loadMore}
|
|
disabled={fetching}
|
|
>
|
|
{fetching ? (
|
|
<>
|
|
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
|
Loading
|
|
</>
|
|
) : (
|
|
"Load More"
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|