import { useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { KeyRound, Loader2, ShieldCheck } from 'lucide-react'; import { BrandLogo, ProjectTagline } from '@/components/id/brand-logo'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { beginTotpLogin, getMobileApiUrl, loginWithPassword, setMobileApiUrl, verifyTotpLogin } from '~/lib/mobile-api'; import { useMobileAuth } from '~/lib/auth'; import { getErrorMessage } from '~/lib/errors'; export function LoginScreen() { const { applyAuth } = useMobileAuth(); const [apiUrl, setApiUrl] = useState(getMobileApiUrl()); const [loginValue, setLoginValue] = useState(''); const [password, setPassword] = useState(''); const [totpCode, setTotpCode] = useState(''); const [totpChallengeToken, setTotpChallengeToken] = useState(null); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); async function submit() { setSubmitting(true); setError(null); try { const validated = await invoke<{ value: string }>('validate_api_base_url', { url: apiUrl }); setMobileApiUrl(validated.value); const auth = await loginWithPassword(loginValue.trim(), password); if ('requiresTotp' in auth) { if (!auth.totpChallengeToken) { throw new Error('Сервер не вернул TOTP challenge'); } setTotpChallengeToken(auth.totpChallengeToken); setError(null); return; } applyAuth(auth); } catch (err) { setError(getErrorMessage(err, 'Не удалось войти')); } finally { setSubmitting(false); } } async function startTotpLogin() { if (!loginValue.trim()) { setError('Укажите почту, телефон или логин'); return; } setSubmitting(true); setError(null); try { const validated = await invoke<{ value: string }>('validate_api_base_url', { url: apiUrl }); setMobileApiUrl(validated.value); const response = await beginTotpLogin(loginValue.trim()); setTotpChallengeToken(response.totpChallengeToken); } catch (err) { setError(getErrorMessage(err, 'Не удалось начать TOTP-вход')); } finally { setSubmitting(false); } } async function submitTotp() { if (!totpChallengeToken) return; setSubmitting(true); setError(null); try { const auth = await verifyTotpLogin(totpChallengeToken, totpCode.trim()); applyAuth(auth); } catch (err) { setError(getErrorMessage(err, 'Неверный код аутентификатора')); } finally { setSubmitting(false); } } return (

Войдите с ID

Мессенджер, QR-вход и TOTP-коды в одном приложении

setApiUrl(event.target.value)} placeholder="https://sso.example.ru/idp-api" className="h-[54px] border-[#555762] bg-transparent text-white placeholder:text-[#8f92a0]" />
setLoginValue(event.target.value)} autoComplete="username" className="h-[54px] border-[#555762] bg-transparent text-white placeholder:text-[#8f92a0]" />
{totpChallengeToken ? (
setTotpCode(event.target.value.replace(/\D/g, '').slice(0, 8))} inputMode="numeric" autoComplete="one-time-code" className="h-[58px] border-[#555762] bg-transparent text-center text-2xl tracking-[0.35em] text-white placeholder:text-[#8f92a0]" placeholder="000000" />
) : (
setPassword(event.target.value)} type="password" autoComplete="current-password" className="h-[54px] border-[#555762] bg-transparent text-white placeholder:text-[#8f92a0]" />
)}
{error ?

{error}

: null} {totpChallengeToken ? ( <> ) : ( <> )}
); }