| "use client"; | |
| import { usePasswordReset } from "@/hooks/usePasswordReset"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { X, AlertCircle, CheckCircle, ArrowLeft } from "lucide-react"; | |
| interface PasswordResetModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| onLoginClick?: () => void; | |
| } | |
| export function PasswordResetModal({ isOpen, onClose, onLoginClick }: PasswordResetModalProps) { | |
| const { | |
| step, | |
| username, | |
| setUsername, | |
| question, | |
| answer, | |
| setAnswer, | |
| newPassword, | |
| setNewPassword, | |
| confirmPassword, | |
| setConfirmPassword, | |
| error, | |
| success, | |
| loading, | |
| requestSecurityQuestion, | |
| submitAnswer, | |
| resetPassword, | |
| goBack, | |
| } = usePasswordReset(); | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4" onClick={onClose}> | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.95 }} | |
| className="bg-white dark:bg-gray-800 rounded-2xl max-w-md w-full shadow-xl" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div className="flex justify-between items-center p-5 border-b dark:border-gray-700"> | |
| <h2 className="text-xl font-bold text-gray-900 dark:text-white">Reset Password</h2> | |
| <button onClick={onClose} className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"> | |
| <X className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| <div className="p-6"> | |
| <AnimatePresence mode="wait"> | |
| {success ? ( | |
| <motion.div | |
| key="success" | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="space-y-4" | |
| > | |
| <div className="bg-green-50 dark:bg-green-900/30 p-4 rounded-xl text-green-700 dark:text-green-400 text-center"> | |
| <CheckCircle className="w-12 h-12 mx-auto mb-2" /> | |
| <p className="font-medium">Password reset successful!</p> | |
| <p className="text-sm mt-1">You can now log in with your new password.</p> | |
| </div> | |
| <button | |
| onClick={onLoginClick || onClose} | |
| className="w-full py-2 bg-primary text-white rounded-lg font-medium hover:bg-primary/90 transition" | |
| > | |
| Go to Login | |
| </button> | |
| </motion.div> | |
| ) : ( | |
| <motion.div key={step} initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-4"> | |
| {step === "ask" && ( | |
| <> | |
| <p className="text-sm text-gray-600 dark:text-gray-400"> | |
| Enter your username to receive your security question. | |
| </p> | |
| <input | |
| type="text" | |
| placeholder="Username" | |
| value={username} | |
| onChange={(e) => setUsername(e.target.value)} | |
| className="w-full px-4 py-2 border rounded-xl dark:bg-gray-700 dark:border-gray-600 focus:ring-2 focus:ring-primary" | |
| disabled={loading} | |
| autoFocus | |
| /> | |
| </> | |
| )} | |
| {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-2">Security question:</p> | |
| <p className="text-gray-900 dark:text-white font-mono text-sm break-words">{question}</p> | |
| </div> | |
| <input | |
| type="text" | |
| placeholder="Your answer" | |
| value={answer} | |
| onChange={(e) => setAnswer(e.target.value)} | |
| className="w-full px-4 py-2 border rounded-xl dark:bg-gray-700 dark:border-gray-600 focus:ring-2 focus:ring-primary" | |
| autoFocus | |
| /> | |
| </> | |
| )} | |
| {step === "reset" && ( | |
| <div className="space-y-4"> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> | |
| New password | |
| </label> | |
| <input | |
| type="password" | |
| placeholder="Min 6 characters" | |
| value={newPassword} | |
| onChange={(e) => setNewPassword(e.target.value)} | |
| className="w-full px-4 py-2 border rounded-xl dark:bg-gray-700 dark:border-gray-600 focus:ring-2 focus:ring-primary" | |
| autoFocus | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> | |
| Confirm new password | |
| </label> | |
| <input | |
| type="password" | |
| placeholder="Confirm" | |
| value={confirmPassword} | |
| onChange={(e) => setConfirmPassword(e.target.value)} | |
| className="w-full px-4 py-2 border rounded-xl dark:bg-gray-700 dark:border-gray-600 focus:ring-2 focus:ring-primary" | |
| /> | |
| </div> | |
| </div> | |
| )} | |
| {error && ( | |
| <div className="flex items-center gap-2 p-3 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 | |
| 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" | |
| > | |
| <ArrowLeft className="w-4 h-4 inline mr-1" /> Back | |
| </button> | |
| )} | |
| <button | |
| onClick={ | |
| step === "ask" | |
| ? requestSecurityQuestion | |
| : step === "question" | |
| ? submitAnswer | |
| : resetPassword | |
| } | |
| disabled={loading} | |
| className={`${step === "ask" ? "flex-1" : "flex-1"} py-2 bg-primary text-white rounded-lg font-medium hover:bg-primary/90 transition disabled:opacity-50`} | |
| > | |
| {loading ? ( | |
| <div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mx-auto" /> | |
| ) : step === "reset" ? ( | |
| "Reset Password" | |
| ) : ( | |
| "Next" | |
| )} | |
| </button> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| </motion.div> | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 7.52 kB
- Xet hash:
- 43f93e113a56022eb54508e2677bc20fae10948f19b017faa3ca43f699210539
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.