fix and update

This commit is contained in:
lendry
2026-07-01 17:00:55 +03:00
parent 607397fcf3
commit 0c3c6d6d82
8 changed files with 323 additions and 184 deletions

View File

@@ -404,8 +404,6 @@ export default function LoginPage() {
setOtpBackupChannels(result.otpBackupChannels ?? []);
if (result.isTotpEnabled) {
const challengeToken = await beginTotpLogin(identifier);
@@ -416,13 +414,17 @@ export default function LoginPage() {
}
const channels = result.otpChannels ?? [];
const backupChannels = result.otpBackupChannels ?? [];
if (channels.length > 1) {
const hasPassword = result.hasPassword;
const primaryChoiceCount = channels.length + (hasPassword ? 1 : 0);
if (primaryChoiceCount > 1) {
setPassword('');
setStep('otpChannel');
@@ -430,6 +432,16 @@ export default function LoginPage() {
}
if (hasPassword) {
setPassword('');
setStep('password');
return;
}
if (channels.length === 1) {
await requestLoginOtp(channels[0].channel, identifier);
@@ -438,8 +450,6 @@ export default function LoginPage() {
}
// Нет основных каналов, но есть резервные — даём выбрать резервную доставку.
if (backupChannels.length > 1) {
setStep('otpChannel');
@@ -456,14 +466,6 @@ export default function LoginPage() {
}
if (result.hasPassword) {
setStep('password');
return;
}
showToast('Для этого аккаунта не настроены каналы для SMS или email-кода');
} catch (error) {
@@ -683,9 +685,17 @@ export default function LoginPage() {
function renderAlternativeMethods(includeOtpRequest: boolean) {
function renderAlternativeMethods(context: 'select' | 'password' | 'otp' | 'totp') {
const hasAlternatives = includeOtpRequest || methods.length > 0 || otpBackupChannels.length > 0;
const passwordMethod = methods.find((method) => method.kind === 'password');
const showPassword = context !== 'password' && context !== 'select' && Boolean(passwordMethod);
const showPrimaryOtp = context !== 'otp' && context !== 'select' && otpChannels.length > 0;
const showBackupOtp = otpBackupChannels.length > 0;
const hasAlternatives = showPassword || showPrimaryOtp || showBackupOtp;
if (!hasAlternatives) return null;
@@ -707,7 +717,7 @@ export default function LoginPage() {
<div className="mt-3 space-y-2">
{includeOtpRequest && otpChannels.length ? (
{showPrimaryOtp ? (
<button
@@ -723,47 +733,43 @@ export default function LoginPage() {
<Mail className="h-5 w-5 text-[#b9bdc9]" />
<span className="text-sm">Получить код на {otpChannels.length > 1 ? 'почту или телефон' : otpChannels[0]?.masked}</span>
<span className="text-sm">
Получить код на {otpChannels.length > 1 ? 'почту или телефон' : otpChannels[0]?.masked}
</span>
</button>
) : null}
{methods.map((method) => {
{showPassword ? (
const Icon = channelIcon[method.channel] ?? KeyRound;
<button
return (
type="button"
<button
onClick={() => {
key={`${method.kind}-${method.channel}`}
if (passwordMethod) void chooseMethod(passwordMethod);
type="button"
}}
onClick={() => void chooseMethod(method)}
disabled={isSubmitting}
disabled={isSubmitting}
className="flex w-full items-center gap-3 rounded-[16px] bg-[#2a2c36] p-3 text-left hover:bg-[#33353f] disabled:opacity-50"
className="flex w-full items-center gap-3 rounded-[16px] bg-[#2a2c36] p-3 text-left hover:bg-[#33353f] disabled:opacity-50"
>
>
<KeyRound className="h-5 w-5 text-[#b9bdc9]" />
<Icon className="h-5 w-5 text-[#b9bdc9]" />
<span className="text-sm">Войти с паролем</span>
<span className="text-sm">
</button>
{method.kind === 'password' ? 'Войти с паролем' : `Код на ${method.masked}`}
) : null}
</span>
</button>
);
})}
{otpBackupChannels.length ? (
{showBackupOtp ? (
<>
@@ -1083,13 +1089,37 @@ export default function LoginPage() {
})}
{otpBackupChannels.length ? (
{methods.some((method) => method.kind === 'password') ? (
<>
<button
<p className="px-1 pt-2 text-xs uppercase tracking-wide text-[#7d818d]">Резервные контакты</p>
type="button"
{otpBackupChannels.map((channel) => {
disabled={isSubmitting}
onClick={() => {
setPassword('');
setStep('password');
}}
className="flex w-full items-center gap-3 rounded-[16px] bg-[#2a2c36] p-3 text-left hover:bg-[#33353f] disabled:opacity-50"
>
<KeyRound className="h-5 w-5 text-[#b9bdc9]" />
<span className="text-sm">Пароль</span>
</button>
) : null}
{otpChannels.length === 0 && !methods.some((method) => method.kind === 'password')
? otpBackupChannels.map((channel) => {
const Icon = channelIcon[channel.channel] ?? Mail;
@@ -1121,45 +1151,14 @@ export default function LoginPage() {
);
})}
})
</>
) : null}
{methods.some((method) => method.kind === 'password') ? (
<>
<p className="px-1 pt-2 text-xs uppercase tracking-wide text-[#7d818d]">или</p>
<button
type="button"
disabled={isSubmitting}
onClick={() => {
const passwordMethod = methods.find((method) => method.kind === 'password');
if (passwordMethod) void chooseMethod(passwordMethod);
}}
className="flex w-full items-center gap-3 rounded-[16px] bg-[#2a2c36] p-3 text-left hover:bg-[#33353f] disabled:opacity-50"
>
<KeyRound className="h-5 w-5 text-[#b9bdc9]" />
<span className="text-sm">Войти с паролем</span>
</button>
</>
) : null}
: null}
</div>
{renderAlternativeMethods('select')}
{totpChallengeToken ? (
<Button type="button" variant="outline" size="lg" className="mt-6 w-full rounded-[18px] border-[#424550] text-white hover:bg-[#2a2c36]" onClick={() => setStep('totp')}>
@@ -1218,7 +1217,7 @@ export default function LoginPage() {
)}
</div>
{renderAlternativeMethods(false)}
{renderAlternativeMethods('otp')}
{totpChallengeToken ? (
@@ -1262,7 +1261,7 @@ export default function LoginPage() {
{isSubmitting ? <p className="mt-3 text-center text-sm text-[#b9bdc9]">Проверяем код...</p> : null}
{renderAlternativeMethods(true)}
{renderAlternativeMethods('totp')}
</div>
@@ -1324,6 +1323,8 @@ export default function LoginPage() {
) : null}
{renderAlternativeMethods('password')}
</form>
) : null}