Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 59 additions & 8 deletions src/components/TransactionHistoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { useSorokit } from "@/context/useSorokit";
import type { Transaction } from "@/lib/client";
import type { NetworkInfo, Transaction } from "@/lib/client";
import { getClient } from "@/lib/client";
import { cn, truncateAddress } from "@/lib/utils";

Expand Down Expand Up @@ -82,6 +82,26 @@ function compareValues(a: Transaction, b: Transaction, field: SortField, dir: So
return dir === "asc" ? aNum - bNum : bNum - aNum;
}

/**
* Maps a Stellar network to its Stellar Expert explorer URL segment.
* Returns `null` for networks Stellar Expert does not index (futurenet,
* localnet), in which case the hash stays as plain text.
*/
function explorerTxUrl(
network: NetworkInfo | null,
hash: string,
): string | null {
if (!network) return null;
const segment =
network.name === "mainnet"
? "public"
: network.name === "testnet"
? "testnet"
: null;
if (!segment) return null;
return `https://stellar.expert/explorer/${segment}/tx/${hash}`;
}

/* ------------------------------------------------------------------ */
/* CSV Export (RFC 4180) */
/* ------------------------------------------------------------------ */
Expand Down Expand Up @@ -266,7 +286,7 @@ function SortHeader({ label, field, currentField, direction, onChange, className
/* ------------------------------------------------------------------ */

export function TransactionHistoryTable({ className, pageSize = PAGE_SIZE }: TransactionHistoryTableProps) {
const { address, isConnected } = useSorokit();
const { address, isConnected, network } = useSorokit();
const [allTxs, setAllTxs] = useState<Transaction[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -410,9 +430,25 @@ export function TransactionHistoryTable({ className, pageSize = PAGE_SIZE }: Tra
<StatusIcon successful={tx.successful} />
</td>
<td className="px-4 py-3">
<span data-txhash className="text-[13px] text-ink font-mono">
{truncateAddress(tx.hash, 8, 6)}
</span>
{(() => {
const url = explorerTxUrl(network, tx.hash);
return url ? (
<a
data-txhash
href={url}
target="_blank"
rel="noreferrer"
className="text-[13px] text-ink font-mono hover:underline hover:text-brand inline-flex items-center gap-1"
>
{truncateAddress(tx.hash, 8, 6)}
<span aria-hidden="true" className="opacity-60">↗</span>
</a>
) : (
<span data-txhash className="text-[13px] text-ink font-mono">
{truncateAddress(tx.hash, 8, 6)}
</span>
);
})()}
{tx.memo && (
<span className="block text-[10px] text-ink-3 mt-0.5" title={tx.memo}>
{truncateMemo(tx.memo)}
Expand Down Expand Up @@ -453,9 +489,24 @@ export function TransactionHistoryTable({ className, pageSize = PAGE_SIZE }: Tra
<StatusIcon successful={tx.successful} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span data-txhash className="text-[13px] text-ink font-mono truncate">
{truncateAddress(tx.hash, 8, 6)}
</span>
{(() => {
const url = explorerTxUrl(network, tx.hash);
return url ? (
<a
data-txhash
href={url}
target="_blank"
rel="noreferrer"
className="text-[13px] text-ink font-mono truncate hover:underline hover:text-brand"
>
{truncateAddress(tx.hash, 8, 6)}
</a>
) : (
<span data-txhash className="text-[13px] text-ink font-mono truncate">
{truncateAddress(tx.hash, 8, 6)}
</span>
);
})()}
<Badge variant={tx.successful ? "success" : "error"} live className="shrink-0">
{tx.successful ? "Success" : "Failed"}
</Badge>
Expand Down