'use client'; import Link from 'next/link'; import { FormEvent, useState } from 'react'; import { useRouter } from 'next/navigation'; import { ChevronLeft, KeyRound, Mail, Network, Phone, ShieldQuestion, UserRound } from 'lucide-react'; import { BrandLogo, ProjectTagline } from '@/components/id/brand-logo'; import { useAuth } from '@/components/id/auth-provider'; import { usePublicSettings } from '@/components/id/public-settings-provider'; import { useToast } from '@/components/id/toast-provider'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { OtpInput } from '@/components/ui/otp-input'; import { PhoneInput, phoneCountries, toE164 } from '@/components/ui/phone-input'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import type { LoginMethod } from '@/lib/api'; type Step = 'identify' | 'otp' | 'password' | 'pin' | 'ldap'; const channelIcon: Record = { email: Mail, backupEmail: Mail, phone: Phone, backupPhone: Phone, password: KeyRound }; export default function LoginPage() { const router = useRouter(); const { identifyLogin, sendLoginOtp, verifyLoginOtp, loginWithPassword, loginWithLdap, completePin } = useAuth(); const { showToast } = useToast(); const { ldapEnabled, ldapUseLdaps } = usePublicSettings(); const [authTab, setAuthTab] = useState<'email' | 'phone'>('email'); const [email, setEmail] = useState(''); const [phoneNumber, setPhoneNumber] = useState(''); const [country, setCountry] = useState(phoneCountries[0]); const [recipient, setRecipient] = useState(''); const [maskedTarget, setMaskedTarget] = useState(''); const [otp, setOtp] = useState(''); const [password, setPassword] = useState(''); const [ldapUsername, setLdapUsername] = useState(''); const [ldapPassword, setLdapPassword] = useState(''); const [pin, setPin] = useState(''); const [pendingSessionId, setPendingSessionId] = useState(null); const [methods, setMethods] = useState([]); const [showMethods, setShowMethods] = useState(false); const [step, setStep] = useState('identify'); const [isSubmitting, setIsSubmitting] = useState(false); function getIdentifier() { return authTab === 'email' ? email.trim() : toE164(country, phoneNumber); } function finishAuth(pinVerified: boolean, sessionId: string) { if (!pinVerified) { setPendingSessionId(sessionId); setStep('pin'); } else { router.push('/'); } } async function handleIdentify(event: FormEvent) { event.preventDefault(); setIsSubmitting(true); try { const identifier = getIdentifier(); setRecipient(identifier); const result = await identifyLogin(identifier); setMethods(result.methods ?? []); const masked = await sendLoginOtp(identifier); setMaskedTarget(masked); setOtp(''); setStep('otp'); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось продолжить вход'); } finally { setIsSubmitting(false); } } async function verifyOtp(code: string) { setIsSubmitting(true); try { const response = await verifyLoginOtp(recipient, code); if (response.auth) { finishAuth(response.auth.pinVerified, response.auth.sessionId); } else { showToast('Не удалось завершить вход'); } } catch (error) { setOtp(''); showToast(error instanceof Error ? error.message : 'Неверный код'); } finally { setIsSubmitting(false); } } async function handlePasswordSubmit(event: FormEvent) { event.preventDefault(); setIsSubmitting(true); try { const auth = await loginWithPassword(recipient, password); finishAuth(auth.pinVerified, auth.sessionId); } catch (error) { showToast(error instanceof Error ? error.message : 'Неверный пароль'); } finally { setIsSubmitting(false); } } async function chooseMethod(method: LoginMethod) { setShowMethods(false); if (method.kind === 'password') { setPassword(''); setStep('password'); return; } setIsSubmitting(true); try { const masked = await sendLoginOtp(recipient, method.channel); setMaskedTarget(masked); setOtp(''); setStep('otp'); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось отправить код'); } finally { setIsSubmitting(false); } } async function handleLdapSubmit(event: FormEvent) { event.preventDefault(); setIsSubmitting(true); try { const auth = await loginWithLdap(ldapUsername.trim(), ldapPassword); finishAuth(auth.pinVerified, auth.sessionId); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось выполнить LDAP-вход'); } finally { setIsSubmitting(false); } } async function handlePinSubmit(event: FormEvent) { event.preventDefault(); if (!pendingSessionId) return; setIsSubmitting(true); try { await completePin(pendingSessionId, pin); router.push('/'); } catch (error) { showToast(error instanceof Error ? error.message : 'Не удалось проверить PIN-код'); } finally { setIsSubmitting(false); } } function resetToIdentify() { setStep('identify'); setShowMethods(false); setOtp(''); setPassword(''); setLdapUsername(''); setLdapPassword(''); } return (

Войдите с ID

{step !== 'identify' ? ( ) : null} {step === 'identify' ? (
setAuthTab(value as 'email' | 'phone')} className="w-full"> Почта Телефон setEmail(event.target.value)} required={authTab === 'email'} /> {ldapEnabled ? ( ) : null}
) : null} {step === 'ldap' ? (

Корпоративный вход

{ldapUseLdaps ? 'LDAPS' : 'LDAP'}

setLdapUsername(event.target.value)} autoComplete="username" required /> setLdapPassword(event.target.value)} autoComplete="current-password" required />
) : null} {step === 'otp' ? (

Код отправлен на

{maskedTarget || recipient}

{isSubmitting ?

Проверяем код...

: null} {methods.length ? (
{showMethods ? (
{methods.map((method) => { const Icon = channelIcon[method.channel] ?? KeyRound; return ( ); })}
) : null}
) : null}
) : null} {step === 'password' ? (

Вход в аккаунт

{recipient}

setPassword(event.target.value)} required />
) : null} {step === 'pin' && pendingSessionId ? (
setPin(event.target.value)} required minLength={4} maxLength={6} />
) : null} {step === 'identify' ? (
) : null}

Узнать больше

); }