From be6de7c796355cea7041ad9d92b9661b13f4e468 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Tue, 19 Apr 2022 23:36:56 -0700 Subject: [PATCH] client/server: clean-up admin page, re-implement user deletion/role toggling --- .../admin/action-dropdown/index.tsx | 43 +++++++++ client/components/admin/post-table.tsx | 94 +++++++++---------- client/components/admin/user-table.tsx | 73 +++++++------- client/components/file-dropdown/index.tsx | 14 +-- .../{settings => }/settings-group/index.tsx | 0 .../settings-group/settings-group.module.css | 0 client/components/settings/index.tsx | 3 +- server/src/routes/admin.ts | 32 +++---- 8 files changed, 145 insertions(+), 114 deletions(-) create mode 100644 client/components/admin/action-dropdown/index.tsx rename client/components/{settings => }/settings-group/index.tsx (100%) rename client/components/{settings => }/settings-group/settings-group.module.css (100%) diff --git a/client/components/admin/action-dropdown/index.tsx b/client/components/admin/action-dropdown/index.tsx new file mode 100644 index 00000000..48c57335 --- /dev/null +++ b/client/components/admin/action-dropdown/index.tsx @@ -0,0 +1,43 @@ +import { Popover, Button } from "@geist-ui/core" +import { MoreVertical } from "@geist-ui/icons" + +type Action = { + title: string + onClick: () => void +} + +const ActionDropdown = ({ + title = "Actions", + actions, + showTitle = false, +}: { + title?: string, + showTitle?: boolean, + actions: Action[] +}) => { + return ( + + {showTitle && + {title} + } + {actions.map(action => ( + + {action.title} + + ))} + + } + hideArrow + > + + + ) +} + +export default ActionDropdown \ No newline at end of file diff --git a/client/components/admin/post-table.tsx b/client/components/admin/post-table.tsx index bb9539aa..c66f99ea 100644 --- a/client/components/admin/post-table.tsx +++ b/client/components/admin/post-table.tsx @@ -1,11 +1,11 @@ -import { Button, Fieldset, Link, Popover, useToasts } from "@geist-ui/core" -import MoreVertical from "@geist-ui/icons/moreVertical" +import SettingsGroup from "@components/settings-group" +import { Fieldset, useToasts } from "@geist-ui/core" +import byteToMB from "@lib/byte-to-mb" import { Post } from "@lib/types" -import Cookies from "js-cookie" +import Table from "rc-table" import { useEffect, useMemo, useState } from "react" import { adminFetcher } from "." -import Table from "rc-table" -import byteToMB from "@lib/byte-to-mb" +import ActionDropdown from "./action-dropdown" const PostTable = () => { const [posts, setPosts] = useState() @@ -35,8 +35,8 @@ const PostTable = () => { visibility: post.visibility, size: post.files ? byteToMB( - post.files.reduce((acc, file) => acc + file.html.length, 0) - ) + post.files.reduce((acc, file) => acc + file.html.length, 0) + ) : 0, actions: "" } @@ -44,27 +44,34 @@ const PostTable = () => { [posts] ) - // const deletePost = async (id: number) => { - // const confirm = window.confirm("Are you sure you want to delete this post?") - // if (!confirm) return - // const res = await adminFetcher(`/posts/${id}`, { - // method: "DELETE", - // }) + const deletePost = async (/* id: string */) => { + return alert("Not implemented") - // const json = await res.json() + // const confirm = window.confirm("Are you sure you want to delete this post?") + // if (!confirm) return + // const res = await adminFetcher(`/posts/${id}`, { + // method: "DELETE", + // }) - // if (res.status === 200) { - // setToast({ - // text: "Post deleted", - // type: "success" - // }) - // } else { - // setToast({ - // text: json.error || "Something went wrong", - // type: "error" - // }) - // } - // } + // const json = await res.json() + + // if (res.status === 200) { + // setToast({ + // text: "Post deleted", + // type: "success" + // }) + + // setPosts((posts) => { + // const newPosts = posts?.filter((post) => post.id !== id) + // return newPosts + // }) + // } else { + // setToast({ + // text: json.error || "Something went wrong", + // type: "error" + // }) + // } + } const tableColumns = [ { @@ -102,37 +109,28 @@ const PostTable = () => { dataIndex: "", key: "actions", width: 50, - render() { + render(post: Post) { return ( - - {/* deletePost(post.id)}>Delete post */} - - } - hideArrow - > - - + deletePost(post.id) + } + ]} + /> ) } } ] return ( -
- Posts - {posts && {posts.length} posts} + {!posts && Loading...} + {posts &&
{posts.length} posts
} {posts && } - + ) } diff --git a/client/components/admin/user-table.tsx b/client/components/admin/user-table.tsx index 427cb4d3..e8beb710 100644 --- a/client/components/admin/user-table.tsx +++ b/client/components/admin/user-table.tsx @@ -1,10 +1,10 @@ -import { Button, Fieldset, Link, Popover, useToasts } from "@geist-ui/core" -import MoreVertical from "@geist-ui/icons/moreVertical" +import { Fieldset, useToasts } from "@geist-ui/core" import { User } from "@lib/types" -import Cookies from "js-cookie" import { useEffect, useMemo, useState } from "react" import { adminFetcher } from "." import Table from "rc-table" +import SettingsGroup from "@components/settings-group" +import ActionDropdown from "./action-dropdown" const UserTable = () => { const [users, setUsers] = useState() @@ -19,7 +19,7 @@ const UserTable = () => { fetchUsers() }, []) - const toggleRole = async (id: number, role: "admin" | "user") => { + const toggleRole = async (id: string, role: "admin" | "user") => { const res = await adminFetcher("/users/toggle-role", { method: "POST", body: { id, role } @@ -33,17 +33,18 @@ const UserTable = () => { type: "success" }) - // TODO: swr should handle updating this - const newUsers = users?.map((user) => { - if (user.id === id.toString()) { - return { - ...user, - role: role === "admin" ? "user" : ("admin" as User["role"]) + setUsers((users) => { + const newUsers = users?.map((user) => { + if (user.id === id) { + return { + ...user, + role + } } - } - return user + return user + }) + return newUsers }) - setUsers(newUsers) } else { setToast({ text: json.error || "Something went wrong", @@ -52,7 +53,7 @@ const UserTable = () => { } } - const deleteUser = async (id: number) => { + const deleteUser = async (id: string) => { const confirm = window.confirm("Are you sure you want to delete this user?") if (!confirm) return const res = await adminFetcher(`/users/${id}`, { @@ -123,42 +124,32 @@ const UserTable = () => { dataIndex: "", key: "actions", width: 50, - render(user: any) { + render(user: User) { return ( - - toggleRole(user.id, user.role)}> - {user.role === "admin" ? "Change role" : "Make admin"} - - deleteUser(user.id)}> - Delete user - - - } - hideArrow - > - - + toggleRole(user.id, user.role === "admin" ? "user" : "admin") + }, + { + title: "Delete", + onClick: () => deleteUser(user.id) + } + ]} + /> ) } } ] return ( -
- Users - {users && {users.length} users} + {!users && Loading...} + {users &&
{users.length} users
} {users &&
} - + ) } diff --git a/client/components/file-dropdown/index.tsx b/client/components/file-dropdown/index.tsx index c90133bd..b9ef188e 100644 --- a/client/components/file-dropdown/index.tsx +++ b/client/components/file-dropdown/index.tsx @@ -1,12 +1,12 @@ -import { Button, Link, Text, Popover } from "@geist-ui/core" -import FileIcon from "@geist-ui/icons/fileText" -import CodeIcon from "@geist-ui/icons/fileFunction" -import styles from "./dropdown.module.css" -import { useCallback, useEffect, useRef, useState } from "react" -import { codeFileExtensions } from "@lib/constants" -import ChevronDown from "@geist-ui/icons/chevronDown" import ShiftBy from "@components/shift-by" +import { Button, Popover } from "@geist-ui/core" +import ChevronDown from "@geist-ui/icons/chevronDown" +import CodeIcon from "@geist-ui/icons/fileFunction" +import FileIcon from "@geist-ui/icons/fileText" +import { codeFileExtensions } from "@lib/constants" import type { File } from "@lib/types" +import { useCallback, useEffect, useState } from "react" +import styles from "./dropdown.module.css" type Item = File & { icon: JSX.Element diff --git a/client/components/settings/settings-group/index.tsx b/client/components/settings-group/index.tsx similarity index 100% rename from client/components/settings/settings-group/index.tsx rename to client/components/settings-group/index.tsx diff --git a/client/components/settings/settings-group/settings-group.module.css b/client/components/settings-group/settings-group.module.css similarity index 100% rename from client/components/settings/settings-group/settings-group.module.css rename to client/components/settings-group/settings-group.module.css diff --git a/client/components/settings/index.tsx b/client/components/settings/index.tsx index 802cb1ea..e6ed9215 100644 --- a/client/components/settings/index.tsx +++ b/client/components/settings/index.tsx @@ -1,7 +1,6 @@ -import { Fieldset, Text, Divider, Note, Input, Textarea, Button } from "@geist-ui/core" import Password from "./sections/password" import Profile from "./sections/profile" -import SettingsGroup from "./settings-group" +import SettingsGroup from "../settings-group" const SettingsPage = () => { return (
{ } }) -// admin.delete("/posts/:id", async (req, res, next) => { -// try { -// const post = await Post.findByPk(req.params.id) -// if (!post) { -// return res.status(404).json({ -// error: "Post not found" -// }) -// } -// await post.destroy() +admin.delete("/posts/:id", async (req, res, next) => { + try { + const post = await Post.findByPk(req.params.id) + if (!post) { + return res.status(404).json({ + error: "Post not found" + }) + } + await post.destroy() -// res.json({ -// success: true -// }) -// } catch (e) { -// next(e) -// } -// }) + res.json({ + success: true + }) + } catch (e) { + next(e) + } +}) admin.get("/posts", async (req, res, next) => { try {