forked from 77media/video-flow
295 lines
10 KiB
TypeScript
295 lines
10 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useCallback, useEffect } from "react";
|
||
import "./style/login.css";
|
||
import { useRouter, useSearchParams } from "next/navigation";
|
||
import React from "react";
|
||
import Link from "next/link";
|
||
import { signInWithGoogle, loginUser } from "@/lib/auth";
|
||
import { GradientText } from "@/components/ui/gradient-text";
|
||
import { GoogleLoginButton } from "@/components/ui/google-login-button";
|
||
import { Eye, EyeOff } from "lucide-react";
|
||
|
||
export default function Login() {
|
||
const [email, setEmail] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [showPassword, setShowPassword] = useState(false);
|
||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||
const [formError, setFormError] = useState("");
|
||
const [successMessage, setSuccessMessage] = useState("");
|
||
const [passwordError, setPasswordError] = useState("");
|
||
const [googleLoading, setGoogleLoading] = useState(false);
|
||
const [emailFocused, setEmailFocused] = useState(false);
|
||
const [passwordFocused, setPasswordFocused] = useState(false);
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
|
||
/** 密码验证函数 */
|
||
/**
|
||
* Password validation function with English prompts
|
||
* @param {string} password - The password to validate
|
||
* @returns {string} - Error message if invalid, empty string if valid
|
||
*/
|
||
const validatePassword = (password: string): string => {
|
||
if (password.length < 8) {
|
||
return "Password must be at least 8 characters";
|
||
}
|
||
if (password.length > 18) {
|
||
return "Password cannot exceed 18 characters";
|
||
}
|
||
if (!/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^*&\.]{8,18}$/.test(password)) {
|
||
return "Password must contain both letters and numbers";
|
||
}
|
||
return "";
|
||
};
|
||
|
||
/** 处理密码输入变化 */
|
||
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const newPassword = e.target.value;
|
||
setPassword(newPassword);
|
||
|
||
if (newPassword) {
|
||
const error = validatePassword(newPassword);
|
||
setPasswordError(error);
|
||
} else {
|
||
setPasswordError("");
|
||
}
|
||
};
|
||
|
||
// Check for registered=true parameter
|
||
useEffect(() => {
|
||
const registered = searchParams?.get("registered");
|
||
if (registered === "true") {
|
||
setSuccessMessage(
|
||
"Registration successful! Please login with your new account."
|
||
);
|
||
}
|
||
|
||
const error = searchParams?.get("error");
|
||
if (error) {
|
||
if (error === "google_oauth") {
|
||
setFormError("Google login failed, please try again.");
|
||
} else if (error === "auth_failed") {
|
||
setFormError("Authentication failed, please try again.");
|
||
} else if (error === "oauth_callback_incomplete") {
|
||
setFormError("OAuth callback processing is incomplete. Please use the Google login button below.");
|
||
} else if (error === "deprecated_oauth_callback") {
|
||
setFormError("The old OAuth method is deprecated. Please use the Google login button below for a better experience.");
|
||
}
|
||
}
|
||
}, [searchParams]);
|
||
|
||
const handleGoogleSignIn = async () => {
|
||
try {
|
||
setGoogleLoading(true);
|
||
setFormError("");
|
||
|
||
// 获取邀请码(从URL参数或其他来源)
|
||
const inviteCode = searchParams?.get("invite") || undefined;
|
||
|
||
// 使用Google GSI SDK进行登录
|
||
await signInWithGoogle(inviteCode);
|
||
} catch (error: any) {
|
||
console.error("Google sign-in error:", error);
|
||
setFormError(error.message || "Google sign-in failed, please try again");
|
||
setGoogleLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||
e.preventDefault();
|
||
|
||
// 验证密码
|
||
const passwordValidationError = validatePassword(password);
|
||
if (passwordValidationError) {
|
||
setPasswordError(passwordValidationError);
|
||
return;
|
||
}
|
||
|
||
setIsSubmitting(true);
|
||
setFormError("");
|
||
setSuccessMessage("");
|
||
|
||
try {
|
||
await loginUser(email, password);
|
||
// 登录成功后跳转到首页
|
||
router.push("/movies");
|
||
} catch (error: any) {
|
||
console.error("Login failed:", error);
|
||
|
||
// 根据错误类型显示不同的错误消息
|
||
setFormError(
|
||
error.message || "Login failed, please try again."
|
||
);
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-screen relative overflow-hidden">
|
||
{/* 背景视频 */}
|
||
<video
|
||
autoPlay
|
||
loop
|
||
muted
|
||
playsInline
|
||
className="absolute inset-0 w-full h-full object-cover"
|
||
data-alt="background-video"
|
||
>
|
||
<source src="/assets/login.mp4" type="video/mp4" />
|
||
{/* 如果没有视频文件,显示渐变背景 */}
|
||
</video>
|
||
|
||
{/* 视频遮罩层 */}
|
||
<div
|
||
className="absolute inset-0 bg-black/20 "
|
||
data-alt="video-overlay"
|
||
></div>
|
||
|
||
{/* Logo */}
|
||
<div
|
||
className="absolute top-8 left-8 z-50"
|
||
data-alt="logo-container"
|
||
onClick={() => router.push("/")}
|
||
>
|
||
<span className="logo-heart cursor-pointer">
|
||
<GradientText
|
||
text="MovieFlow"
|
||
startPercentage={30}
|
||
endPercentage={70}
|
||
/>
|
||
</span>
|
||
{/* beta标签 */}
|
||
<span className="inline-flex items-center px-1.5 py-0.5 text-[8px] font-semibold tracking-wider text-[rgb(212 202 202)] border border-[rgba(106,244,249,0.2)] rounded-full shadow-[0_0_10px_rgba(106,244,249,0.1)]">
|
||
Beta
|
||
</span>
|
||
</div>
|
||
|
||
{/* 登录框 - 居中显示 */}
|
||
<div className="relative bottom-4 z-10 flex items-center justify-center min-h-screen p-4">
|
||
<div className="auth-container max-w-md w-full !bg-black/40 backdrop-blur-lg border border-white/20 rounded-2xl p-6 shadow-2xl">
|
||
<div className="auth-header text-center mb-4">
|
||
<h2 className="text-2xl font-bold text-white pb-2">Login</h2>
|
||
<p className="text-gray-300">Your inspiration is waiting. <br />Log in to bring your vision to life.</p>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
{successMessage && (
|
||
<div className="bg-green-500/20 text-green-300 p-3 mb-4 rounded-lg border border-green-500/20">
|
||
{successMessage}
|
||
</div>
|
||
)}
|
||
|
||
<div className="mb-6 relative">
|
||
<label
|
||
className={`absolute left-3 transition-all duration-200 pointer-events-none ${
|
||
email || emailFocused
|
||
? 'top-0 -translate-y-1/2 text-base text-white bg-[#141400]/30 px-2 rounded'
|
||
: 'opacity-0'
|
||
}`}
|
||
>
|
||
Email
|
||
</label>
|
||
<input
|
||
placeholder="Email"
|
||
required
|
||
className="form-control"
|
||
type="text"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
onFocus={() => setEmailFocused(true)}
|
||
onBlur={() => setEmailFocused(false)}
|
||
/>
|
||
</div>
|
||
<div className="mb-4">
|
||
<div className="relative">
|
||
<label
|
||
className={`absolute left-3 transition-all duration-200 pointer-events-none z-10 ${
|
||
password || passwordFocused
|
||
? 'top-0 -translate-y-1/2 text-base text-white bg-[#141400]/30 px-2 rounded'
|
||
: 'opacity-0'
|
||
}`}
|
||
>
|
||
Password
|
||
</label>
|
||
<input
|
||
placeholder="Password (8-18 characters, letters, numbers and !@#$%^*&.)"
|
||
required
|
||
className={`form-control pr-10 ${
|
||
passwordError ? "border-red-500/50" : ""
|
||
}`}
|
||
type={showPassword ? "text" : "password"}
|
||
value={password}
|
||
onChange={handlePasswordChange}
|
||
onFocus={() => setPasswordFocused(true)}
|
||
onBlur={() => setPasswordFocused(false)}
|
||
/>
|
||
<button
|
||
type="button"
|
||
data-alt="toggle-password-visibility"
|
||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-300 focus:outline-none"
|
||
onClick={() => setShowPassword(!showPassword)}
|
||
>
|
||
{!showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
|
||
</button>
|
||
</div>
|
||
{passwordError && (
|
||
<p className="mt-4 text-sm text-red-400">{passwordError}</p>
|
||
)}
|
||
|
||
{/* <div className="flex justify-end mt-2">
|
||
<a
|
||
className="auth-link small"
|
||
href="/forgot-password"
|
||
data-discover="true"
|
||
>
|
||
Forgot password?
|
||
</a>
|
||
</div> */}
|
||
</div>
|
||
|
||
{formError && (
|
||
<div className="bg-red-500/20 text-red-300 p-3 mb-6 rounded-lg border border-red-500/20">
|
||
{formError}
|
||
</div>
|
||
)}
|
||
|
||
<button
|
||
type="submit"
|
||
className="w-full mt-4 btn py-3 rounded-lg cursor-pointer !bg-[#C039F6] hover:!bg-[#C039F6]/80"
|
||
disabled={isSubmitting || !!passwordError || !password}
|
||
>
|
||
{isSubmitting ? "Logging in..." : "Login"}
|
||
</button>
|
||
{/* 谷歌登录按钮已暂时注释 */}
|
||
{/* <div className="my-4 relative flex items-center">
|
||
<div className="flex-grow border-t border-gray-500/30"></div>
|
||
<span className="flex-shrink mx-4 text-gray-400">or</span>
|
||
<div className="flex-grow border-t border-gray-500/30"></div>
|
||
</div>
|
||
|
||
<GoogleLoginButton
|
||
onClick={handleGoogleSignIn}
|
||
loading={googleLoading}
|
||
disabled={isSubmitting}
|
||
variant="outline"
|
||
size="md"
|
||
className="w-full"
|
||
/> */}
|
||
|
||
<div className="text-center mt-4">
|
||
<p style={{ color: "rgba(255, 255, 255, 0.6)" }}>
|
||
Don't have an account?{" "}
|
||
<Link href="/signup" className="auth-link !text-[#C039F6]">
|
||
Sign up
|
||
</Link>
|
||
</p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|