mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-16 12:23:12 +00:00
chore: update workspace user pages
This commit is contained in:
parent
c0699f159e
commit
a642465f86
@ -189,10 +189,10 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function showCreateShortcutDialog(workspaceId: WorkspaceId, shortcutId?: ShortcutId): void {
|
export default function showCreateShortcutDialog(workspaceId: WorkspaceId, shortcutId?: ShortcutId, onDestory?: () => void): void {
|
||||||
generateDialog(
|
generateDialog(
|
||||||
{
|
{
|
||||||
className: "px-2 sm:px-0",
|
onDestory,
|
||||||
},
|
},
|
||||||
CreateShortcutDialog,
|
CreateShortcutDialog,
|
||||||
{
|
{
|
||||||
|
@ -127,13 +127,7 @@ const CreateWorkspaceDialog: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function showCreateWorkspaceDialog(workspaceId?: WorkspaceId): void {
|
export default function showCreateWorkspaceDialog(workspaceId?: WorkspaceId): void {
|
||||||
generateDialog(
|
generateDialog({}, CreateWorkspaceDialog, {
|
||||||
{
|
workspaceId,
|
||||||
className: "px-2 sm:px-0",
|
});
|
||||||
},
|
|
||||||
CreateWorkspaceDialog,
|
|
||||||
{
|
|
||||||
workspaceId,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import "../../less/base-dialog.less";
|
|||||||
interface DialogConfig {
|
interface DialogConfig {
|
||||||
className?: string;
|
className?: string;
|
||||||
clickSpaceDestroy?: boolean;
|
clickSpaceDestroy?: boolean;
|
||||||
|
onDestory?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props extends DialogConfig, DialogProps {
|
interface Props extends DialogConfig, DialogProps {
|
||||||
@ -38,7 +39,7 @@ const BaseDialog: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`dialog-wrapper ${className}`} onClick={handleSpaceClicked}>
|
<div className={`dialog-wrapper px-2 sm:px-0 ${className}`} onClick={handleSpaceClicked}>
|
||||||
<div className="dialog-container" onClick={(e) => e.stopPropagation()}>
|
<div className="dialog-container" onClick={(e) => e.stopPropagation()}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@ -67,6 +68,10 @@ export function generateDialog<T extends DialogProps>(
|
|||||||
dialog.unmount();
|
dialog.unmount();
|
||||||
tempDiv.remove();
|
tempDiv.remove();
|
||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
|
|
||||||
|
if (config.onDestory) {
|
||||||
|
config.onDestory();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
146
web/src/components/MemberListView.tsx
Normal file
146
web/src/components/MemberListView.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { deleteWorkspaceUser, upsertWorkspaceUser } from "../helpers/api";
|
||||||
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import { workspaceService } from "../services";
|
||||||
|
import toastHelper from "./Toast";
|
||||||
|
import Dropdown from "./common/Dropdown";
|
||||||
|
import { showCommonDialog } from "./Dialog/CommonDialog";
|
||||||
|
import Icon from "./Icon";
|
||||||
|
|
||||||
|
const userRoles = ["Admin", "User"];
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
workspaceUser: WorkspaceUser;
|
||||||
|
userList: WorkspaceUser[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
workspaceUserList: WorkspaceUser[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemberListView: React.FC<Props> = (props: Props) => {
|
||||||
|
const { workspaceId, workspaceUser: currentUser } = props;
|
||||||
|
const [state, setState] = useState<State>({
|
||||||
|
workspaceUserList: [],
|
||||||
|
});
|
||||||
|
const loadingState = useLoading();
|
||||||
|
|
||||||
|
const fetchWorkspaceUserList = () => {
|
||||||
|
loadingState.setLoading();
|
||||||
|
return Promise.all([workspaceService.getWorkspaceUserList(workspaceId)])
|
||||||
|
.then(([workspaceUserList]) => {
|
||||||
|
setState({
|
||||||
|
workspaceUserList: workspaceUserList,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loadingState.setFinish();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWorkspaceUserList();
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const handleWorkspaceUserRoleChange = async (workspaceUser: WorkspaceUser, role: Role) => {
|
||||||
|
if (workspaceUser.userId === currentUser.userId) {
|
||||||
|
toastHelper.error("Cannot change yourself.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await upsertWorkspaceUser({
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
userId: workspaceUser.userId,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
await fetchWorkspaceUserList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteWorkspaceUserButtonClick = (workspaceUser: WorkspaceUser) => {
|
||||||
|
showCommonDialog({
|
||||||
|
title: "Delete Workspace Member",
|
||||||
|
content: `Are you sure to delete member \`${workspaceUser.user.name}\` in this workspace?`,
|
||||||
|
style: "warning",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await deleteWorkspaceUser({
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
userId: workspaceUser.userId,
|
||||||
|
});
|
||||||
|
await fetchWorkspaceUserList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col justify-start items-start">
|
||||||
|
{loadingState.isLoading ? (
|
||||||
|
<></>
|
||||||
|
) : (
|
||||||
|
state.workspaceUserList.map((workspaceUser) => {
|
||||||
|
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 className="flex flex-row justify-start items-center mr-4">
|
||||||
|
<span>{workspaceUser.user.name}</span>
|
||||||
|
{currentUser.userId === workspaceUser.userId && <span className="ml-2 text-gray-400">(yourself)</span>}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-end items-center">
|
||||||
|
{currentUser.role === "ADMIN" ? (
|
||||||
|
<>
|
||||||
|
<Dropdown
|
||||||
|
className="mr-4"
|
||||||
|
trigger={
|
||||||
|
<button className="flex flex-row justify-end items-center cursor-pointer">
|
||||||
|
<span className="font-mono">{workspaceUser.role}</span>
|
||||||
|
<Icon.ChevronDown className="ml-1 w-5 h-auto text-gray-600" />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{userRoles.map((role) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={role}
|
||||||
|
className="w-full px-3 leading-10 flex flex-row justify-between items-center text-left cursor-pointer rounded whitespace-nowrap hover:bg-gray-100"
|
||||||
|
onClick={() => handleWorkspaceUserRoleChange(workspaceUser, role.toUpperCase() as Role)}
|
||||||
|
>
|
||||||
|
<span className="truncate">{role}</span>
|
||||||
|
{workspaceUser.role === role.toLocaleUpperCase() && <Icon.Check className="w-4 h-auto ml-1 shrink-0" />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
actionsClassName="!w-28 !-left-4"
|
||||||
|
></Dropdown>
|
||||||
|
<Dropdown
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="w-full px-3 text-left leading-10 cursor-pointer rounded text-red-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteWorkspaceUserButtonClick(workspaceUser);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
actionsClassName="!w-24"
|
||||||
|
></Dropdown>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="font-mono">{workspaceUser.role}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemberListView;
|
@ -1,9 +1,10 @@
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { shortcutService, workspaceService } from "../services";
|
import { shortcutService, workspaceService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
|
import { showCommonDialog } from "./Dialog/CommonDialog";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
workspaceId: WorkspaceId;
|
workspaceId: WorkspaceId;
|
||||||
@ -20,7 +21,14 @@ const ShortcutListView: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => {
|
const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => {
|
||||||
shortcutService.deleteShortcutById(shortcut.id);
|
showCommonDialog({
|
||||||
|
title: "Delete Shortcut",
|
||||||
|
content: `Are you sure to delete shortcut \`${shortcut.name}\` in this workspace?`,
|
||||||
|
style: "warning",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await shortcutService.deleteShortcutById(shortcut.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
138
web/src/components/UpsertWorkspaceUserDialog.tsx
Normal file
138
web/src/components/UpsertWorkspaceUserDialog.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
|
import { upsertWorkspaceUser } from "../helpers/api";
|
||||||
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import Icon from "./Icon";
|
||||||
|
import { generateDialog } from "./Dialog";
|
||||||
|
import toastHelper from "./Toast";
|
||||||
|
|
||||||
|
interface Props extends DialogProps {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
workspaceUserUpsert: WorkspaceUserUpsert;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpsertWorkspaceUserDialog: React.FC<Props> = (props: Props) => {
|
||||||
|
const { destroy, workspaceId } = props;
|
||||||
|
const [state, setState] = useState<State>({
|
||||||
|
workspaceUserUpsert: {
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
userId: UNKNOWN_ID,
|
||||||
|
role: "USER",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const requestState = useLoading(false);
|
||||||
|
|
||||||
|
const handleUserIdInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const text = e.target.value as string;
|
||||||
|
|
||||||
|
setState({
|
||||||
|
workspaceUserUpsert: Object.assign(state.workspaceUserUpsert, {
|
||||||
|
userId: Number(text),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUserRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const text = e.target.value as string;
|
||||||
|
|
||||||
|
setState({
|
||||||
|
workspaceUserUpsert: Object.assign(state.workspaceUserUpsert, {
|
||||||
|
role: text,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveBtnClick = async () => {
|
||||||
|
if (!state.workspaceUserUpsert.userId) {
|
||||||
|
toastHelper.error("User ID is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestState.setLoading();
|
||||||
|
try {
|
||||||
|
await upsertWorkspaceUser({
|
||||||
|
...state.workspaceUserUpsert,
|
||||||
|
});
|
||||||
|
destroy();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
toastHelper.error(error.response.data.error || error.response.data.message);
|
||||||
|
}
|
||||||
|
requestState.setFinish();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="max-w-full w-80 flex flex-row justify-between items-center mb-4">
|
||||||
|
<p className="text-base">Create Workspace Member</p>
|
||||||
|
<button className="rounded p-1 hover:bg-gray-100" onClick={destroy}>
|
||||||
|
<Icon.X className="w-5 h-auto text-gray-600" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col justify-start items-start">
|
||||||
|
<div className="w-full flex flex-col justify-start items-start mb-3">
|
||||||
|
<span className="mb-2">User ID</span>
|
||||||
|
<input
|
||||||
|
className="w-full rounded border text-sm shadow-inner px-2 py-2"
|
||||||
|
type="number"
|
||||||
|
value={state.workspaceUserUpsert.userId}
|
||||||
|
onChange={handleUserIdInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col justify-start items-start mb-3">
|
||||||
|
<span className="mb-2">Role</span>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="role"
|
||||||
|
id="role-user"
|
||||||
|
value="USER"
|
||||||
|
onChange={handleUserRoleInputChange}
|
||||||
|
checked={state.workspaceUserUpsert.role === "USER"}
|
||||||
|
/>
|
||||||
|
<label htmlFor="role-user" className="ml-1 mr-4">
|
||||||
|
User
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="role"
|
||||||
|
id="role-admin"
|
||||||
|
value="ADMIN"
|
||||||
|
onChange={handleUserRoleInputChange}
|
||||||
|
checked={state.workspaceUserUpsert.role === "ADMIN"}
|
||||||
|
/>
|
||||||
|
<label htmlFor="role-admin" className="ml-1">
|
||||||
|
Admin
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-row justify-end items-center">
|
||||||
|
<button
|
||||||
|
disabled={requestState.isLoading}
|
||||||
|
className={`rounded px-3 leading-9 shadow bg-green-600 text-white hover:bg-green-700 ${
|
||||||
|
requestState.isLoading ? "opacity-80" : ""
|
||||||
|
}`}
|
||||||
|
onClick={handleSaveBtnClick}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function showUpsertWorkspaceUserDialog(workspaceId: WorkspaceId, onDestory?: () => void) {
|
||||||
|
return generateDialog(
|
||||||
|
{
|
||||||
|
onDestory,
|
||||||
|
},
|
||||||
|
UpsertWorkspaceUserDialog,
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { workspaceService } from "../services";
|
import { workspaceService } from "../services";
|
||||||
|
import { showCommonDialog } from "./Dialog/CommonDialog";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
import showCreateWorkspaceDialog from "./CreateWorkspaceDialog";
|
import showCreateWorkspaceDialog from "./CreateWorkspaceDialog";
|
||||||
|
|
||||||
@ -11,7 +12,14 @@ const WorkspaceListView: React.FC<Props> = (props: Props) => {
|
|||||||
const { workspaceList } = props;
|
const { workspaceList } = props;
|
||||||
|
|
||||||
const handleDeleteWorkspaceButtonClick = (workspace: Workspace) => {
|
const handleDeleteWorkspaceButtonClick = (workspace: Workspace) => {
|
||||||
workspaceService.deleteWorkspaceById(workspace.id);
|
showCommonDialog({
|
||||||
|
title: "Delete Workspace",
|
||||||
|
content: `Are you sure to delete workspace \`${workspace.name}\`?`,
|
||||||
|
style: "warning",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await workspaceService.deleteWorkspaceById(workspace.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -76,6 +76,24 @@ export function deleteWorkspaceById(workspaceId: WorkspaceId) {
|
|||||||
return axios.delete(`/api/workspace/${workspaceId}`);
|
return axios.delete(`/api/workspace/${workspaceId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function upsertWorkspaceUser(upsert: WorkspaceUserUpsert) {
|
||||||
|
return axios.post<ResponseObject<WorkspaceUser>>(`/api/workspace/${upsert.workspaceId}/user`, upsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkspaceUserList(workspaceUserFind?: WorkspaceUserFind) {
|
||||||
|
return axios.get<ResponseObject<WorkspaceUser[]>>(`/api/workspace/${workspaceUserFind?.workspaceId}/user`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkspaceUser(workspaceUserFind?: WorkspaceUserFind) {
|
||||||
|
return axios.get<ResponseObject<WorkspaceUser>>(
|
||||||
|
`/api/workspace/${workspaceUserFind?.workspaceId}/user/${workspaceUserFind?.userId ?? ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteWorkspaceUser(workspaceUserDelete: WorkspaceUserDelete) {
|
||||||
|
return axios.delete(`/api/workspace/${workspaceUserDelete.workspaceId}/user/${workspaceUserDelete.userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function getShortcutList(shortcutFind?: ShortcutFind) {
|
export function getShortcutList(shortcutFind?: ShortcutFind) {
|
||||||
const queryList = [];
|
const queryList = [];
|
||||||
if (shortcutFind?.creatorId) {
|
if (shortcutFind?.creatorId) {
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
import { isNull, isUndefined } from "lodash-es";
|
||||||
|
|
||||||
|
export const isNullorUndefined = (value: any) => {
|
||||||
|
return isNull(value) || isUndefined(value);
|
||||||
|
};
|
||||||
|
|
||||||
export function getNowTimeStamp(): number {
|
export function getNowTimeStamp(): number {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,34 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { NavLink, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import { shortcutService, userService, workspaceService } from "../services";
|
import { shortcutService, userService, workspaceService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
|
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import toastHelper from "../components/Toast";
|
import toastHelper from "../components/Toast";
|
||||||
|
import Dropdown from "../components/common/Dropdown";
|
||||||
import Header from "../components/Header";
|
import Header from "../components/Header";
|
||||||
import ShortcutListView from "../components/ShortcutListView";
|
import ShortcutListView from "../components/ShortcutListView";
|
||||||
import { unknownWorkspace } from "../store/modules/workspace";
|
|
||||||
import showCreateShortcutDialog from "../components/CreateShortcutDialog";
|
import showCreateShortcutDialog from "../components/CreateShortcutDialog";
|
||||||
|
import MemberListView from "../components/MemberListView";
|
||||||
|
import showUpsertWorkspaceUserDialog from "../components/UpsertWorkspaceUserDialog";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
|
workspaceUser: WorkspaceUser;
|
||||||
|
userList: WorkspaceUser[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkspaceDetail: React.FC = () => {
|
const WorkspaceDetail: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const location = useLocation();
|
||||||
|
const user = useAppSelector((state) => state.user.user) as User;
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
workspace: unknownWorkspace,
|
workspace: unknownWorkspace,
|
||||||
|
workspaceUser: unknownWorkspaceUser,
|
||||||
|
userList: [],
|
||||||
});
|
});
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
|
||||||
@ -35,28 +44,91 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
workspace,
|
|
||||||
});
|
|
||||||
loadingState.setLoading();
|
loadingState.setLoading();
|
||||||
Promise.all([shortcutService.fetchWorkspaceShortcuts(workspace.id)]).finally(() => {
|
Promise.all([
|
||||||
loadingState.setFinish();
|
shortcutService.fetchWorkspaceShortcuts(workspace.id),
|
||||||
});
|
workspaceService.getWorkspaceUser(workspace.id, user.id),
|
||||||
|
workspaceService.getWorkspaceUserList(workspace.id),
|
||||||
|
])
|
||||||
|
.then(([, workspaceUser, workspaceUserList]) => {
|
||||||
|
setState({
|
||||||
|
workspace,
|
||||||
|
workspaceUser,
|
||||||
|
userList: workspaceUserList,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loadingState.setFinish();
|
||||||
|
});
|
||||||
}, [params.workspaceName]);
|
}, [params.workspaceName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.hash !== "#shortcuts" && location.hash !== "#members") {
|
||||||
|
navigate("#shortcuts");
|
||||||
|
}
|
||||||
|
}, [location.hash]);
|
||||||
|
|
||||||
|
const handleCreateShortcutButtonClick = () => {
|
||||||
|
showCreateShortcutDialog(state.workspace.id, undefined, async () => {
|
||||||
|
if (location.hash !== "#shortcuts") {
|
||||||
|
navigate("#shortcuts");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpsertWorkspaceMemberButtonClick = () => {
|
||||||
|
showUpsertWorkspaceUserDialog(state.workspace.id, async () => {
|
||||||
|
const workspaceUserList = await workspaceService.getWorkspaceUserList(state.workspace.id);
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
userList: workspaceUserList,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (location.hash !== "#members") {
|
||||||
|
navigate("#members");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col justify-start items-start">
|
<div className="w-full h-full flex flex-col justify-start items-start">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="mx-auto max-w-4xl w-full px-3 py-6 flex flex-col justify-start items-start">
|
<div className="mx-auto max-w-4xl w-full px-3 pb-6 flex flex-col justify-start items-start">
|
||||||
<div className="w-full flex flex-row justify-between items-center mb-4">
|
<div className="w-full flex flex-row justify-between items-center mt-4 mb-4">
|
||||||
<span className="font-mono text-gray-400">Shortcut List</span>
|
<div className="flex flex-row justify-start items-center space-x-4">
|
||||||
<button
|
<NavLink to="#shortcuts" className={`${location.hash === "#shortcuts" && "underline"}`}>
|
||||||
className="text-sm flex flex-row justify-start items-center border px-3 leading-10 rounded-lg cursor-pointer hover:shadow"
|
Shortcuts
|
||||||
onClick={() => showCreateShortcutDialog(state.workspace.id)}
|
</NavLink>
|
||||||
>
|
<NavLink to="#members" className={`${location.hash === "#members" && "underline"}`}>
|
||||||
<Icon.Plus className="w-4 h-auto mr-1" /> Create Shortcut
|
Members
|
||||||
</button>
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Dropdown
|
||||||
|
trigger={
|
||||||
|
<button className="w-32 flex flex-row justify-start items-center border px-3 leading-10 rounded-lg cursor-pointer hover:shadow">
|
||||||
|
<Icon.Plus className="w-4 h-auto mr-1" /> Add new...
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="w-full flex flex-row justify-start items-center px-3 leading-10 rounded cursor-pointer hover:bg-gray-100"
|
||||||
|
onClick={handleCreateShortcutButtonClick}
|
||||||
|
>
|
||||||
|
Shortcut
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-full flex flex-row justify-start items-center px-3 leading-10 rounded cursor-pointer hover:bg-gray-100"
|
||||||
|
onClick={handleUpsertWorkspaceMemberButtonClick}
|
||||||
|
>
|
||||||
|
Member
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
actionsClassName="!w-32"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{loadingState.isLoading ? (
|
{loadingState.isLoading ? (
|
||||||
<div className="py-4 w-full flex flex-row justify-center items-center">
|
<div className="py-4 w-full flex flex-row justify-center items-center">
|
||||||
@ -64,7 +136,12 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
loading
|
loading
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ShortcutListView workspaceId={state.workspace.id} shortcutList={shortcutList} />
|
<>
|
||||||
|
{location.hash === "#shortcuts" && <ShortcutListView workspaceId={state.workspace.id} shortcutList={shortcutList} />}
|
||||||
|
{location.hash === "#members" && (
|
||||||
|
<MemberListView workspaceId={state.workspace.id} workspaceUser={state.workspaceUser} userList={state.userList} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createBrowserRouter } from "react-router-dom";
|
import { createBrowserRouter, redirect } from "react-router-dom";
|
||||||
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import { userService, workspaceService } from "../services";
|
import { userService, workspaceService } from "../services";
|
||||||
import Auth from "../pages/Auth";
|
import Auth from "../pages/Auth";
|
||||||
import Home from "../pages/Home";
|
import Home from "../pages/Home";
|
||||||
@ -7,6 +8,10 @@ import WorkspaceDetail from "../pages/WorkspaceDetail";
|
|||||||
import ShortcutRedirector from "../pages/ShortcutRedirector";
|
import ShortcutRedirector from "../pages/ShortcutRedirector";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/user/auth",
|
||||||
|
element: <Auth />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
@ -16,12 +21,13 @@ const router = createBrowserRouter([
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { user } = userService.getState();
|
||||||
|
if (isNullorUndefined(user)) {
|
||||||
|
return redirect("/user/auth");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/user/auth",
|
|
||||||
element: <Auth />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/account",
|
path: "/account",
|
||||||
element: <UserDetail />,
|
element: <UserDetail />,
|
||||||
@ -31,6 +37,11 @@ const router = createBrowserRouter([
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { user } = userService.getState();
|
||||||
|
if (isNullorUndefined(user)) {
|
||||||
|
return redirect("/user/auth");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -43,6 +54,11 @@ const router = createBrowserRouter([
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { user } = userService.getState();
|
||||||
|
if (isNullorUndefined(user)) {
|
||||||
|
return redirect("/user/auth");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,6 +71,11 @@ const router = createBrowserRouter([
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { user } = userService.getState();
|
||||||
|
if (isNullorUndefined(user)) {
|
||||||
|
return redirect("/user/auth");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -60,6 +60,25 @@ const workspaceService = {
|
|||||||
await api.deleteWorkspaceById(id);
|
await api.deleteWorkspaceById(id);
|
||||||
store.dispatch(deleteWorkspace(id));
|
store.dispatch(deleteWorkspace(id));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getWorkspaceUserList: async (id: WorkspaceId) => {
|
||||||
|
const { data } = (
|
||||||
|
await api.getWorkspaceUserList({
|
||||||
|
workspaceId: id,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getWorkspaceUser: async (workspaceId: WorkspaceId, userId: UserId) => {
|
||||||
|
const { data } = (
|
||||||
|
await api.getWorkspaceUser({
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
userId: userId,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default workspaceService;
|
export default workspaceService;
|
||||||
|
@ -5,6 +5,11 @@ export const unknownWorkspace = {
|
|||||||
id: UNKNOWN_ID,
|
id: UNKNOWN_ID,
|
||||||
} as Workspace;
|
} as Workspace;
|
||||||
|
|
||||||
|
export const unknownWorkspaceUser = {
|
||||||
|
workspaceId: UNKNOWN_ID,
|
||||||
|
userId: UNKNOWN_ID,
|
||||||
|
} as WorkspaceUser;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
workspaceList: Workspace[];
|
workspaceList: Workspace[];
|
||||||
}
|
}
|
||||||
|
27
web/src/types/modules/WorkspaceUser.d.ts
vendored
Normal file
27
web/src/types/modules/WorkspaceUser.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
type Role = "ADMIN" | "USER";
|
||||||
|
|
||||||
|
interface WorkspaceUser {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
userId: UserId;
|
||||||
|
user: User;
|
||||||
|
role: Role;
|
||||||
|
createdTs: TimeStamp;
|
||||||
|
updatedTs: TimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkspaceUserUpsert {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
userId: UserId;
|
||||||
|
role: Role;
|
||||||
|
updatedTs?: TimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkspaceUserFind {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
userId?: UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkspaceUserDelete {
|
||||||
|
workspaceId: WorkspaceId;
|
||||||
|
userId: UserId;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user