add cooldown notification repeat
This commit is contained in:
@@ -30,10 +30,18 @@ import { PhoneInput, phoneCountries, toE164 } from '@/components/ui/phone-input'
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
|
||||
import type { AuthTokens, LoginMethod, OtpChannel } from '@/lib/api';
|
||||
import type { AuthTokens, LoginMethod, OtpChannel, OtpSendResponse } from '@/lib/api';
|
||||
|
||||
import { createQrLoginSession, pollQrLoginSession } from '@/lib/api';
|
||||
|
||||
import {
|
||||
clearOtpResendAvailableAt,
|
||||
formatCooldownSeconds,
|
||||
readOtpResendAvailableAt,
|
||||
useOtpResendCooldown,
|
||||
writeOtpResendAvailableAt
|
||||
} from '@/hooks/use-otp-resend-cooldown';
|
||||
|
||||
|
||||
|
||||
type Step = 'identify' | 'otp' | 'otpChannel' | 'password' | 'pin' | 'ldap' | 'qr' | 'totp';
|
||||
@@ -129,6 +137,10 @@ export default function LoginPage() {
|
||||
const [qrExpiresAt, setQrExpiresAt] = useState<string | null>(null);
|
||||
|
||||
const [qrLoading, setQrLoading] = useState(false);
|
||||
const [lastOtpChannel, setLastOtpChannel] = useState<string | undefined>();
|
||||
const [otpResendAvailableAt, setOtpResendAvailableAt] = useState<string | null>(null);
|
||||
const otpResendSecondsLeft = useOtpResendCooldown(otpResendAvailableAt);
|
||||
const canResendOtp = otpResendSecondsLeft <= 0 && !isSubmitting;
|
||||
|
||||
|
||||
|
||||
@@ -180,34 +192,34 @@ export default function LoginPage() {
|
||||
|
||||
|
||||
|
||||
function applyOtpSendResponse(response: OtpSendResponse, loginRecipient: string, channel?: string) {
|
||||
setMaskedTarget(response.maskedTarget);
|
||||
setLastOtpChannel(channel);
|
||||
setOtp('');
|
||||
setShowMethods(false);
|
||||
const resendAt = response.resendAvailableAt ?? new Date(Date.now() + 60_000).toISOString();
|
||||
setOtpResendAvailableAt(resendAt);
|
||||
writeOtpResendAvailableAt(loginRecipient, resendAt);
|
||||
setStep('otp');
|
||||
}
|
||||
|
||||
async function requestLoginOtp(channel?: string, recipientOverride?: string) {
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const loginRecipient = recipientOverride ?? recipient;
|
||||
|
||||
try {
|
||||
|
||||
const masked = await sendLoginOtp(loginRecipient, channel);
|
||||
|
||||
setMaskedTarget(masked);
|
||||
|
||||
setOtp('');
|
||||
|
||||
setShowMethods(false);
|
||||
|
||||
setStep('otp');
|
||||
|
||||
const response = await sendLoginOtp(loginRecipient, channel);
|
||||
applyOtpSendResponse(response, loginRecipient, channel);
|
||||
} catch (error) {
|
||||
|
||||
showToast(error instanceof Error ? error.message : 'Не удалось отправить код');
|
||||
|
||||
} finally {
|
||||
|
||||
setIsSubmitting(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResendOtp() {
|
||||
if (!canResendOtp) return;
|
||||
await requestLoginOtp(lastOtpChannel, recipient);
|
||||
}
|
||||
|
||||
|
||||
@@ -230,6 +242,14 @@ export default function LoginPage() {
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (step !== 'otp' || !recipient) return;
|
||||
const saved = readOtpResendAvailableAt(recipient);
|
||||
if (saved && new Date(saved).getTime() > Date.now()) {
|
||||
setOtpResendAvailableAt(saved);
|
||||
}
|
||||
}, [recipient, step]);
|
||||
|
||||
const startQrLogin = useCallback(async () => {
|
||||
|
||||
setQrLoading(true);
|
||||
@@ -618,6 +638,10 @@ export default function LoginPage() {
|
||||
|
||||
setQrExpiresAt(null);
|
||||
|
||||
clearOtpResendAvailableAt(recipient);
|
||||
setOtpResendAvailableAt(null);
|
||||
setLastOtpChannel(undefined);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,6 +1046,22 @@ export default function LoginPage() {
|
||||
|
||||
{isSubmitting ? <p className="mt-3 text-center text-sm text-[#b9bdc9]">Проверяем код...</p> : null}
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
{canResendOtp ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleResendOtp()}
|
||||
className="text-sm text-[#b9bdc9] underline-offset-4 transition hover:text-white hover:underline"
|
||||
>
|
||||
Запросить новый код
|
||||
</button>
|
||||
) : (
|
||||
<p className="text-sm text-[#b9bdc9]">
|
||||
Запросить новый код через {formatCooldownSeconds(otpResendSecondsLeft)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{renderAlternativeMethods(false)}
|
||||
|
||||
{totpChallengeToken ? (
|
||||
|
||||
Reference in New Issue
Block a user