| import { useState } from "react"; | |
| import { useRegister } from "@/hooks/useRegister"; | |
| import { GlassCard } from "@/components/ui/GlassCard"; | |
| import { Mail, Lock, User, Eye, EyeOff, UserPlus, AlertCircle } from "lucide-react"; | |
| import { motion } from "framer-motion"; | |
| interface RegisterWindowProps { | |
| onLoginClick?: () => void; | |
| onSuccess?: () => void; | |
| } | |
| export function RegisterWindow({ onLoginClick, onSuccess }: RegisterWindowProps) { | |
| const { form, updateForm, error, loading, handleSubmit, passwordStrength, strengthColor, strengthText } = useRegister(); | |
| const [showPassword, setShowPassword] = useState(false); | |
| const [showConfirm, setShowConfirm] = useState(false); | |
| const onSubmit = async (e: React.FormEvent) => { | |
| await handleSubmit(e); | |
| if (!error) { | |
| onSuccess?.(); | |
| // Close window and show login | |
| window.dispatchEvent(new CustomEvent("openWindow", { detail: { app: "login" } })); | |
| if (onLoginClick) onLoginClick(); | |
| } | |
| }; | |
| return ( | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| className="w-full max-w-md" | |
| > | |
| <GlassCard className="p-6 md:p-8"> | |
| <div className="text-center mb-6"> | |
| <h2 className="text-2xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"> | |
| Join IE Research | |
| </h2> | |
| <p className="text-sm text-gray-500 dark:text-gray-400 mt-1"> | |
| Start collaborating with researchers worldwide | |
| </p> | |
| </div> | |
| <form onSubmit={onSubmit} className="space-y-4"> | |
| {/* Username */} | |
| <div className="relative"> | |
| <User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="text" | |
| name="username" | |
| placeholder="Username (min 3 characters)" | |
| value={form.username} | |
| onChange={(e) => updateForm("username", e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 bg-white/50 dark:bg-gray-800/50 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary" | |
| required | |
| disabled={loading} | |
| /> | |
| </div> | |
| {/* Email */} | |
| <div className="relative"> | |
| <Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="email" | |
| name="email" | |
| placeholder="Email (optional)" | |
| value={form.email} | |
| onChange={(e) => updateForm("email", e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 bg-white/50 dark:bg-gray-800/50 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary" | |
| disabled={loading} | |
| /> | |
| </div> | |
| {/* Password */} | |
| <div className="relative"> | |
| <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type={showPassword ? "text" : "password"} | |
| name="password" | |
| placeholder="Password" | |
| value={form.password} | |
| onChange={(e) => updateForm("password", e.target.value)} | |
| className="w-full pl-9 pr-10 py-2 bg-white/50 dark:bg-gray-800/50 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary" | |
| required | |
| disabled={loading} | |
| /> | |
| <button | |
| type="button" | |
| onClick={() => setShowPassword(!showPassword)} | |
| className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600" | |
| > | |
| {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} | |
| </button> | |
| </div> | |
| {/* Confirm Password */} | |
| <div className="relative"> | |
| <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type={showConfirm ? "text" : "password"} | |
| name="confirmPassword" | |
| placeholder="Confirm Password" | |
| value={form.confirmPassword} | |
| onChange={(e) => updateForm("confirmPassword", e.target.value)} | |
| className="w-full pl-9 pr-10 py-2 bg-white/50 dark:bg-gray-800/50 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary" | |
| required | |
| disabled={loading} | |
| /> | |
| <button | |
| type="button" | |
| onClick={() => setShowConfirm(!showConfirm)} | |
| className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600" | |
| > | |
| {showConfirm ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} | |
| </button> | |
| </div> | |
| {/* Password strength indicator */} | |
| <div> | |
| <div className="h-1 w-full bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden"> | |
| <div className={`h-full ${strengthColor()}`} style={{ width: `${passwordStrength * 25}%` }} /> | |
| </div> | |
| <p className="text-xs text-gray-500 dark:text-gray-400 mt-1"> | |
| Password strength: <span className="font-medium">{strengthText()}</span> (min 6 chars) | |
| </p> | |
| </div> | |
| {/* Error message */} | |
| {error && ( | |
| <div className="flex items-center gap-2 p-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm"> | |
| <AlertCircle className="w-4 h-4" /> | |
| <span>{error}</span> | |
| </div> | |
| )} | |
| {/* Submit button */} | |
| <button | |
| type="submit" | |
| disabled={loading} | |
| className="w-full py-2.5 rounded-lg bg-gradient-to-r from-primary to-secondary text-white font-semibold flex items-center justify-center gap-2 transition hover:opacity-90 disabled:opacity-50" | |
| > | |
| {loading ? ( | |
| <div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" /> | |
| ) : ( | |
| <UserPlus className="w-5 h-5" /> | |
| )} | |
| {loading ? "Creating account..." : "Create account"} | |
| </button> | |
| <div className="text-center text-sm text-gray-600 dark:text-gray-400"> | |
| Already have an account?{" "} | |
| <button | |
| type="button" | |
| onClick={() => window.dispatchEvent(new CustomEvent("openWindow", { detail: { app: "login" } }))} | |
| className="text-primary font-semibold hover:underline" | |
| > | |
| Sign In | |
| </button> | |
| </div> | |
| </form> | |
| </GlassCard> | |
| </motion.div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 6.82 kB
- Xet hash:
- b4dcc3b2eebab8b690181a7444490f55b87b1181d85805df715af64112a5e5ff
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.