From b70c638f7cb526facaa7c3e3bfde6fb532aea56b Mon Sep 17 00:00:00 2001 From: Keith Stevens Date: Sat, 7 Jan 2023 19:13:26 +0900 Subject: [PATCH] Adding a very rudimentary admin page that displays a table of users and their basic information --- website/src/components/UsersCell.tsx | 46 +++++++++++++++++++ website/src/pages/admin/index.tsx | 67 ++++++++++++++++++++++++++++ website/src/pages/api/admin/users.ts | 31 +++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 website/src/components/UsersCell.tsx create mode 100644 website/src/pages/admin/index.tsx create mode 100644 website/src/pages/api/admin/users.ts diff --git a/website/src/components/UsersCell.tsx b/website/src/components/UsersCell.tsx new file mode 100644 index 00000000..5c6f4ce8 --- /dev/null +++ b/website/src/components/UsersCell.tsx @@ -0,0 +1,46 @@ +import { Table, TableCaption, TableContainer, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react"; +import { useState } from "react"; +import fetcher from "src/lib/fetcher"; +import useSWR from "swr"; + +/** + * Fetches users from the users api route and then presents them in a simple Chakra table. + */ +const UsersCell = () => { + // Fetch and save the users. + const [users, setUsers] = useState([]); + const { isLoading } = useSWR("/api/admin/users", fetcher, { + onSuccess: (data) => { + setUsers(data); + }, + }); + + // Present users in a naive table. + return ( + + + Users + + + + + + + + + + {users.map((user, index) => ( + + + + + + + ))} + +
IdEmailNameRole
{user.id}{user.email}{user.name}{user.role}
+
+ ); +}; + +export default UsersCell; diff --git a/website/src/pages/admin/index.tsx b/website/src/pages/admin/index.tsx new file mode 100644 index 00000000..e048915e --- /dev/null +++ b/website/src/pages/admin/index.tsx @@ -0,0 +1,67 @@ +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useSession } from "next-auth/react"; +import { useEffect } from "react"; +import { getTransparentHeaderLayout } from "src/components/Layout"; +import UsersCell from "src/components/UsersCell"; + +/** + * Provides the admin index page that will display a list of users and give + * admins the ability to manage their access rights. + */ +const AdminIndex = () => { + const router = useRouter(); + const { data: session, status } = useSession(); + + // Check when the user session is loaded and re-route if the user is not an + // admin. This follows the suggestion by NextJS for handling private pages: + // https://nextjs.org/docs/api-reference/next/router#usage + // + // All admin pages should use the same check and routing steps. + useEffect(() => { + if (status === "loading") { + return; + } + if (session?.user?.role === "admin") { + return; + } + router.push("/"); + }, [session, status]); + + // While loading, just show something. + if (status === "loading") { + return ( + <> + + Open Assistant + + +
loading...
+ + ); + } + + // Show the final page. + // TODO(#237): Display a component that fetches actual user data. + return ( + <> + + Open Assistant + + +
+ +
+ + ); +}; + +AdminIndex.getLayout = getTransparentHeaderLayout; + +export default AdminIndex; diff --git a/website/src/pages/api/admin/users.ts b/website/src/pages/api/admin/users.ts new file mode 100644 index 00000000..186bb253 --- /dev/null +++ b/website/src/pages/api/admin/users.ts @@ -0,0 +1,31 @@ +import { getToken } from "next-auth/jwt"; +import client from "src/lib/prismadb"; + +/** + * Returns a list of user results from the database when the requesting user is + * a logged in admin. + */ +const handler = async (req, res) => { + const token = await getToken({ req }); + + // Return nothing if the user isn't registered or if the user isn't an admin. + if (!token || token.role !== "admin") { + res.status(403).end(); + return; + } + + // Fetch 20 users. + const users = await client.user.findMany({ + select: { + id: true, + role: true, + name: true, + email: true, + }, + take: 20, + }); + + res.status(200).json(users); +}; + +export default handler;