Files
tonePage/apps/frontend/app/console/login/page.tsx
2025-12-19 22:23:16 +08:00

123 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { KeyRound, Phone, FileKey2 } from "lucide-react";
import PasswordLoginMode from "./components/PasswordLoginMode";
import PhoneLoginMode from "./components/SmsLoginMode";
import { useEffect, useState } from "react";
import LoginBG from './components/login-bg.jpg';
import Image from "next/image";
import { handleAPIError } from "@/lib/api/common";
import { useUserStore } from "@/store/useUserStore";
import PasskeyLoginPage from "./components/PasskeyLoginPage";
export type SubmitMode = 'password' | 'sms' | 'passkey';
export default function Login() {
const router = useRouter();
const [loginMode, setLoginMode] = useState<SubmitMode>('password');
const userStore = useUserStore();
useEffect(() => {
if (userStore.user) {
router.replace('/console')
}
}, [userStore, router])
return (
<>
<Header />
<div className="flex flex-1 flex-col items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm md:max-w-3xl">
<div className={cn("flex flex-col gap-6")}>
<Card className="overflow-hidden p-0">
<CardContent className="grid p-0 md:grid-cols-2">
<form className="p-6 md:p-8" onSubmit={async e => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const handler = (await (async () => {
if (loginMode === 'password') {
return import('./components/PasswordLoginMode');
} else if (loginMode === 'sms') {
return import('./components/SmsLoginMode');
} else if (loginMode === 'passkey') {
return import('./components/PasskeyLoginPage');
}
})())?.handleSubmit;
if (!handler) {
return toast.error('登陆状态异常');
}
handler(formData).then((data) => {
useUserStore.getState().setUser(data.user);
// to main page
router.replace('/console');
}, (e) => {
handleAPIError(e, ({ message }) => toast.error(message))
})
}}>
<div className="flex flex-col gap-6">
{loginMode === 'password' ? <PasswordLoginMode /> : null}
{loginMode === 'sms' ? <PhoneLoginMode /> : null}
{loginMode === 'passkey' ? <PasskeyLoginPage /> : null}
<div className="after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t">
<span className="bg-card text-muted-foreground relative z-10 px-2">
使
</span>
</div>
<div className="grid grid-cols-3 gap-4">
{
([['password', KeyRound], ['sms', Phone], ['passkey', FileKey2]] as const).map(([mode, Icon]) => (
<Button key={mode} variant={loginMode === mode ? 'default' : 'outline'} type="button" className="w-full" onClick={() => setLoginMode(mode)}>
<Icon />
</Button>
))
}
</div>
<div className="text-center text-sm">
{" "}
<a className="underline underline-offset-4 cursor-pointer" onClick={() => setLoginMode('sms')}>
</a>
</div>
</div>
</form>
<div className="bg-muted relative hidden md:block">
<Image
src={LoginBG.src}
alt="Image"
width={500}
height={500}
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
priority
quality={100}
/>
</div>
</CardContent>
</Card>
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
{" "}
<a href="#" className="underline underline-offset-4">
</a>{" "}
{" "}
<a href="#" className="underline underline-offset-4">
</a>
</div>
</div >
</div>
</div >
<Footer />
</>
)
}