mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-18 21:19:44 +00:00
chore: update frontend modules
This commit is contained in:
parent
bd627fb250
commit
98fb1264c3
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,9 +7,7 @@ tmp
|
|||||||
# Frontend asset
|
# Frontend asset
|
||||||
web/dist
|
web/dist
|
||||||
|
|
||||||
web-r
|
|
||||||
|
|
||||||
# build folder
|
# build folder
|
||||||
build
|
build
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
2
web/.gitignore
vendored
2
web/.gitignore
vendored
@ -3,5 +3,3 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
.yarn/*
|
|
||||||
|
@ -6,7 +6,6 @@ import useLoading from "../hooks/useLoading";
|
|||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
shortcutId?: ShortcutId;
|
shortcutId?: ShortcutId;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm?: () => void;
|
onConfirm?: () => void;
|
||||||
@ -17,10 +16,9 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { onClose, onConfirm, workspaceId, shortcutId } = props;
|
const { onClose, onConfirm, shortcutId } = props;
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
shortcutCreate: {
|
shortcutCreate: {
|
||||||
workspaceId: workspaceId,
|
|
||||||
name: "",
|
name: "",
|
||||||
link: "",
|
link: "",
|
||||||
description: "",
|
description: "",
|
||||||
@ -36,7 +34,6 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
workspaceId: shortcutTemp.workspaceId,
|
|
||||||
name: shortcutTemp.name,
|
name: shortcutTemp.name,
|
||||||
link: shortcutTemp.link,
|
link: shortcutTemp.link,
|
||||||
description: shortcutTemp.description,
|
description: shortcutTemp.description,
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
import { Button, Input, Modal, ModalDialog } from "@mui/joy";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { workspaceService } from "../services";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId?: WorkspaceId;
|
|
||||||
onClose: () => void;
|
|
||||||
onConfirm?: (workspace: Workspace) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
workspaceCreate: WorkspaceCreate;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateWorkspaceDialog: React.FC<Props> = (props: Props) => {
|
|
||||||
const { onClose, onConfirm, workspaceId } = props;
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
workspaceCreate: {
|
|
||||||
name: "",
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const requestState = useLoading(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (workspaceId) {
|
|
||||||
const workspaceTemp = workspaceService.getWorkspaceById(workspaceId);
|
|
||||||
if (workspaceTemp) {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
workspaceCreate: Object.assign(state.workspaceCreate, {
|
|
||||||
name: workspaceTemp.name,
|
|
||||||
title: workspaceTemp.title,
|
|
||||||
description: workspaceTemp.description,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [workspaceId]);
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, key: string) => {
|
|
||||||
const text = e.target.value as string;
|
|
||||||
const tempObject = {} as any;
|
|
||||||
tempObject[key] = text;
|
|
||||||
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
workspaceCreate: Object.assign(state.workspaceCreate, tempObject),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveBtnClick = async () => {
|
|
||||||
if (!state.workspaceCreate.name) {
|
|
||||||
toast.error("ID is required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!state.workspaceCreate.title) {
|
|
||||||
toast.error("Title is required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestState.setLoading();
|
|
||||||
try {
|
|
||||||
let workspace;
|
|
||||||
if (workspaceId) {
|
|
||||||
workspace = await workspaceService.patchWorkspace({
|
|
||||||
id: workspaceId,
|
|
||||||
...state.workspaceCreate,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
workspace = await workspaceService.createWorkspace({
|
|
||||||
...state.workspaceCreate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onConfirm) {
|
|
||||||
onConfirm(workspace);
|
|
||||||
} else {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(JSON.stringify(error.response.data));
|
|
||||||
}
|
|
||||||
requestState.setFinish();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal open={true}>
|
|
||||||
<ModalDialog>
|
|
||||||
<div className="flex flex-row justify-between items-center w-80">
|
|
||||||
<span className="text-lg font-medium">{workspaceId ? "Edit Workspace" : "Create Workspace"}</span>
|
|
||||||
<Button variant="plain" onClick={onClose}>
|
|
||||||
<Icon.X className="w-5 h-auto text-gray-600" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start mb-3">
|
|
||||||
<span className="mb-2">
|
|
||||||
ID <span className="text-red-600">*</span>
|
|
||||||
</span>
|
|
||||||
<Input
|
|
||||||
className="w-full"
|
|
||||||
type="text"
|
|
||||||
placeholder="Workspace ID is an unique identifier for your workspace."
|
|
||||||
value={state.workspaceCreate.name}
|
|
||||||
onChange={(e) => handleInputChange(e, "name")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start mb-3">
|
|
||||||
<span className="mb-2">
|
|
||||||
Name <span className="text-red-600">*</span>
|
|
||||||
</span>
|
|
||||||
<Input className="w-full" type="text" value={state.workspaceCreate.title} onChange={(e) => handleInputChange(e, "title")} />
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start mb-3">
|
|
||||||
<span className="mb-2">Description</span>
|
|
||||||
<Input
|
|
||||||
className="w-full"
|
|
||||||
type="text"
|
|
||||||
value={state.workspaceCreate.description}
|
|
||||||
onChange={(e) => handleInputChange(e, "description")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
|
||||||
<Button color="neutral" variant="plain" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalDialog>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CreateWorkspaceDialog;
|
|
@ -1,31 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import Dropdown from "./common/Dropdown";
|
import Dropdown from "./common/Dropdown";
|
||||||
import CreateWorkspaceDialog from "./CreateWorkspaceDialog";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
showCreateWorkspaceDialog: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const params = useParams();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useAppSelector((state) => state.user);
|
const { user } = useAppSelector((state) => state.user);
|
||||||
const { workspaceList } = useAppSelector((state) => state.workspace);
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
const activedWorkspace = workspaceList.find((workspace) => workspace.name === params.workspaceName ?? "");
|
|
||||||
|
|
||||||
const handleCreateWorkspaceButtonClick = () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSignOutButtonClick = async () => {
|
const handleSignOutButtonClick = async () => {
|
||||||
await userService.doSignOut();
|
await userService.doSignOut();
|
||||||
@ -41,43 +22,6 @@ const Header: React.FC = () => {
|
|||||||
<img src="/logo.png" className="w-8 h-auto mr-2" alt="" />
|
<img src="/logo.png" className="w-8 h-auto mr-2" alt="" />
|
||||||
Shortify
|
Shortify
|
||||||
</Link>
|
</Link>
|
||||||
{workspaceList.length > 0 && activedWorkspace !== undefined && (
|
|
||||||
<>
|
|
||||||
<span className="font-mono mx-1 text-gray-200">/</span>
|
|
||||||
<Dropdown
|
|
||||||
trigger={
|
|
||||||
<button className="flex flex-row justify-end items-center cursor-pointer">
|
|
||||||
<span className="font-mono">{activedWorkspace?.title}</span>
|
|
||||||
<Icon.ChevronDown className="ml-1 w-5 h-auto text-gray-600" />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
actions={
|
|
||||||
<>
|
|
||||||
{workspaceList.map((workspace) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={workspace.id}
|
|
||||||
to={`/${workspace.name}`}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<span className="truncate">{workspace.title}</span>
|
|
||||||
{workspace.name === activedWorkspace?.name && <Icon.Check className="w-4 h-auto ml-1 shrink-0" />}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<hr className="w-full border-t my-1 border-t-gray-100" />
|
|
||||||
<button
|
|
||||||
className="w-full flex flex-row justify-start items-center px-3 leading-10 rounded cursor-pointer hover:bg-gray-100"
|
|
||||||
onClick={handleCreateWorkspaceButtonClick}
|
|
||||||
>
|
|
||||||
<Icon.Plus className="w-4 h-auto mr-2" /> Create Workspace
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
actionsClassName="!w-48 !-left-4"
|
|
||||||
></Dropdown>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0">
|
||||||
{user ? (
|
{user ? (
|
||||||
@ -114,24 +58,6 @@ const Header: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.showCreateWorkspaceDialog && (
|
|
||||||
<CreateWorkspaceDialog
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onConfirm={(workspace: Workspace) => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
navigate(`/${workspace.name}`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { deleteWorkspaceUser, upsertWorkspaceUser } from "../helpers/api";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
|
||||||
import { workspaceService } from "../services";
|
|
||||||
import Dropdown from "./common/Dropdown";
|
|
||||||
import { showCommonDialog } from "./Alert";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
|
|
||||||
const userRoles = ["Admin", "User"];
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MemberListView: React.FC<Props> = (props: Props) => {
|
|
||||||
const { workspaceId } = props;
|
|
||||||
const user = useAppSelector((state) => state.user.user) as User;
|
|
||||||
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();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const workspace = workspaceService.getWorkspaceById(workspaceId);
|
|
||||||
if (!workspace) {
|
|
||||||
toast.error("workspace not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingState.setFinish();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleWorkspaceUserRoleChange = async (workspaceUser: WorkspaceUser, role: Role) => {
|
|
||||||
if (workspaceUser.userId === currentUser.userId) {
|
|
||||||
toast.error("Cannot change yourself.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await upsertWorkspaceUser({
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
userId: workspaceUser.userId,
|
|
||||||
role,
|
|
||||||
});
|
|
||||||
await workspaceService.fetchWorkspaceById(workspaceId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteWorkspaceUserButtonClick = (workspaceUser: WorkspaceUser) => {
|
|
||||||
showCommonDialog({
|
|
||||||
title: "Delete Workspace Member",
|
|
||||||
content: `Are you sure to delete member \`${workspaceUser.displayName}\` in this workspace?`,
|
|
||||||
style: "danger",
|
|
||||||
onConfirm: async () => {
|
|
||||||
await deleteWorkspaceUser({
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
userId: workspaceUser.userId,
|
|
||||||
});
|
|
||||||
await workspaceService.fetchWorkspaceById(workspaceId);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
|
||||||
{loadingState.isLoading ? (
|
|
||||||
<></>
|
|
||||||
) : (
|
|
||||||
workspace.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.displayName}</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;
|
|
@ -3,9 +3,8 @@ import copy from "copy-to-clipboard";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
import { UNKNOWN_ID } from "../helpers/consts";
|
||||||
import { shortcutService, workspaceService } from "../services";
|
import { shortcutService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
|
|
||||||
import { absolutifyLink } from "../helpers/utils";
|
import { absolutifyLink } from "../helpers/utils";
|
||||||
import { showCommonDialog } from "./Alert";
|
import { showCommonDialog } from "./Alert";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -13,7 +12,6 @@ import Dropdown from "./common/Dropdown";
|
|||||||
import CreateShortcutDialog from "./CreateShortcutDialog";
|
import CreateShortcutDialog from "./CreateShortcutDialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
shortcutList: Shortcut[];
|
shortcutList: Shortcut[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,22 +20,18 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ShortcutListView: React.FC<Props> = (props: Props) => {
|
const ShortcutListView: React.FC<Props> = (props: Props) => {
|
||||||
const { workspaceId, shortcutList } = props;
|
const { shortcutList } = props;
|
||||||
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 [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
currentEditingShortcutId: UNKNOWN_ID,
|
currentEditingShortcutId: UNKNOWN_ID,
|
||||||
});
|
});
|
||||||
const workspace = workspaceList.find((workspace) => workspace.id === workspaceId) ?? unknownWorkspace;
|
|
||||||
const workspaceUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
|
|
||||||
|
|
||||||
const havePermission = (shortcut: Shortcut) => {
|
const havePermission = (shortcut: Shortcut) => {
|
||||||
return workspaceUser.role === "ADMIN" || shortcut.creatorId === user.id;
|
return user.role === "ADMIN" || shortcut.creatorId === user.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyButtonClick = (shortcut: Shortcut) => {
|
const handleCopyButtonClick = (shortcut: Shortcut) => {
|
||||||
const workspace = workspaceService.getWorkspaceById(workspaceId);
|
copy(absolutifyLink(`/${shortcut.name}`));
|
||||||
copy(absolutifyLink(`/${workspace?.name}/${shortcut.name}`));
|
|
||||||
toast.success("Shortcut link copied to clipboard.");
|
toast.success("Shortcut link copied to clipboard.");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +111,6 @@ const ShortcutListView: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
{state.currentEditingShortcutId !== UNKNOWN_ID && (
|
{state.currentEditingShortcutId !== UNKNOWN_ID && (
|
||||||
<CreateShortcutDialog
|
<CreateShortcutDialog
|
||||||
workspaceId={workspaceId}
|
|
||||||
shortcutId={state.currentEditingShortcutId}
|
shortcutId={state.currentEditingShortcutId}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setState({
|
setState({
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
import { Button, Input, Modal, ModalDialog, Radio, RadioGroup } from "@mui/joy";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { workspaceService } from "../services";
|
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
|
||||||
import { upsertWorkspaceUser } from "../helpers/api";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
onClose: () => void;
|
|
||||||
onConfirm?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
workspaceUserUpsert: WorkspaceUserUpsert;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UpsertWorkspaceUserDialog: React.FC<Props> = (props: Props) => {
|
|
||||||
const { onClose, onConfirm, 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) {
|
|
||||||
toast.error("User ID is required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestState.setLoading();
|
|
||||||
try {
|
|
||||||
await upsertWorkspaceUser({
|
|
||||||
...state.workspaceUserUpsert,
|
|
||||||
});
|
|
||||||
|
|
||||||
await workspaceService.fetchWorkspaceById(workspaceId);
|
|
||||||
|
|
||||||
if (onConfirm) {
|
|
||||||
onConfirm();
|
|
||||||
} else {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(JSON.stringify(error.response.data));
|
|
||||||
}
|
|
||||||
requestState.setFinish();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal open={true}>
|
|
||||||
<ModalDialog>
|
|
||||||
<div className="flex flex-row justify-between items-center w-80">
|
|
||||||
<span className="text-lg font-medium">Create Workspace Member</span>
|
|
||||||
<Button variant="plain" onClick={onClose}>
|
|
||||||
<Icon.X className="w-5 h-auto text-gray-600" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start mb-3">
|
|
||||||
<span className="mb-2">User ID</span>
|
|
||||||
<Input
|
|
||||||
className="w-full"
|
|
||||||
type="number"
|
|
||||||
value={state.workspaceUserUpsert.userId <= 0 ? "" : 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>
|
|
||||||
<RadioGroup orientation="horizontal" value={state.workspaceUserUpsert.role} onChange={handleUserRoleInputChange}>
|
|
||||||
<Radio value="USER" label="User" />
|
|
||||||
<Radio value="ADMIN" label="Admin" />
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex flex-row justify-end items-center">
|
|
||||||
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalDialog>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UpsertWorkspaceUserDialog;
|
|
@ -1,94 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { UNKNOWN_ID } from "../helpers/consts";
|
|
||||||
import { workspaceService } from "../services";
|
|
||||||
import { showCommonDialog } from "./Alert";
|
|
||||||
import Dropdown from "./common/Dropdown";
|
|
||||||
import CreateWorkspaceDialog from "./CreateWorkspaceDialog";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceList: Workspace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
currentEditingWorkspaceId: WorkspaceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WorkspaceListView: React.FC<Props> = (props: Props) => {
|
|
||||||
const { workspaceList } = props;
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
currentEditingWorkspaceId: UNKNOWN_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleEditWorkspaceButtonClick = (workspaceId: WorkspaceId) => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
currentEditingWorkspaceId: workspaceId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteWorkspaceButtonClick = (workspace: Workspace) => {
|
|
||||||
showCommonDialog({
|
|
||||||
title: "Delete Workspace",
|
|
||||||
content: `Are you sure to delete workspace \`${workspace.name}\`?`,
|
|
||||||
style: "danger",
|
|
||||||
onConfirm: async () => {
|
|
||||||
await workspaceService.deleteWorkspaceById(workspace.id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
|
||||||
{workspaceList.map((workspace) => {
|
|
||||||
return (
|
|
||||||
<div key={workspace.id} className="w-full flex flex-row justify-between items-start border px-6 py-4 mb-3 rounded-lg">
|
|
||||||
<div className="flex flex-col justify-start items-start">
|
|
||||||
<Link to={`/${workspace.name}`} className="text-lg cursor-pointer hover:underline">
|
|
||||||
{workspace.name}
|
|
||||||
</Link>
|
|
||||||
<span className="text-sm mt-1 text-gray-600">{workspace.description}</span>
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
actions={
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100"
|
|
||||||
onClick={() => handleEditWorkspaceButtonClick(workspace.id)}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="w-full px-3 text-left leading-10 cursor-pointer rounded text-red-600 hover:bg-gray-100"
|
|
||||||
onClick={() => {
|
|
||||||
handleDeleteWorkspaceButtonClick(workspace);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
actionsClassName="!w-24"
|
|
||||||
></Dropdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{state.currentEditingWorkspaceId !== UNKNOWN_ID && (
|
|
||||||
<CreateWorkspaceDialog
|
|
||||||
workspaceId={state.currentEditingWorkspaceId}
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
currentEditingWorkspaceId: UNKNOWN_ID,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceListView;
|
|
@ -1,189 +0,0 @@
|
|||||||
import { Button } from "@mui/joy";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { deleteWorkspaceUser } from "../helpers/api";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
|
||||||
import { workspaceService } from "../services";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
|
|
||||||
import { showCommonDialog } from "./Alert";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
import CreateWorkspaceDialog from "./CreateWorkspaceDialog";
|
|
||||||
import UpsertWorkspaceUserDialog from "./UpsertWorkspaceUserDialog";
|
|
||||||
import MemberListView from "./MemberListView";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
showEditWorkspaceDialog: boolean;
|
|
||||||
showUpsertWorkspaceUserDialog: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WorkspaceSetting: React.FC<Props> = (props: Props) => {
|
|
||||||
const { workspaceId } = props;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const user = useAppSelector((state) => state.user.user) as User;
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
showEditWorkspaceDialog: false,
|
|
||||||
showUpsertWorkspaceUserDialog: false,
|
|
||||||
});
|
|
||||||
const { workspaceList } = useAppSelector((state) => state.workspace);
|
|
||||||
const loadingState = useLoading();
|
|
||||||
const workspace = workspaceList.find((workspace) => workspace.id === workspaceId) ?? unknownWorkspace;
|
|
||||||
const workspaceUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const workspace = workspaceService.getWorkspaceById(workspaceId);
|
|
||||||
if (!workspace) {
|
|
||||||
toast.error("workspace not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingState.setFinish();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEditWorkspaceButtonClick = () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showEditWorkspaceDialog: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpsertWorkspaceMemberButtonClick = () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showUpsertWorkspaceUserDialog: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditWorkspaceDialogConfirm = async () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showEditWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
const workspace = await workspaceService.fetchWorkspaceById(workspaceId);
|
|
||||||
navigate(`/${workspace.name}#setting`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteWorkspaceButtonClick = () => {
|
|
||||||
showCommonDialog({
|
|
||||||
title: "Delete Workspace",
|
|
||||||
content: `Are you sure to delete workspace \`${workspace.name}\`?`,
|
|
||||||
style: "danger",
|
|
||||||
onConfirm: async () => {
|
|
||||||
await workspaceService.deleteWorkspaceById(workspace.id);
|
|
||||||
navigate("/");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExitWorkspaceButtonClick = () => {
|
|
||||||
showCommonDialog({
|
|
||||||
title: "Exit Workspace",
|
|
||||||
content: `Are you sure to exit workspace \`${workspace.name}\`?`,
|
|
||||||
style: "danger",
|
|
||||||
onConfirm: async () => {
|
|
||||||
await deleteWorkspaceUser({
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
userId: workspaceUser.userId,
|
|
||||||
});
|
|
||||||
navigate("/");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
|
||||||
<span className="w-full text-2xl font-medium border-b pb-2 mb-4">General</span>
|
|
||||||
<p className="mb-4">ID: {workspace.name}</p>
|
|
||||||
<p className="mb-4">Name: {workspace.title}</p>
|
|
||||||
<p className="mb-4">Description: {workspace.description || "No description."}</p>
|
|
||||||
{workspaceUser.role === "ADMIN" && (
|
|
||||||
<div className="flex flex-row justify-start items-center">
|
|
||||||
<div className="flex flex-row justify-start items-center space-x-2">
|
|
||||||
<Button variant="soft" onClick={handleEditWorkspaceButtonClick}>
|
|
||||||
<Icon.Edit className="w-4 h-auto mr-1" />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full mt-8 flex flex-col justify-start items-start">
|
|
||||||
<div className="w-full border-b pb-2 mb-4 flex flex-row justify-between items-center">
|
|
||||||
<span className="text-2xl font-medium">Members</span>
|
|
||||||
{workspaceUser.role === "ADMIN" && (
|
|
||||||
<Button variant="soft" onClick={handleUpsertWorkspaceMemberButtonClick}>
|
|
||||||
<Icon.Plus className="w-4 h-auto mr-1" />
|
|
||||||
New member
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<MemberListView workspaceId={workspaceId} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full mt-8 flex flex-col justify-start items-start">
|
|
||||||
<span className="w-full text-2xl font-medium border-b pb-2 mb-4">Danger Zone</span>
|
|
||||||
<div className="flex flex-row justify-start items-center">
|
|
||||||
<div className="flex flex-row justify-start items-center space-x-2">
|
|
||||||
{workspaceUser.role === "ADMIN" ? (
|
|
||||||
<>
|
|
||||||
<Button variant="soft" color="danger" onClick={handleDeleteWorkspaceButtonClick}>
|
|
||||||
<Icon.Trash className="w-4 h-auto mr-1" />
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Button variant="soft" color="danger" onClick={handleExitWorkspaceButtonClick}>
|
|
||||||
Exit
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{state.showEditWorkspaceDialog && (
|
|
||||||
<CreateWorkspaceDialog
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showEditWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onConfirm={() => handleEditWorkspaceDialogConfirm()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{state.showUpsertWorkspaceUserDialog && (
|
|
||||||
<UpsertWorkspaceUserDialog
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showUpsertWorkspaceUserDialog: false,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onConfirm={async () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showUpsertWorkspaceUserDialog: false,
|
|
||||||
});
|
|
||||||
if (location.hash !== "#members") {
|
|
||||||
navigate("#members");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceSetting;
|
|
@ -53,59 +53,11 @@ export function deleteUser(userDelete: UserDelete) {
|
|||||||
return axios.delete(`/api/user/${userDelete.id}`);
|
return axios.delete(`/api/user/${userDelete.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWorkspaceList(find?: WorkspaceFind) {
|
|
||||||
const queryList = [];
|
|
||||||
if (find?.creatorId) {
|
|
||||||
queryList.push(`creatorId=${find.creatorId}`);
|
|
||||||
}
|
|
||||||
if (find?.memberId) {
|
|
||||||
queryList.push(`memberId=${find.memberId}`);
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
return axios.post<ResponseObject<Workspace>>("/api/workspace", create);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function patchWorkspace(patch: WorkspacePatch) {
|
|
||||||
return axios.patch<ResponseObject<Workspace>>(`/api/workspace/${patch.id}`, patch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteWorkspaceById(workspaceId: 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) {
|
||||||
queryList.push(`creatorId=${shortcutFind.creatorId}`);
|
queryList.push(`creatorId=${shortcutFind.creatorId}`);
|
||||||
}
|
}
|
||||||
if (shortcutFind?.workspaceId) {
|
|
||||||
queryList.push(`workspaceId=${shortcutFind.workspaceId}`);
|
|
||||||
}
|
|
||||||
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`);
|
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
(() => {
|
|
||||||
if (!String.prototype.replaceAll) {
|
|
||||||
String.prototype.replaceAll = function (str: any, newStr: any) {
|
|
||||||
// If a regex pattern
|
|
||||||
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
|
|
||||||
return this.replace(str, newStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a string
|
|
||||||
return this.replace(new RegExp(str, "g"), newStr);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
export default null;
|
|
@ -2,7 +2,6 @@ import { createRoot } from "react-dom/client";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./helpers/polyfill";
|
|
||||||
import "./css/index.css";
|
import "./css/index.css";
|
||||||
|
|
||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
|
@ -9,7 +9,7 @@ import useLoading from "../hooks/useLoading";
|
|||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
|
|
||||||
const validateConfig: ValidatorConfig = {
|
const validateConfig: ValidatorConfig = {
|
||||||
minLength: 4,
|
minLength: 3,
|
||||||
maxLength: 24,
|
maxLength: 24,
|
||||||
noSpace: true,
|
noSpace: true,
|
||||||
noChinese: true,
|
noChinese: true,
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { userService, workspaceService } from "../services";
|
import { userService, shortcutService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import WorkspaceListView from "../components/WorkspaceListView";
|
import ShortcutListView from "../components/ShortcutListView";
|
||||||
import CreateWorkspaceDialog from "../components/CreateWorkspaceDialog";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
showCreateWorkspaceDialog: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { workspaceList } = useAppSelector((state) => state.workspace);
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userService.getState().user) {
|
if (!userService.getState().user) {
|
||||||
@ -25,23 +17,11 @@ const Home: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all([workspaceService.fetchWorkspaceList()]).finally(() => {
|
Promise.all([shortcutService.getMyAllShortcuts()]).finally(() => {
|
||||||
const workspaceList = workspaceService.getState().workspaceList;
|
|
||||||
if (workspaceList.length > 0) {
|
|
||||||
navigate(`/${workspaceList[0].name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loadingState.setFinish();
|
loadingState.setFinish();
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCreateWorkspaceButtonClick = () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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 py-6 flex flex-col justify-start items-start">
|
||||||
@ -53,39 +33,10 @@ const Home: React.FC = () => {
|
|||||||
<Icon.Loader className="mr-2 w-5 h-auto animate-spin" />
|
<Icon.Loader className="mr-2 w-5 h-auto animate-spin" />
|
||||||
loading
|
loading
|
||||||
</div>
|
</div>
|
||||||
) : workspaceList.length === 0 ? (
|
|
||||||
<div className="w-full flex flex-col justify-center items-center">
|
|
||||||
<Icon.Frown className="mt-8 w-16 h-auto text-gray-400" />
|
|
||||||
<p className="mt-4 text-xl text-gray-600">Oops, no workspace.</p>
|
|
||||||
<button
|
|
||||||
className="mt-4 text-lg flex flex-row justify-start items-center border px-3 py-2 rounded-lg cursor-pointer hover:shadow"
|
|
||||||
onClick={handleCreateWorkspaceButtonClick}
|
|
||||||
>
|
|
||||||
<Icon.Plus className="w-5 h-auto mr-1" /> Create Workspace
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<WorkspaceListView workspaceList={workspaceList} />
|
<ShortcutListView shortcutList={shortcutList} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.showCreateWorkspaceDialog && (
|
|
||||||
<CreateWorkspaceDialog
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onConfirm={(workspace: Workspace) => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateWorkspaceDialog: false,
|
|
||||||
});
|
|
||||||
navigate(`/${workspace.name}`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,153 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { NavLink, useLocation, useNavigate, useParams } from "react-router-dom";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { shortcutService, userService } from "../services";
|
|
||||||
import { useAppSelector } from "../store";
|
|
||||||
import { unknownWorkspace } from "../store/modules/workspace";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
|
||||||
import Icon from "../components/Icon";
|
|
||||||
import Dropdown from "../components/common/Dropdown";
|
|
||||||
import ShortcutListView from "../components/ShortcutListView";
|
|
||||||
import WorkspaceSetting from "../components/WorkspaceSetting";
|
|
||||||
import CreateShortcutDialog from "../components/CreateShortcutDialog";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
showCreateShortcutDialog: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WorkspaceDetail: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const params = useParams();
|
|
||||||
const location = useLocation();
|
|
||||||
const { workspaceList } = useAppSelector((state) => state.workspace);
|
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
showCreateShortcutDialog: false,
|
|
||||||
});
|
|
||||||
const loadingState = useLoading();
|
|
||||||
const workspace = workspaceList.find((workspace) => workspace.name === params.workspaceName) ?? unknownWorkspace;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userService.getState().user) {
|
|
||||||
navigate("/user/auth");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
toast.error("workspace not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all([shortcutService.fetchWorkspaceShortcuts(workspace.id)]).finally(() => {
|
|
||||||
loadingState.setFinish();
|
|
||||||
});
|
|
||||||
}, [params.workspaceName]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (location.hash !== "#shortcuts" && location.hash !== "#setting") {
|
|
||||||
navigate("#shortcuts");
|
|
||||||
}
|
|
||||||
}, [location.hash]);
|
|
||||||
|
|
||||||
const handleCreateShortcutButtonClick = () => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateShortcutDialog: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<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 mt-4 mb-4">
|
|
||||||
<div className="flex flex-row justify-start items-center space-x-3 sm:space-x-4">
|
|
||||||
<NavLink
|
|
||||||
to="#shortcuts"
|
|
||||||
className={`py-1 text-gray-400 border-b-2 border-b-transparent ${
|
|
||||||
location.hash === "#shortcuts" && "!border-b-black text-black"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Shortcuts
|
|
||||||
</NavLink>
|
|
||||||
<NavLink
|
|
||||||
to="#setting"
|
|
||||||
className={`py-1 text-gray-400 border-b-2 border-b-transparent ${
|
|
||||||
location.hash === "#setting" && "!border-b-black text-black"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Setting
|
|
||||||
</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>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
actionsClassName="!w-32"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{loadingState.isLoading ? (
|
|
||||||
<div className="py-4 w-full flex flex-row justify-center items-center">
|
|
||||||
<Icon.Loader className="mr-2 w-5 h-auto animate-spin" />
|
|
||||||
loading
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{location.hash === "#shortcuts" &&
|
|
||||||
(shortcutList.length === 0 ? (
|
|
||||||
<div className="w-full flex flex-col justify-center items-center">
|
|
||||||
<Icon.Frown className="mt-8 w-16 h-auto text-gray-400" />
|
|
||||||
<p className="mt-4 text-xl text-gray-600">Oops, no shortcut.</p>
|
|
||||||
<button
|
|
||||||
className="mt-4 text-lg flex flex-row justify-start items-center border px-3 py-2 rounded-lg cursor-pointer hover:shadow"
|
|
||||||
onClick={handleCreateShortcutButtonClick}
|
|
||||||
>
|
|
||||||
<Icon.Plus className="w-5 h-auto mr-1" /> Create Shortcut
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ShortcutListView workspaceId={workspace.id} shortcutList={shortcutList} />
|
|
||||||
))}
|
|
||||||
{location.hash === "#setting" && <WorkspaceSetting workspaceId={workspace.id} />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{state.showCreateShortcutDialog && (
|
|
||||||
<CreateShortcutDialog
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
onClose={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateShortcutDialog: false,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onConfirm={() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
showCreateShortcutDialog: false,
|
|
||||||
});
|
|
||||||
if (location.hash !== "#shortcuts") {
|
|
||||||
navigate("#shortcuts");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkspaceDetail;
|
|
@ -1,11 +1,10 @@
|
|||||||
import { createBrowserRouter, redirect } from "react-router-dom";
|
import { createBrowserRouter, redirect } from "react-router-dom";
|
||||||
import { isNullorUndefined } from "../helpers/utils";
|
import { isNullorUndefined } from "../helpers/utils";
|
||||||
import { userService, workspaceService } from "../services";
|
import { userService } from "../services";
|
||||||
import Root from "../layout/Root";
|
import Root from "../layout/Root";
|
||||||
import Auth from "../pages/Auth";
|
import Auth from "../pages/Auth";
|
||||||
import Home from "../pages/Home";
|
import Home from "../pages/Home";
|
||||||
import UserDetail from "../pages/UserDetail";
|
import UserDetail from "../pages/UserDetail";
|
||||||
import WorkspaceDetail from "../pages/WorkspaceDetail";
|
|
||||||
import ShortcutRedirector from "../pages/ShortcutRedirector";
|
import ShortcutRedirector from "../pages/ShortcutRedirector";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -43,23 +42,6 @@ const router = createBrowserRouter([
|
|||||||
// do nth
|
// do nth
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user } = userService.getState();
|
|
||||||
if (isNullorUndefined(user)) {
|
|
||||||
return redirect("/user/auth");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/:workspaceName",
|
|
||||||
element: <WorkspaceDetail />,
|
|
||||||
loader: async () => {
|
|
||||||
try {
|
|
||||||
await userService.initialState();
|
|
||||||
await workspaceService.fetchWorkspaceList();
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
|
|
||||||
const { user } = userService.getState();
|
const { user } = userService.getState();
|
||||||
if (isNullorUndefined(user)) {
|
if (isNullorUndefined(user)) {
|
||||||
return redirect("/user/auth");
|
return redirect("/user/auth");
|
||||||
@ -69,7 +51,7 @@ const router = createBrowserRouter([
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/:workspaceName/:shortcutName",
|
path: "/:shortcutName",
|
||||||
element: <ShortcutRedirector />,
|
element: <ShortcutRedirector />,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import globalService from "./globalService";
|
import globalService from "./globalService";
|
||||||
import shortcutService from "./shortcutService";
|
import shortcutService from "./shortcutService";
|
||||||
import userService from "./userService";
|
import userService from "./userService";
|
||||||
import workspaceService from "./workspaceService";
|
|
||||||
|
|
||||||
export { globalService, shortcutService, userService, workspaceService };
|
export { globalService, shortcutService, userService };
|
||||||
|
@ -15,12 +15,8 @@ const shortcutService = {
|
|||||||
return store.getState().shortcut;
|
return store.getState().shortcut;
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchWorkspaceShortcuts: async (workspaceId: WorkspaceId) => {
|
fetchWorkspaceShortcuts: async () => {
|
||||||
const { data } = (
|
const { data } = (await api.getShortcutList({})).data;
|
||||||
await api.getShortcutList({
|
|
||||||
workspaceId,
|
|
||||||
})
|
|
||||||
).data;
|
|
||||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||||
store.dispatch(setShortcuts(shortcuts));
|
store.dispatch(setShortcuts(shortcuts));
|
||||||
return shortcuts;
|
return shortcuts;
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
import * as api from "../helpers/api";
|
|
||||||
import store from "../store";
|
|
||||||
import { createWorkspace, deleteWorkspace, patchWorkspace, setWorkspaceById, setWorkspaceList } from "../store/modules/workspace";
|
|
||||||
|
|
||||||
const convertResponseModelWorkspace = (workspace: Workspace): Workspace => {
|
|
||||||
return {
|
|
||||||
...workspace,
|
|
||||||
createdTs: workspace.createdTs * 1000,
|
|
||||||
updatedTs: workspace.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const workspaceService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().workspace;
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchWorkspaceList: async () => {
|
|
||||||
const { data } = (await api.getWorkspaceList()).data;
|
|
||||||
const workspaces = data.map((w) => convertResponseModelWorkspace(w));
|
|
||||||
store.dispatch(setWorkspaceList(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) => {
|
|
||||||
const workspaceList = workspaceService.getState().workspaceList;
|
|
||||||
for (const workspace of workspaceList) {
|
|
||||||
if (workspace.name === workspaceName) {
|
|
||||||
return workspace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
getWorkspaceById: (id: WorkspaceId) => {
|
|
||||||
const workspaceList = workspaceService.getState().workspaceList;
|
|
||||||
for (const workspace of workspaceList) {
|
|
||||||
if (workspace.id === id) {
|
|
||||||
return workspace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
createWorkspace: async (create: WorkspaceCreate) => {
|
|
||||||
const { data } = (await api.createWorkspace(create)).data;
|
|
||||||
const workspace = convertResponseModelWorkspace(data);
|
|
||||||
store.dispatch(createWorkspace(workspace));
|
|
||||||
return workspace;
|
|
||||||
},
|
|
||||||
|
|
||||||
patchWorkspace: async (patch: WorkspacePatch) => {
|
|
||||||
const { data } = (await api.patchWorkspace(patch)).data;
|
|
||||||
const workspace = convertResponseModelWorkspace(data);
|
|
||||||
store.dispatch(patchWorkspace(workspace));
|
|
||||||
return workspace;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteWorkspaceById: async (id: WorkspaceId) => {
|
|
||||||
await api.deleteWorkspaceById(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;
|
|
@ -2,14 +2,12 @@ import { configureStore } from "@reduxjs/toolkit";
|
|||||||
import { TypedUseSelectorHook, useSelector } from "react-redux";
|
import { TypedUseSelectorHook, useSelector } from "react-redux";
|
||||||
import globalReducer from "./modules/global";
|
import globalReducer from "./modules/global";
|
||||||
import userReducer from "./modules/user";
|
import userReducer from "./modules/user";
|
||||||
import workspaceReducer from "./modules/workspace";
|
|
||||||
import shortcutReducer from "./modules/shortcut";
|
import shortcutReducer from "./modules/shortcut";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
global: globalReducer,
|
global: globalReducer,
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
workspace: workspaceReducer,
|
|
||||||
shortcut: shortcutReducer,
|
shortcut: shortcutReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
import { UNKNOWN_ID } from "../../helpers/consts";
|
|
||||||
|
|
||||||
export const unknownWorkspace = {
|
|
||||||
id: UNKNOWN_ID,
|
|
||||||
workspaceUserList: [],
|
|
||||||
} as unknown as Workspace;
|
|
||||||
|
|
||||||
export const unknownWorkspaceUser = {
|
|
||||||
workspaceId: UNKNOWN_ID,
|
|
||||||
userId: UNKNOWN_ID,
|
|
||||||
role: "USER",
|
|
||||||
} as unknown as WorkspaceUser;
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
workspaceList: Workspace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceSlice = createSlice({
|
|
||||||
name: "workspace",
|
|
||||||
initialState: {
|
|
||||||
workspaceList: [],
|
|
||||||
} as State,
|
|
||||||
reducers: {
|
|
||||||
setWorkspaceList: (state, action: PayloadAction<Workspace[]>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
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>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
workspaceList: state.workspaceList.concat(action.payload).sort((a, b) => b.createdTs - a.createdTs),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
patchWorkspace: (state, action: PayloadAction<Partial<Workspace>>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
workspaceList: state.workspaceList.map((s) => {
|
|
||||||
if (s.id === action.payload.id) {
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
...action.payload,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
deleteWorkspace: (state, action: PayloadAction<WorkspaceId>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
workspaceList: [...state.workspaceList].filter((workspace) => workspace.id !== action.payload),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setWorkspaceList, setWorkspaceById, createWorkspace, patchWorkspace, deleteWorkspace } = workspaceSlice.actions;
|
|
||||||
|
|
||||||
export default workspaceSlice.reducer;
|
|
28
web/src/types/modules/WorkspaceUser.d.ts
vendored
28
web/src/types/modules/WorkspaceUser.d.ts
vendored
@ -1,28 +0,0 @@
|
|||||||
type Role = "ADMIN" | "USER";
|
|
||||||
|
|
||||||
interface WorkspaceUser {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
userId: UserId;
|
|
||||||
role: Role;
|
|
||||||
createdTs: TimeStamp;
|
|
||||||
updatedTs: TimeStamp;
|
|
||||||
email: string;
|
|
||||||
displayName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceUserUpsert {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
userId: UserId;
|
|
||||||
role: Role;
|
|
||||||
updatedTs?: TimeStamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceUserFind {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
userId?: UserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceUserDelete {
|
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
userId: UserId;
|
|
||||||
}
|
|
4
web/src/types/modules/shortcut.d.ts
vendored
4
web/src/types/modules/shortcut.d.ts
vendored
@ -9,7 +9,6 @@ interface Shortcut {
|
|||||||
creator: User;
|
creator: User;
|
||||||
createdTs: TimeStamp;
|
createdTs: TimeStamp;
|
||||||
updatedTs: TimeStamp;
|
updatedTs: TimeStamp;
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
rowStatus: RowStatus;
|
rowStatus: RowStatus;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
@ -19,8 +18,6 @@ interface Shortcut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ShortcutCreate {
|
interface ShortcutCreate {
|
||||||
workspaceId: WorkspaceId;
|
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
link: string;
|
link: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -38,5 +35,4 @@ interface ShortcutPatch {
|
|||||||
|
|
||||||
interface ShortcutFind {
|
interface ShortcutFind {
|
||||||
creatorId?: UserId;
|
creatorId?: UserId;
|
||||||
workspaceId?: WorkspaceId;
|
|
||||||
}
|
}
|
||||||
|
4
web/src/types/modules/user.d.ts
vendored
4
web/src/types/modules/user.d.ts
vendored
@ -1,5 +1,7 @@
|
|||||||
type UserId = number;
|
type UserId = number;
|
||||||
|
|
||||||
|
type Role = "ADMIN" | "USER";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: UserId;
|
id: UserId;
|
||||||
|
|
||||||
@ -10,6 +12,7 @@ interface User {
|
|||||||
email: string;
|
email: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
openId: string;
|
openId: string;
|
||||||
|
role: Role;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserCreate {
|
interface UserCreate {
|
||||||
@ -22,7 +25,6 @@ interface UserPatch {
|
|||||||
id: UserId;
|
id: UserId;
|
||||||
|
|
||||||
rowStatus?: RowStatus;
|
rowStatus?: RowStatus;
|
||||||
|
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
resetOpenId?: boolean;
|
resetOpenId?: boolean;
|
||||||
|
35
web/src/types/modules/workspace.d.ts
vendored
35
web/src/types/modules/workspace.d.ts
vendored
@ -1,35 +0,0 @@
|
|||||||
type WorkspaceId = number;
|
|
||||||
|
|
||||||
interface Workspace {
|
|
||||||
id: WorkspaceId;
|
|
||||||
|
|
||||||
creatorId: UserId;
|
|
||||||
createdTs: TimeStamp;
|
|
||||||
updatedTs: TimeStamp;
|
|
||||||
rowStatus: RowStatus;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
workspaceUserList: WorkspaceUser[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceCreate {
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspacePatch {
|
|
||||||
id: WorkspaceId;
|
|
||||||
rowStatus?: RowStatus;
|
|
||||||
name?: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkspaceFind {
|
|
||||||
creatorId?: UserId;
|
|
||||||
memberId?: UserId;
|
|
||||||
}
|
|
2574
web/yarn.lock
2574
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user