forked from 77media/video-flow
更新 修改登录和注册页布局
This commit is contained in:
parent
5a38e26983
commit
4a6a99904a
@ -12,14 +12,11 @@ export default function SignupPage() {
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [inviteCode, setInviteCode] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [formError, setFormError] = useState("");
|
||||
const [passwordError, setPasswordError] = useState("");
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(true);
|
||||
const [showActivationModal, setShowActivationModal] = useState(false);
|
||||
const [resendCooldown, setResendCooldown] = useState(60);
|
||||
@ -27,6 +24,8 @@ export default function SignupPage() {
|
||||
const [resendMessage, setResendMessage] = useState("");
|
||||
const [resendError, setResendError] = useState("");
|
||||
const [googleLoading, setGoogleLoading] = useState(false);
|
||||
const [emailFocused, setEmailFocused] = useState(false);
|
||||
const [passwordFocused, setPasswordFocused] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Handle scroll indicator for small screens
|
||||
@ -76,7 +75,7 @@ export default function SignupPage() {
|
||||
if (password.length > 18) {
|
||||
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 "";
|
||||
@ -149,34 +148,8 @@ export default function SignupPage() {
|
||||
} else {
|
||||
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 () => {
|
||||
try {
|
||||
@ -201,12 +174,6 @@ export default function SignupPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证确认密码
|
||||
if (password !== confirmPassword) {
|
||||
setConfirmPasswordError("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证是否同意条款
|
||||
if (!agreeToTerms) {
|
||||
setFormError("Please agree to the Terms of Service and Privacy Policy");
|
||||
@ -219,7 +186,6 @@ export default function SignupPage() {
|
||||
try {
|
||||
// Use new registration API
|
||||
const response = await registerUserWithInvite({
|
||||
name: name,
|
||||
email,
|
||||
password,
|
||||
invite_code: inviteCode || undefined,
|
||||
@ -430,51 +396,54 @@ export default function SignupPage() {
|
||||
<h2 className="text-2xl font-bold text-white mb-2">
|
||||
Sign Up, for free
|
||||
</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>
|
||||
|
||||
{/* Scrollable Middle Content */}
|
||||
<div className="flex-1 overflow-y-auto px-6">
|
||||
<form onSubmit={handleSubmit} className="form-spacing space-y-4">
|
||||
<div className="form-field-spacing">
|
||||
<label className="block text-sm font-medium text-white mb-1">
|
||||
<div className="flex-1 overflow-y-auto px-6 pt-6">
|
||||
<form onSubmit={handleSubmit} className="form-spacing space-y-6">
|
||||
<div className="form-field-spacing 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-black/40 px-1 rounded'
|
||||
: 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onFocus={() => setEmailFocused(true)}
|
||||
onBlur={() => setEmailFocused(false)}
|
||||
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 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"
|
||||
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">
|
||||
Password
|
||||
</label>
|
||||
<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
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="8-18 characters, letters, numbers and !@#$%^*&"
|
||||
placeholder="Password (8-18 characters, letters, numbers and !@#$%^*&.)"
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
onFocus={() => setPasswordFocused(true)}
|
||||
onBlur={() => setPasswordFocused(false)}
|
||||
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"
|
||||
}`}
|
||||
/>
|
||||
@ -488,94 +457,10 @@ export default function SignupPage() {
|
||||
</button>
|
||||
</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 && (
|
||||
<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>
|
||||
|
||||
{formError && (
|
||||
@ -596,9 +481,7 @@ export default function SignupPage() {
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
!!passwordError ||
|
||||
!!confirmPasswordError ||
|
||||
!password ||
|
||||
!confirmPassword
|
||||
!password
|
||||
}
|
||||
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>
|
||||
</p>
|
||||
</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>
|
||||
|
||||
@ -19,6 +19,8 @@ export default function Login() {
|
||||
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();
|
||||
|
||||
@ -35,7 +37,7 @@ export default function Login() {
|
||||
if (password.length > 18) {
|
||||
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 "";
|
||||
@ -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-header text-center mb-4">
|
||||
<h2 className="text-2xl font-bold text-white pb-2">Login</h2>
|
||||
<p className="text-gray-300">
|
||||
Enter your information to access your account
|
||||
</p>
|
||||
<p className="text-gray-300">Your inspiration is waiting. <br />Log in to bring your vision to life.</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
@ -181,22 +181,40 @@ export default function Login() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="form-label">Username</label>
|
||||
<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-black/60 px-2 rounded'
|
||||
: 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
placeholder="Enter your username or email"
|
||||
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">
|
||||
<label className="form-label">Password</label>
|
||||
<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
|
||||
placeholder="8-18 characters, letters, numbers and !@#$%^*&"
|
||||
placeholder="Password (8-18 characters, letters, numbers and !@#$%^*&.)"
|
||||
required
|
||||
className={`form-control pr-10 ${
|
||||
passwordError ? "border-red-500/50" : ""
|
||||
@ -204,6 +222,8 @@ export default function Login() {
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
onFocus={() => setPasswordFocused(true)}
|
||||
onBlur={() => setPasswordFocused(false)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@ -215,10 +235,7 @@ export default function Login() {
|
||||
</button>
|
||||
</div>
|
||||
{passwordError && (
|
||||
<p className="mt-1 text-sm text-red-400">{passwordError}</p>
|
||||
)}
|
||||
{password && !passwordError && (
|
||||
<p className="mt-1 text-sm text-green-400">✓ Password format is correct</p>
|
||||
<p className="mt-4 text-sm text-red-400">{passwordError}</p>
|
||||
)}
|
||||
|
||||
{/* <div className="flex justify-end mt-2">
|
||||
@ -233,7 +250,7 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
{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}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -121,9 +121,9 @@
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
border-color: #6AF4F9;
|
||||
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 {
|
||||
|
||||
@ -616,12 +616,10 @@ export const registerUser = async ({
|
||||
};
|
||||
|
||||
export const registerUserWithInvite = async ({
|
||||
name,
|
||||
password,
|
||||
email,
|
||||
invite_code,
|
||||
}: {
|
||||
name: string;
|
||||
password: string;
|
||||
email: string;
|
||||
invite_code?: string;
|
||||
@ -634,7 +632,6 @@ export const registerUserWithInvite = async ({
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
password,
|
||||
email,
|
||||
invite_code,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user