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

[Tenscan] personal transaction/contract deployment details page #1990

Merged
55 changes: 53 additions & 2 deletions tools/tenscan/frontend/api/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { jsonHexToObj } from "@/src/lib/utils";
import { httpRequest } from ".";
import { apiRoutes } from "@/src/routes";
import { apiRoutes, ethMethods, tenCustomQueryMethods } from "@/src/routes";
import { pathToUrl } from "@/src/routes/router";
import { ResponseDataInterface } from "@/src/types/interfaces";
import { ResponseDataInterface, ToastType } from "@/src/types/interfaces";
import {
TransactionCount,
Price,
TransactionResponse,
Transaction,
} from "@/src/types/interfaces/TransactionInterfaces";
import { showToast } from "@/src/components/ui/use-toast";

export const fetchTransactions = async (
payload?: Record<string, any>
Expand Down Expand Up @@ -41,3 +43,52 @@ export const fetchTransactionByHash = async (
url: pathToUrl(apiRoutes.getTransactionByHash, { hash }),
});
};

export const personalTransactionsData = async (
provider: any,
walletAddress: string | null,
options: Record<string, any>
) => {
try {
if (provider && walletAddress) {
const requestPayload = {
address: walletAddress,
pagination: {
...options,
},
};
const personalTxResp = await provider.send(ethMethods.getStorageAt, [
tenCustomQueryMethods.listPersonalTransactions,
JSON.stringify(requestPayload),
null,
]);
const personalTxData = jsonHexToObj(personalTxResp);
return personalTxData;
}

return null;
} catch (error) {
console.error("Error fetching personal transactions:", error);
showToast(ToastType.DESTRUCTIVE, "Error fetching personal transactions");
throw error;
}
};

export const fetchPersonalTxnByHash = async (
provider: any,
hash: string
): Promise<any> => {
try {
if (provider) {
const personalTxnResp = await provider.send(
ethMethods.getTransactionReceipt,
[hash]
);
return personalTxnResp;
}
} catch (error) {
console.error("Error fetching personal transaction:", error);
showToast(ToastType.DESTRUCTIVE, "Error fetching personal transaction");
throw error;
}
};
60 changes: 34 additions & 26 deletions tools/tenscan/frontend/pages/tx/[hash].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { fetchBatchByHash } from "@/api/batches";
import { fetchTransactionByHash } from "@/api/transactions";
import Layout from "@/src/components/layouts/default-layout";
import { TransactionDetailsComponent } from "@/src/components/modules/transactions/transaction-details";
Expand All @@ -9,7 +8,6 @@ import {
CardHeader,
CardTitle,
CardContent,
CardDescription,
} from "@/src/components/ui/card";
import { Skeleton } from "@/src/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
Expand All @@ -28,30 +26,40 @@ export default function TransactionDetails() {

return (
<Layout>
{isLoading ? (
<Skeleton className="h-full w-full" />
) : transactionDetails ? (
<Card className="col-span-3">
<CardHeader>
<CardTitle>Transaction Details</CardTitle>
</CardHeader>
<CardContent>
<TransactionDetailsComponent
transactionDetails={transactionDetails}
/>
</CardContent>
</Card>
) : (
<EmptyState
title="Transaction not found"
description="The transaction you are looking for does not exist."
action={
<Button onClick={() => router.push("/transactions")}>
Go back
</Button>
}
/>
)}
<Card className="col-span-3">
{isLoading ? (
<>
<Skeleton className="h-10 w-100" />
<Skeleton className="h-10 w-100" />
<Skeleton className="h-10 w-100" />
<Skeleton className="h-10 w-100" />
<Skeleton className="h-10 w-100" />
<Skeleton className="h-10 w-100" />
</>
) : transactionDetails ? (
<>
<CardHeader>
<CardTitle>Transaction Details</CardTitle>
</CardHeader>
<CardContent>
<TransactionDetailsComponent
transactionDetails={transactionDetails}
/>
</CardContent>
</>
) : (
<EmptyState
title="Transaction not found"
description="The transaction you are looking for does not exist."
action={
<Button onClick={() => router.push("/transactions")}>
Go back
</Button>
}
className="p-8"
/>
)}
</Card>
</Layout>
);
}
Expand Down
83 changes: 83 additions & 0 deletions tools/tenscan/frontend/pages/tx/personal/[hash].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Layout from "@/src/components/layouts/default-layout";
import EmptyState from "@/src/components/modules/common/empty-state";
import { Button } from "@/src/components/ui/button";
import {
Card,
CardHeader,
CardTitle,
CardContent,
} from "@/src/components/ui/card";
import { Skeleton } from "@/src/components/ui/skeleton";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/router";
import { fetchPersonalTxnByHash } from "@/api/transactions";
import { useWalletConnection } from "@/src/components/providers/wallet-provider";
import { PersonalTxnDetailsComponent } from "@/src/components/modules/personal/personal-txn-details";
import ConnectWalletButton from "@/src/components/modules/common/connect-wallet";
import { ethereum } from "@/src/lib/utils";

export default function TransactionDetails() {
const router = useRouter();
const { provider, walletConnected } = useWalletConnection();
const { hash } = router.query;

const { data: transactionDetails, isLoading } = useQuery({
queryKey: ["personalTxnData", hash],
queryFn: () => fetchPersonalTxnByHash(provider, hash as string),
enabled: !!provider && !!hash,
});

return (
<Layout>
{walletConnected ? (
isLoading ? (
<Skeleton className="h-full w-full" />
) : transactionDetails ? (
<Card className="col-span-3">
<CardHeader>
<CardTitle>Transaction Details</CardTitle>
</CardHeader>
<CardContent>
<PersonalTxnDetailsComponent
transactionDetails={transactionDetails}
/>
</CardContent>
</Card>
) : (
<EmptyState
title="Transaction not found"
description="The transaction you are looking for does not exist."
action={
<Button onClick={() => router.push("/personal")}>Go back</Button>
}
/>
)
) : (
<EmptyState
title="Connect Wallet"
description="Connect your wallet to view transaction details."
action={
<div className="flex flex-col space-y-2">
<ConnectWalletButton
text={
ethereum
? "Connect Wallet to continue"
: "Install MetaMask to continue"
}
/>
<Button variant={"link"} onClick={() => router.push("/personal")}>
Go back
</Button>
</div>
}
/>
)}
</Layout>
);
}

export async function getServerSideProps(context: any) {
return {
props: {},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export function DataTablePagination<TData>({
setPage(table.getState().pagination.pageIndex + 1);
table.nextPage();
}}
disabled={
table.getState().pagination.pageSize >
table?.getFilteredRowModel()?.rows?.length
}
// uncomment the following line when total count feature is implemented
// disabled={!table.getCanNextPage()}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cn } from "@/src/lib/utils";
import Image from "next/image";
import React from "react";

Expand All @@ -8,23 +9,32 @@ const EmptyState = ({
imageSrc,
imageAlt,
action,
className,
}: {
title?: string;
description?: string;
icon?: React.ReactNode;
imageSrc?: string;
imageAlt?: string;
action?: React.ReactNode;
className?: string;
}) => {
return (
<div className="flex flex-col items-center justify-center h-full">
<div
className={cn(
"flex flex-col items-center justify-center space-y-4",
className
)}
>
<div className="flex flex-col items-center justify-center space-y-4">
{icon && <div className="w-24 h-24">{icon}</div>}
{imageSrc && (
<Image
src={imageSrc}
alt={imageAlt || "Empty state"}
className="w-24 h-24 rounded-full"
width={96}
height={96}
/>
)}
{title && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export default function AnalyticsCard({
</CardHeader>
<CardContent>
<div className="text-2xl font-bold truncate mb-1">
{item.value ? (
item.value
) : (
{item.loading ? (
<Skeleton className="w-[100px] h-[20px] rounded-full" />
) : (
item.value
)}
</div>
{item?.change && (
Expand Down
17 changes: 14 additions & 3 deletions tools/tenscan/frontend/src/components/modules/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ interface RecentData {
}

export default function Dashboard() {
const { price, transactions, transactionCount } = useTransactionsService();
const { contractCount } = useContractsService();
const { batches, latestBatch } = useBatchesService();
const {
price,
isPriceLoading,
transactions,
transactionCount,
isTransactionCountLoading,
} = useTransactionsService();
const { contractCount, isContractCountLoading } = useContractsService();
const { batches, latestBatch, isLatestBatchLoading } = useBatchesService();
const { rollups } = useRollupsService();

const DASHBOARD_DATA = [
Expand All @@ -53,6 +59,7 @@ export default function Dashboard() {
// TODO: add change
// change: "+20.1%",
icon: RocketIcon,
loading: isPriceLoading,
},
{
title: "Latest L2 Batch",
Expand All @@ -62,6 +69,7 @@ export default function Dashboard() {
// TODO: add change
// change: "+20.1%",
icon: LayersIcon,
loading: isLatestBatchLoading,
},
{
title: "Latest L1 Rollup",
Expand All @@ -77,6 +85,7 @@ export default function Dashboard() {
// TODO: add change
// change: "+20.1%",
icon: CubeIcon,
loading: isLatestBatchLoading,
},
{
title: "Transactions",
Expand All @@ -86,13 +95,15 @@ export default function Dashboard() {
// TODO: add change
// change: "+20.1%",
icon: ReaderIcon,
loading: isTransactionCountLoading,
},
{
title: "Contracts",
value: contractCount?.count ? formatNumber(contractCount.count) : "N/A",
// TODO: add change
// change: "+20.1%",
icon: FileTextIcon,
loading: isContractCountLoading,
},
{
title: "Nodes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const columns: ColumnDef<PersonalTransactions>[] = [
id: "actions",
cell: ({ row }) => {
return (
<Link href={`/tx/${row.original.transactionHash}`}>
<Link href={`/tx/personal/${row.original.transactionHash}`}>
<EyeOpenIcon className="h-5 w-5 text-muted-foreground hover:text-primary transition-colors cursor-pointer" />
</Link>
);
Expand Down
Loading
Loading