diff --git a/apps/web/src/app/(dashboard)/admin/nav.tsx b/apps/web/src/app/(dashboard)/admin/nav.tsx
index 3b87a9b13..0b59335bf 100644
--- a/apps/web/src/app/(dashboard)/admin/nav.tsx
+++ b/apps/web/src/app/(dashboard)/admin/nav.tsx
@@ -37,10 +37,12 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => {
'justify-start md:w-full',
pathname?.startsWith('/admin/users') && 'bg-secondary',
)}
- disabled
+ asChild
>
-
- Users (Coming Soon)
+
+
+ Users
+
);
diff --git a/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx
new file mode 100644
index 000000000..3b8cfa287
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx
@@ -0,0 +1,8 @@
+export default function UserPage() {
+ return (
+
+
Hey
+ Ho
+
+ );
+}
diff --git a/apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx b/apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx
new file mode 100644
index 000000000..890d5cd48
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx
@@ -0,0 +1,135 @@
+'use client';
+
+import { useTransition } from 'react';
+
+import Link from 'next/link';
+
+import { Edit, Loader } from 'lucide-react';
+
+import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
+import { Role } from '@documenso/prisma/client';
+import { Button } from '@documenso/ui/primitives/button';
+import { DataTable } from '@documenso/ui/primitives/data-table';
+import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
+
+interface User {
+ id: number;
+ name: string | null;
+ email: string;
+ roles: Role[];
+ Subscription: Subscription[];
+}
+
+interface Subscription {
+ id: number;
+ status: string;
+ planId: string | null;
+ priceId: string | null;
+ createdAt: Date | null;
+ periodEnd: Date | null;
+}
+
+type UsersDataTableProps = {
+ users: User[];
+ perPage: number;
+ page: number;
+ totalPages: number;
+};
+
+export const UsersDataTable = ({ users, perPage, page, totalPages }: UsersDataTableProps) => {
+ const [isPending, startTransition] = useTransition();
+ const updateSearchParams = useUpdateSearchParams();
+ console.log(users);
+
+ const onPaginationChange = (page: number, perPage: number) => {
+ startTransition(() => {
+ updateSearchParams({
+ page,
+ perPage,
+ });
+ });
+ };
+
+ return (
+
+
{row.original.id}
,
+ },
+ {
+ header: 'Name',
+ accessorKey: 'name',
+ cell: ({ row }) => {row.original.name}
,
+ },
+ {
+ header: 'Email',
+ accessorKey: 'email',
+ cell: ({ row }) => {row.original.email}
,
+ },
+ {
+ header: 'Roles',
+ accessorKey: 'roles',
+ cell: ({ row }) => {
+ return (
+ <>
+ {row.original.roles.map((role: string, idx: number) => {
+ return (
+
+ {role} {}
+
+ );
+ })}
+ >
+ );
+ },
+ },
+ {
+ header: 'Subscription status',
+ accessorKey: 'subscription',
+ cell: ({ row }) => {
+ return (
+ <>
+ {row.original.Subscription.map((subscription: Subscription, idx: number) => {
+ return {subscription.status};
+ })}
+ >
+ );
+ },
+ },
+ {
+ header: 'Edit',
+ accessorKey: 'edit',
+ cell: ({ row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+ ]}
+ data={users}
+ perPage={perPage}
+ currentPage={page}
+ totalPages={totalPages}
+ onPaginationChange={onPaginationChange}
+ >
+ {(table) => }
+
+
+ {isPending && (
+
+
+
+ )}
+
+ );
+};
diff --git a/apps/web/src/app/(dashboard)/admin/users/page.tsx b/apps/web/src/app/(dashboard)/admin/users/page.tsx
new file mode 100644
index 000000000..7f8b9af47
--- /dev/null
+++ b/apps/web/src/app/(dashboard)/admin/users/page.tsx
@@ -0,0 +1,35 @@
+import { findUsers } from '@documenso/lib/server-only/user/get-all-users';
+
+/*
+1. retrieve all users from the db
+2. display them in a table
+*/
+import { UsersDataTable } from './data-table-users';
+
+type AdminManageUsersProps = {
+ searchParams?: {
+ page?: number;
+ perPage?: number;
+ };
+};
+
+export default async function AdminManageUsers({ searchParams = {} }: AdminManageUsersProps) {
+ const page = Number(searchParams.page) || 1;
+ const perPage = Number(searchParams.perPage) || 10;
+
+ const results = await findUsers({ page, perPage });
+
+ return (
+
+ );
+}
diff --git a/packages/lib/server-only/user/get-all-users.ts b/packages/lib/server-only/user/get-all-users.ts
new file mode 100644
index 000000000..157a75d4a
--- /dev/null
+++ b/packages/lib/server-only/user/get-all-users.ts
@@ -0,0 +1,37 @@
+import { prisma } from '@documenso/prisma';
+
+type getAllUsersProps = {
+ page: number;
+ perPage: number;
+};
+
+export const findUsers = async ({ page = 1, perPage = 10 }: getAllUsersProps) => {
+ const [users, count] = await Promise.all([
+ await prisma.user.findMany({
+ select: {
+ id: true,
+ name: true,
+ email: true,
+ roles: true,
+ Subscription: {
+ select: {
+ id: true,
+ status: true,
+ planId: true,
+ priceId: true,
+ createdAt: true,
+ periodEnd: true,
+ },
+ },
+ },
+ skip: Math.max(page - 1, 0) * perPage,
+ take: perPage,
+ }),
+ await prisma.user.count(),
+ ]);
+
+ return {
+ users,
+ totalPages: Math.ceil(count / perPage),
+ };
+};