aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-07-29 22:41:32 +0800
committercrupest <crupest@outlook.com>2023-07-29 22:41:32 +0800
commit77b03b17a59655c1eeb00e0a818c81f8ea5e326e (patch)
tree1374a66fb26910171df8eda54db1b61206f8aeba /FrontEnd/src
parent22e8f24e7f7574915e4c75d3c6a5498f6e621ee8 (diff)
downloadtimeline-77b03b17a59655c1eeb00e0a818c81f8ea5e326e.tar.gz
timeline-77b03b17a59655c1eeb00e0a818c81f8ea5e326e.tar.bz2
timeline-77b03b17a59655c1eeb00e0a818c81f8ea5e326e.zip
...
Diffstat (limited to 'FrontEnd/src')
-rw-r--r--FrontEnd/src/App.tsx2
-rw-r--r--FrontEnd/src/pages/login/index.css10
-rw-r--r--FrontEnd/src/pages/login/index.tsx133
-rw-r--r--FrontEnd/src/views/common/input/InputGroup.tsx23
4 files changed, 163 insertions, 5 deletions
diff --git a/FrontEnd/src/App.tsx b/FrontEnd/src/App.tsx
index 92fe0652..8f2bf6b0 100644
--- a/FrontEnd/src/App.tsx
+++ b/FrontEnd/src/App.tsx
@@ -7,7 +7,7 @@ import LoadingPage from "./views/common/LoadingPage";
import AboutPage from "./pages/about";
import SettingPage from "./pages/setting";
import Center from "./views/center";
-import Login from "./views/login";
+import Login from "./pages/login";
import Register from "./views/register";
import TimelinePage from "./views/timeline";
import Search from "./views/search";
diff --git a/FrontEnd/src/pages/login/index.css b/FrontEnd/src/pages/login/index.css
new file mode 100644
index 00000000..d78b3587
--- /dev/null
+++ b/FrontEnd/src/pages/login/index.css
@@ -0,0 +1,10 @@
+.login-page {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.login-page-welcome {
+ text-align: center;
+ font-size: 2em;
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/login/index.tsx b/FrontEnd/src/pages/login/index.tsx
new file mode 100644
index 00000000..9aee455f
--- /dev/null
+++ b/FrontEnd/src/pages/login/index.tsx
@@ -0,0 +1,133 @@
+import { useState, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { Trans } from "react-i18next";
+
+import { useUser, userService } from "@/services/user";
+
+import { useC } from "@/views/common/common";
+import LoadingButton from "@/views/common/button/LoadingButton";
+import {
+ InputErrorDict,
+ InputGroup,
+ useInputs,
+} from "@/views/common/input/InputGroup";
+import Page from "@/views/common/Page";
+
+import "./index.css";
+
+export default function LoginPage() {
+ const c = useC();
+
+ const user = useUser();
+
+ const navigate = useNavigate();
+
+ const [process, setProcess] = useState<boolean>(false);
+ const [error, setError] = useState<string | null>(null);
+
+ const { hasErrorAndDirty, confirm, setAllDisabled, inputGroupProps } =
+ useInputs({
+ init: {
+ scheme: {
+ inputs: [
+ {
+ key: "username",
+ type: "text",
+ label: "user.username",
+ },
+ {
+ key: "password",
+ type: "text",
+ label: "user.password",
+ password: true,
+ },
+ {
+ key: "rememberMe",
+ type: "bool",
+ label: "user.rememberMe",
+ },
+ ],
+ validator: ({ username, password }) => {
+ const result: InputErrorDict = {};
+ if (username === "") {
+ result["username"] = "login.emptyUsername";
+ }
+ if (password === "") {
+ result["password"] = "login.emptyPassword";
+ }
+ return result;
+ },
+ },
+ dataInit: {},
+ },
+ });
+
+ useEffect(() => {
+ if (user != null) {
+ const id = setTimeout(() => navigate("/"), 3000);
+ return () => {
+ clearTimeout(id);
+ };
+ }
+ }, [navigate, user]);
+
+ if (user != null) {
+ return <p>{c("login.alreadyLogin")}</p>;
+ }
+
+ const submit = (): void => {
+ const confirmResult = confirm();
+ if (confirmResult.type === "ok") {
+ const { username, password, rememberMe } = confirmResult.values;
+ setAllDisabled(true);
+ setProcess(true);
+ userService
+ .login(
+ {
+ username: username as string,
+ password: password as string,
+ },
+ rememberMe as boolean,
+ )
+ .then(
+ () => {
+ if (history.length === 0) {
+ navigate("/");
+ } else {
+ navigate(-1);
+ }
+ },
+ (e: Error) => {
+ setProcess(false);
+ setAllDisabled(false);
+ setError(e.message);
+ },
+ );
+ }
+ };
+
+ return (
+ <Page className="login-page">
+ <div className="login-page-container">
+ <div className="login-page-welcome">{c("welcome")}</div>
+ <InputGroup {...inputGroupProps} />
+ {error ? <p className="cru-color-danger">{c(error)}</p> : null}
+ <div className="login-page-button-row">
+ <LoadingButton
+ loading={process}
+ onClick={(e) => {
+ submit();
+ e.preventDefault();
+ }}
+ disabled={hasErrorAndDirty}
+ >
+ {c("user.login")}
+ </LoadingButton>
+ </div>
+ <Trans i18nKey="login.noAccount">
+ 0<Link to="/register">1</Link>2
+ </Trans>
+ </div>
+ </Page>
+ );
+}
diff --git a/FrontEnd/src/views/common/input/InputGroup.tsx b/FrontEnd/src/views/common/input/InputGroup.tsx
index 3d1e3ada..ee89b05c 100644
--- a/FrontEnd/src/views/common/input/InputGroup.tsx
+++ b/FrontEnd/src/views/common/input/InputGroup.tsx
@@ -23,7 +23,7 @@
* `useInputs` hook takes care of logic and generate props for `InputGroup`.
*/
-import { useState, Ref } from "react";
+import { useState, Ref, useId } from "react";
import classNames from "classnames";
import { useC, Text, ThemeColor } from "../common";
@@ -332,6 +332,8 @@ export function InputGroup({
}: InputGroupProps) {
const c = useC();
+ const id = useId();
+
return (
<div
ref={containerRef}
@@ -357,6 +359,8 @@ export function InputGroup({
onChange(index, value);
};
+ const inputId = `${id}-${key}`;
+
if (type === "text") {
const { password } = item;
return (
@@ -364,8 +368,13 @@ export function InputGroup({
key={key}
className={getContainerClassName(password && "password")}
>
- {label && <label className="cru-input-label">{c(label)}</label>}
+ {label && (
+ <label className="cru-input-label" htmlFor={inputId}>
+ {c(label)}
+ </label>
+ )}
<input
+ id={inputId}
type={password ? "password" : "text"}
value={value}
onChange={(event) => {
@@ -382,6 +391,7 @@ export function InputGroup({
return (
<div key={key} className={getContainerClassName()}>
<input
+ id={inputId}
type="checkbox"
checked={value}
onChange={(event) => {
@@ -390,7 +400,9 @@ export function InputGroup({
}}
disabled={disabled}
/>
- <label className="cru-input-label-inline">{c(label)}</label>
+ <label className="cru-input-label-inline" htmlFor={inputId}>
+ {c(label)}
+ </label>
{error && <div className="cru-input-error">{c(error)}</div>}
{helper && <div className="cru-input-helper">{c(helper)}</div>}
</div>
@@ -398,8 +410,11 @@ export function InputGroup({
} else if (type === "select") {
return (
<div key={key} className={getContainerClassName()}>
- <label className="cru-input-label">{c(label)}</label>
+ <label className="cru-input-label" htmlFor={inputId}>
+ {c(label)}
+ </label>
<select
+ id={inputId}
value={value}
onChange={(event) => {
const e = event.target.value;