mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix: add resend email cooldown and allow email change for all auth types
Add 60-second cooldown timer on resend verification email button to prevent spam. Remove auth_type restriction on change email button so OAuth users can also change their email.
This commit is contained in:
@@ -2902,6 +2902,7 @@
|
|||||||
"notVerified": "Not verified",
|
"notVerified": "Not verified",
|
||||||
"verificationRequired": "Please verify your email to use email login.",
|
"verificationRequired": "Please verify your email to use email login.",
|
||||||
"resendVerification": "Resend Verification Email",
|
"resendVerification": "Resend Verification Email",
|
||||||
|
"resendIn": "Resend in {{seconds}} sec.",
|
||||||
"verificationResent": "Verification email resent!",
|
"verificationResent": "Verification email resent!",
|
||||||
"canLoginWithEmail": "You can now log in using your email and password.",
|
"canLoginWithEmail": "You can now log in using your email and password.",
|
||||||
"emailAlreadyRegistered": "This email is already linked to another account. If this is your email, log in with it or contact support.",
|
"emailAlreadyRegistered": "This email is already linked to another account. If this is your email, log in with it or contact support.",
|
||||||
|
|||||||
@@ -2402,6 +2402,7 @@
|
|||||||
"notVerified": "تایید نشده",
|
"notVerified": "تایید نشده",
|
||||||
"verificationRequired": "لطفاً ایمیل خود را تایید کنید تا از ورود با ایمیل استفاده کنید.",
|
"verificationRequired": "لطفاً ایمیل خود را تایید کنید تا از ورود با ایمیل استفاده کنید.",
|
||||||
"resendVerification": "ارسال مجدد ایمیل تایید",
|
"resendVerification": "ارسال مجدد ایمیل تایید",
|
||||||
|
"resendIn": "ارسال مجدد در {{seconds}} ثانیه",
|
||||||
"verificationResent": "ایمیل تایید مجدداً ارسال شد!",
|
"verificationResent": "ایمیل تایید مجدداً ارسال شد!",
|
||||||
"canLoginWithEmail": "اکنون میتوانید با ایمیل و رمز عبور وارد شوید.",
|
"canLoginWithEmail": "اکنون میتوانید با ایمیل و رمز عبور وارد شوید.",
|
||||||
"emailAlreadyRegistered": "این ایمیل قبلاً به حساب دیگری متصل شده است. اگر این ایمیل شماست، با آن وارد شوید یا با پشتیبانی تماس بگیرید.",
|
"emailAlreadyRegistered": "این ایمیل قبلاً به حساب دیگری متصل شده است. اگر این ایمیل شماست، با آن وارد شوید یا با پشتیبانی تماس بگیرید.",
|
||||||
|
|||||||
@@ -3454,6 +3454,7 @@
|
|||||||
"notVerified": "Не подтверждён",
|
"notVerified": "Не подтверждён",
|
||||||
"verificationRequired": "Подтвердите email для использования входа по почте.",
|
"verificationRequired": "Подтвердите email для использования входа по почте.",
|
||||||
"resendVerification": "Отправить письмо повторно",
|
"resendVerification": "Отправить письмо повторно",
|
||||||
|
"resendIn": "Повторить через {{seconds}} сек.",
|
||||||
"verificationResent": "Письмо отправлено повторно!",
|
"verificationResent": "Письмо отправлено повторно!",
|
||||||
"canLoginWithEmail": "Теперь вы можете входить с помощью email и пароля.",
|
"canLoginWithEmail": "Теперь вы можете входить с помощью email и пароля.",
|
||||||
"emailAlreadyRegistered": "Этот email уже привязан к другому аккаунту. Если это ваш email, войдите через него или обратитесь в поддержку.",
|
"emailAlreadyRegistered": "Этот email уже привязан к другому аккаунту. Если это ваш email, войдите через него или обратитесь в поддержку.",
|
||||||
|
|||||||
@@ -2401,6 +2401,7 @@
|
|||||||
"notVerified": "未验证",
|
"notVerified": "未验证",
|
||||||
"verificationRequired": "请验证邮箱以使用邮箱登录。",
|
"verificationRequired": "请验证邮箱以使用邮箱登录。",
|
||||||
"resendVerification": "重新发送验证邮件",
|
"resendVerification": "重新发送验证邮件",
|
||||||
|
"resendIn": "{{seconds}} 秒后重新发送",
|
||||||
"verificationResent": "验证邮件已重新发送!",
|
"verificationResent": "验证邮件已重新发送!",
|
||||||
"canLoginWithEmail": "现在您可以使用邮箱和密码登录。",
|
"canLoginWithEmail": "现在您可以使用邮箱和密码登录。",
|
||||||
"emailAlreadyRegistered": "此邮箱已绑定到其他账户。如果这是您的邮箱,请使用邮箱登录或联系客服。",
|
"emailAlreadyRegistered": "此邮箱已绑定到其他账户。如果这是您的邮箱,请使用邮箱登录或联系客服。",
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export default function Profile() {
|
|||||||
const [changeCode, setChangeCode] = useState('');
|
const [changeCode, setChangeCode] = useState('');
|
||||||
const [changeError, setChangeError] = useState<string | null>(null);
|
const [changeError, setChangeError] = useState<string | null>(null);
|
||||||
const [resendCooldown, setResendCooldown] = useState(0);
|
const [resendCooldown, setResendCooldown] = useState(0);
|
||||||
|
const [verificationResendCooldown, setVerificationResendCooldown] = useState(0);
|
||||||
const newEmailInputRef = useRef<HTMLInputElement>(null);
|
const newEmailInputRef = useRef<HTMLInputElement>(null);
|
||||||
const codeInputRef = useRef<HTMLInputElement>(null);
|
const codeInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ export default function Profile() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setSuccess(t('profile.verificationResent'));
|
setSuccess(t('profile.verificationResent'));
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setVerificationResendCooldown(UI.RESEND_COOLDOWN_SEC);
|
||||||
},
|
},
|
||||||
onError: (err: { response?: { data?: { detail?: string } } }) => {
|
onError: (err: { response?: { data?: { detail?: string } } }) => {
|
||||||
setError(err.response?.data?.detail || t('common.error'));
|
setError(err.response?.data?.detail || t('common.error'));
|
||||||
@@ -228,7 +230,7 @@ export default function Profile() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resend cooldown timer
|
// Resend cooldown timers
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resendCooldown <= 0) return;
|
if (resendCooldown <= 0) return;
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@@ -237,6 +239,14 @@ export default function Profile() {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [resendCooldown]);
|
}, [resendCooldown]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (verificationResendCooldown <= 0) return;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setVerificationResendCooldown((prev) => Math.max(0, prev - 1));
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [verificationResendCooldown]);
|
||||||
|
|
||||||
// Auto-focus inputs on step change
|
// Auto-focus inputs on step change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@@ -454,34 +464,34 @@ export default function Profile() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => resendVerificationMutation.mutate()}
|
onClick={() => resendVerificationMutation.mutate()}
|
||||||
loading={resendVerificationMutation.isPending}
|
loading={resendVerificationMutation.isPending}
|
||||||
|
disabled={verificationResendCooldown > 0}
|
||||||
>
|
>
|
||||||
{t('profile.resendVerification')}
|
{verificationResendCooldown > 0
|
||||||
|
? t('profile.resendIn', { seconds: verificationResendCooldown })
|
||||||
|
: t('profile.resendVerification')}
|
||||||
</Button>
|
</Button>
|
||||||
{(user.auth_type === 'telegram' || user.auth_type === 'email') && (
|
<button
|
||||||
<button
|
onClick={() => setChangeEmailStep('email')}
|
||||||
onClick={() => setChangeEmailStep('email')}
|
className="text-sm text-accent-400 transition-colors hover:text-accent-300"
|
||||||
className="text-sm text-accent-400 transition-colors hover:text-accent-300"
|
>
|
||||||
>
|
{t('profile.changeEmail.button')}
|
||||||
{t('profile.changeEmail.button')}
|
</button>
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{user.email_verified &&
|
{user.email_verified && (
|
||||||
(user.auth_type === 'telegram' || user.auth_type === 'email') && (
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<p className="text-sm text-dark-400">{t('profile.canLoginWithEmail')}</p>
|
||||||
<p className="text-sm text-dark-400">{t('profile.canLoginWithEmail')}</p>
|
<button
|
||||||
<button
|
onClick={() => setChangeEmailStep('email')}
|
||||||
onClick={() => setChangeEmailStep('email')}
|
className="flex items-center gap-2 text-sm text-accent-400 transition-colors hover:text-accent-300"
|
||||||
className="flex items-center gap-2 text-sm text-accent-400 transition-colors hover:text-accent-300"
|
>
|
||||||
>
|
<PencilIcon />
|
||||||
<PencilIcon />
|
<span>{t('profile.changeEmail.button')}</span>
|
||||||
<span>{t('profile.changeEmail.button')}</span>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Inline email change flow */}
|
{/* Inline email change flow */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
Reference in New Issue
Block a user