CoolFace
Apppublic

DataScope26/WEB

sourceHugging Faceupdated 7d agoView on Hugging Face
0likes
AccountRecoveryModal.jsx286 linesDownload Raw Back to components
1import { Eye, EyeOff, AtSign, Loader, ShieldCheck } from "lucide-react";2import { useMemo, useRef, useState } from "react";3import { useAccountRecovery } from "../hooks/useAccountRecovery.js";4import ButtonPrimary from "./ButtonPrimary.jsx";5import ButtonSecondary from "./ButtonSecondary.jsx";6import ButtonClose from "./ButtonClose.jsx";7import "./AccountRecoveryModal.css";8 9export default function AccountRecoveryModal({ isOpen, onClose, onLoginClick }) {10  const {11    isLoading: recoveryLoading,12    error: recoveryError,13    success: recoverySuccess,14    sendRecoveryCode,15    verifyRecoveryCode,16    resetPasswordWithRecoveryCode,17  } = useAccountRecovery();18 19  const [showPassword, setShowPassword] = useState(false);20  const [message, setMessage] = useState("");21  const [recoveryStep, setRecoveryStep] = useState("request");22  const [recoveryEmail, setRecoveryEmail] = useState("");23  const [recoveryCode, setRecoveryCode] = useState(Array(6).fill(""));24  const [newPassword, setNewPassword] = useState("");25  const [confirmPassword, setConfirmPassword] = useState("");26  const codeRefs = useRef([]);27 28  const recoveryCodeValue = useMemo(() => recoveryCode.join(""), [recoveryCode]);29 30  const moveToNextCodeInput = (index, value) => {31    if (!/\d/.test(value) && value !== "") return;32 33    const nextCode = [...recoveryCode];34    nextCode[index] = value;35    setRecoveryCode(nextCode);36 37    if (value && index < 5) {38      codeRefs.current[index + 1]?.focus();39    }40  };41 42  const handleRecoveryCodeKeyDown = (index, event) => {43    if (event.key === "Backspace" && !recoveryCode[index] && index > 0) {44      codeRefs.current[index - 1]?.focus();45    }46  };47 48  const handleRecoveryRequest = async () => {49    const trimmedEmail = recoveryEmail.trim().toLowerCase();50    if (!trimmedEmail) {51      setMessage("Ingresá tu email para continuar.");52      return;53    }54 55    const ok = await sendRecoveryCode(trimmedEmail);56    if (ok) {57      setRecoveryStep("code");58      setMessage("");59    }60  };61 62  const handleRecoveryVerification = async () => {63    const trimmedEmail = recoveryEmail.trim().toLowerCase();64    const code = recoveryCodeValue;65 66    if (!trimmedEmail || code.length !== 6) {67      setMessage("Ingresá el código completo de 6 dígitos.");68      return;69    }70 71    const ok = await verifyRecoveryCode(trimmedEmail, code);72    if (ok) {73      setRecoveryStep("reset");74      setMessage("");75    }76  };77 78  const handlePasswordReset = async () => {79    const trimmedEmail = recoveryEmail.trim().toLowerCase();80    const code = recoveryCodeValue;81 82    if (!trimmedEmail || code.length !== 6) {83      setMessage("El código ingresado no es válido.");84      return;85    }86 87    if (newPassword.length < 8 || !/[A-Z]/.test(newPassword) || !/[a-z]/.test(newPassword)) {88      setMessage("La contraseña debe tener al menos 8 caracteres, una mayúscula y una minúscula.");89      return;90    }91 92    if (newPassword !== confirmPassword) {93      setMessage("Las contraseñas no coinciden.");94      return;95    }96 97    const ok = await resetPasswordWithRecoveryCode(trimmedEmail, code, newPassword);98    if (ok) {99      setRecoveryStep("success");100      setMessage("");101    }102  };103 104  const goToLogin = () => {105    setRecoveryStep("request");106    setRecoveryEmail("");107    setRecoveryCode(Array(6).fill(""));108    setNewPassword("");109    setConfirmPassword("");110    setMessage("");111    onLoginClick();112  };113 114  if (!isOpen) return null;115 116  return (117    <>118      <div className="modal-backdrop" onClick={onClose} />119      <div className="recovery-modal">120        <ButtonClose onClick={onClose} />121 122        {recoveryStep === "request" && (123          <>124            <div className="recovery-modal__header">125              <span className="recovery-modal__badge">Seguridad</span>126              <h2>Recuperar acceso</h2>127            </div>128 129            <p className="recovery-modal__description">130              Ingresá tu email para recibir el código de recuperación.131            </p>132 133            <div className="input-group recovery-input-group">134              <label>EMAIL</label>135              <div className="input-icon">136                <input137                  type="email"138                  value={recoveryEmail}139                  onChange={(event) => setRecoveryEmail(event.target.value)}140                  placeholder="nombre@usuario.com"141                  maxLength={80}142                  autoComplete="email"143                />144                <AtSign size={18} className="icon" />145              </div>146            </div>147 148            {(recoveryError || message) && (149              <p className="error-text error-text--animated">{recoveryError || message}</p>150            )}151 152            <div className="recovery-actions">153              <ButtonPrimary className="recovery-btn" onClick={handleRecoveryRequest} disabled={recoveryLoading}>154                {recoveryLoading ? <Loader size={18} className="loader-icon-animation" /> : "Enviar código"}155              </ButtonPrimary>156              <ButtonSecondary className="recovery-btn" onClick={onClose}>Volver</ButtonSecondary>157            </div>158          </>159        )}160 161        {recoveryStep === "code" && (162          <>163            <div className="recovery-modal__header">164              <span className="recovery-modal__badge">Verificación</span>165              <h2>Ingresá el código</h2>166            </div>167 168            <p className="recovery-modal__description">169              Te enviamos un código de 6 dígitos a <strong>{recoveryEmail}</strong>.170            </p>171 172            <div className="recovery-code-grid">173              {recoveryCode.map((digit, index) => (174                <input175                  key={index}176                  ref={(element) => (codeRefs.current[index] = element)}177                  type="text"178                  inputMode="numeric"179                  maxLength={1}180                  value={digit}181                  onChange={(event) => moveToNextCodeInput(index, event.target.value.slice(-1))}182                  onKeyDown={(event) => handleRecoveryCodeKeyDown(index, event)}183                  className="recovery-code-input"184                  autoComplete="one-time-code"185                />186              ))}187            </div>188 189            {(recoveryError || message) && (190              <p className="error-text error-text--animated">{recoveryError || message}</p>191            )}192 193            <div className="recovery-actions">194              <ButtonPrimary className="recovery-btn" onClick={handleRecoveryVerification} disabled={recoveryLoading}>195                {recoveryLoading ? <Loader size={18} className="loader-icon-animation" /> : "Confirmar código"}196              </ButtonPrimary>197              <ButtonSecondary className="recovery-btn" onClick={() => setRecoveryStep("request")}>Solicitar otro</ButtonSecondary>198            </div>199          </>200        )}201 202        {recoveryStep === "reset" && (203          <>204            <div className="recovery-modal__header">205              <span className="recovery-modal__badge">Nueva contraseña</span>206              <h2>Actualizá tu acceso</h2>207            </div>208 209            <p className="recovery-modal__description">210              Elegí una contraseña segura para continuar.211            </p>212 213            <div className="input-group recovery-input-group">214              <label>NUEVA CONTRASEÑA</label>215              <div className="input-icon">216                <input217                  type={showPassword ? "text" : "password"}218                  value={newPassword}219                  onChange={(event) => setNewPassword(event.target.value)}220                  placeholder="••••••••"221                  maxLength={60}222                />223                {showPassword ? (224                  <EyeOff size={18} className="icon" style={{ cursor: "pointer" }} onClick={() => setShowPassword(!showPassword)} />225                ) : (226                  <Eye size={18} className="icon" style={{ cursor: "pointer" }} onClick={() => setShowPassword(!showPassword)} />227                )}228              </div>229            </div>230 231            <div className="input-group recovery-input-group">232              <label>REPETIR CONTRASEÑA</label>233              <div className="input-icon">234                <input235                  type={showPassword ? "text" : "password"}236                  value={confirmPassword}237                  onChange={(event) => setConfirmPassword(event.target.value)}238                  placeholder="••••••••"239                  maxLength={60}240                />241              </div>242            </div>243 244            <div className="password-checklist recovery-password-checklist">245              <div className={newPassword.length >= 8 ? "password-checklist__ok" : "password-checklist__warn"}>246                {newPassword.length >= 8 ? "✓" : "•"} Mínimo 8 caracteres247              </div>248              <div className={/[A-Z]/.test(newPassword) && /[a-z]/.test(newPassword) ? "password-checklist__ok" : "password-checklist__warn"}>249                {/[A-Z]/.test(newPassword) && /[a-z]/.test(newPassword) ? "✓" : "•"} Mayúscula y minúscula250              </div>251            </div>252 253            {(recoveryError || message) && (254              <p className="error-text error-text--animated">{recoveryError || message}</p>255            )}256 257            <div className="recovery-actions">258              <ButtonPrimary className="recovery-btn" onClick={handlePasswordReset} disabled={recoveryLoading}>259                {recoveryLoading ? <Loader size={18} className="loader-icon-animation" /> : "Guardar contraseña"}260              </ButtonPrimary>261              <ButtonSecondary className="recovery-btn" onClick={() => setRecoveryStep("code")}>Atrás</ButtonSecondary>262            </div>263          </>264        )}265 266        {recoveryStep === "success" && (267          <>268            <div className="recovery-modal__header">269              <span className="recovery-modal__badge success-badge"><ShieldCheck size={16} /> Listo</span>270              <h2>Contraseña actualizada</h2>271            </div>272 273            <p className="recovery-modal__description">274              Tu contraseña fue actualizada correctamente. Ya podés iniciar sesión con tu nueva clave.275            </p>276 277            <div className="recovery-actions">278              <ButtonPrimary className="recovery-btn" onClick={goToLogin}>Iniciar sesión</ButtonPrimary>279            </div>280          </>281        )}282      </div>283    </>284  );285}286