diff --git a/contracts/src/bridge/frontend/src/lib/constants.ts b/contracts/src/bridge/frontend/src/lib/constants.ts index 947d68e8f5..6006fb8955 100644 --- a/contracts/src/bridge/frontend/src/lib/constants.ts +++ b/contracts/src/bridge/frontend/src/lib/constants.ts @@ -1,6 +1,6 @@ export const socialLinks = { github: "https://github.com/ten-protocol", - discord: "https://discord.gg/QJZ39Den7d", + discord: "https://discord.gg/tenprotocol", twitter: "https://twitter.com/tenprotocol", twitterHandle: "@tenprotocol", }; diff --git a/tools/tenscan/frontend/.nvmrc b/tools/tenscan/frontend/.nvmrc new file mode 100644 index 0000000000..7ea6a59d34 --- /dev/null +++ b/tools/tenscan/frontend/.nvmrc @@ -0,0 +1 @@ +v20.11.0 diff --git a/tools/tenscan/frontend/api/batches.ts b/tools/tenscan/frontend/api/batches.ts index d05314b54a..10ff925eaf 100644 --- a/tools/tenscan/frontend/api/batches.ts +++ b/tools/tenscan/frontend/api/batches.ts @@ -6,6 +6,7 @@ import { Batch, BatchDetails, BatchResponse, + LatestBatch, } from "@/src/types/interfaces/BatchInterfaces"; export const fetchBatches = async ( @@ -20,8 +21,8 @@ export const fetchBatches = async ( export const fetchLatestBatch = async ( payload?: Record -): Promise> => { - return await httpRequest>({ +): Promise> => { + return await httpRequest>({ method: "get", url: pathToUrl(apiRoutes.getLatestBatch), searchParams: payload, @@ -36,3 +37,23 @@ export const fetchBatchByHash = async ( url: pathToUrl(apiRoutes.getBatchByHash, { hash }), }); }; + +export const fetchBatchByHeight = async ( + height: string +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getBatchByHeight, { height }), + }); +}; + +export const fetchBatchTransactions = async ( + fullHash: string, + options?: Record +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getBatchTransactions, { fullHash }), + searchParams: options, + }); +}; diff --git a/tools/tenscan/frontend/api/rollups.ts b/tools/tenscan/frontend/api/rollups.ts index 375f0e0ecf..eddfc0ed12 100644 --- a/tools/tenscan/frontend/api/rollups.ts +++ b/tools/tenscan/frontend/api/rollups.ts @@ -2,8 +2,13 @@ import { httpRequest } from "."; import { apiRoutes } from "@/src/routes"; import { pathToUrl } from "@/src/routes/router"; import { ResponseDataInterface } from "@/src/types/interfaces"; +import { Batch, BatchResponse } from "@/src/types/interfaces/BatchInterfaces"; +import { + Rollup, + RollupsResponse, +} from "@/src/types/interfaces/RollupInterfaces"; -export const fetchRollups = async ( +export const fetchLatestRollups = async ( payload?: Record ): Promise> => { return await httpRequest>({ @@ -13,6 +18,16 @@ export const fetchRollups = async ( }); }; +export const fetchRollups = async ( + payload?: Record +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getRollups), + searchParams: payload, + }); +}; + export const decryptEncryptedRollup = async ({ StrData, }: { @@ -24,3 +39,32 @@ export const decryptEncryptedRollup = async ({ data: { StrData }, }); }; + +export const fetchRollupByHash = async ( + hash: string +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getRollupByHash, { hash }), + }); +}; + +export const fetchRollupByBatchSequence = async ( + seq: string +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getRollupByBatchSequence, { seq }), + }); +}; + +export const fetchBatchesInRollups = async ( + hash: string, + options: Record +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getBatchesInRollup, { hash }), + searchParams: options, + }); +}; diff --git a/tools/tenscan/frontend/api/transactions.ts b/tools/tenscan/frontend/api/transactions.ts index 19ad461fb4..7958581faf 100644 --- a/tools/tenscan/frontend/api/transactions.ts +++ b/tools/tenscan/frontend/api/transactions.ts @@ -6,6 +6,7 @@ import { TransactionCount, Price, TransactionResponse, + Transaction, } from "@/src/types/interfaces/TransactionInterfaces"; export const fetchTransactions = async ( @@ -31,3 +32,12 @@ export const fetchEtherPrice = async (): Promise => { url: apiRoutes.getEtherPrice, }); }; + +export const fetchTransactionByHash = async ( + hash: string +): Promise> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getTransactionByHash, { hash }), + }); +}; diff --git a/tools/tenscan/frontend/package.json b/tools/tenscan/frontend/package.json index dd73980224..87793b2534 100644 --- a/tools/tenscan/frontend/package.json +++ b/tools/tenscan/frontend/package.json @@ -37,9 +37,9 @@ "next": "14.0.1", "next-themes": "^0.2.1", "path-to-regexp": "^6.2.1", - "react": "^18", + "react": "^18.2.0", "react-day-picker": "^8.9.1", - "react-dom": "^18", + "react-dom": "^18.2.0", "react-json-pretty": "^2.2.0", "recharts": "^2.9.3", "tailwind-merge": "^2.0.0", diff --git a/tools/tenscan/frontend/pages/address/[address].tsx b/tools/tenscan/frontend/pages/address/[address].tsx index bb8a057374..46bc8e225b 100644 --- a/tools/tenscan/frontend/pages/address/[address].tsx +++ b/tools/tenscan/frontend/pages/address/[address].tsx @@ -21,3 +21,9 @@ const AddressDetails = () => { }; export default AddressDetails; + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/batches/[hash].tsx b/tools/tenscan/frontend/pages/batch/[hash].tsx similarity index 82% rename from tools/tenscan/frontend/pages/batches/[hash].tsx rename to tools/tenscan/frontend/pages/batch/[hash].tsx index 964444aa77..cd55e6f65c 100644 --- a/tools/tenscan/frontend/pages/batches/[hash].tsx +++ b/tools/tenscan/frontend/pages/batch/[hash].tsx @@ -1,6 +1,6 @@ import { fetchBatchByHash } from "@/api/batches"; import Layout from "@/src/components/layouts/default-layout"; -import { BatchDetailsComponent } from "@/src/components/modules/batches/batch-details"; +import { BatchHashDetailsComponent } from "@/src/components/modules/batches/batch-hash-details"; import { Card, CardHeader, @@ -36,7 +36,7 @@ export default function Batch() { - + ) : ( @@ -45,3 +45,9 @@ export default function Batch() { ); } + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/batch/height/[height].tsx b/tools/tenscan/frontend/pages/batch/height/[height].tsx new file mode 100644 index 0000000000..7982d825cf --- /dev/null +++ b/tools/tenscan/frontend/pages/batch/height/[height].tsx @@ -0,0 +1,53 @@ +import { fetchBatchByHeight } from "@/api/batches"; +import Layout from "@/src/components/layouts/default-layout"; +import { BatchHeightDetailsComponent } from "@/src/components/modules/batches/batch-height-details"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/src/components/ui/card"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "next/router"; + +export default function Batch() { + const router = useRouter(); + const { height } = router.query; + + const { data, isLoading } = useQuery({ + queryKey: ["batchHeight", height], + queryFn: () => fetchBatchByHeight(height as string), + }); + + const batchDetails = data?.item; + + return ( + + {isLoading ? ( + + ) : batchDetails ? ( + + + Batch #{Number(batchDetails?.header?.number)} + + Overview of the batch #{Number(batchDetails?.header?.number)} + + + + + + + ) : ( +
Batch not found
+ )} +
+ ); +} + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/batch/txs/[fullHash].tsx b/tools/tenscan/frontend/pages/batch/txs/[fullHash].tsx new file mode 100644 index 0000000000..1d0748d532 --- /dev/null +++ b/tools/tenscan/frontend/pages/batch/txs/[fullHash].tsx @@ -0,0 +1,66 @@ +import { fetchBatchTransactions } from "@/api/batches"; +import Layout from "@/src/components/layouts/default-layout"; +import { DataTable } from "@/src/components/modules/common/data-table/data-table"; +import { columns } from "@/src/components/modules/batches/transaction-columns"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/src/components/ui/card"; +import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "next/router"; +import { getOptions } from "@/src/lib/constants"; +import TruncatedAddress from "@/src/components/modules/common/truncated-address"; + +export default function BatchTransactions() { + const router = useRouter(); + const { fullHash } = router.query; + const options = getOptions(router.query); + + const { data, isLoading, refetch } = useQuery({ + queryKey: ["batchTransactions", { fullHash, options }], + queryFn: () => fetchBatchTransactions(fullHash as string, options), + }); + + const { TransactionsData, Total } = data?.result || { + TransactionsData: [], + Total: 0, + }; + + return ( + + + + Transactions + +

Overview of all transactions in this batch:

+ +
+
+ + + +
+
+ ); +} + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/batches/index.tsx b/tools/tenscan/frontend/pages/batches/index.tsx index fd0b27f098..e8d88d3fbc 100644 --- a/tools/tenscan/frontend/pages/batches/index.tsx +++ b/tools/tenscan/frontend/pages/batches/index.tsx @@ -4,7 +4,8 @@ import { DataTable } from "@/src/components/modules/common/data-table/data-table import Layout from "@/src/components/layouts/default-layout"; import { Metadata } from "next"; import { useBatchesService } from "@/src/services/useBatchesService"; -import { formatNumber } from "@/src/lib/utils"; +import { getItem } from "@/src/lib/utils"; +import { ItemPosition } from "@/src/types/interfaces"; export const metadata: Metadata = { title: "Batches", @@ -12,7 +13,8 @@ export const metadata: Metadata = { }; export default function Batches() { - const { batches, refetchBatches, setNoPolling } = useBatchesService(); + const { batches, refetchBatches, isBatchesLoading, setNoPolling } = + useBatchesService(); const { BatchesData, Total } = batches?.result || { BatchesData: [], Total: 0, @@ -20,30 +22,43 @@ export default function Batches() { React.useEffect(() => { setNoPolling(true); + + return () => { + setNoPolling(false); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const firstBatchHeight = Number(getItem(BatchesData, "height")); + const lastBatchHeight = Number( + getItem(BatchesData, "height", ItemPosition.LAST) + ); + return (

Batches

-

- {formatNumber(Total)} Batches found. -

+ {BatchesData?.length > 0 && ( +

+ Showing batches #{firstBatchHeight}{" "} + {lastBatchHeight !== firstBatchHeight && + "to #" + lastBatchHeight} + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(Total)} batches. */} +

+ )}
- {BatchesData ? ( - - ) : ( -

Loading...

- )} +
); diff --git a/tools/tenscan/frontend/pages/blocks/index.tsx b/tools/tenscan/frontend/pages/blocks/index.tsx index 566ddbc3e0..158a1214dd 100644 --- a/tools/tenscan/frontend/pages/blocks/index.tsx +++ b/tools/tenscan/frontend/pages/blocks/index.tsx @@ -4,7 +4,8 @@ import { DataTable } from "@/src/components/modules/common/data-table/data-table import Layout from "@/src/components/layouts/default-layout"; import { Metadata } from "next"; import { useBlocksService } from "@/src/services/useBlocksService"; -import { formatNumber } from "@/src/lib/utils"; +import { getItem } from "@/src/lib/utils"; +import { ItemPosition } from "@/src/types/interfaces"; export const metadata: Metadata = { title: "Blocks", @@ -12,7 +13,8 @@ export const metadata: Metadata = { }; export default function Blocks() { - const { blocks, setNoPolling, refetchBlocks } = useBlocksService(); + const { blocks, setNoPolling, refetchBlocks, isBlocksLoading } = + useBlocksService(); const { BlocksData, Total } = blocks?.result || { BlocksData: [], Total: 0, @@ -24,27 +26,36 @@ export default function Blocks() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const firstBlockNumber = Number(getItem(BlocksData, "blockHeader.number")); + const lastBlockNumber = Number( + getItem(BlocksData, "blockHeader.number", ItemPosition.LAST) + ); + return (

Blocks

-

- {formatNumber(Total)} Blocks found. -

+ {BlocksData?.length > 0 && ( +

+ Showing blocks #{firstBlockNumber}{" "} + {lastBlockNumber !== firstBlockNumber && + "to #" + lastBlockNumber} + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(Total)} blocks. */} +

+ )}
- {BlocksData ? ( - - ) : ( -

Loading...

- )} +
); diff --git a/tools/tenscan/frontend/pages/docs/[id].tsx b/tools/tenscan/frontend/pages/docs/[id].tsx index 3400f95f5b..56ca02d6d0 100644 --- a/tools/tenscan/frontend/pages/docs/[id].tsx +++ b/tools/tenscan/frontend/pages/docs/[id].tsx @@ -100,3 +100,9 @@ const Document = () => { }; export default Document; + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/rollup/[hash]/batches.tsx b/tools/tenscan/frontend/pages/rollup/[hash]/batches.tsx new file mode 100644 index 0000000000..7df649aef2 --- /dev/null +++ b/tools/tenscan/frontend/pages/rollup/[hash]/batches.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { columns } from "@/src/components/modules/batches/columns"; +import { DataTable } from "@/src/components/modules/common/data-table/data-table"; +import Layout from "@/src/components/layouts/default-layout"; +import { Metadata } from "next"; +import { formatNumber } from "@/src/lib/utils"; +import { useRollupsService } from "@/src/services/useRollupsService"; + +export const metadata: Metadata = { + title: "Batches", + description: "A table of Batches.", +}; + +export default function RollupBatches() { + const { rollupBatches, isRollupBatchesLoading, refetchRollupBatches } = + useRollupsService(); + + const { BatchesData, Total } = rollupBatches?.result || { + BatchesData: [], + Total: 0, + }; + + return ( + +
+
+
+

Batches

+ {/* uncomment the following line when total count feature is implemented */} + {/*

+ {formatNumber(Total)} Batch(es) found in this rollup. +

*/} +
+
+ {BatchesData ? ( + + ) : ( +

Loading...

+ )} +
+
+ ); +} + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/rollup/[hash]/index.tsx b/tools/tenscan/frontend/pages/rollup/[hash]/index.tsx new file mode 100644 index 0000000000..889f780409 --- /dev/null +++ b/tools/tenscan/frontend/pages/rollup/[hash]/index.tsx @@ -0,0 +1,61 @@ +import { fetchRollupByHash } from "@/api/rollups"; +import Layout from "@/src/components/layouts/default-layout"; +import { RollupDetailsComponent } from "@/src/components/modules/rollups/rollup-details"; +import EmptyState from "@/src/components/modules/common/empty-state"; +import { Button } from "@/src/components/ui/button"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/src/components/ui/card"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "next/router"; + +export default function RollupDetails() { + const router = useRouter(); + const { hash } = router.query; + + const { data, isLoading } = useQuery({ + queryKey: ["rollupDetails", hash], + queryFn: () => fetchRollupByHash(hash as string), + }); + + const rollupDetails = data?.item; + + return ( + + {isLoading ? ( + + ) : rollupDetails ? ( + + + Rollup #{Number(rollupDetails?.ID)} + + Overview of rollup #{Number(rollupDetails?.ID)} + + + + + + + ) : ( + router.push("/rollups")}>Go back + } + /> + )} + + ); +} + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/rollup/batch/sequence/[sequence].tsx b/tools/tenscan/frontend/pages/rollup/batch/sequence/[sequence].tsx new file mode 100644 index 0000000000..eb8b2716d8 --- /dev/null +++ b/tools/tenscan/frontend/pages/rollup/batch/sequence/[sequence].tsx @@ -0,0 +1,61 @@ +import { fetchRollupByBatchSequence } from "@/api/rollups"; +import Layout from "@/src/components/layouts/default-layout"; +import EmptyState from "@/src/components/modules/common/empty-state"; +import { RollupDetailsComponent } from "@/src/components/modules/rollups/rollup-details"; +import { Button } from "@/src/components/ui/button"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/src/components/ui/card"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "next/router"; + +export default function RollupBatchSequenceDetails() { + const router = useRouter(); + const { sequence } = router.query; + + const { data, isLoading } = useQuery({ + queryKey: ["rollupSequenceDetails", sequence], + queryFn: () => fetchRollupByBatchSequence(sequence as string), + }); + + const rollupDetails = data?.item; + + return ( + + {isLoading ? ( + + ) : rollupDetails ? ( + + + Rollup #{Number(rollupDetails?.ID)} + + Overview of the Rollup with batch sequence #{Number(sequence)} + + + + + + + ) : ( + router.push("/rollups")}>Go back + } + /> + )} + + ); +} + +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/pages/rollups/index.tsx b/tools/tenscan/frontend/pages/rollups/index.tsx new file mode 100644 index 0000000000..8793dd2ad4 --- /dev/null +++ b/tools/tenscan/frontend/pages/rollups/index.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import { DataTable } from "@/src/components/modules/common/data-table/data-table"; +import Layout from "@/src/components/layouts/default-layout"; +import { useRollupsService } from "@/src/services/useRollupsService"; +import { Metadata } from "next"; +import { columns } from "@/src/components/modules/rollups/columns"; +import { getItem } from "@/src/lib/utils"; +import { ItemPosition } from "@/src/types/interfaces"; + +export const metadata: Metadata = { + title: "Rollups", + description: "A table of rollups.", +}; + +export default function Rollups() { + const { rollups, setNoPolling, isRollupsLoading, refetchRollups } = + useRollupsService(); + const { RollupsData, Total } = rollups?.result || { + RollupsData: [], + Total: 0, + }; + + React.useEffect(() => { + setNoPolling(true); + + return () => { + setNoPolling(false); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const firstRollupID = Number(getItem(RollupsData, "ID")); + const lastRollupID = Number(getItem(RollupsData, "ID", ItemPosition.LAST)); + + return ( + +
+
+
+

Rollups

+ {RollupsData?.length > 0 && ( +

+ Showing rollups #{firstRollupID}{" "} + {lastRollupID !== firstRollupID && "to #" + lastRollupID} + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(Total)} rollups. */} +

+ )} +
+
+ +
+
+ ); +} diff --git a/tools/tenscan/frontend/pages/transactions/index.tsx b/tools/tenscan/frontend/pages/transactions/index.tsx index 06ca008bb8..2a63269e16 100644 --- a/tools/tenscan/frontend/pages/transactions/index.tsx +++ b/tools/tenscan/frontend/pages/transactions/index.tsx @@ -4,7 +4,8 @@ import { DataTable } from "@/src/components/modules/common/data-table/data-table import Layout from "@/src/components/layouts/default-layout"; import { useTransactionsService } from "@/src/services/useTransactionsService"; import { Metadata } from "next"; -import { formatNumber } from "@/src/lib/utils"; +import { getItem } from "@/src/lib/utils"; +import { ItemPosition } from "@/src/types/interfaces"; export const metadata: Metadata = { title: "Transactions", @@ -12,8 +13,12 @@ export const metadata: Metadata = { }; export default function Transactions() { - const { transactions, refetchTransactions, setNoPolling } = - useTransactionsService(); + const { + transactions, + refetchTransactions, + setNoPolling, + isTransactionsLoading, + } = useTransactionsService(); const { TransactionsData, Total } = transactions?.result || { TransactionsData: [], Total: 0, @@ -21,30 +26,47 @@ export default function Transactions() { React.useEffect(() => { setNoPolling(true); + + return () => { + setNoPolling(false); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const firstBatchHeight = getItem(TransactionsData, "BatchHeight"); + const lastBatchHeight = getItem( + TransactionsData, + "BatchHeight", + ItemPosition.LAST + ); + return (

Transactions

-

- {formatNumber(Total)} Transactions found. -

+ {TransactionsData?.length > 0 && ( +

+ Showing transactions in batch + {firstBatchHeight !== lastBatchHeight && "es"} # + {firstBatchHeight}{" "} + {firstBatchHeight !== lastBatchHeight && + "to #" + lastBatchHeight} + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(Total)} transactions. */} +

+ )}
- {TransactionsData ? ( - - ) : ( -

Loading...

- )} +
); diff --git a/tools/tenscan/frontend/pages/tx/[hash].tsx b/tools/tenscan/frontend/pages/tx/[hash].tsx index b1b9ea7402..8c7018e582 100644 --- a/tools/tenscan/frontend/pages/tx/[hash].tsx +++ b/tools/tenscan/frontend/pages/tx/[hash].tsx @@ -1,23 +1,63 @@ +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"; import EmptyState from "@/src/components/modules/common/empty-state"; import { Button } from "@/src/components/ui/button"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/src/components/ui/card"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { useQuery } from "@tanstack/react-query"; import { useRouter } from "next/router"; -import React from "react"; -const TransactionDetails = () => { - const { push } = useRouter(); +export default function TransactionDetails() { + const router = useRouter(); + const { hash } = router.query; + + const { data, isLoading } = useQuery({ + queryKey: ["transactionDetails", hash], + queryFn: () => fetchTransactionByHash(hash as string), + }); + + const transactionDetails = data?.item; return ( - push("/")}>Go Home} - /> + {isLoading ? ( + + ) : transactionDetails ? ( + + + Transaction Details + + + + + + ) : ( + router.push("/transactions")}> + Go back + + } + /> + )} ); -}; +} -export default TransactionDetails; +export async function getServerSideProps(context: any) { + return { + props: {}, + }; +} diff --git a/tools/tenscan/frontend/public/docs/terms.json b/tools/tenscan/frontend/public/docs/terms.json index ff0769351e..6becc3b6ed 100644 --- a/tools/tenscan/frontend/public/docs/terms.json +++ b/tools/tenscan/frontend/public/docs/terms.json @@ -55,8 +55,8 @@ { "heading": "8. LIMITATIONS OF LIABILITY", "content": [ - "TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL THE INDEMNIFIED PARTIES OR ANY OF THEM BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR:
  • ANY INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS, OR REVENUE, BUSINESS INTERRUPTION, LOSS OF DATA, LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR MALFUNCTION;
  • ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY;
  • ANY AMOUNT, IN THE AGGREGATE, IN EXCESS OF ONE-HUNDRED POUNDS (£100); OR
  • ANY MATTER BEYOND THE REASONABLE CONTROL OF THE INDEMNIFIED PARTIES OR ANY OF THEM.
", - "SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE FOREGOING LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU.", + "To the fullest extent allowed by applicable law, under no circumstances and under no legal theory (including, without limitation, tort, contract, strict liability, or otherwise) shall the indemnified parties or any of them be liable to you or to any other person for:
  • any indirect, special, incidental, punitive or consequential damages of any kind, including damages for lost profits, business, or revenue, business interruption, loss of data, loss of business opportunity, goodwill or reputation, work stoppage, accuracy of results, or computer failure or malfunction;
  • any substitute goods, services or technology;
  • any amount, in the aggregate, in excess of one-hundred pounds (£100); or
  • any matter beyond the reasonable control of the indemnified parties or any of them.
", + "Some jurisdictions do not allow the exclusion or limitation of incidental or consequential or certain other damages, so the foregoing limitations and exclusions may not apply to you.", "Nothing in these Terms is intended to exclude or limit our liability for death or personal injury caused by our negligence, or for fraud or fraudulent misrepresentation, or to affect your statutory rights." ] }, diff --git a/tools/tenscan/frontend/src/components/health-indicator.tsx b/tools/tenscan/frontend/src/components/health-indicator.tsx index 4dd21804cd..c5c0f296dd 100644 --- a/tools/tenscan/frontend/src/components/health-indicator.tsx +++ b/tools/tenscan/frontend/src/components/health-indicator.tsx @@ -1,6 +1,13 @@ import React from "react"; import { Badge, badgeVariants } from "./ui/badge"; import { useGeneralService } from "../services/useGeneralService"; +import { BarChart } from "lucide-react"; +import { + TooltipProvider, + TooltipTrigger, + TooltipContent, + Tooltip, +} from "./ui/tooltip"; const HealthIndicator = () => { const [status, setStatus] = React.useState(false); @@ -20,16 +27,30 @@ const HealthIndicator = () => { }, [testnetStatus]); return ( -
-

Testnet Status:

- - {status ? "Live" : "Down"} - -
+ + + +
+ Testnet Status + + + {status ? "Live" : "Down"} + +
+
+ + {status ? "Testnet status: Live" : "Testnet status: Down"} + +
+
); }; diff --git a/tools/tenscan/frontend/src/components/layouts/header.tsx b/tools/tenscan/frontend/src/components/layouts/header.tsx index a04de0fea8..9ba8f94c00 100644 --- a/tools/tenscan/frontend/src/components/layouts/header.tsx +++ b/tools/tenscan/frontend/src/components/layouts/header.tsx @@ -3,6 +3,7 @@ import { ModeToggle } from "../mode-toggle"; import ConnectWalletButton from "../modules/common/connect-wallet"; import Link from "next/link"; import { HamburgerMenuIcon } from "@radix-ui/react-icons"; +import { X } from "lucide-react"; import { useState } from "react"; import { Button } from "../ui/button"; import HealthIndicator from "../health-indicator"; @@ -12,7 +13,7 @@ export default function Header() { return (
- + Logo -
- +
+
@@ -48,14 +49,14 @@ const MobileMenu = () => { const [menuOpen, setMenuOpen] = useState(false); return ( -
- +
+ {menuOpen && (
@@ -63,6 +64,7 @@ const MobileMenu = () => {
+
diff --git a/tools/tenscan/frontend/src/components/main-nav.tsx b/tools/tenscan/frontend/src/components/main-nav.tsx index 1e422c897c..704af970cb 100644 --- a/tools/tenscan/frontend/src/components/main-nav.tsx +++ b/tools/tenscan/frontend/src/components/main-nav.tsx @@ -42,7 +42,7 @@ const NavItem = ({ navLink }: { navLink: NavLink }) => { {navLink.label} - + {navLink.subNavLinks && navLink.subNavLinks.map((subNavLink: NavLink) => ( diff --git a/tools/tenscan/frontend/src/components/modules/batches/batch-details.tsx b/tools/tenscan/frontend/src/components/modules/batches/batch-details.tsx deleted file mode 100644 index 430281453b..0000000000 --- a/tools/tenscan/frontend/src/components/modules/batches/batch-details.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Separator } from "@/src/components/ui/separator"; -import TruncatedAddress from "../common/truncated-address"; -import KeyValueItem, { KeyValueList } from "@/src/components/ui/key-value"; -import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; -import { Badge } from "@/src/components/ui/badge"; -import { BatchDetails } from "@/src/types/interfaces/BatchInterfaces"; - -export function BatchDetailsComponent({ - batchDetails, -}: { - batchDetails: BatchDetails; -}) { - return ( -
- - - } - /> - - } - /> - } - /> - - } - /> - - } - /> - - {formatTimeAgo(batchDetails?.Header?.timestamp)} - - } - /> - } - /> - - - - - } - /> - - - } - /> - } - /> - - - - - - - - - } - isLastItem - /> - -
- ); -} diff --git a/tools/tenscan/frontend/src/components/modules/batches/batch-hash-details.tsx b/tools/tenscan/frontend/src/components/modules/batches/batch-hash-details.tsx new file mode 100644 index 0000000000..f48a609f0b --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/batches/batch-hash-details.tsx @@ -0,0 +1,201 @@ +import { Separator } from "@/src/components/ui/separator"; +import TruncatedAddress from "../common/truncated-address"; +import KeyValueItem, { KeyValueList } from "@/src/components/ui/key-value"; +import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; +import { Badge } from "@/src/components/ui/badge"; +import { Batch, BatchDetails } from "@/src/types/interfaces/BatchInterfaces"; +import Link from "next/link"; +import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons"; +import { Button } from "../../ui/button"; +import { useRollupsService } from "@/src/services/useRollupsService"; +import JSONPretty from "react-json-pretty"; +import { useState } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../../ui/tooltip"; +import Copy from "../common/copy"; + +export function BatchHashDetailsComponent({ + batchDetails, +}: { + batchDetails: BatchDetails; +}) { + const { decryptedRollup, decryptEncryptedData } = useRollupsService(); + const [showDecryptedData, setShowDecryptedData] = useState(false); + + return ( +
+ + + {"#" + Number(batchDetails?.Header?.number)} + + } + /> + } + /> + + } + /> + } + /> + + } + /> + + } + /> + + {formatTimeAgo(batchDetails?.Header?.timestamp)} + + } + /> + } + /> + + + + + } + /> + + } + /> + + + + + + {batchDetails?.TxHashes?.map((txHash, index) => ( +
  • + +
  • + ))} + + ) : ( + "-" + ) + } + isLastItem + /> +
    + + + +
    + {" "} + + {showDecryptedData && decryptedRollup ? ( + + + + + + + Copy Decrypted Data to Clipboard + + + + ) : null} +
    + {decryptedRollup && showDecryptedData ? ( + <> + + + + ) : null} + + } + isLastItem + /> +
    +
    + ); +} diff --git a/tools/tenscan/frontend/src/components/modules/batches/batch-height-details.tsx b/tools/tenscan/frontend/src/components/modules/batches/batch-height-details.tsx new file mode 100644 index 0000000000..c19a2d07c5 --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/batches/batch-height-details.tsx @@ -0,0 +1,196 @@ +import { Separator } from "@/src/components/ui/separator"; +import TruncatedAddress from "../common/truncated-address"; +import KeyValueItem, { KeyValueList } from "@/src/components/ui/key-value"; +import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; +import { Badge } from "@/src/components/ui/badge"; +import { Batch, BatchDetails } from "@/src/types/interfaces/BatchInterfaces"; +import Link from "next/link"; +import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons"; +import { Button } from "../../ui/button"; +import { useRollupsService } from "@/src/services/useRollupsService"; +import JSONPretty from "react-json-pretty"; +import { useState } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../../ui/tooltip"; + +export function BatchHeightDetailsComponent({ + batchDetails, +}: { + batchDetails: Batch; +}) { + const { decryptedRollup, decryptEncryptedData } = useRollupsService(); + const [showDecryptedData, setShowDecryptedData] = useState(false); + + return ( +
    + + + + + } + /> + + } + /> + } + /> + + } + /> + + } + /> + + {formatTimeAgo(batchDetails?.header?.timestamp)} + + } + /> + } + /> + + {formatNumber(batchDetails?.header?.gasLimit)} + + } + /> + + {formatNumber(batchDetails?.header?.gasUsed)} + + } + /> + + + } + /> + + } + /> + + + + + + {batchDetails?.txCount || "-"} + + } + isLastItem + /> + + + + +
    + {" "} + +
    + {decryptedRollup && showDecryptedData ? ( + <> + + + + ) : null} + + } + isLastItem + /> +
    +
    + ); +} diff --git a/tools/tenscan/frontend/src/components/modules/batches/columns.tsx b/tools/tenscan/frontend/src/components/modules/batches/columns.tsx index f67fdc6955..c53138b099 100644 --- a/tools/tenscan/frontend/src/components/modules/batches/columns.tsx +++ b/tools/tenscan/frontend/src/components/modules/batches/columns.tsx @@ -6,7 +6,6 @@ import { DataTableColumnHeader } from "../common/data-table/data-table-column-he import TruncatedAddress from "../common/truncated-address"; import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; import { Batch } from "@/src/types/interfaces/BatchInterfaces"; -import { EyeOpenIcon } from "@radix-ui/react-icons"; import Link from "next/link"; import { Badge } from "../../ui/badge"; @@ -19,9 +18,14 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { return (
    - - #{Number(row.getValue("number"))} - + + + #{Number(row.original.height)} + +
    ); }, @@ -37,8 +41,8 @@ export const columns: ColumnDef[] = [ return (
    - {row.getValue("timestamp") - ? formatTimeAgo(row.getValue("timestamp")) + {row.original.header.timestamp + ? formatTimeAgo(row.original.header.timestamp) : "N/A"}
    @@ -57,7 +61,7 @@ export const columns: ColumnDef[] = [
    - {formatNumber(row.getValue("gasUsed"))} + {formatNumber(row.original?.header?.gasUsed) || "N/A"}
    @@ -75,7 +79,9 @@ export const columns: ColumnDef[] = [ return (
    - {formatNumber(row.getValue("gasLimit"))} + + {formatNumber(row.original?.header?.gasUsed) || "N/A"} +
    ); @@ -89,7 +95,12 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - return ; + return ( + + ); }, enableSorting: false, enableHiding: false, @@ -100,41 +111,45 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - return ; + return ; }, enableSorting: false, enableHiding: false, }, { - accessorKey: "l1Proof", + accessorKey: "sequence", header: ({ column }) => ( - + ), cell: ({ row }) => { - return ; + return ( + + {row.original.sequence} + + ); }, enableSorting: false, enableHiding: false, }, { - accessorKey: "miner", + accessorKey: "txCount", header: ({ column }) => ( - + ), - cell: ({ row }) => { - return ; - }, - enableSorting: false, - enableHiding: false, - }, - { - id: "actions", cell: ({ row }) => { return ( - - + + {row.original.txCount} ); }, + enableSorting: false, + enableHiding: false, }, ]; diff --git a/tools/tenscan/frontend/src/components/modules/batches/transaction-columns.tsx b/tools/tenscan/frontend/src/components/modules/batches/transaction-columns.tsx new file mode 100644 index 0000000000..4e1e97ff75 --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/batches/transaction-columns.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { Badge } from "@/src/components/ui/badge"; + +import { statuses } from "../transactions/constants"; +import { DataTableColumnHeader } from "../common/data-table/data-table-column-header"; +import { Transaction } from "@/src/types/interfaces/TransactionInterfaces"; +import TruncatedAddress from "../common/truncated-address"; +import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; +import Link from "next/link"; +import { EyeOpenIcon } from "@radix-ui/react-icons"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "BatchTimestamp", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
    + + {formatTimeAgo(row.getValue("BatchTimestamp"))} + +
    + ); + }, + enableSorting: false, + enableHiding: false, + }, + + { + accessorKey: "TransactionHash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "Finality", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const finality = statuses.find( + (finality) => finality.value === row.getValue("Finality") + ); + + if (!finality) { + return null; + } + + return ( +
    + {finality.icon && ( + + )} + {finality.label} +
    + ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, +]; diff --git a/tools/tenscan/frontend/src/components/modules/blocks/columns.tsx b/tools/tenscan/frontend/src/components/modules/blocks/columns.tsx index 3400768554..ee189d114b 100644 --- a/tools/tenscan/frontend/src/components/modules/blocks/columns.tsx +++ b/tools/tenscan/frontend/src/components/modules/blocks/columns.tsx @@ -22,7 +22,7 @@ export const columns: ColumnDef[] = [ return (
    - {Number(blockHeader?.number)} + #{Number(blockHeader?.number)}
    ); @@ -60,7 +60,10 @@ export const columns: ColumnDef[] = [ return Number(row.original.rollupHash) === 0 ? ( No rollup ) : ( - + ); }, enableSorting: false, @@ -69,7 +72,7 @@ export const columns: ColumnDef[] = [ { accessorKey: "blockHeader.gasUsed", header: ({ column }) => ( - + ), cell: ({ row }) => { const blockHeader = row.original.blockHeader as BlockHeader; @@ -96,7 +99,9 @@ export const columns: ColumnDef[] = [ return (
    - {formatNumber(blockHeader?.gasLimit)} + + {formatNumber(blockHeader?.gasLimit)} +
    ); diff --git a/tools/tenscan/frontend/src/components/modules/common/connect-wallet.tsx b/tools/tenscan/frontend/src/components/modules/common/connect-wallet.tsx index e33e0d7c6c..0bdba6765e 100644 --- a/tools/tenscan/frontend/src/components/modules/common/connect-wallet.tsx +++ b/tools/tenscan/frontend/src/components/modules/common/connect-wallet.tsx @@ -10,7 +10,7 @@ const ConnectWalletButton = ({ text }: { text?: string }) => { return ( diff --git a/tools/tenscan/frontend/src/components/modules/common/data-table/data-table-pagination.tsx b/tools/tenscan/frontend/src/components/modules/common/data-table/data-table-pagination.tsx index 45ce947a9f..d221bdf261 100644 --- a/tools/tenscan/frontend/src/components/modules/common/data-table/data-table-pagination.tsx +++ b/tools/tenscan/frontend/src/components/modules/common/data-table/data-table-pagination.tsx @@ -2,10 +2,8 @@ import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, - DoubleArrowRightIcon, } from "@radix-ui/react-icons"; -import { Table } from "@tanstack/react-table"; - +import { PaginationState, Table } from "@tanstack/react-table"; import { Button } from "@/src/components/ui/button"; import { Select, @@ -14,28 +12,50 @@ import { SelectTrigger, SelectValue, } from "@/src/components/ui/select"; -import { formatNumber } from "@/src/lib/utils"; +import { Input } from "@/src/components/ui/input"; +import { useState } from "react"; interface DataTablePaginationProps { table: Table; + refetch?: () => void; + setPagination: (pagination: PaginationState) => void; } export function DataTablePagination({ table, + refetch, + setPagination, }: DataTablePaginationProps) { + const [page, setPage] = useState(table.getState().pagination.pageIndex); + + const handlePageChange = (e: React.ChangeEvent) => { + setPage(Number(e.target.value)); + }; + + const handleKey = (e: React.KeyboardEvent) => { + if ( + e.key === "Enter" && + page > 0 && + page !== table.getState().pagination.pageIndex + ) { + table.setPageIndex(page); + refetch?.(); + } + }; + return ( -
    -
    - {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. +
    +
    + Showing {table?.getFilteredRowModel()?.rows?.length} row(s)
    -
    +

    Rows per page

    - Page {formatNumber(table.getState().pagination.pageIndex + 1)} of{" "} - {formatNumber(table.getPageCount())} + Page + e.target.select()} + onBlur={() => setPage(table.getState().pagination.pageIndex)} + /> + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(table.getPageCount())} */}
    - + */}
    diff --git a/tools/tenscan/frontend/src/components/modules/common/data-table/data-table.tsx b/tools/tenscan/frontend/src/components/modules/common/data-table/data-table.tsx index 477ebbd53e..982835d6d4 100644 --- a/tools/tenscan/frontend/src/components/modules/common/data-table/data-table.tsx +++ b/tools/tenscan/frontend/src/components/modules/common/data-table/data-table.tsx @@ -4,6 +4,8 @@ import * as React from "react"; import { ColumnDef, ColumnFiltersState, + OnChangeFn, + PaginationState, SortingState, VisibilityState, flexRender, @@ -27,6 +29,8 @@ import { import { DataTablePagination } from "./data-table-pagination"; import { DataTableToolbar } from "./data-table-toolbar"; import { useRouter } from "next/router"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { Button } from "@/src/components/ui/button"; interface DataTableProps { columns: ColumnDef[]; @@ -39,6 +43,10 @@ interface DataTableProps { updateQueryParams?: (query: any) => void; refetch?: () => void; total: number; + isLoading?: boolean; + noPagination?: boolean; + noResultsText?: string; + noResultsMessage?: string; } export function DataTable({ @@ -47,7 +55,12 @@ export function DataTable({ toolbar, refetch, total, + isLoading, + noPagination, + noResultsText, + noResultsMessage, }: DataTableProps) { + const { query, push, pathname } = useRouter(); const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); @@ -55,10 +68,25 @@ export function DataTable({ [] ); const [sorting, setSorting] = React.useState([]); - const [pagination, setPagination] = React.useState({ - pageIndex: 0, - pageSize: 20, - }); + + const pagination = React.useMemo(() => { + return { + pageIndex: Number(query.page) || 1, + pageSize: Number(query.size) || 20, + }; + }, [query.page, query.size]); + + const setPagination: OnChangeFn = (func) => { + const { pageIndex, pageSize } = + typeof func === "function" ? func(pagination) : func; + const newPageIndex = pagination.pageSize !== pageSize ? 1 : pageIndex; + const params = { + ...query, + page: newPageIndex > 0 ? newPageIndex : 1, + size: pageSize <= 100 ? pageSize : 100, + }; + push({ pathname, query: params }); + }; const table = useReactTable({ data, @@ -72,7 +100,7 @@ export function DataTable({ }, onPaginationChange: setPagination, manualPagination: true, - pageCount: Math.ceil(total / pagination.pageSize), + // pageCount: Math.ceil(total / pagination.pageSize), enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, @@ -86,18 +114,11 @@ export function DataTable({ getFacetedUniqueValues: getFacetedUniqueValues(), }); - const { query, push, pathname } = useRouter(); - const { pageIndex, pageSize } = table.getState().pagination; - - React.useEffect(() => { - const params = { ...query, page: pageIndex + 1, size: pageSize }; - push({ pathname, query: params }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageIndex, pageSize]); - return (
    - + {data && ( + + )}
    @@ -119,7 +140,18 @@ export function DataTable({ ))} - {table?.getRowModel()?.rows?.length && data ? ( + {isLoading ? ( + <> + + + + + + + ) : data && table?.getRowModel()?.rows?.length ? ( table.getRowModel().rows.map((row) => ( ({ colSpan={columns.length} className="h-24 text-center" > - No results. + {pagination.pageIndex > 1 ? ( +

    + No {noResultsText || "results"} found for the selected + filters. + +

    + ) : ( +

    + {noResultsMessage || + `No ${noResultsText || "results"} found.`} +

    + )}
    )}
    - + {data && !isLoading && !noPagination && ( + + )}
    ); } diff --git a/tools/tenscan/frontend/src/components/modules/common/truncated-address.tsx b/tools/tenscan/frontend/src/components/modules/common/truncated-address.tsx index fffa21fad3..789a21e8fd 100644 --- a/tools/tenscan/frontend/src/components/modules/common/truncated-address.tsx +++ b/tools/tenscan/frontend/src/components/modules/common/truncated-address.tsx @@ -8,17 +8,25 @@ import { } from "@/src/components/ui/tooltip"; import Copy from "./copy"; +import Link from "next/link"; const TruncatedAddress = ({ address, prefixLength, suffixLength, showCopy = true, + link, }: { address: string; prefixLength?: number; suffixLength?: number; showCopy?: boolean; + link?: + | string + | { + pathname: string; + query: { [key: string]: string | number }; + }; }) => { const truncatedAddress = `${address?.substring( 0, @@ -29,14 +37,29 @@ const TruncatedAddress = ({ <> {address ? (
    - - - {truncatedAddress} - -

    {address}

    -
    -
    -
    + {link ? ( + + + + + {truncatedAddress} + + + +

    {address}

    +
    +
    +
    + ) : ( + + + {truncatedAddress} + +

    {address}

    +
    +
    +
    + )} {showCopy && }
    ) : ( diff --git a/tools/tenscan/frontend/src/components/modules/dashboard/analytics-card.tsx b/tools/tenscan/frontend/src/components/modules/dashboard/analytics-card.tsx index 996daf0e27..2caec11f59 100644 --- a/tools/tenscan/frontend/src/components/modules/dashboard/analytics-card.tsx +++ b/tools/tenscan/frontend/src/components/modules/dashboard/analytics-card.tsx @@ -5,12 +5,13 @@ import { CardContent, } from "@/src/components/ui/card"; import { Skeleton } from "@/src/components/ui/skeleton"; +import { DashboardAnalyticsData } from "@/src/types/interfaces"; import React from "react"; export default function AnalyticsCard({ item, }: { - item: { title: string; value: string; change: string; icon: any }; + item: DashboardAnalyticsData; }) { return ( @@ -26,7 +27,7 @@ export default function AnalyticsCard({ )}
    - {item.change && ( + {item?.change && (

    {item.change}

    )} diff --git a/tools/tenscan/frontend/src/components/modules/dashboard/index.tsx b/tools/tenscan/frontend/src/components/modules/dashboard/index.tsx index f195ff81eb..f2ec71e079 100644 --- a/tools/tenscan/frontend/src/components/modules/dashboard/index.tsx +++ b/tools/tenscan/frontend/src/components/modules/dashboard/index.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { CalendarDateRangePicker } from "@/src/components/date-range-picker"; import { CardHeader, CardTitle, @@ -22,19 +21,28 @@ import { useBatchesService } from "@/src/services/useBatchesService"; import TruncatedAddress from "../common/truncated-address"; import { useContractsService } from "@/src/services/useContractsService"; import { Skeleton } from "@/src/components/ui/skeleton"; -import { RecentBlocks } from "./recent-blocks"; -import { useBlocksService } from "@/src/services/useBlocksService"; import AnalyticsCard from "./analytics-card"; import Link from "next/link"; import { cn, formatNumber } from "@/src/lib/utils"; import { Badge } from "../../ui/badge"; import { BlocksIcon } from "lucide-react"; +import { useRollupsService } from "@/src/services/useRollupsService"; +import { RecentRollups } from "./recent-rollups"; +import { DashboardAnalyticsData } from "@/src/types/interfaces"; + +interface RecentData { + title: string; + data: any; + component: JSX.Element; + goTo: string; + className: string; +} export default function Dashboard() { const { price, transactions, transactionCount } = useTransactionsService(); const { contractCount } = useContractsService(); const { batches, latestBatch } = useBatchesService(); - const { blocks } = useBlocksService(); + const { rollups } = useRollupsService(); const DASHBOARD_DATA = [ { @@ -95,10 +103,10 @@ export default function Dashboard() { const RECENT_DATA = [ { - title: "Recent Blocks", - data: blocks, - component: , - goTo: "/blocks", + title: "Recent Rollups", + data: rollups, + component: , + goTo: "/rollups", className: "col-span-1 md:col-span-2 lg:col-span-3", }, { @@ -123,17 +131,17 @@ export default function Dashboard() {

    Tenscan

    - {DASHBOARD_DATA.map((item: any, index) => ( + {DASHBOARD_DATA.map((item: DashboardAnalyticsData, index: number) => ( ))}
    - {RECENT_DATA.map((item: any, index) => ( + {RECENT_DATA.map((item: RecentData, index) => ( - + {item.title}

    - #{Number(batch?.number)} + + #{Number(batch?.height)} +

    - {formatTimeAgo(batch?.timestamp)} + {formatTimeAgo(batch?.header?.timestamp)}

    - +
    ))} diff --git a/tools/tenscan/frontend/src/components/modules/dashboard/recent-blocks.tsx b/tools/tenscan/frontend/src/components/modules/dashboard/recent-rollups.tsx similarity index 56% rename from tools/tenscan/frontend/src/components/modules/dashboard/recent-blocks.tsx rename to tools/tenscan/frontend/src/components/modules/dashboard/recent-rollups.tsx index 9ead81d7bc..30353562a7 100644 --- a/tools/tenscan/frontend/src/components/modules/dashboard/recent-blocks.tsx +++ b/tools/tenscan/frontend/src/components/modules/dashboard/recent-rollups.tsx @@ -1,26 +1,33 @@ import TruncatedAddress from "../common/truncated-address"; import { formatTimeAgo } from "@/src/lib/utils"; import { Avatar, AvatarFallback } from "@/src/components/ui/avatar"; -import { Block } from "@/src/types/interfaces/BlockInterfaces"; +import { + Rollup, + RollupsResponse, +} from "@/src/types/interfaces/RollupInterfaces"; +import Link from "next/link"; -export function RecentBlocks({ blocks }: { blocks: any }) { +export function RecentRollups({ rollups }: { rollups: any }) { return (
    - {blocks?.result?.BlocksData.map((block: Block, i: number) => ( + {rollups?.result?.RollupsData?.map((rollup: Rollup, i: number) => (
    - BK + RP

    - #{Number(block?.blockHeader?.number)} + #{Number(rollup?.ID)}

    - {formatTimeAgo(block?.blockHeader?.timestamp)} + {formatTimeAgo(rollup?.Timestamp)}

    - +
    ))} diff --git a/tools/tenscan/frontend/src/components/modules/dashboard/recent-transactions.tsx b/tools/tenscan/frontend/src/components/modules/dashboard/recent-transactions.tsx index 9133fcbc89..79698a5788 100644 --- a/tools/tenscan/frontend/src/components/modules/dashboard/recent-transactions.tsx +++ b/tools/tenscan/frontend/src/components/modules/dashboard/recent-transactions.tsx @@ -3,6 +3,7 @@ import { Avatar, AvatarFallback } from "@/src/components/ui/avatar"; import { Transaction } from "@/src/types/interfaces/TransactionInterfaces"; import { Badge } from "../../ui/badge"; import { formatTimeAgo } from "@/src/lib/utils"; +import Link from "next/link"; export function RecentTransactions({ transactions }: { transactions: any }) { return ( @@ -15,14 +16,23 @@ export function RecentTransactions({ transactions }: { transactions: any }) {

    - #{Number(transaction?.BatchHeight)} + Batch + + #{Number(transaction?.BatchHeight)} +

    {formatTimeAgo(transaction?.BatchTimestamp)}

    - +
    {transaction?.Finality} diff --git a/tools/tenscan/frontend/src/components/modules/personal/columns.tsx b/tools/tenscan/frontend/src/components/modules/personal/columns.tsx index b769e3d42c..8bdbaa3fae 100644 --- a/tools/tenscan/frontend/src/components/modules/personal/columns.tsx +++ b/tools/tenscan/frontend/src/components/modules/personal/columns.tsx @@ -8,6 +8,8 @@ import { DataTableColumnHeader } from "../common/data-table/data-table-column-he import { PersonalTransactions } from "@/src/types/interfaces/TransactionInterfaces"; import TruncatedAddress from "../common/truncated-address"; import { formatNumber } from "@/src/lib/utils"; +import Link from "next/link"; +import { EyeOpenIcon } from "@radix-ui/react-icons"; export const columns: ColumnDef[] = [ { @@ -123,14 +125,14 @@ export const columns: ColumnDef[] = [ return value.includes(row.getValue(id)); }, }, - // { - // id: "actions", - // cell: ({ row }) => { - // return ( - // - // - // - // ); - // }, - // }, + { + id: "actions", + cell: ({ row }) => { + return ( + + + + ); + }, + }, ]; diff --git a/tools/tenscan/frontend/src/components/modules/personal/index.tsx b/tools/tenscan/frontend/src/components/modules/personal/index.tsx index 9870a674f2..03630d4c07 100644 --- a/tools/tenscan/frontend/src/components/modules/personal/index.tsx +++ b/tools/tenscan/frontend/src/components/modules/personal/index.tsx @@ -2,8 +2,6 @@ import React from "react"; import { columns } from "@/src/components/modules/personal/columns"; import { DataTable } from "@/src/components/modules/common/data-table/data-table"; import { useTransactionsService } from "@/src/services/useTransactionsService"; -import { Skeleton } from "@/src/components/ui/skeleton"; -import { formatNumber } from "@/src/lib/utils"; export default function PersonalTransactions() { const { personalTxns, setNoPolling, personalTxnsLoading } = @@ -15,7 +13,10 @@ export default function PersonalTransactions() { React.useEffect(() => { setNoPolling(true); - return () => setNoPolling(false); + + return () => { + setNoPolling(false); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -26,18 +27,19 @@ export default function PersonalTransactions() {

    Personal Transactions

    -

    + {/* uncomment the following line when total count feature is implemented */} + {/*

    {formatNumber(Total)} personal transaction(s). -

    +

    */}
    - {personalTxnsLoading ? ( - - ) : Receipts ? ( - - ) : ( -

    No transactions found.

    - )} + ); } diff --git a/tools/tenscan/frontend/src/components/modules/rollups/columns.tsx b/tools/tenscan/frontend/src/components/modules/rollups/columns.tsx new file mode 100644 index 0000000000..fe76d6fa3c --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/rollups/columns.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { DataTableColumnHeader } from "../common/data-table/data-table-column-header"; +import TruncatedAddress from "../common/truncated-address"; +import { formatTimeAgo } from "@/src/lib/utils"; +import Link from "next/link"; +import { EyeOpenIcon } from "@radix-ui/react-icons"; +import { Rollup } from "@/src/types/interfaces/RollupInterfaces"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "ID", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
    + {row.getValue("ID")} +
    + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "Hash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "Timestamp", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
    + + {formatTimeAgo(row.getValue("Timestamp"))} + +
    + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "L1Hash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "FirstSeq", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
    + + + {row.getValue("FirstSeq")} + + +
    + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "LastSeq", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
    + + + {row.getValue("LastSeq")} + + +
    + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + id: "actions", + cell: ({ row }) => { + return ( + + + + ); + }, + }, +]; diff --git a/tools/tenscan/frontend/src/components/modules/rollups/constants.tsx b/tools/tenscan/frontend/src/components/modules/rollups/constants.tsx new file mode 100644 index 0000000000..2ab030f09f --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/rollups/constants.tsx @@ -0,0 +1,45 @@ +import { + ArrowDownIcon, + ArrowRightIcon, + ArrowUpIcon, + CheckCircledIcon, + ClockIcon, +} from "@radix-ui/react-icons"; + +export const labels = [ + { + value: "Final", + label: "Final", + }, +]; + +export const statuses = [ + { + value: "Final", + label: "Final", + icon: CheckCircledIcon, + }, + { + value: "Pending", + label: "Pending", + icon: ClockIcon, + }, +]; + +export const priorities = [ + { + label: "Low", + value: "low", + icon: ArrowDownIcon, + }, + { + label: "Medium", + value: "medium", + icon: ArrowRightIcon, + }, + { + label: "High", + value: "high", + icon: ArrowUpIcon, + }, +]; diff --git a/tools/tenscan/frontend/src/components/modules/rollups/rollup-details.tsx b/tools/tenscan/frontend/src/components/modules/rollups/rollup-details.tsx new file mode 100644 index 0000000000..b53eb0e81e --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/rollups/rollup-details.tsx @@ -0,0 +1,125 @@ +import TruncatedAddress from "../common/truncated-address"; +import KeyValueItem, { KeyValueList } from "@/src/components/ui/key-value"; +import { formatTimeAgo } from "@/src/lib/utils"; +import { Rollup } from "@/src/types/interfaces/RollupInterfaces"; +import Link from "next/link"; + +export function RollupDetailsComponent({ + rollupDetails, +}: { + rollupDetails: Rollup; +}) { + return ( +
    + + + + + } + /> + + } + /> + } + /> + + {"#" + rollupDetails?.FirstSeq} + + } + /> + + {"#" + rollupDetails?.LastSeq} + + } + /> + + } + /> + + } + /> + + } + /> + 0 + ? rollupDetails?.Header?.crossChainMessages?.map((msg, index) => ( +
    + + + + {"#" + msg.Sequence} + + } + /> + + + ( +
    {payload}
    + ))} + /> + +
    +
    + )) + : "No cross chain messages found." + } + isLastItem + /> +
    +
    + ); +} diff --git a/tools/tenscan/frontend/src/components/modules/transactions/columns.tsx b/tools/tenscan/frontend/src/components/modules/transactions/columns.tsx index fda982927c..a615dad2cb 100644 --- a/tools/tenscan/frontend/src/components/modules/transactions/columns.tsx +++ b/tools/tenscan/frontend/src/components/modules/transactions/columns.tsx @@ -7,7 +7,8 @@ import { statuses } from "./constants"; import { DataTableColumnHeader } from "../common/data-table/data-table-column-header"; import { Transaction } from "@/src/types/interfaces/TransactionInterfaces"; import TruncatedAddress from "../common/truncated-address"; -import { formatNumber, formatTimeAgo } from "@/src/lib/utils"; +import { formatTimeAgo } from "@/src/lib/utils"; +import Link from "next/link"; export const columns: ColumnDef[] = [ { @@ -19,12 +20,14 @@ export const columns: ColumnDef[] = [ return (
    - #{formatNumber(row.getValue("BatchHeight"))} + #{row.getValue("BatchHeight")}
    ); }, - enableSorting: false, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, enableHiding: false, }, @@ -42,7 +45,9 @@ export const columns: ColumnDef[] = [
    ); }, - enableSorting: false, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, enableHiding: false, }, @@ -52,7 +57,12 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - return ; + return ( + + ); }, enableSorting: false, enableHiding: false, diff --git a/tools/tenscan/frontend/src/components/modules/transactions/transaction-details.tsx b/tools/tenscan/frontend/src/components/modules/transactions/transaction-details.tsx new file mode 100644 index 0000000000..9b1ba535b8 --- /dev/null +++ b/tools/tenscan/frontend/src/components/modules/transactions/transaction-details.tsx @@ -0,0 +1,59 @@ +import TruncatedAddress from "../common/truncated-address"; +import KeyValueItem, { KeyValueList } from "@/src/components/ui/key-value"; +import { formatTimeAgo } from "@/src/lib/utils"; +import { Badge } from "@/src/components/ui/badge"; +import { Transaction } from "@/src/types/interfaces/TransactionInterfaces"; +import { BadgeType } from "@/src/types/interfaces"; +import Link from "next/link"; + +export function TransactionDetailsComponent({ + transactionDetails, +}: { + transactionDetails: Transaction; +}) { + return ( +
    + + + {"#" + Number(transactionDetails?.BatchHeight)} + + } + /> + + } + /> + + + {transactionDetails?.Finality} + + } + isLastItem + /> + +
    + ); +} diff --git a/tools/tenscan/frontend/src/lib/constants.ts b/tools/tenscan/frontend/src/lib/constants.ts index ebff60209e..c456eb7bd8 100644 --- a/tools/tenscan/frontend/src/lib/constants.ts +++ b/tools/tenscan/frontend/src/lib/constants.ts @@ -1,6 +1,6 @@ export const socialLinks = { github: "https://github.com/ten-protocol", - discord: "https://discord.gg/QJZ39Den7d", + discord: "https://discord.gg/tenprotocol", twitter: "https://twitter.com/tenprotocol", twitterHandle: "@tenprotocol", }; @@ -11,25 +11,20 @@ export const pricePollingInterval = 60 * 1000; export const RESET_COPIED_TIMEOUT = 2000; -export const getOptions = (query: { - page?: string | string[]; - size?: string | string[]; -}) => { - const offset = - query.page && query.size - ? (parseInt(query.page as string, 10) - 1) * - parseInt(query.size as string, 10) - : 0; - const options = { - offset: Number.isNaN(offset) ? 0 : offset, - size: Number.isNaN(parseInt(query.size as string, 10)) - ? 10 - : parseInt(query.size as string, 10), - // sort: query.sort ? (query.sort as string) : "blockNumber", - // order: query.order ? (query.order as string) : "desc", - // filter: query.filter ? (query.filter as string) : "", +const calculateOffset = (page: number, size: number) => { + if (page <= 0) return 0; + return (page - 1) * size; +}; + +export const getOptions = (query: { page?: number; size?: number }) => { + const defaultSize = 20; + const size = query.size ? (query.size > 100 ? 100 : query.size) : defaultSize; + const page = query.page || 1; + const offset = calculateOffset(page, size); + return { + offset: Number.isNaN(offset) || offset < 0 ? 0 : offset, + size, }; - return options; }; export const version = process.env.NEXT_PUBLIC_FE_VERSION; diff --git a/tools/tenscan/frontend/src/lib/utils.ts b/tools/tenscan/frontend/src/lib/utils.ts index 83fbff0f8f..d4cf0b32cc 100644 --- a/tools/tenscan/frontend/src/lib/utils.ts +++ b/tools/tenscan/frontend/src/lib/utils.ts @@ -1,6 +1,7 @@ import { type ClassValue, clsx } from "clsx"; import { formatDistanceToNow } from "date-fns"; import { twMerge } from "tailwind-merge"; +import { ItemPosition } from "../types/interfaces"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -25,3 +26,26 @@ export const formatNumber = (number: string | number) => { const num = Number(number); return num.toLocaleString(); }; + +export const getItem = ( + arr: T[], + key: string, + position: ItemPosition = ItemPosition.FIRST +) => { + if (!arr || !arr.length) { + return null; + } + + const keys = key.split("."); + const item = position === ItemPosition.FIRST ? arr[0] : arr[arr.length - 1]; + let value: any = item; + + for (const k of keys) { + if (value[k] === undefined) { + return null; + } + value = value[k]; + } + + return value; +}; diff --git a/tools/tenscan/frontend/src/routes/index.ts b/tools/tenscan/frontend/src/routes/index.ts index f41e673e56..61433a5d39 100644 --- a/tools/tenscan/frontend/src/routes/index.ts +++ b/tools/tenscan/frontend/src/routes/index.ts @@ -3,8 +3,11 @@ import { NavLink } from "../types/interfaces"; export const apiRoutes = { // **** BATCHES **** getLatestBatch: "/items/batch/latest/", - getBatches: "/items/batches/", + getBatches: "/items/v2/batches/", getBatchByHash: "/items/batch/:hash", + getBatchByHeight: "/items/batch/height/:height", + getBatchTransactions: "/items/batch/:fullHash/transactions", + getBatchesInRollup: "/items/rollup/:hash/batches", // **** BLOCKS **** getBlocks: "/items/blocks/", @@ -16,6 +19,7 @@ export const apiRoutes = { // **** TRANSACTIONS **** getTransactions: "/items/transactions/", getTransactionCount: "/count/transactions/", + getTransactionByHash: "/items/transaction/:hash", getEtherPrice: "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd", @@ -23,6 +27,9 @@ export const apiRoutes = { // **** ROLLUPS **** getLatestRollup: "/items/rollup/latest/", decryptEncryptedRollup: "/actions/decryptTxBlob/", + getRollups: "/items/rollups/", + getRollupByHash: "/items/rollup/:hash", + getRollupByBatchSequence: "/items/rollup/batch/:seq", // **** INFO **** getHealthStatus: "/info/health/", @@ -61,13 +68,13 @@ export const NavLinks: NavLink[] = [ isExternal: false, }, { - href: "/blocks", - label: "Blocks", + href: "/batches", + label: "Batches", isExternal: false, }, { - href: "/batches", - label: "Batches", + href: "/rollups", + label: "Rollups", isExternal: false, }, ], diff --git a/tools/tenscan/frontend/src/services/useRollupsService.ts b/tools/tenscan/frontend/src/services/useRollupsService.ts index e75fc02771..2853b44491 100644 --- a/tools/tenscan/frontend/src/services/useRollupsService.ts +++ b/tools/tenscan/frontend/src/services/useRollupsService.ts @@ -1,26 +1,67 @@ -import { decryptEncryptedRollup, fetchRollups } from "@/api/rollups"; +import { + decryptEncryptedRollup, + fetchBatchesInRollups, + fetchLatestRollups, + fetchRollups, +} from "@/api/rollups"; import { toast } from "@/src/components/ui/use-toast"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useState } from "react"; +import { getOptions, pollingInterval } from "../lib/constants"; +import { useRouter } from "next/router"; export const useRollupsService = () => { + const { query } = useRouter(); + + const [noPolling, setNoPolling] = useState(false); const [decryptedRollup, setDecryptedRollup] = useState(); - const { data: rollups, isLoading: isRollupsLoading } = useQuery({ - queryKey: ["rollups"], - queryFn: () => fetchRollups(), + const options = getOptions(query); + + const { data: latestRollups } = useQuery({ + queryKey: ["latestRollups"], + queryFn: () => fetchLatestRollups(), + }); + + const { + data: rollups, + isLoading: isRollupsLoading, + refetch: refetchRollups, + } = useQuery({ + queryKey: ["rollups", options], + queryFn: () => fetchRollups(options), + refetchInterval: noPolling ? false : pollingInterval, }); const { mutate: decryptEncryptedData } = useMutation({ mutationFn: decryptEncryptedRollup, onSuccess: (data: any) => { setDecryptedRollup(data); - toast({ description: "Decryption successful!" }); }, onError: (error: any) => { toast({ description: error.message }); }, }); - return { rollups, isRollupsLoading, decryptEncryptedData, decryptedRollup }; + const { + data: rollupBatches, + isLoading: isRollupBatchesLoading, + refetch: refetchRollupBatches, + } = useQuery({ + queryKey: ["rollupBatches", { hash: query.hash, options }], + queryFn: () => fetchBatchesInRollups(query.hash as string, options), + }); + + return { + rollups, + latestRollups, + refetchRollups, + isRollupsLoading, + decryptEncryptedData, + decryptedRollup, + setNoPolling, + rollupBatches, + isRollupBatchesLoading, + refetchRollupBatches, + }; }; diff --git a/tools/tenscan/frontend/src/types/interfaces/BatchInterfaces.ts b/tools/tenscan/frontend/src/types/interfaces/BatchInterfaces.ts index a8fa9c7717..45773a008d 100644 --- a/tools/tenscan/frontend/src/types/interfaces/BatchInterfaces.ts +++ b/tools/tenscan/frontend/src/types/interfaces/BatchInterfaces.ts @@ -1,31 +1,34 @@ +import { CrossChainMessage } from "./RollupInterfaces"; + export type Batch = { - parentHash: string; - stateRoot: string; - transactionsRoot: string; - receiptsRoot: string; - number: number; - sequencerOrderNo: number; - gasLimit: number; - gasUsed: number; - timestamp: string; - extraData: string; - baseFee: number; - coinbase: string; - l1Proof: string; - R: number; - S: number; - crossChainMessages: any[]; - inboundCrossChainHash: string; - inboundCrossChainHeight: number; - transfersTree: string; + sequence: number; hash: string; - sha3Uncles: string; - miner: string; - logsBloom: string; - difficulty: string; - nonce: string; - baseFeePerGas: number; - EncryptedTxBlob: string; + fullHash: string; + height: number; + txCount: number; + header: { + hash: string; + parentHash: string; + stateRoot: string; + transactionsRoot: string; + receiptsRoot: string; + number: string; + sequencerOrderNo: string; + gasLimit: string; + gasUsed: string; + timestamp: string; + extraData: string; + baseFeePerGas: string; + miner: string; + l1Proof: string; + signature: string; + crossChainMessages: []; + inboundCrossChainHash: string; + inboundCrossChainHeight: string; + TransfersTree: string; + crossChainTree: string; + }; + encryptedTxBlob: string; }; export type BatchDetails = { @@ -48,7 +51,7 @@ export type BatchDetails = { crossChainMessages: any[]; inboundCrossChainHash: string; inboundCrossChainHeight: number; - transfersTree: string; + TransfersTree: string; hash: string; sha3Uncles: string; miner: string; @@ -57,10 +60,33 @@ export type BatchDetails = { nonce: string; baseFeePerGas: number; }; - TxHashes: []; + TxHashes: string[]; EncryptedTxBlob: string; }; +export interface LatestBatch { + hash: string; + parentHash: string; + stateRoot: string; + transactionsRoot: string; + receiptsRoot: string; + number: string; + sequencerOrderNo: string; + gasLimit: string; + gasUsed: string; + timestamp: string; + extraData: string; + baseFeePerGas: string; + miner: string; + l1Proof: string; + signature: string; + crossChainMessages: CrossChainMessage[]; + inboundCrossChainHash: string; + inboundCrossChainHeight: string; + TransfersTree: string; + crossChainTree: string; +} + export type BatchResponse = { BatchesData: Batch[]; Total: string; diff --git a/tools/tenscan/frontend/src/types/interfaces/RollupInterfaces.ts b/tools/tenscan/frontend/src/types/interfaces/RollupInterfaces.ts new file mode 100644 index 0000000000..e99caabc5c --- /dev/null +++ b/tools/tenscan/frontend/src/types/interfaces/RollupInterfaces.ts @@ -0,0 +1,32 @@ +export interface RollupsResponse { + RollupsData: Rollup[]; + Total: number; +} + +export interface Rollup { + ID: number; + Hash: string; + FirstSeq: number; + LastSeq: number; + Timestamp: number; + Header: Header; + L1Hash: string; +} + +export interface Header { + CompressionL1Head: string; + crossChainMessages: CrossChainMessage[]; + PayloadHash: string; + Signature: string; + LastBatchSeqNo: number; + hash: string; +} + +export type CrossChainMessage = { + Sender: string; + Sequence: number; + Nonce: number; + Topic: number; + Payload: string[]; + ConsistencyLevel: number; +}; diff --git a/tools/tenscan/frontend/src/types/interfaces/index.ts b/tools/tenscan/frontend/src/types/interfaces/index.ts index 923786fedc..9ab1d962e6 100644 --- a/tools/tenscan/frontend/src/types/interfaces/index.ts +++ b/tools/tenscan/frontend/src/types/interfaces/index.ts @@ -79,3 +79,23 @@ export enum ToastType { DESTRUCTIVE = "destructive", DEFAULT = "default", } + +export enum BadgeType { + SUCCESS = "success", + SECONDARY = "secondary", + DESTRUCTIVE = "destructive", + DEFAULT = "default", + OUTLINE = "outline", +} + +export interface DashboardAnalyticsData { + title: string; + value: string | number | JSX.Element; + change?: string; + icon: any; +} + +export enum ItemPosition { + FIRST = "first", + LAST = "last", +} diff --git a/tools/tenscan/frontend/styles/globals.css b/tools/tenscan/frontend/styles/globals.css index 442bbd2700..598e71e97e 100644 --- a/tools/tenscan/frontend/styles/globals.css +++ b/tools/tenscan/frontend/styles/globals.css @@ -76,7 +76,9 @@ body { @apply bg-background text-foreground; - font-feature-settings: "rlig" 1, "calt" 1; + font-feature-settings: + "rlig" 1, + "calt" 1; font-family: "Quicksand", sans-serif; } @@ -90,6 +92,17 @@ font-family: "DMSans", sans-serif; } + input[type="number"] { + -webkit-appearance: textfield; + -moz-appearance: textfield; + appearance: textfield; + } + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + appearance: none; + -webkit-appearance: none; + } + /* styles for docs */ .prose ul { list-style: disc; diff --git a/tools/walletextension/frontend/public/docs/terms.json b/tools/walletextension/frontend/public/docs/terms.json index ff0769351e..6becc3b6ed 100644 --- a/tools/walletextension/frontend/public/docs/terms.json +++ b/tools/walletextension/frontend/public/docs/terms.json @@ -55,8 +55,8 @@ { "heading": "8. LIMITATIONS OF LIABILITY", "content": [ - "TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL THE INDEMNIFIED PARTIES OR ANY OF THEM BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR:
    • ANY INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS, OR REVENUE, BUSINESS INTERRUPTION, LOSS OF DATA, LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR MALFUNCTION;
    • ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY;
    • ANY AMOUNT, IN THE AGGREGATE, IN EXCESS OF ONE-HUNDRED POUNDS (£100); OR
    • ANY MATTER BEYOND THE REASONABLE CONTROL OF THE INDEMNIFIED PARTIES OR ANY OF THEM.
    ", - "SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE FOREGOING LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU.", + "To the fullest extent allowed by applicable law, under no circumstances and under no legal theory (including, without limitation, tort, contract, strict liability, or otherwise) shall the indemnified parties or any of them be liable to you or to any other person for:
    • any indirect, special, incidental, punitive or consequential damages of any kind, including damages for lost profits, business, or revenue, business interruption, loss of data, loss of business opportunity, goodwill or reputation, work stoppage, accuracy of results, or computer failure or malfunction;
    • any substitute goods, services or technology;
    • any amount, in the aggregate, in excess of one-hundred pounds (£100); or
    • any matter beyond the reasonable control of the indemnified parties or any of them.
    ", + "Some jurisdictions do not allow the exclusion or limitation of incidental or consequential or certain other damages, so the foregoing limitations and exclusions may not apply to you.", "Nothing in these Terms is intended to exclude or limit our liability for death or personal injury caused by our negligence, or for fraud or fraudulent misrepresentation, or to affect your statutory rights." ] }, diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index de81e9447d..20e064c61a 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -5,7 +5,7 @@ export const tenscanLink = "https://testnet.tenscan.io"; export const socialLinks = { github: "https://github.com/ten-protocol", - discord: "https://discord.gg/fVXstswaJY", + discord: "https://discord.gg/tenprotocol", twitter: "https://twitter.com/tenprotocol", twitterHandle: "@tenprotocol", };