chore: init web project

This commit is contained in:
Steven
2022-09-12 09:53:15 +08:00
parent e82c821396
commit 5a96e67c6d
66 changed files with 5101 additions and 0 deletions

122
web/src/pages/Auth.tsx Normal file
View File

@@ -0,0 +1,122 @@
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import * as api from "../helpers/api";
import { validate, ValidatorConfig } from "../helpers/validator";
import { userService } from "../services";
import useLoading from "../hooks/useLoading";
import Icon from "../components/Icon";
import Only from "../components/common/OnlyWhen";
import toastHelper from "../components/Toast";
import "../less/auth.less";
const validateConfig: ValidatorConfig = {
minLength: 4,
maxLength: 24,
noSpace: true,
noChinese: true,
};
const Auth: React.FC = () => {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false);
useEffect(() => {
if (userService.getState().user) {
navigate("/");
return;
}
api.getSystemStatus().then(({ data }) => {
const { data: status } = data;
if (status.profile.mode === "dev") {
setEmail("demo@iamcorgi.com");
setPassword("secret");
}
});
}, []);
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setEmail(text);
};
const handlePasswordInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setPassword(text);
};
const handleSigninBtnsClick = async () => {
if (actionBtnLoadingState.isLoading) {
return;
}
const emailValidResult = validate(email, validateConfig);
if (!emailValidResult.result) {
toastHelper.error("Email: " + emailValidResult.reason);
return;
}
const passwordValidResult = validate(password, validateConfig);
if (!passwordValidResult.result) {
toastHelper.error("Password: " + passwordValidResult.reason);
return;
}
try {
actionBtnLoadingState.setLoading();
await api.signin(email, password);
const user = await userService.doSignIn();
if (user) {
navigate("/", {
replace: true,
});
} else {
toastHelper.error("Login failed");
}
} catch (error: any) {
console.error(error);
toastHelper.error(error.response.data.message);
}
actionBtnLoadingState.setFinish();
};
return (
<div className="page-wrapper auth">
<div className="page-container">
<div className="auth-form-wrapper">
<div className="page-header-container">
<div className="title-container">
<img className="logo-img" src="/logo-full.webp" alt="" />
</div>
<p className="slogan-text">Corgi</p>
</div>
<div className={`page-content-container ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}>
<div className="form-item-container input-form-container">
<span className={`normal-text ${email ? "not-null" : ""}`}>Email</span>
<input type="email" value={email} onChange={handleEmailInputChanged} />
</div>
<div className="form-item-container input-form-container">
<span className={`normal-text ${password ? "not-null" : ""}`}>Password</span>
<input type="password" value={password} onChange={handlePasswordInputChanged} />
</div>
</div>
<div className="action-btns-container">
<button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick()}
>
<Only when={actionBtnLoadingState.isLoading}>
<Icon.Loader className="img-icon" />
</Only>
Sign in
</button>
</div>
</div>
</div>
</div>
);
};
export default Auth;

39
web/src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,39 @@
import { useEffect } from "react";
import { workspaceService } from "../services";
import { useAppSelector } from "../store";
import useLoading from "../hooks/useLoading";
import Icon from "../components/Icon";
import Header from "../components/Header";
import WorkspaceListView from "../components/WorkspaceListView";
import showCreateWorkspaceDialog from "../components/CreateWorkspaceDialog";
const Home: React.FC = () => {
const { workspaceList } = useAppSelector((state) => state.workspace);
const loadingState = useLoading();
useEffect(() => {
Promise.all([workspaceService.fetchWorkspaceList()]).finally(() => {
loadingState.setFinish();
});
}, []);
return (
<div className="w-full h-full flex flex-col justify-start items-start">
<Header />
{loadingState.isLoading ? null : (
<div className="mx-auto max-w-4xl w-full px-3 py-6 flex flex-col justify-start items-start">
<p className="font-mono mb-2 text-gray-400">Workspace List</p>
<WorkspaceListView workspaceList={workspaceList} />
<div
className="flex flex-row justify-start items-center border px-3 py-3 rounded-lg mt-4 cursor-pointer"
onClick={() => showCreateWorkspaceDialog()}
>
<Icon.Plus className="w-5 h-auto mr-1" /> Create Workspace
</div>
</div>
)}
</div>
);
};
export default Home;

View File

@@ -0,0 +1,71 @@
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { shortcutService, workspaceService } from "../services";
import { useAppSelector } from "../store";
import useLoading from "../hooks/useLoading";
import Icon from "../components/Icon";
import toastHelper from "../components/Toast";
import Header from "../components/Header";
import ShortcutListView from "../components/ShortcutListView";
import { unknownWorkspace } from "../store/modules/workspace";
import showCreateShortcutDialog from "../components/CreateShortcutDialog";
interface State {
workspace: Workspace;
}
const WorkspaceDetail: React.FC = () => {
const navigate = useNavigate();
const params = useParams();
const { shortcutList } = useAppSelector((state) => state.shortcut);
const [state, setState] = useState<State>({
workspace: unknownWorkspace,
});
const loadingState = useLoading();
useEffect(() => {
const workspace = workspaceService.getWorkspaceById(Number(params.workspaceId));
if (!workspace) {
toastHelper.error("workspace not found");
return;
}
setState({
...state,
workspace,
});
Promise.all([shortcutService.fetchWorkspaceShortcuts(workspace.id)]).finally(() => {
loadingState.setFinish();
});
}, []);
const handleBackToHome = () => {
navigate("/");
};
return (
<div className="w-full h-full flex flex-col justify-start items-start">
<Header />
{loadingState.isLoading ? null : (
<div className="mx-auto max-w-4xl w-full px-3 py-6 flex flex-col justify-start items-start">
<div
className="flex flex-row justify-start items-center mb-4 text-gray-600 border rounded px-2 py-1 cursor-pointer"
onClick={() => handleBackToHome()}
>
<Icon.ChevronLeft className="w-5 h-auto mr-1" /> Back to Home
</div>
<p className="font-mono mb-2 text-gray-600">Workspace: {state?.workspace.name}</p>
<ShortcutListView shortcutList={shortcutList} />
<div
className="flex flex-row justify-start items-center border px-3 py-3 rounded-lg mt-4 cursor-pointer"
onClick={() => showCreateShortcutDialog(state.workspace.id)}
>
<Icon.Plus className="w-5 h-auto mr-1" /> Create Shortcut
</div>
</div>
)}
</div>
);
};
export default WorkspaceDetail;