From 2493232c8491b51c26e3841f99ae5a18d23f8560 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Tue, 17 Nov 2020 17:31:42 +0800
Subject: ...

---
 FrontEnd/src/app/views/admin/UserAdmin.tsx | 352 ++++++++++-------------------
 1 file changed, 115 insertions(+), 237 deletions(-)

(limited to 'FrontEnd/src/app/views/admin/UserAdmin.tsx')

diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx
index 856e6136..3432cddf 100644
--- a/FrontEnd/src/app/views/admin/UserAdmin.tsx
+++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx
@@ -1,5 +1,4 @@
 import React, { useState, useEffect } from "react";
-import axios from "axios";
 import {
   ListGroup,
   Row,
@@ -13,77 +12,22 @@ import OperationDialog from "../common/OperationDialog";
 import AdminSubPage from "./AdminSubPage";
 
 import { User, AuthUser } from "@/services/user";
-import { getHttpUserClient } from "@/http/user";
+import { getHttpUserClient, HttpUser } from "@/http/user";
 
-const apiBaseUrl = "/api";
-
-interface CreateUserInfo {
-  username: string;
-  password: string;
-}
-
-async function createUser(user: CreateUserInfo, token: string): Promise<User> {
-  const res = await axios.post<User>(
-    `${apiBaseUrl}/userop/createuser?token=${token}`,
-    user
-  );
-  return res.data;
-}
-
-function deleteUser(username: string, token: string): Promise<void> {
-  return axios.delete(`${apiBaseUrl}/users/${username}?token=${token}`);
-}
-
-function changeUsername(
-  oldUsername: string,
-  newUsername: string,
-  token: string
-): Promise<void> {
-  return axios.patch(`${apiBaseUrl}/users/${oldUsername}?token=${token}`, {
-    username: newUsername,
-  });
-}
-
-function changePassword(
-  username: string,
-  newPassword: string,
-  token: string
-): Promise<void> {
-  return axios.patch(`${apiBaseUrl}/users/${username}?token=${token}`, {
-    password: newPassword,
-  });
-}
-
-const kChangeUsername = "changeusername";
-const kChangePassword = "changepassword";
-const kChangePermission = "changepermission";
+const kModify = "modify";
 const kDelete = "delete";
 
-type TChangeUsername = typeof kChangeUsername;
-type TChangePassword = typeof kChangePassword;
-type TChangePermission = typeof kChangePermission;
+type TModify = typeof kModify;
 type TDelete = typeof kDelete;
 
-type ContextMenuItem =
-  | TChangeUsername
-  | TChangePassword
-  | TChangePermission
-  | TDelete;
+type ContextMenuItem = TModify | TDelete;
 
 interface UserCardProps {
-  onContextMenu: (item: ContextMenuItem) => void;
+  on: { [key in ContextMenuItem]: () => void };
   user: User;
 }
 
-const UserItem: React.FC<UserCardProps> = (props) => {
-  const user = props.user;
-
-  const createClickCallback = (item: ContextMenuItem): (() => void) => {
-    return () => {
-      props.onContextMenu(item);
-    };
-  };
-
+const UserItem: React.FC<UserCardProps> = ({ user, on }) => {
   return (
     <ListGroup.Item className="container">
       <Row className="align-items-center">
@@ -101,19 +45,8 @@ const UserItem: React.FC<UserCardProps> = (props) => {
               Manage
             </Dropdown.Toggle>
             <Dropdown.Menu>
-              <Dropdown.Item onClick={createClickCallback(kChangeUsername)}>
-                Change Username
-              </Dropdown.Item>
-              <Dropdown.Item onClick={createClickCallback(kChangePassword)}>
-                Change Password
-              </Dropdown.Item>
-              <Dropdown.Item onClick={createClickCallback(kChangePermission)}>
-                Change Permission
-              </Dropdown.Item>
-              <Dropdown.Item
-                className="text-danger"
-                onClick={createClickCallback(kDelete)}
-              >
+              <Dropdown.Item onClick={on["modify"]}>Modify</Dropdown.Item>
+              <Dropdown.Item className="text-danger" onClick={on["delete"]}>
                 Delete
               </Dropdown.Item>
             </Dropdown.Menu>
@@ -124,16 +57,20 @@ const UserItem: React.FC<UserCardProps> = (props) => {
   );
 };
 
-interface DialogProps {
+interface DialogProps<TData = undefined, TReturn = undefined> {
   open: boolean;
   close: () => void;
+  token: string;
+  data: TData;
+  onSuccess: (data: TReturn) => void;
 }
 
-interface CreateUserDialogProps extends DialogProps {
-  process: (user: CreateUserInfo) => Promise<void>;
-}
-
-const CreateUserDialog: React.FC<CreateUserDialogProps> = (props) => {
+const CreateUserDialog: React.FC<DialogProps<undefined, HttpUser>> = ({
+  open,
+  close,
+  token,
+  onSuccess,
+}) => {
   return (
     <OperationDialog
       title="Create"
@@ -146,13 +83,17 @@ const CreateUserDialog: React.FC<CreateUserDialogProps> = (props) => {
         ] as const
       }
       onProcess={([username, password]) =>
-        props.process({
-          username: username,
-          password: password,
-        })
+        getHttpUserClient().createUser(
+          {
+            username,
+            password,
+          },
+          token
+        )
       }
-      close={props.close}
-      open={props.open}
+      close={close}
+      open={open}
+      onSuccessAndClose={onSuccess}
     />
   );
 };
@@ -161,110 +102,64 @@ const UsernameLabel: React.FC = (props) => {
   return <span style={{ color: "blue" }}>{props.children}</span>;
 };
 
-interface UserDeleteDialogProps extends DialogProps {
-  username: string;
-  process: () => Promise<void>;
-}
-
-const UserDeleteDialog: React.FC<UserDeleteDialogProps> = (props) => {
+const UserDeleteDialog: React.FC<DialogProps<
+  { username: string },
+  unknown
+>> = ({ open, close, token, data: { username }, onSuccess }) => {
   return (
     <OperationDialog
-      open={props.open}
-      close={props.close}
+      open={open}
+      close={close}
       title="Dangerous"
       titleColor="dangerous"
       inputPrompt={() => (
         <>
-          {"You are deleting user "}
-          <UsernameLabel>{props.username}</UsernameLabel>
-          {" !"}
+          You are deleting user <UsernameLabel>{username}</UsernameLabel> !
         </>
       )}
-      onProcess={props.process}
+      onProcess={() => getHttpUserClient().delete(username, token)}
+      onSuccessAndClose={onSuccess}
     />
   );
 };
 
-interface UserModifyDialogProps<T> extends DialogProps {
-  username: string;
-  process: (value: T) => Promise<void>;
-}
-
-const UserChangeUsernameDialog: React.FC<UserModifyDialogProps<string>> = (
-  props
-) => {
+const UserModifyDialog: React.FC<DialogProps<
+  {
+    oldUser: HttpUser;
+  },
+  HttpUser
+>> = ({ open, close, token, data: { oldUser }, onSuccess }) => {
   return (
     <OperationDialog
-      open={props.open}
-      close={props.close}
+      open={open}
+      close={close}
       title="Caution"
       titleColor="dangerous"
       inputPrompt={() => (
         <>
-          {"You are change the username of user "}
-          <UsernameLabel>{props.username}</UsernameLabel>
-          {" !"}
+          You are change the password of user
+          <UsernameLabel>{oldUser.username}</UsernameLabel> !
         </>
       )}
-      inputScheme={[{ type: "text", label: "New Username" }]}
-      onProcess={([newUsername]) => {
-        return props.process(newUsername);
-      }}
-    />
-  );
-};
-
-const UserChangePasswordDialog: React.FC<UserModifyDialogProps<string>> = (
-  props
-) => {
-  return (
-    <OperationDialog
-      open={props.open}
-      close={props.close}
-      title="Caution"
-      titleColor="dangerous"
-      inputPrompt={() => (
-        <>
-          {"You are change the password of user "}
-          <UsernameLabel>{props.username}</UsernameLabel>
-          {" !"}
-        </>
-      )}
-      inputScheme={[{ type: "text", label: "New Password" }]}
-      onProcess={([newPassword]) => {
-        return props.process(newPassword);
-      }}
-    />
-  );
-};
-
-interface UserChangePermissionDialogProps extends DialogProps {
-  username: string;
-  newPermission: boolean;
-  process: () => Promise<void>;
-}
-
-const UserChangePermissionDialog: React.FC<UserChangePermissionDialogProps> = (
-  props
-) => {
-  return (
-    <OperationDialog
-      open={props.open}
-      close={props.close}
-      title="Caution"
-      titleColor="dangerous"
-      inputPrompt={() => (
-        <>
-          {"You are change user "}
-          <UsernameLabel>{props.username}</UsernameLabel>
-          {" to "}
-          <span style={{ color: "orange" }}>
-            {props.newPermission ? "administrator" : "normal user"}
-          </span>
-          {" !"}
-        </>
-      )}
-      onProcess={props.process}
+      inputScheme={
+        [
+          { type: "text", label: "New Username", initValue: oldUser.username },
+          { type: "text", label: "New Password" },
+          { type: "text", label: "New Nickname", initValue: oldUser.nickname },
+        ] as const
+      }
+      onProcess={([username, password, nickname]) =>
+        getHttpUserClient().patch(
+          oldUser.username,
+          {
+            username: username !== oldUser.username ? username : undefined,
+            password: password !== "" ? password : undefined,
+            nickname: nickname !== oldUser.nickname ? nickname : undefined,
+          },
+          token
+        )
+      }
+      onSuccessAndClose={onSuccess}
     />
   );
 };
@@ -279,23 +174,18 @@ const UserAdmin: React.FC<UserAdminProps> = (props) => {
     | {
         type: "create";
       }
-    | { type: TDelete; username: string }
     | {
-        type: TChangeUsername;
-        username: string;
+        type: TModify;
+        user: HttpUser;
       }
-    | {
-        type: TChangePassword;
-        username: string;
-      }
-    | {
-        type: TChangePermission;
-        username: string;
-        newPermission: boolean;
-      };
+    | { type: TDelete; username: string };
 
   const [users, setUsers] = useState<User[] | null>(null);
   const [dialog, setDialog] = useState<DialogInfo>(null);
+  const [usersVersion, setUsersVersion] = useState<number>(0);
+  const updateUsers = (): void => {
+    setUsersVersion(usersVersion + 1);
+  };
 
   const token = props.user.token;
 
@@ -311,20 +201,19 @@ const UserAdmin: React.FC<UserAdminProps> = (props) => {
     return () => {
       subscribe = false;
     };
-  }, []);
+  }, [usersVersion]);
 
   let dialogNode: React.ReactNode;
-  if (dialog)
+  if (dialog) {
     switch (dialog.type) {
       case "create":
         dialogNode = (
           <CreateUserDialog
             open
             close={() => setDialog(null)}
-            process={async (user) => {
-              const u = await createUser(user, token);
-              setUsers((oldUsers) => [...(oldUsers ?? []), u]);
-            }}
+            token={token}
+            data={undefined}
+            onSuccess={updateUsers}
           />
         );
         break;
@@ -333,52 +222,25 @@ const UserAdmin: React.FC<UserAdminProps> = (props) => {
           <UserDeleteDialog
             open
             close={() => setDialog(null)}
-            username={dialog.username}
-            process={async () => {
-              await deleteUser(dialog.username, token);
-              setUsers((oldUsers) =>
-                (oldUsers ?? []).filter((u) => u.username !== dialog.username)
-              );
-            }}
-          />
-        );
-        break;
-      case kChangeUsername:
-        dialogNode = (
-          <UserChangeUsernameDialog
-            open
-            close={() => setDialog(null)}
-            username={dialog.username}
-            process={async (newUsername) => {
-              await changeUsername(dialog.username, newUsername, token);
-              setUsers((oldUsers) => {
-                const users = (oldUsers ?? []).slice();
-                const findedUser = users.find(
-                  (u) => u.username === dialog.username
-                );
-                if (findedUser) findedUser.username = newUsername;
-                return users;
-              });
-            }}
+            token={token}
+            data={{ username: dialog.username }}
+            onSuccess={updateUsers}
           />
         );
         break;
-      case kChangePassword:
+      case kModify:
         dialogNode = (
-          <UserChangePasswordDialog
+          <UserModifyDialog
             open
             close={() => setDialog(null)}
-            username={dialog.username}
-            process={async (newPassword) => {
-              await changePassword(dialog.username, newPassword, token);
-            }}
+            token={token}
+            data={{ oldUser: dialog.user }}
+            onSuccess={updateUsers}
           />
         );
         break;
-      case kChangePermission: {
-        break;
-      }
     }
+  }
 
   if (users) {
     const userComponents = users.map((user) => {
@@ -386,24 +248,40 @@ const UserAdmin: React.FC<UserAdminProps> = (props) => {
         <UserItem
           key={user.username}
           user={user}
-          onContextMenu={(item) => {}}
+          on={{
+            modify: () => {
+              setDialog({
+                type: "modify",
+                user,
+              });
+            },
+            delete: () => {
+              setDialog({
+                type: "delete",
+                username: user.username,
+              });
+            },
+          }}
         />
       );
     });
 
     return (
       <AdminSubPage>
-        <Button
-          variant="success"
-          onClick={() =>
-            setDialog({
-              type: "create",
-            })
-          }
-          className="align-self-end"
-        >
-          Create User
-        </Button>
+        <Row className="justify-content-end my-2">
+          <Col xs="auto">
+            <Button
+              variant="success"
+              onClick={() =>
+                setDialog({
+                  type: "create",
+                })
+              }
+            >
+              Create User
+            </Button>
+          </Col>
+        </Row>
         {userComponents}
         {dialogNode}
       </AdminSubPage>
-- 
cgit v1.2.3

