Feat: support email login (#490)

* may login with email

* update login layout

* update login style
This commit is contained in:
an-lee
2024-04-07 11:38:21 +08:00
committed by GitHub
parent 3d9a1ccba6
commit 52287357d5
4 changed files with 238 additions and 115 deletions

View File

@@ -6,6 +6,11 @@ import {
Sheet,
SheetContent,
SheetTrigger,
Label,
Card,
CardContent,
CardHeader,
CardTitle,
} from "@renderer/components/ui";
import { useContext, useEffect, useRef, useState } from "react";
import { AppSettingsProviderContext } from "@renderer/context";
@@ -110,62 +115,82 @@ export const LoginForm = () => {
return (
<>
<div className="w-full max-w-sm px-6 flex flex-col space-y-4">
<Button
variant="outline"
size="lg"
className="w-full h-12 relative rounded-full"
onClick={() => handleLogin("github")}
>
<img
src="assets/github-mark.png"
className="w-8 h-8 absolute left-4"
alt="github-logo"
/>
<span className="text-lg">GitHub</span>
</Button>
<Button
variant="outline"
size="lg"
className="w-full h-12 relative rounded-full"
onClick={() => handleLogin("mixin")}
>
<img
src="assets/mixin-logo.png"
className="w-8 h-8 absolute left-4"
alt="mixin-logo"
/>
<span className="text-lg">Mixin Messenger</span>
</Button>
<Sheet>
<SheetTrigger asChild>
<Button
variant="outline"
size="lg"
className="w-full h-12 relative rounded-full"
>
<img
src="assets/bandu-logo.svg"
className="w-10 h-10 absolute left-4"
alt="bandu-logo"
/>
<span className="text-lg"></span>
</Button>
</SheetTrigger>
<SheetContent side="bottom" className="h-screen">
<div className="w-full h-full flex">
<div className="m-auto">
<PandoLoginForm />
</div>
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>{t('login')}</CardTitle>
</CardHeader>
<CardContent>
<EmailLoginForm />
<div className="">
<Separator className="my-4" />
<div className="flex items-center justify-center text-xs text-muted-foreground mb-4">
{t('youCanAlsoLoginWith')}
</div>
</SheetContent>
</Sheet>
</div>
<div className="flex items-center space-x-2 justify-center">
<Button
variant="outline"
size="icon"
data-tooltip-id="global-tooltip"
data-tooltip-content="GitHub"
className="w-10 h-10 rounded-full"
onClick={() => handleLogin("github")}
>
<img
src="assets/github-mark.png"
className="w-full h-full"
alt="github-logo"
/>
</Button>
<Button
variant="outline"
size="icon"
data-tooltip-id="global-tooltip"
data-tooltip-content="Mixin"
className="w-10 h-10 rounded-full p-1"
onClick={() => handleLogin("mixin")}
>
<img
src="assets/mixin-logo.png"
className="w-full h-full"
alt="mixin-logo"
/>
</Button>
<Sheet>
<SheetTrigger asChild>
<Button
variant="outline"
size="icon"
data-tooltip-id="global-tooltip"
data-tooltip-content="学升"
className="w-10 h-10 rounded-full"
>
<img
src="assets/bandu-logo.svg"
className="w-full h-full"
alt="bandu-logo"
/>
</Button>
</SheetTrigger>
<SheetContent side="bottom" className="h-screen">
<div className="w-full h-full flex">
<div className="m-auto">
<PandoLoginForm />
</div>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</CardContent>
</Card>
<div
className={`absolute top-0 left-0 w-screen h-screen z-10 flex flex-col overflow-hidden ${
webviewUrl ? "" : "hidden"
}`}
className={`absolute top-0 left-0 w-screen h-screen z-10 flex flex-col overflow-hidden ${webviewUrl ? "" : "hidden"
}`}
>
<div className="flex items-center py-2 px-6">
<Button variant="ghost" onClick={() => setWebviewUrl(null)}>
@@ -181,6 +206,100 @@ export const LoginForm = () => {
);
};
const EmailLoginForm = () => {
const [email, setEmail] = useState<string>("");
const [code, setCode] = useState<string>("");
const [codeSent, setCodeSent] = useState<boolean>(false);
const [countdown, setCountdown] = useState<number>(0);
const { login, webApi } = useContext(AppSettingsProviderContext);
useEffect(() => {
if (countdown > 0) {
setTimeout(() => {
setCountdown(countdown - 1);
}, 1000);
}
}, [countdown]);
return (
<div className="w-full">
<div className="w-full grid gap-4 mb-6">
<div className="grid gap-2">
<Label htmlFor="email">{t('email')}</Label>
<Input
id="email"
className="h-10"
type="email"
placeholder="m@example.com"
required
value={email}
disabled={countdown > 0}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="code">{t('verificationCode')}</Label>
<Input
id="code"
className="h-10"
type="text"
required
minLength={5}
maxLength={5}
placeholder={t("verificationCode")}
value={code}
onChange={(e) => setCode(e.target.value)}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<Button
variant="secondary"
size="lg"
className="w-full"
disabled={!email || countdown > 0}
onClick={() => {
webApi
.loginCode({ email })
.then(() => {
toast.success(t("codeSent"));
setCodeSent(true);
setCountdown(120);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{countdown > 0 && <span className="mr-2">{countdown}</span>}
<span>{codeSent ? t("resend") : t("sendCode")}</span>
</Button>
<Button
variant="default"
size="lg"
className="w-full"
disabled={!code || code.length < 5 || !email}
onClick={() => {
webApi
.auth({ provider: "email", code, email })
.then((user) => {
if (user?.id && user?.accessToken) login(user);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{t("login")}
</Button>
</div>
</div>
);
}
const PandoLoginForm = () => {
const ref = useRef<HTMLInputElement>(null);
const [iti, setIti] = useState<any>(null);
@@ -234,45 +353,23 @@ const PandoLoginForm = () => {
<img src="assets/bandu-logo.svg" className="w-20 h-20" alt="bandu" />
</div>
<div className="mb-12">
<div className="mb-2">
<input
onInput={validatePhone}
onBlur={validatePhone}
className="border text-lg py-2 px-4 rounded"
ref={ref}
/>
</div>
{phoneNumber && (
<div className="mb-8">
<Button
variant="default"
size="lg"
className="w-full"
disabled={countdown > 0}
onClick={() => {
webApi
.loginCode({ phoneNumber })
.then(() => {
toast.success(t("codeSent"));
setCodeSent(true);
setCountdown(60);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{countdown > 0 && <span className="mr-2">{countdown}</span>}
<span>{codeSent ? t("resend") : t("sendCode")}</span>
</Button>
<div className="grid gap-6">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="phone">{t('phoneNumber')}</Label>
<input
id="phone"
value={phoneNumber}
onInput={validatePhone}
onBlur={validatePhone}
className="border text-lg py-2 px-4 rounded"
ref={ref}
/>
</div>
)}
{codeSent && (
<div className="mb-2 w-full">
<div className="grid gap-2">
<Label htmlFor="verrificationCode">{t('verificationCode')}</Label>
<Input
id="verrificationCode"
className="border py-2 h-10 px-4 rounded"
type="text"
minLength={5}
@@ -282,30 +379,49 @@ const PandoLoginForm = () => {
onChange={(e) => setCode(e.target.value)}
/>
</div>
)}
</div>
{code && (
<div>
<Button
variant="default"
size="lg"
className="w-full"
disabled={!code || code.length < 5 || !phoneNumber}
onClick={() => {
webApi
.auth({ provider: "bandu", code, phoneNumber })
.then((user) => {
if (user?.id && user?.accessToken) login(user);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{t("login")}
</Button>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<Button
variant="secondary"
size="lg"
className="w-full"
disabled={!phoneNumber || countdown > 0}
onClick={() => {
webApi
.loginCode({ phoneNumber })
.then(() => {
toast.success(t("codeSent"));
setCodeSent(true);
setCountdown(60);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{countdown > 0 && <span className="mr-2">{countdown}</span>}
<span>{codeSent ? t("resend") : t("sendCode")}</span>
</Button>
<Button
variant="default"
size="lg"
className="w-full"
disabled={!code || code.length < 5 || !phoneNumber}
onClick={() => {
webApi
.auth({ provider: "bandu", code, phoneNumber })
.then((user) => {
if (user?.id && user?.accessToken) login(user);
})
.catch((err) => {
toast.error(err.message);
});
}}
>
{t("login")}
</Button>
</div>
</div>
</div>
);