feat: update workspace detail page

This commit is contained in:
steven 2022-10-04 18:51:53 +08:00
parent 922f8e6f95
commit 572a93c5f0
9 changed files with 94 additions and 121 deletions

View File

@ -1,5 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect } from "react";
import { deleteWorkspaceUser, upsertWorkspaceUser } from "../helpers/api"; import { deleteWorkspaceUser, upsertWorkspaceUser } from "../helpers/api";
import { useAppSelector } from "../store";
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import { workspaceService } from "../services"; import { workspaceService } from "../services";
import toastHelper from "./Toast"; import toastHelper from "./Toast";
@ -11,36 +13,25 @@ const userRoles = ["Admin", "User"];
interface Props { interface Props {
workspaceId: WorkspaceId; workspaceId: WorkspaceId;
workspaceUser: WorkspaceUser;
userList: WorkspaceUser[];
}
interface State {
workspaceUserList: WorkspaceUser[];
} }
const MemberListView: React.FC<Props> = (props: Props) => { const MemberListView: React.FC<Props> = (props: Props) => {
const { workspaceId, workspaceUser: currentUser } = props; const { workspaceId } = props;
const [state, setState] = useState<State>({ const user = useAppSelector((state) => state.user.user) as User;
workspaceUserList: [], const { workspaceList } = useAppSelector((state) => state.workspace);
}); const workspace = workspaceList.find((workspace) => workspace.id === workspaceId) ?? unknownWorkspace;
const currentUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
const loadingState = useLoading(); const loadingState = useLoading();
const fetchWorkspaceUserList = async () => {
loadingState.setLoading();
try {
const [workspaceUserList] = await Promise.all([workspaceService.getWorkspaceUserList(workspaceId)]);
setState({
workspaceUserList: workspaceUserList,
});
} finally {
loadingState.setFinish();
}
};
useEffect(() => { useEffect(() => {
fetchWorkspaceUserList(); const workspace = workspaceService.getWorkspaceById(workspaceId);
}, [props]); if (!workspace) {
toastHelper.error("workspace not found");
return;
}
loadingState.setFinish();
}, []);
const handleWorkspaceUserRoleChange = async (workspaceUser: WorkspaceUser, role: Role) => { const handleWorkspaceUserRoleChange = async (workspaceUser: WorkspaceUser, role: Role) => {
if (workspaceUser.userId === currentUser.userId) { if (workspaceUser.userId === currentUser.userId) {
@ -53,20 +44,20 @@ const MemberListView: React.FC<Props> = (props: Props) => {
userId: workspaceUser.userId, userId: workspaceUser.userId,
role, role,
}); });
await fetchWorkspaceUserList(); await workspaceService.fetchWorkspaceById(workspaceId);
}; };
const handleDeleteWorkspaceUserButtonClick = (workspaceUser: WorkspaceUser) => { const handleDeleteWorkspaceUserButtonClick = (workspaceUser: WorkspaceUser) => {
showCommonDialog({ showCommonDialog({
title: "Delete Workspace Member", title: "Delete Workspace Member",
content: `Are you sure to delete member \`${workspaceUser.user.name}\` in this workspace?`, content: `Are you sure to delete member \`${workspaceUser.name}\` in this workspace?`,
style: "warning", style: "warning",
onConfirm: async () => { onConfirm: async () => {
await deleteWorkspaceUser({ await deleteWorkspaceUser({
workspaceId: workspaceId, workspaceId: workspaceId,
userId: workspaceUser.userId, userId: workspaceUser.userId,
}); });
await fetchWorkspaceUserList(); await workspaceService.fetchWorkspaceById(workspaceId);
}, },
}); });
}; };
@ -76,11 +67,11 @@ const MemberListView: React.FC<Props> = (props: Props) => {
{loadingState.isLoading ? ( {loadingState.isLoading ? (
<></> <></>
) : ( ) : (
state.workspaceUserList.map((workspaceUser) => { workspace.workspaceUserList.map((workspaceUser) => {
return ( return (
<div key={workspaceUser.userId} className="w-full flex flex-row justify-between items-start border px-6 py-4 mb-3 rounded-lg"> <div key={workspaceUser.userId} className="w-full flex flex-row justify-between items-start border px-6 py-4 mb-3 rounded-lg">
<div className="flex flex-row justify-start items-center mr-4"> <div className="flex flex-row justify-start items-center mr-4">
<span>{workspaceUser.user.name}</span> <span>{workspaceUser.name}</span>
{currentUser.userId === workspaceUser.userId && <span className="ml-2 text-gray-400">(yourself)</span>} {currentUser.userId === workspaceUser.userId && <span className="ml-2 text-gray-400">(yourself)</span>}
</div> </div>
<div className="flex flex-row justify-end items-center"> <div className="flex flex-row justify-end items-center">

View File

@ -1,5 +1,6 @@
import { Dialog, DialogContent, DialogTitle } from "@mui/material"; import { Dialog, DialogContent, DialogTitle } from "@mui/material";
import { useState } from "react"; import { useState } from "react";
import { workspaceService } from "../services";
import { UNKNOWN_ID } from "../helpers/consts"; import { UNKNOWN_ID } from "../helpers/consts";
import { upsertWorkspaceUser } from "../helpers/api"; import { upsertWorkspaceUser } from "../helpers/api";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
@ -59,6 +60,8 @@ const UpsertWorkspaceUserDialog: React.FC<Props> = (props: Props) => {
...state.workspaceUserUpsert, ...state.workspaceUserUpsert,
}); });
await workspaceService.fetchWorkspaceById(workspaceId);
if (onConfirm) { if (onConfirm) {
onConfirm(); onConfirm();
} else { } else {

View File

@ -14,8 +14,6 @@ interface Props {
} }
interface State { interface State {
workspace: Workspace;
workspaceUser: WorkspaceUser;
showEditWorkspaceDialog: boolean; showEditWorkspaceDialog: boolean;
} }
@ -24,11 +22,12 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
const navigate = useNavigate(); const navigate = useNavigate();
const user = useAppSelector((state) => state.user.user) as User; const user = useAppSelector((state) => state.user.user) as User;
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
workspace: unknownWorkspace,
workspaceUser: unknownWorkspaceUser,
showEditWorkspaceDialog: false, showEditWorkspaceDialog: false,
}); });
const { workspaceList } = useAppSelector((state) => state.workspace);
const loadingState = useLoading(); const loadingState = useLoading();
const workspace = workspaceList.find((workspace) => workspace.id === workspaceId) ?? unknownWorkspace;
const workspaceUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
useEffect(() => { useEffect(() => {
const workspace = workspaceService.getWorkspaceById(workspaceId); const workspace = workspaceService.getWorkspaceById(workspaceId);
@ -37,18 +36,7 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
return; return;
} }
loadingState.setLoading();
Promise.all([workspaceService.getWorkspaceUser(workspace.id, user.id)])
.then(([workspaceUser]) => {
setState({
...state,
workspace,
workspaceUser,
});
})
.finally(() => {
loadingState.setFinish(); loadingState.setFinish();
});
}, []); }, []);
const handleEditWorkspaceButtonClick = () => { const handleEditWorkspaceButtonClick = () => {
@ -58,32 +46,22 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
}); });
}; };
const handleEditWorkspaceDialogConfirm = () => { const handleEditWorkspaceDialogConfirm = async () => {
const prevWorkspace = state.workspace;
const workspace = workspaceService.getWorkspaceById(workspaceId);
if (!workspace) {
toastHelper.error("workspace not found");
return;
}
setState({ setState({
...state, ...state,
workspace: workspace,
showEditWorkspaceDialog: false, showEditWorkspaceDialog: false,
}); });
const workspace = await workspaceService.fetchWorkspaceById(workspaceId);
if (prevWorkspace.name !== workspace.name) {
navigate(`/${workspace.name}#setting`); navigate(`/${workspace.name}#setting`);
}
}; };
const handleDeleteWorkspaceButtonClick = () => { const handleDeleteWorkspaceButtonClick = () => {
showCommonDialog({ showCommonDialog({
title: "Delete Workspace", title: "Delete Workspace",
content: `Are you sure to delete workspace \`${state.workspace.name}\`?`, content: `Are you sure to delete workspace \`${workspace.name}\`?`,
style: "warning", style: "warning",
onConfirm: async () => { onConfirm: async () => {
await workspaceService.deleteWorkspaceById(state.workspace.id); await workspaceService.deleteWorkspaceById(workspace.id);
navigate("/"); navigate("/");
}, },
}); });
@ -92,12 +70,12 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
const handleExitWorkspaceButtonClick = () => { const handleExitWorkspaceButtonClick = () => {
showCommonDialog({ showCommonDialog({
title: "Exit Workspace", title: "Exit Workspace",
content: `Are you sure to exit workspace \`${state.workspace.name}\`?`, content: `Are you sure to exit workspace \`${workspace.name}\`?`,
style: "warning", style: "warning",
onConfirm: async () => { onConfirm: async () => {
await deleteWorkspaceUser({ await deleteWorkspaceUser({
workspaceId: state.workspace.id, workspaceId: workspace.id,
userId: state.workspaceUser.userId, userId: workspaceUser.userId,
}); });
navigate("/"); navigate("/");
}, },
@ -107,11 +85,11 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
return ( return (
<> <>
<div className="w-full flex flex-col justify-start items-start"> <div className="w-full flex flex-col justify-start items-start">
<p className="text-3xl mt-4 mb-4">{state.workspace.name}</p> <p className="text-3xl mt-4 mb-4">{workspace.name}</p>
<p className="mb-4">{state.workspace.description}</p> <p className="mb-4">{workspace.description}</p>
<div className="border-t pt-4 mt-2 flex flex-row justify-start items-center"> <div className="border-t pt-4 mt-2 flex flex-row justify-start items-center">
<div className="flex flex-row justify-start items-center space-x-2"> <div className="flex flex-row justify-start items-center space-x-2">
{state.workspaceUser.role === "ADMIN" ? ( {workspaceUser.role === "ADMIN" ? (
<> <>
<button className="border rounded-md px-3 leading-8 hover:shadow" onClick={handleEditWorkspaceButtonClick}> <button className="border rounded-md px-3 leading-8 hover:shadow" onClick={handleEditWorkspaceButtonClick}>
Edit Edit
@ -139,14 +117,14 @@ const WorkspaceSetting: React.FC<Props> = (props: Props) => {
{state.showEditWorkspaceDialog && ( {state.showEditWorkspaceDialog && (
<CreateWorkspaceDialog <CreateWorkspaceDialog
workspaceId={state.workspace.id} workspaceId={workspace.id}
onClose={() => { onClose={() => {
setState({ setState({
...state, ...state,
showEditWorkspaceDialog: false, showEditWorkspaceDialog: false,
}); });
}} }}
onConfirm={handleEditWorkspaceDialogConfirm} onConfirm={() => handleEditWorkspaceDialogConfirm()}
/> />
)} )}
</> </>

View File

@ -64,6 +64,10 @@ export function getWorkspaceList(find?: WorkspaceFind) {
return axios.get<ResponseObject<Workspace[]>>(`/api/workspace?${queryList.join("&")}`); return axios.get<ResponseObject<Workspace[]>>(`/api/workspace?${queryList.join("&")}`);
} }
export function getWorkspaceById(workspaceId: WorkspaceId) {
return axios.get<ResponseObject<Workspace>>(`/api/workspace/${workspaceId}`);
}
export function createWorkspace(create: WorkspaceCreate) { export function createWorkspace(create: WorkspaceCreate) {
return axios.post<ResponseObject<Workspace>>("/api/workspace", create); return axios.post<ResponseObject<Workspace>>("/api/workspace", create);
} }

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { NavLink, useLocation, useNavigate, useParams } from "react-router-dom"; import { NavLink, useLocation, useNavigate, useParams } from "react-router-dom";
import { shortcutService, userService, workspaceService } from "../services"; import { shortcutService, userService } from "../services";
import { useAppSelector } from "../store"; import { useAppSelector } from "../store";
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace"; import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
@ -15,9 +15,6 @@ import CreateShortcutDialog from "../components/CreateShortcutDialog";
import UpsertWorkspaceUserDialog from "../components/UpsertWorkspaceUserDialog"; import UpsertWorkspaceUserDialog from "../components/UpsertWorkspaceUserDialog";
interface State { interface State {
workspace: Workspace;
workspaceUser: WorkspaceUser;
userList: WorkspaceUser[];
showCreateShortcutDialog: boolean; showCreateShortcutDialog: boolean;
showUpsertWorkspaceUserDialog: boolean; showUpsertWorkspaceUserDialog: boolean;
} }
@ -27,15 +24,15 @@ const WorkspaceDetail: React.FC = () => {
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const user = useAppSelector((state) => state.user.user) as User; const user = useAppSelector((state) => state.user.user) as User;
const { workspaceList } = useAppSelector((state) => state.workspace);
const { shortcutList } = useAppSelector((state) => state.shortcut); const { shortcutList } = useAppSelector((state) => state.shortcut);
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
workspace: unknownWorkspace,
workspaceUser: unknownWorkspaceUser,
userList: [],
showCreateShortcutDialog: false, showCreateShortcutDialog: false,
showUpsertWorkspaceUserDialog: false, showUpsertWorkspaceUserDialog: false,
}); });
const loadingState = useLoading(); const loadingState = useLoading();
const workspace = workspaceList.find((workspace) => workspace.name === params.workspaceName) ?? unknownWorkspace;
const workspaceUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
useEffect(() => { useEffect(() => {
if (!userService.getState().user) { if (!userService.getState().user) {
@ -43,27 +40,12 @@ const WorkspaceDetail: React.FC = () => {
return; return;
} }
const workspace = workspaceService.getWorkspaceByName(params.workspaceName ?? "");
if (!workspace) { if (!workspace) {
toastHelper.error("workspace not found"); toastHelper.error("workspace not found");
return; return;
} }
loadingState.setLoading(); Promise.all([shortcutService.fetchWorkspaceShortcuts(workspace.id)]).finally(() => {
Promise.all([
shortcutService.fetchWorkspaceShortcuts(workspace.id),
workspaceService.getWorkspaceUser(workspace.id, user.id),
workspaceService.getWorkspaceUserList(workspace.id),
])
.then(([, workspaceUser, workspaceUserList]) => {
setState({
...state,
workspace,
workspaceUser,
userList: workspaceUserList,
});
})
.finally(() => {
loadingState.setFinish(); loadingState.setFinish();
}); });
}, [params.workspaceName]); }, [params.workspaceName]);
@ -120,12 +102,14 @@ const WorkspaceDetail: React.FC = () => {
> >
Shortcut Shortcut
</button> </button>
{workspaceUser.role === "ADMIN" && (
<button <button
className="w-full flex flex-row justify-start items-center px-3 leading-10 rounded cursor-pointer hover:bg-gray-100" className="w-full flex flex-row justify-start items-center px-3 leading-10 rounded cursor-pointer hover:bg-gray-100"
onClick={handleUpsertWorkspaceMemberButtonClick} onClick={handleUpsertWorkspaceMemberButtonClick}
> >
Member Member
</button> </button>
)}
</> </>
} }
actionsClassName="!w-32" actionsClassName="!w-32"
@ -139,17 +123,9 @@ const WorkspaceDetail: React.FC = () => {
</div> </div>
) : ( ) : (
<> <>
{location.hash === "#shortcuts" && <ShortcutListView workspaceId={state.workspace.id} shortcutList={shortcutList} />} {location.hash === "#shortcuts" && <ShortcutListView workspaceId={workspace.id} shortcutList={shortcutList} />}
{location.hash === "#members" && ( {location.hash === "#members" && <MemberListView workspaceId={workspace.id} />}
<MemberListView {location.hash === "#setting" && <WorkspaceSetting workspaceId={workspace.id} />}
// enforce to re-fetch member list.
key={Date.now()}
workspaceId={state.workspace.id}
workspaceUser={state.workspaceUser}
userList={state.userList}
/>
)}
{location.hash === "#setting" && <WorkspaceSetting workspaceId={state.workspace.id} />}
</> </>
)} )}
</div> </div>
@ -157,7 +133,7 @@ const WorkspaceDetail: React.FC = () => {
{state.showCreateShortcutDialog && ( {state.showCreateShortcutDialog && (
<CreateShortcutDialog <CreateShortcutDialog
workspaceId={state.workspace.id} workspaceId={workspace.id}
onClose={() => { onClose={() => {
setState({ setState({
...state, ...state,
@ -178,7 +154,7 @@ const WorkspaceDetail: React.FC = () => {
{state.showUpsertWorkspaceUserDialog && ( {state.showUpsertWorkspaceUserDialog && (
<UpsertWorkspaceUserDialog <UpsertWorkspaceUserDialog
workspaceId={state.workspace.id} workspaceId={workspace.id}
onClose={() => { onClose={() => {
setState({ setState({
...state, ...state,
@ -186,13 +162,10 @@ const WorkspaceDetail: React.FC = () => {
}); });
}} }}
onConfirm={async () => { onConfirm={async () => {
const workspaceUserList = await workspaceService.getWorkspaceUserList(state.workspace.id);
setState({ setState({
...state, ...state,
userList: workspaceUserList,
showUpsertWorkspaceUserDialog: false, showUpsertWorkspaceUserDialog: false,
}); });
if (location.hash !== "#members") { if (location.hash !== "#members") {
navigate("#members"); navigate("#members");
} }

View File

@ -1,6 +1,6 @@
import * as api from "../helpers/api"; import * as api from "../helpers/api";
import store from "../store"; import store from "../store";
import { createWorkspace, deleteWorkspace, patchWorkspace, setWorkspaceList } from "../store/modules/workspace"; import { createWorkspace, deleteWorkspace, patchWorkspace, setWorkspaceById, setWorkspaceList } from "../store/modules/workspace";
const convertResponseModelWorkspace = (workspace: Workspace): Workspace => { const convertResponseModelWorkspace = (workspace: Workspace): Workspace => {
return { return {
@ -22,6 +22,13 @@ const workspaceService = {
return workspaces; return workspaces;
}, },
fetchWorkspaceById: async (workspaceId: WorkspaceId) => {
const { data } = (await api.getWorkspaceById(workspaceId)).data;
const workspace = convertResponseModelWorkspace(data);
store.dispatch(setWorkspaceById(workspace));
return workspace;
},
getWorkspaceByName: (workspaceName: string) => { getWorkspaceByName: (workspaceName: string) => {
const workspaceList = workspaceService.getState().workspaceList; const workspaceList = workspaceService.getState().workspaceList;
for (const workspace of workspaceList) { for (const workspace of workspaceList) {

View File

@ -3,12 +3,14 @@ import { UNKNOWN_ID } from "../../helpers/consts";
export const unknownWorkspace = { export const unknownWorkspace = {
id: UNKNOWN_ID, id: UNKNOWN_ID,
} as Workspace; workspaceUserList: [],
} as unknown as Workspace;
export const unknownWorkspaceUser = { export const unknownWorkspaceUser = {
workspaceId: UNKNOWN_ID, workspaceId: UNKNOWN_ID,
userId: UNKNOWN_ID, userId: UNKNOWN_ID,
} as WorkspaceUser; role: "USER",
} as unknown as WorkspaceUser;
interface State { interface State {
workspaceList: Workspace[]; workspaceList: Workspace[];
@ -26,6 +28,18 @@ const workspaceSlice = createSlice({
workspaceList: action.payload, workspaceList: action.payload,
}; };
}, },
setWorkspaceById: (state, action: PayloadAction<Workspace>) => {
return {
...state,
workspaceList: state.workspaceList.map((s) => {
if (s.id === action.payload.id) {
return action.payload;
} else {
return s;
}
}),
};
},
createWorkspace: (state, action: PayloadAction<Workspace>) => { createWorkspace: (state, action: PayloadAction<Workspace>) => {
return { return {
...state, ...state,
@ -56,6 +70,6 @@ const workspaceSlice = createSlice({
}, },
}); });
export const { setWorkspaceList, createWorkspace, patchWorkspace, deleteWorkspace } = workspaceSlice.actions; export const { setWorkspaceList, setWorkspaceById, createWorkspace, patchWorkspace, deleteWorkspace } = workspaceSlice.actions;
export default workspaceSlice.reducer; export default workspaceSlice.reducer;

View File

@ -3,10 +3,11 @@ type Role = "ADMIN" | "USER";
interface WorkspaceUser { interface WorkspaceUser {
workspaceId: WorkspaceId; workspaceId: WorkspaceId;
userId: UserId; userId: UserId;
user: User;
role: Role; role: Role;
createdTs: TimeStamp; createdTs: TimeStamp;
updatedTs: TimeStamp; updatedTs: TimeStamp;
email: string;
name: string;
} }
interface WorkspaceUserUpsert { interface WorkspaceUserUpsert {

View File

@ -10,6 +10,8 @@ interface Workspace {
name: string; name: string;
description: string; description: string;
workspaceUserList: WorkspaceUser[];
} }
interface WorkspaceCreate { interface WorkspaceCreate {