Production-ready improvements: security hardening, Redis sessions, retry logic, updated pricing

Changes:
- Removed hardcoded admin credentials (now requires env vars)
- Added Redis session storage with in-memory fallback
- Improved CORS configuration with warnings for development mode
- Added retry_with_backoff decorator for translation API calls
- Updated pricing: Starter=, Pro=, Business=
- Stripe price IDs now loaded from environment variables
- Added redis to requirements.txt
- Updated .env.example with all new configuration options
- Created COMPREHENSIVE_REVIEW_AND_PLAN.md with deployment roadmap
- Frontend: Updated pricing page, new UI components
This commit is contained in:
2025-12-31 10:43:31 +01:00
parent 721b18dbbd
commit c4d6cae735
27 changed files with 7824 additions and 2181 deletions

View File

@@ -3,10 +3,13 @@
import { useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { Eye, EyeOff, Mail, Lock, ArrowRight, Loader2 } from "lucide-react";
import { Eye, EyeOff, Mail, Lock, ArrowRight, Loader2, Shield, CheckCircle, AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
function LoginForm() {
const router = useRouter();
@@ -18,6 +21,54 @@ function LoginForm() {
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [isValidating, setIsValidating] = useState({
email: false,
password: false,
});
const [isFocused, setIsFocused] = useState({
email: false,
password: false,
});
const [showSuccess, setShowSuccess] = useState(false);
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validatePassword = (password: string) => {
return password.length >= 8;
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
setIsValidating(prev => ({ ...prev, email: value.length > 0 }));
};
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setPassword(value);
setIsValidating(prev => ({ ...prev, password: value.length > 0 }));
};
const handleEmailBlur = () => {
setIsValidating(prev => ({ ...prev, email: false }));
setIsFocused(prev => ({ ...prev, email: false }));
};
const handlePasswordBlur = () => {
setIsValidating(prev => ({ ...prev, password: false }));
setIsFocused(prev => ({ ...prev, password: false }));
};
const handleEmailFocus = () => {
setIsFocused(prev => ({ ...prev, email: true }));
};
const handlePasswordFocus = () => {
setIsFocused(prev => ({ ...prev, password: true }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -42,114 +93,252 @@ function LoginForm() {
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem("user", JSON.stringify(data.user));
// Redirect
router.push(redirect);
// Show success animation
setShowSuccess(true);
setTimeout(() => {
router.push(redirect);
}, 1000);
} catch (err: any) {
setError(err.message || "Login failed");
} finally {
setLoading(false);
}
};
const getEmailValidationState = () => {
if (!isValidating.email) return "";
if (email.length === 0) return "";
return validateEmail(email) ? "valid" : "invalid";
};
const getPasswordValidationState = () => {
if (!isValidating.password) return "";
if (password.length === 0) return "";
return validatePassword(password) ? "valid" : "invalid";
};
return (
<>
{/* Card */}
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/50 p-8">
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-white mb-2">Welcome back</h1>
<p className="text-zinc-400">Sign in to continue translating</p>
</div>
{error && (
<div className="mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email" className="text-zinc-300">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="email"
type="email"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="pl-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
{/* Enhanced Login Card */}
<Card
variant="elevated"
className="w-full max-w-md mx-auto overflow-hidden animate-fade-in"
>
<CardHeader className="text-center pb-6">
{/* Logo */}
<Link href="/" className="inline-flex items-center gap-3 mb-6 group">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-accent text-white font-bold text-xl shadow-lg group-hover:shadow-xl group-hover:shadow-primary/25 transition-all duration-300 group-hover:-translate-y-0.5">
A
</div>
</div>
<span className="text-2xl font-semibold text-white group-hover:text-primary transition-colors duration-300">
Translate Co.
</span>
</Link>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password" className="text-zinc-300">Password</Label>
<Link href="/auth/forgot-password" className="text-sm text-teal-400 hover:text-teal-300">
Forgot password?
</Link>
<CardTitle className="text-2xl font-bold text-white mb-2">
Welcome back
</CardTitle>
<CardDescription className="text-text-secondary">
Sign in to continue translating
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Success Message */}
{showSuccess && (
<div className="rounded-lg bg-success/10 border border-success/30 p-4 animate-slide-up">
<div className="flex items-center gap-3">
<CheckCircle className="h-5 w-5 text-success" />
<span className="text-success font-medium">Login successful! Redirecting...</span>
</div>
</div>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="pl-10 pr-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300"
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
)}
{/* Error Message */}
{error && (
<div className="rounded-lg bg-destructive/10 border border-destructive/30 p-4 animate-slide-up">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
<div>
<p className="text-destructive font-medium">Authentication Error</p>
<p className="text-destructive/80 text-sm mt-1">{error}</p>
</div>
</div>
</div>
</div>
)}
<Button
type="submit"
disabled={loading}
className="w-full bg-teal-500 hover:bg-teal-600 text-white"
>
{loading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<>
Sign In
<ArrowRight className="ml-2 h-4 w-4" />
</>
)}
</Button>
</form>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Email Field */}
<div className="space-y-3">
<Label htmlFor="email" className="text-text-secondary font-medium">
Email Address
</Label>
<div className="relative">
<Input
id="email"
type="email"
placeholder="you@example.com"
value={email}
onChange={handleEmailChange}
onBlur={handleEmailBlur}
onFocus={handleEmailFocus}
required
className={cn(
"pl-12 h-12 text-lg",
getEmailValidationState() === "valid" && "border-success focus:border-success",
getEmailValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.email && "ring-2 ring-primary/20"
)}
leftIcon={<Mail className="h-5 w-5" />}
/>
{/* Validation Indicator */}
{isValidating.email && (
<div className="absolute right-3 top-1/2 -translate-y-1/2">
{getEmailValidationState() === "valid" && (
<CheckCircle className="h-5 w-5 text-success animate-fade-in" />
)}
{getEmailValidationState() === "invalid" && (
<AlertTriangle className="h-5 w-5 text-destructive animate-fade-in" />
)}
</div>
)}
</div>
</div>
<div className="mt-6 text-center text-sm text-zinc-400">
Don&apos;t have an account?{" "}
{/* Password Field */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label htmlFor="password" className="text-text-secondary font-medium">
Password
</Label>
<Link
href="/auth/forgot-password"
className="text-sm text-primary hover:text-primary/80 transition-colors duration-200"
>
Forgot password?
</Link>
</div>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••••"
value={password}
onChange={handlePasswordChange}
onBlur={handlePasswordBlur}
onFocus={handlePasswordFocus}
required
className={cn(
"pl-12 pr-12 h-12 text-lg",
getPasswordValidationState() === "valid" && "border-success focus:border-success",
getPasswordValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.password && "ring-2 ring-primary/20"
)}
leftIcon={<Lock className="h-5 w-5" />}
rightIcon={
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="text-muted-foreground hover:text-foreground transition-colors duration-200"
>
{showPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
}
/>
{/* Validation Indicator */}
{isValidating.password && (
<div className="absolute right-12 top-1/2 -translate-y-1/2">
{getPasswordValidationState() === "valid" && (
<CheckCircle className="h-5 w-5 text-success animate-fade-in" />
)}
{getPasswordValidationState() === "invalid" && (
<AlertTriangle className="h-5 w-5 text-destructive animate-fade-in" />
)}
</div>
)}
</div>
{/* Password Strength Indicator */}
{isValidating.password && password.length > 0 && (
<div className="mt-2 space-y-1">
<div className="flex justify-between text-xs text-text-tertiary">
<span>Password strength</span>
<span className={cn(
password.length < 8 && "text-destructive",
password.length >= 8 && password.length < 12 && "text-warning",
password.length >= 12 && "text-success"
)}>
{password.length < 8 && "Weak"}
{password.length >= 8 && password.length < 12 && "Fair"}
{password.length >= 12 && "Strong"}
</span>
</div>
<div className="w-full bg-border-subtle rounded-full h-1 overflow-hidden">
<div
className={cn(
"h-full transition-all duration-300 ease-out",
password.length < 8 && "bg-destructive w-1/3",
password.length >= 8 && password.length < 12 && "bg-warning w-2/3",
password.length >= 12 && "bg-success w-full"
)}
/>
</div>
</div>
)}
</div>
{/* Submit Button */}
<Button
type="submit"
disabled={loading || !email || !password}
variant="premium"
size="lg"
className="w-full h-12 text-lg group"
>
{loading ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Signing in...
</>
) : (
<>
Sign In
<ArrowRight className="ml-2 h-5 w-5 transition-transform duration-200 group-hover:translate-x-1" />
</>
)}
</Button>
</form>
</CardContent>
</Card>
{/* Enhanced Footer */}
<div className="mt-8 text-center">
<p className="text-sm text-text-tertiary mb-6">
Don't have an account?{" "}
<Link
href={`/auth/register${redirect !== "/" ? `?redirect=${redirect}` : ""}`}
className="text-teal-400 hover:text-teal-300"
className="text-primary hover:text-primary/80 font-medium transition-colors duration-200"
>
Sign up for free
</Link>
</div>
</div>
{/* Features reminder */}
<div className="mt-8 text-center">
<p className="text-sm text-zinc-500 mb-3">Start with our free plan:</p>
<div className="flex flex-wrap justify-center gap-2">
{["5 docs/day", "10 pages/doc", "Free forever"].map((feature) => (
<span
key={feature}
className="px-3 py-1 rounded-full bg-zinc-800 text-zinc-400 text-xs"
>
{feature}
</span>
))}
</p>
{/* Trust Indicators */}
<div className="flex flex-wrap justify-center gap-6 text-xs text-text-tertiary">
<div className="flex items-center gap-2">
<Shield className="h-4 w-4 text-success" />
<span>Secure login</span>
</div>
<div className="flex items-center gap-2">
<CheckCircle className="h-4 w-4 text-primary" />
<span>SSL encrypted</span>
</div>
</div>
</div>
</>
@@ -158,32 +347,40 @@ function LoginForm() {
function LoadingFallback() {
return (
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/50 p-8">
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-teal-500" />
</div>
</div>
<Card variant="elevated" className="w-full max-w-md mx-auto">
<CardContent className="flex items-center justify-center py-16">
<div className="text-center space-y-4">
<Loader2 className="h-12 w-12 animate-spin text-primary mx-auto" />
<p className="text-lg font-medium text-foreground">Loading...</p>
<div className="w-16 h-1 bg-border-subtle rounded-full overflow-hidden">
<div className="h-full bg-primary animate-loading-shimmer" />
</div>
</div>
</CardContent>
</Card>
);
}
export default function LoginPage() {
return (
<div className="min-h-screen bg-gradient-to-b from-[#1a1a1a] to-[#262626] flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-teal-500 text-white font-bold text-xl">
A
</div>
<span className="text-2xl font-semibold text-white">Translate Co.</span>
</Link>
</div>
<Suspense fallback={<LoadingFallback />}>
<LoginForm />
</Suspense>
<div className="min-h-screen bg-gradient-to-br from-surface via-surface-elevated to-background flex items-center justify-center p-4 relative overflow-hidden">
{/* Background Effects */}
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5" />
<div className="absolute inset-0 bg-[url('/grid.svg')] opacity-5" />
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/90 to-background/50" />
</div>
{/* Animated Background Elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-20 left-10 w-32 h-32 bg-primary/5 rounded-full blur-3xl animate-float" />
<div className="absolute bottom-20 right-20 w-24 h-24 bg-accent/5 rounded-full blur-2xl animate-float-delayed" />
<div className="absolute top-1/2 right-1/4 w-16 h-16 bg-success/5 rounded-full blur-xl animate-float-slow" />
</div>
<Suspense fallback={<LoadingFallback />}>
<LoginForm />
</Suspense>
</div>
);
}

View File

@@ -3,10 +3,26 @@
import { useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { Eye, EyeOff, Mail, Lock, User, ArrowRight, Loader2 } from "lucide-react";
import {
Eye,
EyeOff,
Mail,
Lock,
User,
ArrowRight,
Loader2,
Shield,
CheckCircle,
AlertTriangle,
UserPlus,
Info
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
function RegisterForm() {
const router = useRouter();
@@ -18,22 +34,180 @@ function RegisterForm() {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [step, setStep] = useState(1);
const [showSuccess, setShowSuccess] = useState(false);
const [isValidating, setIsValidating] = useState({
name: false,
email: false,
password: false,
confirmPassword: false,
});
const [isFocused, setIsFocused] = useState({
name: false,
email: false,
password: false,
confirmPassword: false,
});
const validateName = (name: string) => {
return name.trim().length >= 2;
};
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validatePassword = (password: string) => {
return password.length >= 8;
};
const validateConfirmPassword = (password: string, confirmPassword: string) => {
return password === confirmPassword && password.length > 0;
};
// Real-time validation
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setName(value);
setIsValidating(prev => ({ ...prev, name: value.length > 0 }));
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
setIsValidating(prev => ({ ...prev, email: value.length > 0 }));
};
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setPassword(value);
setIsValidating(prev => ({ ...prev, password: value.length > 0 }));
};
const handleConfirmPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setConfirmPassword(value);
setIsValidating(prev => ({ ...prev, confirmPassword: value.length > 0 }));
};
const handleNameBlur = () => {
setIsValidating(prev => ({ ...prev, name: false }));
setIsFocused(prev => ({ ...prev, name: false }));
};
const handleEmailBlur = () => {
setIsValidating(prev => ({ ...prev, email: false }));
setIsFocused(prev => ({ ...prev, email: false }));
};
const handlePasswordBlur = () => {
setIsValidating(prev => ({ ...prev, password: false }));
setIsFocused(prev => ({ ...prev, password: false }));
};
const handleConfirmPasswordBlur = () => {
setIsValidating(prev => ({ ...prev, confirmPassword: false }));
setIsFocused(prev => ({ ...prev, confirmPassword: false }));
};
const handleNameFocus = () => {
setIsFocused(prev => ({ ...prev, name: true }));
};
const handleEmailFocus = () => {
setIsFocused(prev => ({ ...prev, email: true }));
};
const handlePasswordFocus = () => {
setIsFocused(prev => ({ ...prev, password: true }));
};
const handleConfirmPasswordFocus = () => {
setIsFocused(prev => ({ ...prev, confirmPassword: true }));
};
const getNameValidationState = () => {
if (!isValidating.name) return "";
if (name.length === 0) return "";
return validateName(name) ? "valid" : "invalid";
};
const getEmailValidationState = () => {
if (!isValidating.email) return "";
if (email.length === 0) return "";
return validateEmail(email) ? "valid" : "invalid";
};
const getPasswordValidationState = () => {
if (!isValidating.password) return "";
if (password.length === 0) return "";
return validatePassword(password) ? "valid" : "invalid";
};
const getConfirmPasswordValidationState = () => {
if (!isValidating.confirmPassword) return "";
if (confirmPassword.length === 0) return "";
return validateConfirmPassword(password, confirmPassword) ? "valid" : "invalid";
};
const getPasswordStrength = () => {
if (password.length === 0) return { strength: 0, text: "", color: "" };
let strength = 0;
let text = "";
let color = "";
if (password.length >= 8) strength++;
if (password.length >= 12) strength++;
if (/[A-Z]/.test(password)) strength++;
if (/[a-z]/.test(password)) strength++;
if (/[0-9]/.test(password)) strength++;
if (/[^A-Za-z0-9]/.test(password)) strength++;
if (strength <= 2) {
text = "Weak";
color = "text-destructive";
} else if (strength <= 3) {
text = "Fair";
color = "text-warning";
} else {
text = "Strong";
color = "text-success";
}
return { strength, text, color };
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (password !== confirmPassword) {
setError("Passwords do not match");
// Validate all fields
if (!validateName(name)) {
setError("Name must be at least 2 characters");
return;
}
if (password.length < 8) {
if (!validateEmail(email)) {
setError("Please enter a valid email address");
return;
}
if (!validatePassword(password)) {
setError("Password must be at least 8 characters");
return;
}
if (!validateConfirmPassword(password, confirmPassword)) {
setError("Passwords do not match");
return;
}
setLoading(true);
@@ -55,168 +229,373 @@ function RegisterForm() {
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem("user", JSON.stringify(data.user));
// Redirect
router.push(redirect);
// Show success animation
setShowSuccess(true);
setTimeout(() => {
router.push(redirect);
}, 1500);
} catch (err: any) {
setError(err.message || "Registration failed");
} finally {
setLoading(false);
}
};
const passwordStrength = getPasswordStrength();
return (
<>
{/* Card */}
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/50 p-8">
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-white mb-2">Create an account</h1>
<p className="text-zinc-400">Start translating documents for free</p>
</div>
{error && (
<div className="mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm">
{error}
</div>
{/* Enhanced Registration Card */}
<Card
variant="elevated"
className={cn(
"w-full max-w-md mx-auto overflow-hidden animate-fade-in",
showSuccess && "scale-95 opacity-0"
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name" className="text-zinc-300">Full Name</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="name"
type="text"
placeholder="John Doe"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="pl-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
>
<CardHeader className="text-center pb-6">
{/* Logo */}
<Link href="/" className="inline-flex items-center gap-3 mb-6 group">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-accent text-white font-bold text-xl shadow-lg group-hover:shadow-xl group-hover:shadow-primary/25 transition-all duration-300 group-hover:-translate-y-0.5">
A
</div>
</div>
<span className="text-2xl font-semibold text-white group-hover:text-primary transition-colors duration-300">
Translate Co.
</span>
</Link>
<div className="space-y-2">
<Label htmlFor="email" className="text-zinc-300">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="email"
type="email"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="pl-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
<CardTitle className="text-2xl font-bold text-white mb-2">
Create an account
</CardTitle>
<CardDescription className="text-text-secondary">
Start translating documents for free
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Success Message */}
{showSuccess && (
<div className="rounded-lg bg-success/10 border border-success/30 p-6 mb-6 animate-slide-up">
<div className="flex items-center gap-3">
<CheckCircle className="h-8 w-8 text-success animate-pulse" />
<div>
<p className="text-lg font-medium text-success mb-1">Registration Successful!</p>
<p className="text-sm text-success/80">Redirecting to your dashboard...</p>
</div>
</div>
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="password" className="text-zinc-300">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
className="pl-10 pr-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300"
{/* Error Message */}
{error && (
<div className="rounded-lg bg-destructive/10 border border-destructive/30 p-4 mb-6 animate-slide-up">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-destructive mb-1">Registration Error</p>
<p className="text-sm text-destructive/80">{error}</p>
</div>
</div>
</div>
)}
{/* Progress Steps */}
<div className="flex items-center justify-center mb-8">
{[1, 2, 3].map((stepNumber) => (
<div
key={stepNumber}
className={cn(
"flex items-center justify-center w-8 h-8 rounded-full transition-all duration-300",
step === stepNumber
? "bg-primary text-white scale-110"
: "bg-surface text-text-tertiary"
)}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
<span className="text-sm font-medium">{stepNumber}</span>
</div>
))}
<div className="h-0.5 bg-border-subtle flex-1 mx-2" />
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword" className="text-zinc-300">Confirm Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-500" />
<Input
id="confirmPassword"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
className="pl-10 bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
/>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Name Field */}
<div className="space-y-3">
<Label htmlFor="name" className="text-text-secondary font-medium">
Full Name
</Label>
<div className="relative">
<Input
id="name"
type="text"
placeholder="John Doe"
value={name}
onChange={handleNameChange}
onBlur={handleNameBlur}
onFocus={handleNameFocus}
required
className={cn(
"pl-12 h-12 text-lg",
getNameValidationState() === "valid" && "border-success focus:border-success",
getNameValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.name && "ring-2 ring-primary/20"
)}
leftIcon={<User className="h-5 w-5" />}
/>
{/* Validation Indicator */}
{isValidating.name && (
<div className="absolute right-3 top-1/2 -translate-y-1/2">
{getNameValidationState() === "valid" && (
<CheckCircle className="h-5 w-5 text-success animate-fade-in" />
)}
{getNameValidationState() === "invalid" && (
<AlertTriangle className="h-5 w-5 text-destructive animate-fade-in" />
)}
</div>
)}
</div>
</div>
{/* Email Field */}
<div className="space-y-3">
<Label htmlFor="email" className="text-text-secondary font-medium">
Email Address
</Label>
<div className="relative">
<Input
id="email"
type="email"
placeholder="you@example.com"
value={email}
onChange={handleEmailChange}
onBlur={handleEmailBlur}
onFocus={handleEmailFocus}
required
className={cn(
"pl-12 h-12 text-lg",
getEmailValidationState() === "valid" && "border-success focus:border-success",
getEmailValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.email && "ring-2 ring-primary/20"
)}
leftIcon={<Mail className="h-5 w-5" />}
/>
{/* Validation Indicator */}
{isValidating.email && (
<div className="absolute right-3 top-1/2 -translate-y-1/2">
{getEmailValidationState() === "valid" && (
<CheckCircle className="h-5 w-5 text-success animate-fade-in" />
)}
{getEmailValidationState() === "invalid" && (
<AlertTriangle className="h-5 w-5 text-destructive animate-fade-in" />
)}
</div>
)}
</div>
</div>
{/* Password Field */}
<div className="space-y-3">
<Label htmlFor="password" className="text-text-secondary font-medium">
Password
</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="•••••••••••"
value={password}
onChange={handlePasswordChange}
onBlur={handlePasswordBlur}
onFocus={handlePasswordFocus}
required
minLength={8}
className={cn(
"pl-12 pr-12 h-12 text-lg",
getPasswordValidationState() === "valid" && "border-success focus:border-success",
getPasswordValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.password && "ring-2 ring-primary/20"
)}
leftIcon={<Lock className="h-5 w-5" />}
rightIcon={
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="text-muted-foreground hover:text-foreground transition-colors duration-200"
>
{showPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
}
/>
{/* Password Strength Indicator */}
{password.length > 0 && (
<div className="absolute right-12 top-1/2 -translate-y-1/2">
<div className="flex items-center gap-1">
<div className="flex space-x-1">
{[1, 2, 3, 4].map((level) => (
<div
key={level}
className={cn(
"w-1 h-1 rounded-full",
level <= passwordStrength.strength ? "bg-success" : "bg-border"
)}
/>
))}
</div>
<span className={cn("text-xs", passwordStrength.color)}>
{passwordStrength.text}
</span>
</div>
</div>
)}
</div>
</div>
{/* Confirm Password Field */}
<div className="space-y-3">
<Label htmlFor="confirmPassword" className="text-text-secondary font-medium">
Confirm Password
</Label>
<div className="relative">
<Input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
placeholder="•••••••••••"
value={confirmPassword}
onChange={handleConfirmPasswordChange}
onBlur={handleConfirmPasswordBlur}
onFocus={handleConfirmPasswordFocus}
required
className={cn(
"pl-12 pr-12 h-12 text-lg",
getConfirmPasswordValidationState() === "valid" && "border-success focus:border-success",
getConfirmPasswordValidationState() === "invalid" && "border-destructive focus:border-destructive",
isFocused.confirmPassword && "ring-2 ring-primary/20"
)}
leftIcon={<Lock className="h-5 w-5" />}
rightIcon={
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="text-muted-foreground hover:text-foreground transition-colors duration-200"
>
{showConfirmPassword ? (
<EyeOff className="h-5 w-5" />
) : (
<Eye className="h-5 w-5" />
)}
</button>
}
/>
{/* Validation Indicator */}
{isValidating.confirmPassword && (
<div className="absolute right-3 top-1/2 -translate-y-1/2">
{getConfirmPasswordValidationState() === "valid" && (
<CheckCircle className="h-5 w-5 text-success animate-fade-in" />
)}
{getConfirmPasswordValidationState() === "invalid" && (
<AlertTriangle className="h-5 w-5 text-destructive animate-fade-in" />
)}
</div>
)}
</div>
</div>
{/* Submit Button */}
<Button
type="submit"
disabled={loading || !name || !email || !password || !confirmPassword}
variant="premium"
size="lg"
className="w-full h-12 text-lg group"
>
{loading ? (
<>
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
Creating Account...
</>
) : (
<>
<UserPlus className="mr-2 h-5 w-5 transition-transform duration-200 group-hover:scale-110" />
Create Account
<ArrowRight className="ml-2 h-5 w-5 transition-transform duration-200 group-hover:translate-x-1" />
</>
)}
</Button>
</form>
{/* Sign In Link */}
<div className="text-center">
<p className="text-sm text-text-tertiary mb-4">
Already have an account?{" "}
<Link
href={`/auth/login${redirect !== "/" ? `?redirect=${redirect}` : ""}`}
className="text-primary hover:text-primary/80 font-medium transition-colors duration-200"
>
Sign in
</Link>
</p>
</div>
<Button
type="submit"
disabled={loading}
className="w-full bg-teal-500 hover:bg-teal-600 text-white"
>
{loading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<>
Create Account
<ArrowRight className="ml-2 h-4 w-4" />
</>
)}
</Button>
</form>
<div className="mt-6 text-center text-sm text-zinc-400">
Already have an account?{" "}
<Link
href={`/auth/login${redirect !== "/" ? `?redirect=${redirect}` : ""}`}
className="text-teal-400 hover:text-teal-300"
>
Sign in
</Link>
</div>
<div className="mt-6 text-center text-xs text-zinc-500">
By creating an account, you agree to our{" "}
<Link href="/terms" className="text-zinc-400 hover:text-zinc-300">
Terms of Service
</Link>{" "}
and{" "}
<Link href="/privacy" className="text-zinc-400 hover:text-zinc-300">
Privacy Policy
</Link>
</div>
</div>
{/* Terms and Privacy */}
<div className="text-center text-xs text-text-tertiary space-y-2">
<p>
By creating an account, you agree to our{" "}
<Link href="/terms" className="text-primary hover:text-primary/80 transition-colors duration-200">
Terms of Service
</Link>
{" "} and{" "}
<Link href="/privacy" className="text-primary hover:text-primary/80 transition-colors duration-200">
Privacy Policy
</Link>
</p>
</div>
</CardContent>
</Card>
</>
);
}
function LoadingFallback() {
return (
<div className="rounded-2xl border border-zinc-800 bg-zinc-900/50 p-8">
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-teal-500" />
</div>
</div>
<Card variant="elevated" className="w-full max-w-md mx-auto">
<CardContent className="flex items-center justify-center py-16">
<div className="text-center space-y-4">
<Loader2 className="h-12 w-12 animate-spin text-primary mx-auto" />
<p className="text-lg font-medium text-foreground">Creating your account...</p>
<div className="w-16 h-1 bg-border-subtle rounded-full overflow-hidden">
<div className="h-full bg-primary animate-loading-shimmer" />
</div>
</div>
</CardContent>
</Card>
);
}
export default function RegisterPage() {
return (
<div className="min-h-screen bg-gradient-to-b from-[#1a1a1a] to-[#262626] flex items-center justify-center p-4">
<div className="min-h-screen bg-gradient-to-br from-surface via-surface-elevated to-background flex items-center justify-center p-4 relative overflow-hidden">
{/* Background Effects */}
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5" />
<div className="absolute inset-0 bg-[url('/grid.svg')] opacity-5" />
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/90 to-background/50" />
</div>
{/* Floating Elements */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-20 left-10 w-20 h-20 bg-primary/10 rounded-full blur-xl animate-pulse" />
<div className="absolute top-40 right-20 w-32 h-32 bg-accent/10 rounded-full blur-2xl animate-pulse animation-delay-2000" />
<div className="absolute bottom-20 left-1/4 w-16 h-16 bg-success/10 rounded-full blur-lg animate-pulse animation-delay-4000" />
</div>
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<Link href="/" className="inline-flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-teal-500 text-white font-bold text-xl">
A
</div>
<span className="text-2xl font-semibold text-white">Translate Co.</span>
</Link>
</div>
<Suspense fallback={<LoadingFallback />}>
<RegisterForm />
</Suspense>