diff --git a/web/src/components/CreateMemoRelationDialog.tsx b/web/src/components/CreateMemoRelationDialog.tsx index 98af069c..ecea9ba9 100644 --- a/web/src/components/CreateMemoRelationDialog.tsx +++ b/web/src/components/CreateMemoRelationDialog.tsx @@ -88,13 +88,13 @@ const CreateMemoRelationDialog: React.FC = (props: Props) => { return ( <> -
+

{"Add references"}

destroy()}>
-
+
= (props: Props) => { } onChange={(_, value) => setSelectedMemos(value)} /> -
+
diff --git a/web/src/components/Settings/MemberSection.tsx b/web/src/components/Settings/MemberSection.tsx index f0b14cfe..5e676e02 100644 --- a/web/src/components/Settings/MemberSection.tsx +++ b/web/src/components/Settings/MemberSection.tsx @@ -1,9 +1,10 @@ -import { Button, Dropdown, Input, Menu, MenuButton } from "@mui/joy"; +import { Button, Dropdown, Input, Menu, MenuButton, Radio, RadioGroup } from "@mui/joy"; +import { sortBy } from "lodash-es"; import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { userServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { UserNamePrefix, useUserStore } from "@/store/v1"; +import { UserNamePrefix, stringifyUserRole, useUserStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v2/common"; import { User, User_Role } from "@/types/proto/api/v2/user_service"; import { useTranslate } from "@/utils/i18n"; @@ -12,8 +13,7 @@ import { showCommonDialog } from "../Dialog/CommonDialog"; import Icon from "../Icon"; interface State { - createUserUsername: string; - createUserPassword: string; + creatingUser: User; } const MemberSection = () => { @@ -21,36 +21,56 @@ const MemberSection = () => { const currentUser = useCurrentUser(); const userStore = useUserStore(); const [state, setState] = useState({ - createUserUsername: "", - createUserPassword: "", + creatingUser: User.fromPartial({ + username: "", + password: "", + role: User_Role.USER, + }), }); - const [userList, setUserList] = useState([]); + const [users, setUsers] = useState([]); + const sortedUsers = sortBy(users, "id"); useEffect(() => { - fetchUserList(); + fetchUsers(); }, []); - const fetchUserList = async () => { + const fetchUsers = async () => { const users = await userStore.fetchUsers(); - setUserList(users); + setUsers(users); }; const handleUsernameInputChange = (event: React.ChangeEvent) => { setState({ ...state, - createUserUsername: event.target.value, + creatingUser: { + ...state.creatingUser, + username: event.target.value, + }, }); }; const handlePasswordInputChange = (event: React.ChangeEvent) => { setState({ ...state, - createUserPassword: event.target.value, + creatingUser: { + ...state.creatingUser, + password: event.target.value, + }, + }); + }; + + const handleUserRoleInputChange = (event: React.ChangeEvent) => { + setState({ + ...state, + creatingUser: { + ...state.creatingUser, + role: Number(event.target.value) as User_Role, + }, }); }; const handleCreateUserBtnClick = async () => { - if (state.createUserUsername === "" || state.createUserPassword === "") { + if (state.creatingUser.username === "" || state.creatingUser.password === "") { toast.error(t("message.fill-form")); return; } @@ -58,18 +78,22 @@ const MemberSection = () => { try { await userServiceClient.createUser({ user: { - name: `${UserNamePrefix}${state.createUserUsername}`, - password: state.createUserPassword, - role: User_Role.USER, + name: `${UserNamePrefix}${state.creatingUser.username}`, + password: state.creatingUser.password, + role: state.creatingUser.role, }, }); } catch (error: any) { toast.error(error.details); } - await fetchUserList(); + await fetchUsers(); setState({ - createUserUsername: "", - createUserPassword: "", + ...state, + creatingUser: User.fromPartial({ + username: "", + password: "", + role: User_Role.USER, + }), }); }; @@ -91,7 +115,7 @@ const MemberSection = () => { }, updateMask: ["row_status"], }); - fetchUserList(); + fetchUsers(); }, }); }; @@ -104,7 +128,7 @@ const MemberSection = () => { }, updateMask: ["row_status"], }); - fetchUserList(); + fetchUsers(); }; const handleDeleteUserClick = (user: User) => { @@ -115,7 +139,7 @@ const MemberSection = () => { dialogName: "delete-user-dialog", onConfirm: async () => { await userStore.deleteUser(user.name); - fetchUserList(); + fetchUsers(); }, }); }; @@ -126,13 +150,25 @@ const MemberSection = () => {
{t("common.username")} - +
{t("common.password")} - +
-
+
+ {t("common.role")} + + + + +
+
@@ -144,9 +180,12 @@ const MemberSection = () => { - + @@ -160,9 +199,10 @@ const MemberSection = () => { - {userList.map((user) => ( + {sortedUsers.map((user) => ( - + +
+ ID + {t("common.role")} + {t("common.username")}
{user.id}{user.id}{stringifyUserRole(user.role)} {user.username} {user.rowStatus === RowStatus.ARCHIVED && "(Archived)"} diff --git a/web/src/locales/en.json b/web/src/locales/en.json index c468d766..f35d0545 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -64,7 +64,8 @@ "mark": "Mark", "profile": "Profile", "inbox": "Inbox", - "search": "Search" + "search": "Search", + "role": "Role" }, "router": { "go-to-home": "Go to Home", diff --git a/web/src/store/v1/user.ts b/web/src/store/v1/user.ts index 03726d92..0231f4b9 100644 --- a/web/src/store/v1/user.ts +++ b/web/src/store/v1/user.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { combine } from "zustand/middleware"; import { authServiceClient, userServiceClient } from "@/grpcweb"; -import { User, UserSetting } from "@/types/proto/api/v2/user_service"; +import { User, UserSetting, User_Role } from "@/types/proto/api/v2/user_service"; import { UserNamePrefix, extractUsernameFromName } from "./resourceName"; interface State { @@ -125,3 +125,13 @@ export const useUserStore = create( }, })) ); + +export const stringifyUserRole = (role: User_Role) => { + if (role === User_Role.HOST) { + return "Host"; + } else if (role === User_Role.ADMIN) { + return "Admin"; + } else { + return "User"; + } +};