diff --git a/apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx b/apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx
new file mode 100644
index 000000000..9f10381cb
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx
@@ -0,0 +1,25 @@
+import { Trans } from '@lingui/macro';
+
+import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
+import { getSigningVolume } from '@documenso/lib/server-only/admin/get-signing-volume';
+
+import { DataTableDemo as Table } from './table';
+
+export default async function Leaderboard() {
+ setupI18nSSR();
+
+ const signingVolume = await getSigningVolume();
+
+ console.log(signingVolume);
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/(dashboard)/admin/leaderboard/table.tsx b/apps/web/src/app/(dashboard)/admin/leaderboard/table.tsx
new file mode 100644
index 000000000..ece498bd9
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/admin/leaderboard/table.tsx
@@ -0,0 +1,263 @@
+'use client';
+
+import * as React from 'react';
+
+import type {
+ ColumnDef,
+ ColumnFiltersState,
+ SortingState,
+ VisibilityState,
+} from '@tanstack/react-table';
+import {
+ flexRender,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from '@tanstack/react-table';
+import {
+ ChevronDownIcon as CaretSortIcon,
+ ChevronDownIcon as DotsHorizontalIcon,
+} from 'lucide-react';
+
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@documenso/ui/primitives/dropdown-menu';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@documenso/ui/primitives/table';
+
+const data: Payment[] = [
+ {
+ id: 'm5gr84i9',
+ amount: 316,
+ status: 'success',
+ email: 'ken99@yahoo.com',
+ },
+ {
+ id: '3u1reuv4',
+ amount: 242,
+ status: 'success',
+ email: 'Abe45@gmail.com',
+ },
+ {
+ id: 'derv1ws0',
+ amount: 837,
+ status: 'processing',
+ email: 'Monserrat44@gmail.com',
+ },
+ {
+ id: '5kma53ae',
+ amount: 874,
+ status: 'success',
+ email: 'Silas22@gmail.com',
+ },
+ {
+ id: 'bhqecj4p',
+ amount: 721,
+ status: 'failed',
+ email: 'carmella@hotmail.com',
+ },
+ {
+ id: '5kma53ae',
+ amount: 874,
+ status: 'success',
+ email: 'Silas22@gmail.com',
+ },
+ {
+ id: '5kma53ae',
+ amount: 874,
+ status: 'success',
+ email: 'Silas22@gmail.com',
+ },
+ {
+ id: '5kma53ae',
+ amount: 874,
+ status: 'success',
+ email: 'Silas22@gmail.com',
+ },
+ {
+ id: '5kma53ae',
+ amount: 874,
+ status: 'success',
+ email: 'Silas22@gmail.com',
+ },
+];
+
+export type Payment = {
+ id: string;
+ amount: number;
+ status: 'pending' | 'processing' | 'success' | 'failed';
+ email: string;
+};
+
+export const columns: ColumnDef[] = [
+ {
+ accessorKey: 'status',
+ header: 'Status',
+ cell: ({ row }) => {row.getValue('status')}
,
+ },
+ {
+ accessorKey: 'email',
+ header: ({ column }) => {
+ return (
+
+ );
+ },
+ cell: ({ row }) => {row.getValue('email')}
,
+ },
+ {
+ accessorKey: 'amount',
+ header: () => Amount
,
+ cell: ({ row }) => {
+ const amount = parseFloat(row.getValue('amount'));
+
+ // Format the amount as a dollar amount
+ const formatted = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ }).format(amount);
+
+ return {formatted}
;
+ },
+ },
+ {
+ id: 'actions',
+ enableHiding: false,
+ cell: ({ row }) => {
+ const payment = row.original;
+
+ return (
+
+
+
+
+
+ Actions
+ navigator.clipboard.writeText(payment.id)}>
+ Copy payment ID
+
+
+ View customer
+ View payment details
+
+
+ );
+ },
+ },
+];
+
+export function DataTableDemo() {
+ const [sorting, setSorting] = React.useState([]);
+ const [columnFilters, setColumnFilters] = React.useState([]);
+ const [columnVisibility, setColumnVisibility] = React.useState({});
+ const [rowSelection, setRowSelection] = React.useState({});
+
+ const table = useReactTable({
+ data,
+ columns,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ onColumnVisibilityChange: setColumnVisibility,
+ onRowSelectionChange: setRowSelection,
+ state: {
+ sorting,
+ columnFilters,
+ columnVisibility,
+ rowSelection,
+ },
+ });
+
+ return (
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(header.column.columnDef.header, header.getContext())}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{' '}
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/(dashboard)/admin/nav.tsx b/apps/web/src/app/(dashboard)/admin/nav.tsx
index cf0bb81f2..bcae0fc75 100644
--- a/apps/web/src/app/(dashboard)/admin/nav.tsx
+++ b/apps/web/src/app/(dashboard)/admin/nav.tsx
@@ -6,7 +6,7 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Trans } from '@lingui/macro';
-import { BarChart3, FileStack, Settings, Users, Wallet2 } from 'lucide-react';
+import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -80,6 +80,20 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => {
+
+