import { useState, useCallback } from "react"; import { useTranslation } from 'react-i18next'; import MagnifierLogo from "./MagnifierLogo"; import { useAuth } from "../hooks/useAuth"; import { useRegister } from "../hooks/useRegister"; import {FLOAT_ICONS} from "../config/authConstants"; import "../styles/global.css"; import "../styles/auth.css"; import likeSmile from '../assets/success.png'; import { useCheckLogin } from "../hooks/useRegister"; // ─── helpers ────────────────────────────────────────────────────────────────── function getStrength(val, t) { if (!val) return { level: 0, label: "", color: "transparent", cls: "" }; let s = 0; if (val.length >= 6) s++; if (val.length >= 10) s++; if (/[A-Z]/.test(val) || /[0-9]/.test(val)) s++; if (/[^A-Za-z0-9]/.test(val)) s++; const labels = ["", t('auth.strengthWeak'), t('auth.strengthMedium'), t('auth.strengthGood'), t('auth.strengthExcellent')]; const colors = ["", "#ff6b6b", "#ffd166", "#2de0c8", "#2de0c8"]; const cls = ["", "sw", "sm", "ss", "ss"]; return { level: s, label: labels[s], color: colors[s], cls: cls[s] }; } // ─── Shared eye button ──────────────────────────────────────────────────────── function EyeBtn({ show, onToggle}) { return ( ); } // ─── Step dots ──────────────────────────────────────────────────────────────── function StepDots({ current, total }) { return (
{Array.from({ length: total }).map((_, i) => (
))}
); } // ═══════════════════════════════════════════════════════════════════════════════ // LOGIN FORM // ═══════════════════════════════════════════════════════════════════════════════ function LoginForm({ onSwitchToRegister }) { const { t } = useTranslation(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPass, setShowPass] = useState(false); const { login, loading, error } = useAuth(); return (

{t('auth.welcomeBack')}

{ e.preventDefault(); login(email, password); }} noValidate>
setEmail(e.target.value)} autoComplete="off" readOnly onFocus={(e) => e.target.removeAttribute("readOnly")} />
setPassword(e.target.value)} style={{ paddingRight: "44px" }} autoComplete="off" readOnly onFocus={(e) => e.target.removeAttribute("readOnly")} /> setShowPass(v => !v)} />
{error &&

{error}

}

{t('auth.noAccount')}{" "}

); } // ─── step 0+1: name + login + passwords ─────────────────────────────────────── function RegStep01({ data, onChange, onNext }) { const { t } = useTranslation(); const [show1, setShow1] = useState(false); const [show2, setShow2] = useState(false); const [errors, setErrors] = useState({}); const strength = getStrength(data.password, t); const matched = data.password && data.password === data.password2; const { checking: checkingLogin, exists: loginExists } = useCheckLogin(data.login); const validate = () => { const e = {}; if (!data.name.trim()) e.name = t('auth.validateNameRequired'); if (!data.login.trim()) e.login = t('auth.validateLoginRequired'); else if (data.login.length < 3) e.login = t('auth.loginMinChars'); if (data.password.length < 6) e.password = t('auth.validatePasswordMin'); else if (data.password !== data.password2) e.password2 = t('auth.validatePasswordMatch'); setErrors(e); return !Object.keys(e).length; }; return ( <>
{ onChange("name", e.target.value); setErrors(p => ({...p, name: ""})); }} autoComplete="off" /> {errors.name &&

{errors.name}

}
{ onChange("login", e.target.value); setErrors(p => ({...p, login: ""})); }} autoComplete="off" /> {checkingLogin &&

{t('auth.checking')}

} {!checkingLogin && data.login.length >= 3 && (

{loginExists ? t('auth.loginTaken') : t('auth.loginFree')}

)} {errors.login &&

{errors.login}

}
{ onChange("password", e.target.value); setErrors(p => ({...p, password: ""})); }} autoComplete="new-password" /> setShow1(v => !v)}/>
{data.password && ( <>
{[0, 1, 2, 3].map(i => (
))}

{strength.label}

)} {errors.password &&

{errors.password}

}
{ onChange("password2", e.target.value); setErrors(p => ({...p, password2: ""})); }} autoComplete="new-password" /> setShow2(v => !v)}/>
{data.password2 && (

{matched ? t('auth.passwordsMatch') : t('auth.passwordsNoMatch')}

)} {errors.password2 &&

{errors.password2}

}
); } // ─── step 2: confirm ────────────────────────────────────────────────────────── function RegStep2({data, onSubmit, onBack, loading, error}) { const { t } = useTranslation(); return ( <>
{t('auth.nameLabel')} {data.name}
{t('auth.loginLabel')} @{data.login}
{error &&

{error}

} ); } // ─── success ────────────────────────────────────────────────────────────────── function RegSuccess({name, onLogin}) { const { t } = useTranslation(); return (
success

{t('auth.welcome', { name })}

{t('auth.accountCreated')}

); } function RegisterForm({onSwitchToLogin}) { const { t } = useTranslation(); const [step, setStep] = useState(0); const [form, setForm] = useState( {name: "", login: "", password: "", password2: ""}); const {register, loading, error, success} = useRegister(); const setField = useCallback((key, val) => setForm(p => ({ ...p, [key]: val })), []); if (success) { return (
); } return (

{t('auth.createAccount')}

{step === 0 && ( setStep(1)} /> )} {step === 1 && ( register({ name: form.name, login: form.login, password: form.password })} onBack={() => setStep(0)} loading={loading} error={error} /> )} {step === 0 && (

{t('auth.hasAccount')}{" "}

)}
); } // ═══════════════════════════════════════════════════════════════════════════════ // AUTH PAGE — единственная страница, переключает login ↔ register // ═══════════════════════════════════════════════════════════════════════════════ export default function Authentication() { const { t } = useTranslation(); const [mode, setMode] = useState("login"); // "login" | "register" const [animating, setAnimating] = useState(false); const switchTo = (next) => { if (animating) return; setAnimating(true); // Небольшая задержка — карточка уходит, потом приходит новая setTimeout(() => { setMode(next); setAnimating(false); }, 320); }; return (
{FLOAT_ICONS.map(({ icon, delay, size, ...pos }, i) => ( ))}

{t('app.name')}

{mode === "login" ? switchTo("register")} /> : switchTo("login")} /> }
); }