更新 修改登录和注册页布局

This commit is contained in:
moux1024 2025-09-20 18:02:33 +08:00
parent 5a38e26983
commit 4a6a99904a
4 changed files with 84 additions and 169 deletions

View File

@ -12,14 +12,11 @@ export default function SignupPage() {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [inviteCode, setInviteCode] = useState(""); const [inviteCode, setInviteCode] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [formError, setFormError] = useState(""); const [formError, setFormError] = useState("");
const [passwordError, setPasswordError] = useState(""); const [passwordError, setPasswordError] = useState("");
const [confirmPasswordError, setConfirmPasswordError] = useState("");
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [agreeToTerms, setAgreeToTerms] = useState(true); const [agreeToTerms, setAgreeToTerms] = useState(true);
const [showActivationModal, setShowActivationModal] = useState(false); const [showActivationModal, setShowActivationModal] = useState(false);
const [resendCooldown, setResendCooldown] = useState(60); const [resendCooldown, setResendCooldown] = useState(60);
@ -27,6 +24,8 @@ export default function SignupPage() {
const [resendMessage, setResendMessage] = useState(""); const [resendMessage, setResendMessage] = useState("");
const [resendError, setResendError] = useState(""); const [resendError, setResendError] = useState("");
const [googleLoading, setGoogleLoading] = useState(false); const [googleLoading, setGoogleLoading] = useState(false);
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
const router = useRouter(); const router = useRouter();
// Handle scroll indicator for small screens // Handle scroll indicator for small screens
@ -76,7 +75,7 @@ export default function SignupPage() {
if (password.length > 18) { if (password.length > 18) {
return "Password cannot exceed 18 characters"; return "Password cannot exceed 18 characters";
} }
if (!/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^*&]{8,18}$/.test(password)) { if (!/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^*&\.]{8,18}$/.test(password)) {
return "Password must contain both letters and numbers"; return "Password must contain both letters and numbers";
} }
return ""; return "";
@ -149,34 +148,8 @@ export default function SignupPage() {
} else { } else {
setPasswordError(""); setPasswordError("");
} }
// 如果确认密码已输入,重新验证确认密码
if (confirmPassword) {
if (newPassword !== confirmPassword) {
setConfirmPasswordError("Passwords do not match");
} else {
setConfirmPasswordError("");
}
}
}; };
/** 处理确认密码输入变化 */
const handleConfirmPasswordChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const newConfirmPassword = e.target.value;
setConfirmPassword(newConfirmPassword);
if (newConfirmPassword) {
if (password !== newConfirmPassword) {
setConfirmPasswordError("Passwords do not match");
} else {
setConfirmPasswordError("");
}
} else {
setConfirmPasswordError("");
}
};
const handleGoogleSignIn = async () => { const handleGoogleSignIn = async () => {
try { try {
@ -201,12 +174,6 @@ export default function SignupPage() {
return; return;
} }
// 验证确认密码
if (password !== confirmPassword) {
setConfirmPasswordError("Passwords do not match");
return;
}
// 验证是否同意条款 // 验证是否同意条款
if (!agreeToTerms) { if (!agreeToTerms) {
setFormError("Please agree to the Terms of Service and Privacy Policy"); setFormError("Please agree to the Terms of Service and Privacy Policy");
@ -219,7 +186,6 @@ export default function SignupPage() {
try { try {
// Use new registration API // Use new registration API
const response = await registerUserWithInvite({ const response = await registerUserWithInvite({
name: name,
email, email,
password, password,
invite_code: inviteCode || undefined, invite_code: inviteCode || undefined,
@ -430,51 +396,54 @@ export default function SignupPage() {
<h2 className="text-2xl font-bold text-white mb-2"> <h2 className="text-2xl font-bold text-white mb-2">
Sign Up, for free Sign Up, for free
</h2> </h2>
<p className="text-gray-300">Create your account to get started</p> <p className="text-gray-300">Start Turning Your Ideas Into Stunning Videos</p>
</div> </div>
{/* Scrollable Middle Content */} {/* Scrollable Middle Content */}
<div className="flex-1 overflow-y-auto px-6"> <div className="flex-1 overflow-y-auto px-6 pt-6">
<form onSubmit={handleSubmit} className="form-spacing space-y-4"> <form onSubmit={handleSubmit} className="form-spacing space-y-6">
<div className="form-field-spacing"> <div className="form-field-spacing relative">
<label className="block text-sm font-medium text-white mb-1"> <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-black/40 px-1 rounded'
: 'opacity-0'
}`}
>
Email Email
</label> </label>
<input <input
type="email" type="email"
placeholder="your@email.com" placeholder="Email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
onFocus={() => setEmailFocused(true)}
onBlur={() => setEmailFocused(false)}
required required
className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent" className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-1 focus:border-custom-blue/80"
/>
</div>
<div className="form-field-spacing">
<label className="block text-sm font-medium text-white mb-1">
Name
</label>
<input
type="text"
placeholder="Your name"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/> />
</div> </div>
<div className="form-field-spacing"> <div className="form-field-spacing">
<label className="block text-sm font-medium text-white mb-1">
Password
</label>
<div className="relative"> <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-black/40 px-1 rounded'
: 'opacity-0'
}`}
>
Password
</label>
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
placeholder="8-18 characters, letters, numbers and !@#$%^*&" placeholder="Password (8-18 characters, letters, numbers and !@#$%^*&.)"
value={password} value={password}
onChange={handlePasswordChange} onChange={handlePasswordChange}
onFocus={() => setPasswordFocused(true)}
onBlur={() => setPasswordFocused(false)}
required required
className={`w-full px-4 py-3 pr-12 rounded-lg bg-black/30 border text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent ${ className={`w-full px-4 py-3 pr-12 rounded-lg bg-black/30 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-1 focus:border-custom-blue/80 ${
passwordError ? "border-red-500/50" : "border-white/20" passwordError ? "border-red-500/50" : "border-white/20"
}`} }`}
/> />
@ -488,94 +457,10 @@ export default function SignupPage() {
</button> </button>
</div> </div>
<div className="form-field-spacing mt-3">
<label className="block text-sm font-medium text-white mb-1">
Confirm Password
</label>
<div className="relative">
<input
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm your password"
value={confirmPassword}
onChange={handleConfirmPasswordChange}
required
className={`w-full px-4 py-3 pr-12 rounded-lg bg-black/30 border text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent ${
confirmPasswordError
? "border-red-500/50"
: "border-white/20"
}`}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
data-alt="toggle-confirm-password-visibility"
>
{showConfirmPassword ? (
<EyeOff size={20} />
) : (
<Eye size={20} />
)}
</button>
</div>
</div>
{passwordError && ( {passwordError && (
<p className="mt-1 text-sm text-red-400">{passwordError}</p> <p className="mt-4 text-sm text-red-400">{passwordError}</p>
)} )}
{confirmPasswordError && (
<p className="mt-1 text-sm text-red-400">
{confirmPasswordError}
</p>
)}
{password && !passwordError && (
<p className="mt-1 text-sm text-green-400">
Password format is correct
</p>
)}
{confirmPassword && !confirmPasswordError && (
<p className="mt-1 text-sm text-green-400"> Passwords match</p>
)}
</div>
<div className="form-field-spacing">
<label className="block text-sm font-medium text-white mb-1">
Invite Code
</label>
<input
type="text"
placeholder="Enter invite code if you have one"
value={inviteCode}
onChange={(e) => setInviteCode(e.target.value)}
className="w-full px-4 py-3 rounded-lg bg-black/30 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
<div className="form-field-spacing flex items-start space-x-3">
<label
htmlFor="agreeToTerms"
className="text-sm text-gray-300 leading-relaxed"
data-alt="terms-privacy-info"
>
By clicking Sign Up, you agree to our{' '}
<button
type="button"
onClick={handleTermsClick}
className="text-purple-400 hover:text-purple-300 underline"
data-alt="terms-link"
>
Terms of Service
</button>{' '}
and acknowledge that you have read and understand our{' '}
<button
type="button"
onClick={handlePrivacyClick}
className="text-purple-400 hover:text-purple-300 underline"
data-alt="privacy-link"
>
Privacy Policy
</button>
</label>
</div> </div>
{formError && ( {formError && (
@ -596,9 +481,7 @@ export default function SignupPage() {
disabled={ disabled={
isSubmitting || isSubmitting ||
!!passwordError || !!passwordError ||
!!confirmPasswordError || !password
!password ||
!confirmPassword
} }
className="flex-1 py-3 rounded-lg cursor-pointer bg-[#C039F6] hover:bg-[#C039F6]/80 text-white font-medium transition-colors disabled:opacity-70 disabled:cursor-not-allowed" className="flex-1 py-3 rounded-lg cursor-pointer bg-[#C039F6] hover:bg-[#C039F6]/80 text-white font-medium transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
> >
@ -633,6 +516,24 @@ export default function SignupPage() {
</Link> </Link>
</p> </p>
</div> </div>
<div className="text-center mt-4">
<button
type="button"
onClick={handleTermsClick}
className="text-[#C039F6] hover:text-purple-300 underline"
data-alt="terms-link"
>
Terms of Service
</button>{' '}
<button
type="button"
onClick={handlePrivacyClick}
className="text-[#C039F6] hover:text-[#C039F6]/80 underline"
data-alt="privacy-link"
>
Privacy Policy
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,6 +19,8 @@ export default function Login() {
const [successMessage, setSuccessMessage] = useState(""); const [successMessage, setSuccessMessage] = useState("");
const [passwordError, setPasswordError] = useState(""); const [passwordError, setPasswordError] = useState("");
const [googleLoading, setGoogleLoading] = useState(false); const [googleLoading, setGoogleLoading] = useState(false);
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -35,7 +37,7 @@ export default function Login() {
if (password.length > 18) { if (password.length > 18) {
return "Password cannot exceed 18 characters"; return "Password cannot exceed 18 characters";
} }
if (!/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^*&]{8,18}$/.test(password)) { if (!/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^*&\.]{8,18}$/.test(password)) {
return "Password must contain both letters and numbers"; return "Password must contain both letters and numbers";
} }
return ""; return "";
@ -169,9 +171,7 @@ export default function Login() {
<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-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"> <div className="auth-header text-center mb-4">
<h2 className="text-2xl font-bold text-white pb-2">Login</h2> <h2 className="text-2xl font-bold text-white pb-2">Login</h2>
<p className="text-gray-300"> <p className="text-gray-300">Your inspiration is waiting. <br />Log in to bring your vision to life.</p>
Enter your information to access your account
</p>
</div> </div>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@ -181,22 +181,40 @@ export default function Login() {
</div> </div>
)} )}
<div className="mb-4"> <div className="mb-6 relative">
<label className="form-label">Username</label> <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-black/60 px-2 rounded'
: 'opacity-0'
}`}
>
Email
</label>
<input <input
placeholder="Enter your username or email" placeholder="Email"
required required
className="form-control" className="form-control"
type="text" type="text"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
onFocus={() => setEmailFocused(true)}
onBlur={() => setEmailFocused(false)}
/> />
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="form-label">Password</label>
<div className="relative"> <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-black/60 px-2 rounded'
: 'opacity-0'
}`}
>
Password
</label>
<input <input
placeholder="8-18 characters, letters, numbers and !@#$%^*&" placeholder="Password (8-18 characters, letters, numbers and !@#$%^*&.)"
required required
className={`form-control pr-10 ${ className={`form-control pr-10 ${
passwordError ? "border-red-500/50" : "" passwordError ? "border-red-500/50" : ""
@ -204,6 +222,8 @@ export default function Login() {
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
value={password} value={password}
onChange={handlePasswordChange} onChange={handlePasswordChange}
onFocus={() => setPasswordFocused(true)}
onBlur={() => setPasswordFocused(false)}
/> />
<button <button
type="button" type="button"
@ -215,10 +235,7 @@ export default function Login() {
</button> </button>
</div> </div>
{passwordError && ( {passwordError && (
<p className="mt-1 text-sm text-red-400">{passwordError}</p> <p className="mt-4 text-sm text-red-400">{passwordError}</p>
)}
{password && !passwordError && (
<p className="mt-1 text-sm text-green-400"> Password format is correct</p>
)} )}
{/* <div className="flex justify-end mt-2"> {/* <div className="flex justify-end mt-2">
@ -233,7 +250,7 @@ export default function Login() {
</div> </div>
{formError && ( {formError && (
<div className="bg-red-500/20 text-red-300 p-3 mb-4 rounded-lg border border-red-500/20"> <div className="bg-red-500/20 text-red-300 p-3 mb-6 rounded-lg border border-red-500/20">
{formError} {formError}
</div> </div>
)} )}

View File

@ -121,9 +121,9 @@
.form-control:focus { .form-control:focus {
outline: none; outline: none;
border-color: rgba(255, 255, 255, 0.3); border-color: #6AF4F9;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.05); box-shadow: 0 0 0 2px rgba(106, 244, 249, 0.2);
} }
.form-control::placeholder { .form-control::placeholder {

View File

@ -616,12 +616,10 @@ export const registerUser = async ({
}; };
export const registerUserWithInvite = async ({ export const registerUserWithInvite = async ({
name,
password, password,
email, email,
invite_code, invite_code,
}: { }: {
name: string;
password: string; password: string;
email: string; email: string;
invite_code?: string; invite_code?: string;
@ -634,7 +632,6 @@ export const registerUserWithInvite = async ({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
name,
password, password,
email, email,
invite_code, invite_code,