335 lines
15 KiB
JavaScript
335 lines
15 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Eye, EyeOff, User, Lock, Mail, Camera, UserPlus, ArrowLeft, Check } from 'lucide-react';
|
|
|
|
// Mock navigation function for demo
|
|
const mockNavigate = (path) => {
|
|
console.log(`Navigating to: ${path}`);
|
|
alert(`Would navigate to: ${path}`);
|
|
};
|
|
|
|
const CreateAccountPage = () => {
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [reEnterPassword, setReEnterPassword] = useState('');
|
|
const [avatarImage, setAvatarImage] = useState(null);
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showReEnterPassword, setShowReEnterPassword] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [mounted, setMounted] = useState(false);
|
|
const [errorMessage, setErrorMessage] = useState('');
|
|
const [passwordStrength, setPasswordStrength] = useState(0);
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
// Calculate password strength
|
|
let strength = 0;
|
|
if (password.length >= 8) strength++;
|
|
if (/[A-Z]/.test(password)) strength++;
|
|
if (/[0-9]/.test(password)) strength++;
|
|
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
|
setPasswordStrength(strength);
|
|
}, [password]);
|
|
|
|
const handleCreateAccount = async (e) => {
|
|
e.preventDefault();
|
|
setErrorMessage('');
|
|
setIsLoading(true);
|
|
|
|
if (!email || !password || !reEnterPassword) {
|
|
setErrorMessage('All fields are required.');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (password !== reEnterPassword) {
|
|
setErrorMessage('Passwords do not match.');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
setErrorMessage('Password must be at least 8 characters long.');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
// Simulate API call
|
|
setTimeout(() => {
|
|
console.log('Account created successfully!');
|
|
alert('Account created successfully!');
|
|
mockNavigate('/login');
|
|
setIsLoading(false);
|
|
}, 2000);
|
|
};
|
|
|
|
const handleAvatarChange = (e) => {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
setAvatarImage(reader.result);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
};
|
|
|
|
const getPasswordStrengthColor = () => {
|
|
if (passwordStrength === 0) return 'bg-gray-300';
|
|
if (passwordStrength === 1) return 'bg-red-400';
|
|
if (passwordStrength === 2) return 'bg-yellow-400';
|
|
if (passwordStrength === 3) return 'bg-blue-400';
|
|
return 'bg-green-400';
|
|
};
|
|
|
|
const getPasswordStrengthText = () => {
|
|
if (passwordStrength === 0) return 'Enter password';
|
|
if (passwordStrength === 1) return 'Weak';
|
|
if (passwordStrength === 2) return 'Fair';
|
|
if (passwordStrength === 3) return 'Good';
|
|
return 'Strong';
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-purple-100 relative overflow-hidden">
|
|
{/* Background Elements */}
|
|
<div className="absolute inset-0">
|
|
{/* Subtle geometric shapes */}
|
|
<div className="absolute top-20 left-20 w-32 h-32 bg-purple-200 rounded-full opacity-20 animate-pulse"></div>
|
|
<div className="absolute bottom-20 right-20 w-24 h-24 bg-purple-300 rounded-full opacity-20 animate-pulse" style={{ animationDelay: '1s' }}></div>
|
|
<div className="absolute top-1/2 right-10 w-16 h-16 bg-purple-400 rounded-full opacity-15 animate-pulse" style={{ animationDelay: '2s' }}></div>
|
|
|
|
{/* Grid pattern */}
|
|
<div className="absolute inset-0 opacity-5" style={{
|
|
backgroundImage: `url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%236B46C1' fill-opacity='0.4'%3E%3Cpath d='M20 20h20v20H20V20zm-20 0h20v20H0V20z'/%3E%3C/g%3E%3C/svg%3E")`
|
|
}}></div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
|
{/* Background Logo */}
|
|
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
<div className={`text-center transition-all duration-2000 ${mounted ? 'opacity-5 scale-100' : 'opacity-0 scale-95'}`}>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{/* Create Account Card */}
|
|
<div className={`relative w-full max-w-lg transition-all duration-1000 ${mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
|
{/* Card Background */}
|
|
<div className="bg-white rounded-3xl shadow-2xl border border-purple-100 overflow-hidden">
|
|
{/* Header Section */}
|
|
<div className="bg-gradient-to-r from-purple-600 to-purple-700 p-8 text-center relative">
|
|
<div className="absolute top-4 left-4">
|
|
<button
|
|
onClick={() => mockNavigate('/login')}
|
|
className="p-2 text-white hover:bg-white hover:bg-opacity-20 rounded-xl transition-all duration-200"
|
|
>
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-white bg-opacity-20 rounded-2xl">
|
|
<UserPlus className="w-8 h-8 text-white" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white mb-2">Create Account</h1>
|
|
<p className="text-purple-100">Join our community today</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Form Section */}
|
|
<div className="p-8 space-y-6">
|
|
{/* Error Message */}
|
|
{errorMessage && (
|
|
<div className="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700 text-sm">
|
|
{errorMessage}
|
|
</div>
|
|
)}
|
|
|
|
{/* Avatar Upload */}
|
|
<div className="text-center">
|
|
<div className="relative inline-block">
|
|
<input
|
|
accept="image/*"
|
|
id="avatar-input"
|
|
type="file"
|
|
className="hidden"
|
|
onChange={handleAvatarChange}
|
|
/>
|
|
<label htmlFor="avatar-input" className="cursor-pointer group">
|
|
<div className="relative">
|
|
<img
|
|
alt="Avatar"
|
|
src={avatarImage || 'https://via.placeholder.com/120/E5E7EB/6B7280?text=Photo'}
|
|
className="w-28 h-28 rounded-full border-4 border-purple-100 group-hover:border-purple-300 transition-all duration-200 object-cover"
|
|
/>
|
|
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 rounded-full transition-all duration-200 flex items-center justify-center">
|
|
<Camera className="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
|
|
</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mt-2">Click to upload your photo</p>
|
|
</div>
|
|
|
|
{/* Form Fields */}
|
|
<div className="space-y-5">
|
|
{/* Email Field */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-gray-700">Email Address</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
<Mail className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
</div>
|
|
<input
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
|
placeholder="Enter your email"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Password Field */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-gray-700">Password</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
</div>
|
|
<input
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className="w-full pl-12 pr-12 py-4 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
|
|
placeholder="Create a strong password"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Password Strength Indicator */}
|
|
{password && (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center space-x-2">
|
|
<div className="flex-1 bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className={`h-2 rounded-full transition-all duration-300 ${getPasswordStrengthColor()}`}
|
|
style={{ width: `${(passwordStrength / 4) * 100}%` }}
|
|
></div>
|
|
</div>
|
|
<span className="text-xs font-medium text-gray-600">
|
|
{getPasswordStrengthText()}
|
|
</span>
|
|
</div>
|
|
<div className="text-xs text-gray-500 space-y-1">
|
|
<div className="flex items-center space-x-2">
|
|
<div className={`w-2 h-2 rounded-full ${password.length >= 8 ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
<span>At least 8 characters</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className={`w-2 h-2 rounded-full ${/[A-Z]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
<span>One uppercase letter</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<div className={`w-2 h-2 rounded-full ${/[0-9]/.test(password) ? 'bg-green-400' : 'bg-gray-300'}`}></div>
|
|
<span>One number</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Re-enter Password Field */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-gray-700">Confirm Password</label>
|
|
<div className="relative group">
|
|
<div className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
|
<Lock className="w-5 h-5 text-gray-400 group-focus-within:text-purple-500 transition-colors" />
|
|
</div>
|
|
<input
|
|
type={showReEnterPassword ? 'text' : 'password'}
|
|
value={reEnterPassword}
|
|
onChange={(e) => setReEnterPassword(e.target.value)}
|
|
className={`w-full pl-12 pr-12 py-4 bg-gray-50 border rounded-xl focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 ${
|
|
reEnterPassword && password !== reEnterPassword
|
|
? 'border-red-300 focus:ring-red-500'
|
|
: reEnterPassword && password === reEnterPassword
|
|
? 'border-green-300 focus:ring-green-500'
|
|
: 'border-gray-200 focus:ring-purple-500'
|
|
}`}
|
|
placeholder="Confirm your password"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowReEnterPassword(!showReEnterPassword)}
|
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
{showReEnterPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
|
</button>
|
|
{reEnterPassword && password === reEnterPassword && (
|
|
<div className="absolute right-12 top-1/2 transform -translate-y-1/2">
|
|
<Check className="w-5 h-5 text-green-500" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
{reEnterPassword && password !== reEnterPassword && (
|
|
<p className="text-xs text-red-500">Passwords do not match</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Create Account Button */}
|
|
<button
|
|
type="button"
|
|
onClick={handleCreateAccount}
|
|
disabled={isLoading || !email || !password || !reEnterPassword || password !== reEnterPassword}
|
|
className="w-full py-4 px-6 bg-gradient-to-r from-purple-600 to-purple-700 text-white font-semibold rounded-xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:transform-none disabled:cursor-not-allowed"
|
|
>
|
|
<div className="flex items-center justify-center space-x-2">
|
|
{isLoading ? (
|
|
<>
|
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
|
<span>Creating Account...</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<UserPlus className="w-5 h-5" />
|
|
<span>Create Account</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</button>
|
|
|
|
{/* Login Link */}
|
|
<div className="text-center pt-4 border-t border-gray-100">
|
|
<p className="text-gray-600 mb-3">Already have an account?</p>
|
|
<button
|
|
type="button"
|
|
onClick={() => mockNavigate('/login')}
|
|
className="inline-flex items-center space-x-2 text-purple-600 hover:text-purple-700 font-semibold transition-colors"
|
|
>
|
|
<User className="w-4 h-4" />
|
|
<span>Sign In</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CreateAccountPage; |