diff options
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/App.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/pages/login/index.css | 10 | ||||
-rw-r--r-- | FrontEnd/src/pages/login/index.tsx | 133 | ||||
-rw-r--r-- | FrontEnd/src/views/common/input/InputGroup.tsx | 23 |
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; |