From 333d1610fad10e70759545f223959503288a02c6 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Thu, 10 Jul 2025 19:34:31 +0000
Subject: feat: Add invite user support

---
 apps/web/components/admin/UserList.tsx | 214 +++++++++++++++++----------------
 1 file changed, 112 insertions(+), 102 deletions(-)

(limited to 'apps/web/components/admin/UserList.tsx')

diff --git a/apps/web/components/admin/UserList.tsx b/apps/web/components/admin/UserList.tsx
index 2dd86277..3313fe60 100644
--- a/apps/web/components/admin/UserList.tsx
+++ b/apps/web/components/admin/UserList.tsx
@@ -19,6 +19,8 @@ import { useSession } from "next-auth/react";
 
 import ActionConfirmingDialog from "../ui/action-confirming-dialog";
 import AddUserDialog from "./AddUserDialog";
+import { AdminCard } from "./AdminCard";
+import InvitesList from "./InvitesList";
 import ResetPasswordDialog from "./ResetPasswordDialog";
 import UpdateUserDialog from "./UpdateUserDialog";
 
@@ -57,110 +59,118 @@ export default function UsersSection() {
 
   return (
     <div className="flex flex-col gap-4">
-      <div className="mb-2 flex items-center justify-between text-xl font-medium">
-        <span>{t("admin.users_list.users_list")}</span>
-        <AddUserDialog>
-          <ButtonWithTooltip tooltip="Create User" variant="outline">
-            <UserPlus size={16} />
-          </ButtonWithTooltip>
-        </AddUserDialog>
-      </div>
+      <AdminCard>
+        <div className="flex flex-col gap-4">
+          <div className="mb-2 flex items-center justify-between text-xl font-medium">
+            <span>{t("admin.users_list.users_list")}</span>
+            <AddUserDialog>
+              <ButtonWithTooltip tooltip="Create User" variant="outline">
+                <UserPlus size={16} />
+              </ButtonWithTooltip>
+            </AddUserDialog>
+          </div>
 
-      <Table>
-        <TableHeader className="bg-gray-200">
-          <TableHead>{t("common.name")}</TableHead>
-          <TableHead>{t("common.email")}</TableHead>
-          <TableHead>{t("admin.users_list.num_bookmarks")}</TableHead>
-          <TableHead>{t("common.quota")}</TableHead>
-          <TableHead>Storage Quota</TableHead>
-          <TableHead>{t("admin.users_list.asset_sizes")}</TableHead>
-          <TableHead>{t("common.role")}</TableHead>
-          <TableHead>{t("admin.users_list.local_user")}</TableHead>
-          <TableHead>{t("common.actions")}</TableHead>
-        </TableHeader>
-        <TableBody>
-          {users.users.map((u) => (
-            <TableRow key={u.id}>
-              <TableCell className="py-1">{u.name}</TableCell>
-              <TableCell className="py-1">{u.email}</TableCell>
-              <TableCell className="py-1">
-                {userStats[u.id].numBookmarks}
-              </TableCell>
-              <TableCell className="py-1">
-                {u.bookmarkQuota ?? t("admin.users_list.unlimited")}
-              </TableCell>
-              <TableCell className="py-1">
-                {u.storageQuota
-                  ? toHumanReadableSize(u.storageQuota)
-                  : t("admin.users_list.unlimited")}
-              </TableCell>
-              <TableCell className="py-1">
-                {toHumanReadableSize(userStats[u.id].assetSizes)}
-              </TableCell>
-              <TableCell className="py-1">
-                {u.role && t(`common.roles.${u.role}`)}
-              </TableCell>
-              <TableCell className="py-1">
-                {u.localUser ? <Check /> : <X />}
-              </TableCell>
-              <TableCell className="flex gap-1 py-1">
-                <ActionConfirmingDialog
-                  title={t("admin.users_list.delete_user")}
-                  description={t(
-                    "admin.users_list.delete_user_confirm_description",
-                    {
-                      name: u.name ?? "this user",
-                    },
-                  )}
-                  actionButton={(setDialogOpen) => (
-                    <ActionButton
-                      variant="destructive"
-                      loading={isDeletionPending}
-                      onClick={async () => {
-                        await deleteUser({ userId: u.id });
-                        setDialogOpen(false);
-                      }}
+          <Table>
+            <TableHeader className="bg-gray-200">
+              <TableHead>{t("common.name")}</TableHead>
+              <TableHead>{t("common.email")}</TableHead>
+              <TableHead>{t("admin.users_list.num_bookmarks")}</TableHead>
+              <TableHead>{t("common.quota")}</TableHead>
+              <TableHead>Storage Quota</TableHead>
+              <TableHead>{t("admin.users_list.asset_sizes")}</TableHead>
+              <TableHead>{t("common.role")}</TableHead>
+              <TableHead>{t("admin.users_list.local_user")}</TableHead>
+              <TableHead>{t("common.actions")}</TableHead>
+            </TableHeader>
+            <TableBody>
+              {users.users.map((u) => (
+                <TableRow key={u.id}>
+                  <TableCell className="py-1">{u.name}</TableCell>
+                  <TableCell className="py-1">{u.email}</TableCell>
+                  <TableCell className="py-1">
+                    {userStats[u.id].numBookmarks}
+                  </TableCell>
+                  <TableCell className="py-1">
+                    {u.bookmarkQuota ?? t("admin.users_list.unlimited")}
+                  </TableCell>
+                  <TableCell className="py-1">
+                    {u.storageQuota
+                      ? toHumanReadableSize(u.storageQuota)
+                      : t("admin.users_list.unlimited")}
+                  </TableCell>
+                  <TableCell className="py-1">
+                    {toHumanReadableSize(userStats[u.id].assetSizes)}
+                  </TableCell>
+                  <TableCell className="py-1">
+                    {u.role && t(`common.roles.${u.role}`)}
+                  </TableCell>
+                  <TableCell className="py-1">
+                    {u.localUser ? <Check /> : <X />}
+                  </TableCell>
+                  <TableCell className="flex gap-1 py-1">
+                    <ActionConfirmingDialog
+                      title={t("admin.users_list.delete_user")}
+                      description={t(
+                        "admin.users_list.delete_user_confirm_description",
+                        {
+                          name: u.name ?? "this user",
+                        },
+                      )}
+                      actionButton={(setDialogOpen) => (
+                        <ActionButton
+                          variant="destructive"
+                          loading={isDeletionPending}
+                          onClick={async () => {
+                            await deleteUser({ userId: u.id });
+                            setDialogOpen(false);
+                          }}
+                        >
+                          Delete
+                        </ActionButton>
+                      )}
                     >
-                      Delete
-                    </ActionButton>
-                  )}
-                >
-                  <ButtonWithTooltip
-                    tooltip={t("admin.users_list.delete_user")}
-                    variant="outline"
-                    disabled={session!.user.id == u.id}
-                  >
-                    <Trash size={16} color="red" />
-                  </ButtonWithTooltip>
-                </ActionConfirmingDialog>
-                <ResetPasswordDialog userId={u.id}>
-                  <ButtonWithTooltip
-                    tooltip={t("admin.users_list.reset_password")}
-                    variant="outline"
-                    disabled={session!.user.id == u.id || !u.localUser}
-                  >
-                    <KeyRound size={16} color="red" />
-                  </ButtonWithTooltip>
-                </ResetPasswordDialog>
-                <UpdateUserDialog
-                  userId={u.id}
-                  currentRole={u.role!}
-                  currentQuota={u.bookmarkQuota}
-                  currentStorageQuota={u.storageQuota}
-                >
-                  <ButtonWithTooltip
-                    tooltip="Edit User"
-                    variant="outline"
-                    disabled={session!.user.id == u.id}
-                  >
-                    <Pencil size={16} color="red" />
-                  </ButtonWithTooltip>
-                </UpdateUserDialog>
-              </TableCell>
-            </TableRow>
-          ))}
-        </TableBody>
-      </Table>
+                      <ButtonWithTooltip
+                        tooltip={t("admin.users_list.delete_user")}
+                        variant="outline"
+                        disabled={session!.user.id == u.id}
+                      >
+                        <Trash size={16} color="red" />
+                      </ButtonWithTooltip>
+                    </ActionConfirmingDialog>
+                    <ResetPasswordDialog userId={u.id}>
+                      <ButtonWithTooltip
+                        tooltip={t("admin.users_list.reset_password")}
+                        variant="outline"
+                        disabled={session!.user.id == u.id || !u.localUser}
+                      >
+                        <KeyRound size={16} color="red" />
+                      </ButtonWithTooltip>
+                    </ResetPasswordDialog>
+                    <UpdateUserDialog
+                      userId={u.id}
+                      currentRole={u.role!}
+                      currentQuota={u.bookmarkQuota}
+                      currentStorageQuota={u.storageQuota}
+                    >
+                      <ButtonWithTooltip
+                        tooltip="Edit User"
+                        variant="outline"
+                        disabled={session!.user.id == u.id}
+                      >
+                        <Pencil size={16} color="red" />
+                      </ButtonWithTooltip>
+                    </UpdateUserDialog>
+                  </TableCell>
+                </TableRow>
+              ))}
+            </TableBody>
+          </Table>
+        </div>
+      </AdminCard>
+
+      <AdminCard>
+        <InvitesList />
+      </AdminCard>
     </div>
   );
 }
-- 
cgit v1.2.3-70-g09d2

