mike/frontend/src/app/login/page.tsx
willchen96 3a10943200 feat: implement multi-factor authentication (MFA) setup and verification flow
- Add SecurityPage component for managing MFA settings, including enrollment and verification.
- Create MfaLoginGate to handle MFA verification state during login.
- Develop MfaVerificationPopup for user input of verification codes.
- Implement VerifyMfaPage for the MFA verification process after login.
- Introduce reusable VerificationCodeInput component for entering verification codes.
- Integrate Supabase MFA API for managing factors and verification.
- Add loading states and error handling for a better user experience.
2026-06-10 03:48:08 +08:00

137 lines
5.8 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import Link from "next/link";
import { SiteLogo } from "@/components/site-logo";
import { useAuth } from "@/contexts/AuthContext";
const authGlassCardClassName =
"rounded-2xl border border-white/70 bg-white/72 p-8 shadow-[0_4px_14px_rgba(15,23,42,0.045),inset_0_1px_0_rgba(255,255,255,0.86),inset_0_-8px_18px_rgba(255,255,255,0.12)] backdrop-blur-2xl";
const authInputClassName =
"rounded-lg border border-transparent bg-gray-100 px-3 shadow-none focus-visible:border-gray-200 focus-visible:ring-2 focus-visible:ring-gray-300/45";
export default function LoginPage() {
const router = useRouter();
const { isAuthenticated, authLoading } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!authLoading && isAuthenticated) {
router.replace("/assistant");
}
}, [authLoading, isAuthenticated, router]);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw error;
router.push("/assistant");
} catch (error: any) {
setError(error.message || "An error occurred during login");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-dvh bg-gray-50/80 flex items-start justify-center px-6 pt-32 md:pt-40 pb-10 relative">
<div className="absolute top-4 md:top-8 left-1/2 -translate-x-1/2">
<SiteLogo size="lg" asLink />
</div>
<div className="w-full max-w-md">
{/* Login Form */}
<div className={`${authGlassCardClassName} mb-4`}>
<div className="flex justify-between items-center mb-6">
<h2 className="text-left text-2xl font-serif">
Log In
</h2>
<div className="bg-gray-200/70 p-1 rounded-lg flex text-xs font-medium shadow-[inset_0_1px_0_rgba(255,255,255,0.65),inset_0_-3px_8px_rgba(148,163,184,0.16)] backdrop-blur-xl">
<span className="text-gray-700 px-3 py-1 bg-white/85 rounded-md shadow-[0_1px_4px_rgba(15,23,42,0.06)]">
Log in
</span>
<Link
href="/signup"
className="px-3 py-1 text-gray-500 hover:text-gray-900"
>
Sign up
</Link>
</div>
</div>
<form onSubmit={handleLogin} className="space-y-4">
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-2"
>
Email
</label>
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
className={`w-full ${authInputClassName}`}
/>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-2"
>
Password
</label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
required
className={`w-full ${authInputClassName}`}
/>
</div>
{error && (
<div className="text-red-600 text-sm bg-red-50 p-3 rounded">
{error}
</div>
)}
<Button
type="submit"
disabled={loading}
className="w-full mt-5 bg-black hover:bg-gray-900 text-white"
>
{loading ? "Logging in..." : "Log in"}
</Button>
</form>
</div>
<p className="text-center text-xs text-gray-500 leading-relaxed px-2">
Mike hosted on MikeOSS.com is currently a demo service.
Please do not upload, submit, or store sensitive,
confidential, privileged, client, or personally
identifiable documents.
</p>
</div>
</div>
);
}