| import { Link } from "react-router-dom"; | |
| import { usePasswordReset } from "@/hooks/usePasswordReset"; | |
| import { GlassCard } from "@/components/ui/GlassCard"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { Mail, Shield, Lock, CheckCircle, AlertCircle, ArrowLeft } from "lucide-react"; | |
| export function ForgotPasswordWindow() { | |
| const { | |
| step, | |
| username, | |
| setUsername, | |
| question, | |
| answer, | |
| setAnswer, | |
| newPassword, | |
| setNewPassword, | |
| confirmPassword, | |
| setConfirmPassword, | |
| error, | |
| success, | |
| loading, | |
| requestSecurityQuestion, | |
| submitAnswer, | |
| resetPassword, | |
| goBack, | |
| } = usePasswordReset(); | |
| return ( | |
| <GlassCard className="p-6 w-full max-w-md mx-auto"> | |
| <motion.div | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| transition={{ duration: 0.3 }} | |
| > | |
| <h2 className="text-xl font-bold text-center mb-4 text-gray-900 dark:text-white"> | |
| Reset Password | |
| </h2> | |
| <AnimatePresence mode="wait"> | |
| {success ? ( | |
| <motion.div | |
| key="success" | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| className="text-center space-y-4" | |
| > | |
| <div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-xl"> | |
| <CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-2" /> | |
| <p className="text-green-700 dark:text-green-400 font-medium">Password reset successful!</p> | |
| <p className="text-sm text-gray-600 dark:text-gray-400 mt-1"> | |
| You can now log in with your new password. | |
| </p> | |
| </div> | |
| <Link to="/login" className="inline-block text-primary hover:underline"> | |
| Go to login → | |
| </Link> | |
| </motion.div> | |
| ) : ( | |
| <motion.div | |
| key={step} | |
| initial={{ opacity: 0, x: step === "ask" ? 0 : 20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| exit={{ opacity: 0, x: -20 }} | |
| className="space-y-4" | |
| > | |
| {step === "ask" && ( | |
| <> | |
| <div className="relative"> | |
| <Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="text" | |
| placeholder="Username" | |
| value={username} | |
| onChange={(e) => setUsername(e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 glass-card rounded-lg focus:ring-2 focus:ring-primary" | |
| required | |
| disabled={loading} | |
| autoFocus | |
| /> | |
| </div> | |
| </> | |
| )} | |
| {step === "question" && ( | |
| <> | |
| <div className="bg-gray-50 dark:bg-gray-700/50 p-4 rounded-xl"> | |
| <p className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> | |
| Security question: | |
| </p> | |
| <p className="text-gray-900 dark:text-white font-mono text-sm break-words"> | |
| {question} | |
| </p> | |
| </div> | |
| <div className="relative"> | |
| <Shield className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="text" | |
| placeholder="Your answer" | |
| value={answer} | |
| onChange={(e) => setAnswer(e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 glass-card rounded-lg" | |
| required | |
| autoFocus | |
| /> | |
| </div> | |
| </> | |
| )} | |
| {step === "reset" && ( | |
| <div className="space-y-4"> | |
| <div className="relative"> | |
| <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="password" | |
| placeholder="New password (min 6 chars)" | |
| value={newPassword} | |
| onChange={(e) => setNewPassword(e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 glass-card rounded-lg" | |
| required | |
| autoFocus | |
| /> | |
| </div> | |
| <div className="relative"> | |
| <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
| <input | |
| type="password" | |
| placeholder="Confirm password" | |
| value={confirmPassword} | |
| onChange={(e) => setConfirmPassword(e.target.value)} | |
| className="w-full pl-9 pr-3 py-2 glass-card rounded-lg" | |
| required | |
| /> | |
| </div> | |
| </div> | |
| )} | |
| {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> | |
| )} | |
| <div className="flex gap-3 pt-2"> | |
| {step !== "ask" && ( | |
| <button | |
| type="button" | |
| onClick={goBack} | |
| disabled={loading} | |
| className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition flex items-center justify-center gap-2" | |
| > | |
| <ArrowLeft className="w-4 h-4" /> Back | |
| </button> | |
| )} | |
| <button | |
| onClick={ | |
| step === "ask" | |
| ? requestSecurityQuestion | |
| : step === "question" | |
| ? submitAnswer | |
| : resetPassword | |
| } | |
| disabled={loading} | |
| className={`${step === "ask" ? "flex-1" : "flex-1"} py-2 rounded-lg bg-gradient-to-r from-primary to-secondary text-white font-semibold hover:opacity-90 transition disabled:opacity-50 flex items-center justify-center gap-2`} | |
| > | |
| {loading ? ( | |
| <div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" /> | |
| ) : step === "reset" ? ( | |
| "Reset Password" | |
| ) : ( | |
| "Next" | |
| )} | |
| </button> | |
| </div> | |
| <div className="text-center text-sm"> | |
| <Link to="/login" className="text-primary hover:underline">Back to login</Link> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </motion.div> | |
| </GlassCard> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 7.22 kB
- Xet hash:
- e243a706fd2b24b52b57b163314d8bcd7875fd8f40c652d26982249a28f37527
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.