mirror of
https://github.com/aykhans/slash-e.git
synced 2025-07-23 21:34:24 +00:00
chore: init web project
This commit is contained in:
122
web/src/pages/Auth.tsx
Normal file
122
web/src/pages/Auth.tsx
Normal 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
39
web/src/pages/Home.tsx
Normal 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;
|
71
web/src/pages/WorkspaceDetail.tsx
Normal file
71
web/src/pages/WorkspaceDetail.tsx
Normal 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;
|
Reference in New Issue
Block a user