Files
ColdBreeze-chat-web/src/routes/login/index.tsx
T
2024-11-15 09:53:51 +08:00

205 lines
6.6 KiB
TypeScript

/* eslint-disable no-undef */
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
import BASE_URL from "@/app/config";
import { useLoginMutation } from "@/app/services/auth";
import { useGetLoginConfigQuery, useGetSMTPStatusQuery } from "@/app/services/server";
import { useAppSelector } from "@/app/store";
import Divider from "@/components/Divider";
import Button from "@/components/styled/Button";
import Input from "@/components/styled/Input";
import StyledLabel from "@/components/styled/Label";
import IconBack from "@/assets/icons/arrow.left.svg";
import MagicLinkLogin from "./MagicLinkLogin";
import SignUpLink from "./SignUpLink";
import SocialLoginButtons from "./SocialLoginButtons";
import { shallowEqual } from "react-redux";
const defaultInput = {
email: "",
password: ""
};
export default function LoginPage() {
const { name: serverName, logo } = useAppSelector((store) => store.server, shallowEqual);
const { t } = useTranslation("auth");
const { t: ct } = useTranslation();
const { data: enableSMTP, isLoading: loadingSMTPStatus } = useGetSMTPStatusQuery();
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
const [emailInputted, setEmailInputted] = useState(false);
const [input, setInput] = useState(defaultInput);
useEffect(() => {
const query = new URLSearchParams(location.search);
const code = query.get("code");
const state = query.get("state");
const magic_token = query.get("magic_token");
const exists = query.get("exists");
// oidc
const fromOIDC = code && state;
if (fromOIDC) {
login({
code,
state,
type: "oidc"
});
}
// magic link
if (magic_token && typeof exists !== "undefined") {
const isLogin = exists == "true";
if (isLogin) {
// login
login({
magic_token,
type: "magiclink"
});
} else {
// reg with magic link and set name only
// navigate(`/register/set_name/login?magic_token=${magic_token}`);
location.href = `/#/register/set_name/login?magic_token=${magic_token}`;
}
}
}, []);
useEffect(() => {
if (error) {
switch ((error as FetchBaseQueryError).status) {
case 401:
case 404:
toast.error("Username or Password incorrect");
break;
case 410:
toast.error(
"No associated account found, please contact user admin for an invitation link to join."
);
break;
// 451 有解析错误,暂时先客户端处理
case "PARSING_ERROR":
break;
default:
toast.error("Something Error");
break;
}
return;
}
}, [error]);
useEffect(() => {
console.log("login success", isSuccess);
if (isSuccess) {
toast.success(ct("tip.login"));
// setInput(defaultInput);
// navigateTo("/");
}
}, [isSuccess]);
const handleLogin = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const enableMagicLink = enableSMTP && loginConfig?.magic_link;
if (enableMagicLink && !emailInputted) {
setEmailInputted(true);
return;
}
login({
...input,
type: "password"
});
};
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
const { type } = evt.target.dataset as { type?: "email" | "password" };
const { value } = evt.target;
if (!type) return;
const newInput = { ...input, [type]: value };
setInput(newInput);
};
const handleBack = () => {
setEmailInputted(false);
};
const { email, password } = input;
if (!loginConfigSuccess) return null;
const { magic_link, who_can_sign_up: whoCanSignUp } = loginConfig;
const enableMagicLink = enableSMTP && magic_link;
const hideSocials = (enableMagicLink && emailInputted) || whoCanSignUp == "InvitationOnly";
const showSignIn = !enableMagicLink || emailInputted;
if (loadingSMTPStatus) return null;
return (
<div className="flex-center h-screen dark:bg-gray-700">
<div className="relative py-8 px-10 shadow-md rounded-xl">
{emailInputted && (
<IconBack
role="button"
className="absolute left-7 top-8 w-10 h-10 stroke-gray-300"
onClick={handleBack}
/>
)}
<div className="flex-center flex-col pb-6 w-full">
<img
src={logo || `${BASE_URL}/resource/organization/logo?t=${Date.now()}`}
alt="logo"
className="w-14 h-14 mb-3 md:mb-7 rounded-full"
/>
<h2 className="font-semibold text-center text-balance text-2xl max-w-[420px] text-gray-800 dark:text-white">
{t("login.title", { name: serverName })}
</h2>
</div>
<form
className="flex flex-col gap-5 md:min-w-[360px] w-full"
autoComplete="false"
onSubmit={handleLogin}
>
{!emailInputted && (
<div className="flex flex-col gap-1">
<StyledLabel>Email</StyledLabel>
<Input
autoFocus
className="large"
name="email"
value={email}
type="email"
required
placeholder={t("placeholder_email")}
data-type="email"
onChange={handleInput}
/>
</div>
)}
{(!enableMagicLink || emailInputted) && (
<div className="">
<StyledLabel>Password</StyledLabel>
<Input
className="large"
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder={t("placeholder_pwd")}
/>
</div>
)}
{showSignIn ? (
<Button type="submit" disabled={isLoading}>
{isLoading ? "Signing" : t("sign_in")}
</Button>
) : (
<Button type="submit">{t("continue")}</Button>
)}
</form>
<Divider content="OR" />
<div className="socials flex flex-col gap-3">
{emailInputted && <MagicLinkLogin email={input.email} />}
{!hideSocials && <SocialLoginButtons />}
</div>
{whoCanSignUp === "EveryOne" && <SignUpLink />}
</div>
</div>
);
}