refactor: login with magic link
This commit is contained in:
@@ -30,12 +30,12 @@
|
|||||||
"placeholder_email": "Enter your Email",
|
"placeholder_email": "Enter your Email",
|
||||||
"placeholder_pwd": "Enter Password",
|
"placeholder_pwd": "Enter Password",
|
||||||
"placeholder_confirm_pwd": "Confirm Password",
|
"placeholder_confirm_pwd": "Confirm Password",
|
||||||
"check_email": "Check your email",
|
"check_email": "Check your inbox",
|
||||||
"check_email_desc": " We’ve sent you a magic link to {{email}}. Click on the link to continue.",
|
"check_email_desc": "We’ve just emailed a magic link to <0>{{email}}</0>. Once it arrives, it will be valid for 15 minutes.",
|
||||||
"use_different": "Use a different email",
|
"back_sign_in": "Back to Sign in",
|
||||||
"welcome": "Welcome to {{name}} server",
|
"welcome": "Welcome to {{name}} server",
|
||||||
"guest_login_tip": "Please scan QR code or sign in to send a message",
|
"guest_login_tip": "Please scan QR code or sign in to send a message",
|
||||||
"enter": "Enter ",
|
"enter": "Sign in to ",
|
||||||
"sign_in": "Sign In",
|
"sign_in": "Sign In",
|
||||||
"sign_up": "Sign Up",
|
"sign_up": "Sign Up",
|
||||||
"signing_up": "Signing Up",
|
"signing_up": "Signing Up",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"continue": "引き続き",
|
"continue": "引き続き",
|
||||||
"check_email": "メールボックスを確認してください",
|
"check_email": "メールボックスを確認してください",
|
||||||
"check_email_desc": "メールボックスにリンクを送信します:{{email}}にチェックし確認してください",
|
"check_email_desc": "メールボックスにリンクを送信します:{{email}}にチェックし確認してください",
|
||||||
"use_different": "別のメールアドレスを使用する",
|
"back_sign_in": "別のメールアドレスを使用する",
|
||||||
"placeholder_email": "メールアドレスを入力してください",
|
"placeholder_email": "メールアドレスを入力してください",
|
||||||
"placeholder_pwd": "パスワードを入力してください",
|
"placeholder_pwd": "パスワードを入力してください",
|
||||||
"placeholder_confirm_pwd": "パスワードを再入力してください",
|
"placeholder_confirm_pwd": "パスワードを再入力してください",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"placeholder_confirm_pwd": "Parola Onayı",
|
"placeholder_confirm_pwd": "Parola Onayı",
|
||||||
"check_email": "E-Posta'nızı kontrol edin",
|
"check_email": "E-Posta'nızı kontrol edin",
|
||||||
"check_email_desc": " {{email}} adresine sihirli bağlantı gönderildi. Devam etmek için lütfen bağlantıya tıklayın.",
|
"check_email_desc": " {{email}} adresine sihirli bağlantı gönderildi. Devam etmek için lütfen bağlantıya tıklayın.",
|
||||||
"use_different": "Başka bir e-posta kullan",
|
"back_sign_in": "Başka bir e-posta kullan",
|
||||||
"welcome": "{{name}} sunucusu'na hoşgeldiniz!",
|
"welcome": "{{name}} sunucusu'na hoşgeldiniz!",
|
||||||
"guest_login_tip": "Mesaj gönderebilmek için lütfen QR kodu okutun veya oturum açın.",
|
"guest_login_tip": "Mesaj gönderebilmek için lütfen QR kodu okutun veya oturum açın.",
|
||||||
"enter": "Giriş ",
|
"enter": "Giriş ",
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
},
|
},
|
||||||
"continue": "继续",
|
"continue": "继续",
|
||||||
"check_email": "检查你的邮箱",
|
"check_email": "检查你的邮箱",
|
||||||
"check_email_desc": "我们将链接已发送到你的邮箱:{{email}},请前往查收",
|
"check_email_desc": "我们已将链接发送至邮箱:<0>{{email}}</0>,有效期15分钟,请尽快通过该链接登录。",
|
||||||
"use_different": "使用其它邮箱",
|
"back_sign_in": "返回登录",
|
||||||
"placeholder_name": "输入用户名",
|
"placeholder_name": "输入用户名",
|
||||||
"placeholder_email": "输入邮箱地址",
|
"placeholder_email": "输入邮箱地址",
|
||||||
"placeholder_pwd": "输入密码",
|
"placeholder_pwd": "输入密码",
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const PageRoutes = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/send_magic_link"
|
path="/send_magic_link/:email"
|
||||||
element={
|
element={
|
||||||
<LazyIt>
|
<LazyIt>
|
||||||
<RequireNoAuth>
|
<RequireNoAuth>
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import Button from "@/components/styled/Button";
|
import Button from "@/components/styled/Button";
|
||||||
|
|
||||||
export default function MagicLinkLogin() {
|
type Props = {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
export default function MagicLinkLogin({ email }: Props) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
navigate("/send_magic_link");
|
navigate(`/send_magic_link/${email}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className="w-full" onClick={handleLogin}>
|
<Button className="w-full ghost" onClick={handleLogin}>
|
||||||
Sign in with Magic Link
|
Sign in with a Magic Link
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-27
@@ -11,6 +11,7 @@ import { useAppSelector } from "@/app/store";
|
|||||||
import Divider from "@/components/Divider";
|
import Divider from "@/components/Divider";
|
||||||
import Button from "@/components/styled/Button";
|
import Button from "@/components/styled/Button";
|
||||||
import Input from "@/components/styled/Input";
|
import Input from "@/components/styled/Input";
|
||||||
|
import StyledLabel from "@/components/styled/Label";
|
||||||
import MagicLinkLogin from "./MagicLinkLogin";
|
import MagicLinkLogin from "./MagicLinkLogin";
|
||||||
import SignUpLink from "./SignUpLink";
|
import SignUpLink from "./SignUpLink";
|
||||||
import SocialLoginButtons from "./SocialLoginButtons";
|
import SocialLoginButtons from "./SocialLoginButtons";
|
||||||
@@ -22,6 +23,7 @@ export default function LoginPage() {
|
|||||||
const { data: enableSMTP, isLoading: loadingSMTPStatus } = useGetSMTPStatusQuery();
|
const { data: enableSMTP, isLoading: loadingSMTPStatus } = useGetSMTPStatusQuery();
|
||||||
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
|
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
|
||||||
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
|
const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
|
||||||
|
const [emailInputted, setEmailInputted] = useState(false);
|
||||||
const [input, setInput] = useState({
|
const [input, setInput] = useState({
|
||||||
email: "",
|
email: "",
|
||||||
password: ""
|
password: ""
|
||||||
@@ -90,6 +92,11 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
const handleLogin = (evt: FormEvent<HTMLFormElement>) => {
|
const handleLogin = (evt: FormEvent<HTMLFormElement>) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
const enableMagicLink = enableSMTP && loginConfig?.magic_link;
|
||||||
|
if (enableMagicLink && !emailInputted) {
|
||||||
|
setEmailInputted(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
login({
|
login({
|
||||||
...input,
|
...input,
|
||||||
type: "password"
|
type: "password"
|
||||||
@@ -111,7 +118,8 @@ export default function LoginPage() {
|
|||||||
const { magic_link, who_can_sign_up: whoCanSignUp } = loginConfig;
|
const { magic_link, who_can_sign_up: whoCanSignUp } = loginConfig;
|
||||||
|
|
||||||
const enableMagicLink = enableSMTP && magic_link;
|
const enableMagicLink = enableSMTP && magic_link;
|
||||||
|
const hideSocials = enableMagicLink && emailInputted;
|
||||||
|
const showSignIn = !enableMagicLink || emailInputted;
|
||||||
if (loadingSMTPStatus) return null;
|
if (loadingSMTPStatus) return null;
|
||||||
return (
|
return (
|
||||||
<div className="flex-center h-screen dark:bg-gray-700">
|
<div className="flex-center h-screen dark:bg-gray-700">
|
||||||
@@ -127,36 +135,50 @@ export default function LoginPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<form className="flex flex-col gap-5 w-80 md:min-w-[360px] " onSubmit={handleLogin}>
|
<form className="flex flex-col gap-5 w-80 md:min-w-[360px] " onSubmit={handleLogin}>
|
||||||
<Input
|
{!emailInputted && (
|
||||||
className="large"
|
<div className="flex flex-col gap-1">
|
||||||
name="email"
|
<StyledLabel>Email</StyledLabel>
|
||||||
value={email}
|
<Input
|
||||||
type="email"
|
className="large"
|
||||||
required
|
name="email"
|
||||||
placeholder={t("placeholder_email")}
|
value={email}
|
||||||
data-type="email"
|
type="email"
|
||||||
onChange={handleInput}
|
required
|
||||||
/>
|
placeholder={t("placeholder_email")}
|
||||||
<Input
|
data-type="email"
|
||||||
className="large"
|
onChange={handleInput}
|
||||||
type="password"
|
/>
|
||||||
value={password}
|
</div>
|
||||||
name="password"
|
)}
|
||||||
required
|
{(!enableMagicLink || emailInputted) && (
|
||||||
data-type="password"
|
<div className="">
|
||||||
onChange={handleInput}
|
<StyledLabel>Password</StyledLabel>
|
||||||
placeholder={t("placeholder_pwd")}
|
<Input
|
||||||
/>
|
className="large"
|
||||||
<Button type="submit" disabled={isLoading}>
|
type="password"
|
||||||
{isLoading ? "Signing" : t("sign_in")}
|
value={password}
|
||||||
</Button>
|
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>
|
</form>
|
||||||
<Divider content="OR" />
|
<Divider content="OR" />
|
||||||
<div className="socials flex flex-col gap-3">
|
<div className="socials flex flex-col gap-3">
|
||||||
{enableMagicLink && <MagicLinkLogin />}
|
{emailInputted && <MagicLinkLogin email={input.email} />}
|
||||||
<SocialLoginButtons />
|
{!hideSocials && <SocialLoginButtons />}
|
||||||
</div>
|
</div>
|
||||||
{whoCanSignUp === "EveryOne" && <SignUpLink />}
|
{whoCanSignUp === "EveryOne" && !hideSocials && <SignUpLink />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
import { FC, MouseEvent } from "react";
|
import { FC, MouseEvent } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import BASE_URL from "@/app/config";
|
||||||
import Button from "@/components/styled/Button";
|
import Button from "@/components/styled/Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
email: string;
|
email: string;
|
||||||
reset?: (e: MouseEvent<HTMLButtonElement>) => void;
|
handleBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SentTip: FC<Props> = ({ email, reset }) => {
|
const SentTip: FC<Props> = ({ email, handleBack }) => {
|
||||||
const { t } = useTranslation("auth");
|
const { t } = useTranslation("auth");
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center max-w-sm">
|
||||||
<h2 className="font-semibold text-2xl text-gray-800 dark:text-gray-200 mb-2">
|
<img
|
||||||
|
src={`${BASE_URL}/resource/organization/logo`}
|
||||||
|
alt="logo"
|
||||||
|
className="w-14 h-14 mb-2 md:mb-7 rounded-full"
|
||||||
|
/>
|
||||||
|
<h2 className="font-semibold text-2xl text-gray-900 dark:text-gray-100 mb-2">
|
||||||
{t("check_email")}
|
{t("check_email")}
|
||||||
</h2>
|
</h2>
|
||||||
<span className="text-center text-gray-500 mb-6">{t("check_email_desc", { email })}</span>
|
<span className="text-center text-gray-500 mb-6">
|
||||||
<Button onClick={reset} className="main flex-center">
|
<Trans i18nKey={"check_email_desc"} ns="auth" values={{ email }}>
|
||||||
{t("use_different")}
|
<strong className="font-bold" />
|
||||||
|
</Trans>
|
||||||
|
</span>
|
||||||
|
<Button onClick={handleBack} className="flex-center ghost">
|
||||||
|
{t("back_sign_in")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import BASE_URL from "@/app/config";
|
|
||||||
import { useSendLoginMagicLinkMutation } from "@/app/services/auth";
|
import { useSendLoginMagicLinkMutation } from "@/app/services/auth";
|
||||||
import { useAppSelector } from "@/app/store";
|
|
||||||
import Divider from "@/components/Divider";
|
|
||||||
import Button from "@/components/styled/Button";
|
|
||||||
import Input from "@/components/styled/Input";
|
|
||||||
import SocialLoginButtons from "../login/SocialLoginButtons";
|
|
||||||
import SignInLink from "../reg/SignInLink";
|
|
||||||
import SentTip from "./SentTip";
|
import SentTip from "./SentTip";
|
||||||
|
|
||||||
export default function SendMagicLinkPage() {
|
export default function SendMagicLinkPage() {
|
||||||
const { t } = useTranslation("auth");
|
const { email = "" } = useParams();
|
||||||
const serverName = useAppSelector((store) => store.server.name);
|
|
||||||
const [sent, setSent] = useState(false);
|
|
||||||
const [sendMagicLink, { isSuccess, isLoading, error }] = useSendLoginMagicLinkMutation();
|
const [sendMagicLink, { isSuccess, isLoading, error }] = useSendLoginMagicLinkMutation();
|
||||||
const navigateTo = useNavigate();
|
const navigateTo = useNavigate();
|
||||||
const [email, setEmail] = useState("");
|
useEffect(() => {
|
||||||
|
if (email) {
|
||||||
|
sendMagicLink(email);
|
||||||
|
}
|
||||||
|
}, [email]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
toast.success("Send Email Successfully!");
|
toast.success("Send Email Successfully!");
|
||||||
setSent(true);
|
|
||||||
}
|
}
|
||||||
}, [isSuccess]);
|
}, [isSuccess]);
|
||||||
|
|
||||||
@@ -52,64 +45,14 @@ export default function SendMagicLinkPage() {
|
|||||||
navigateTo("/login");
|
navigateTo("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = (evt: FormEvent<HTMLFormElement>) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
sendMagicLink(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInput = (evt: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const { value } = evt.target;
|
|
||||||
setEmail(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
setEmail("");
|
|
||||||
setSent(false);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-center h-screen dark:bg-gray-700">
|
<div className="flex-center h-screen">
|
||||||
<div className="py-8 px-10 shadow rounded-xl">
|
<div className="py-8 px-10 shadow-md rounded-xl bg-white dark:bg-gray-700">
|
||||||
{sent ? (
|
{isSuccess ? (
|
||||||
<SentTip email={email} reset={handleReset} />
|
<SentTip email={email} handleBack={handlePwdPath} />
|
||||||
) : (
|
) : isLoading ? (
|
||||||
<>
|
<div className="">Sending...</div>
|
||||||
<div className="flex flex-col items-center">
|
) : null}
|
||||||
<img
|
|
||||||
src={`${BASE_URL}/resource/organization/logo`}
|
|
||||||
alt="logo"
|
|
||||||
className="w-14 h-14 mb-7 rounded-full"
|
|
||||||
/>
|
|
||||||
<h2 className="font-semibold text-2xl text-gray-800 dark:text-gray-200 mb-2">
|
|
||||||
{t("enter")} {serverName}{" "}
|
|
||||||
</h2>
|
|
||||||
<span className="text-center text-gray-500 mb-6">{t("placeholder_email")}</span>
|
|
||||||
</div>
|
|
||||||
<form onSubmit={handleLogin} className="flex flex-col gap-5 w-[360px]">
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
className="large"
|
|
||||||
name="email"
|
|
||||||
autoFocus
|
|
||||||
value={email}
|
|
||||||
required
|
|
||||||
// pattern={`^\S+@\S+\.\S+$`}
|
|
||||||
placeholder={t("placeholder_email")}
|
|
||||||
onChange={handleInput}
|
|
||||||
/>
|
|
||||||
<Button type="submit" disabled={isLoading || !email}>
|
|
||||||
{isLoading ? "Sending" : t("continue")}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<Divider content="OR" />
|
|
||||||
<Button onClick={handlePwdPath} className="w-full">
|
|
||||||
{t("login.password")}
|
|
||||||
</Button>
|
|
||||||
<div className="flex flex-col gap-3 py-3 empty:hidden">
|
|
||||||
<SocialLoginButtons />
|
|
||||||
</div>
|
|
||||||
<SignInLink />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user