@@ -3,33 +3,59 @@
import Link from 'next/link' ;
import { FormEvent , useState } from 'react' ;
import { useRouter } from 'next/navigation' ;
import { ShieldCheck } from 'lucide-react' ;
import { ChevronLeft , UserRound } from 'lucide-react' ;
import { BrandLogo } from '@/components/id/brand-logo' ;
import { useAuth } from '@/components/id/auth-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' ;
type Step = 'contact' | 'otp' | 'pin' ;
export default function RegisterPage() {
const router = useRouter ( ) ;
const { sendLoginOtp } = useAuth ( ) ;
const { sendLoginOtp , verifyLoginOtp , completePin } = useAuth ( ) ;
const { showToast } = useToast ( ) ;
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 [ pin , setPin ] = useState ( '' ) ;
const [ pendingSessionId , setPendingSessionId ] = useState < string | null > ( null ) ;
const [ step , setStep ] = useState < Step > ( 'contact' ) ;
const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
async function handleSubmit ( event : FormEvent < HTMLFormElement > ) {
function getIdentifier() {
return authTab === 'email' ? email . trim ( ) : toE164 ( country , phoneNumber ) ;
}
function finishRegistration ( pinVerified : boolean , sessionId : string ) {
if ( ! pinVerified ) {
setPendingSessionId ( sessionId ) ;
setStep ( 'pin' ) ;
return ;
}
showToast ( 'Добро пожаловать! ID успешно создан.' ) ;
router . push ( '/' ) ;
}
async function handleSendCode ( event : FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
setIsSubmitting ( true ) ;
try {
const recipient = authTab === 'email' ? email . trim ( ) : toE164 ( country , phoneNumber ) ;
await sendLoginOtp ( recipient ) ;
showToast ( 'Код отправлен. Подтвердите е г о на экране входа.' ) ;
router . push ( '/auth/login' ) ;
const identifier = getIdentifier ( ) ;
setRecipient ( identifier ) ;
const masked = await sendLoginOtp ( identifier ) ;
setMaskedTarget ( masked ) ;
setOtp ( '' ) ;
setStep ( 'otp' ) ;
showToast ( 'Код отправлен. Введите е г о ниже.' ) ;
} catch ( error ) {
showToast ( error instanceof Error ? error . message : 'Н е удалось отправить код' ) ;
} finally {
@@ -37,41 +63,129 @@ export default function RegisterPage() {
}
}
async function verifyOtp ( code : string ) {
setIsSubmitting ( true ) ;
try {
const response = await verifyLoginOtp ( recipient , code ) ;
if ( response . auth ) {
finishRegistration ( response . auth . pinVerified , response . auth . sessionId ) ;
} else {
showToast ( 'Н е удалось завершить регистрацию' ) ;
}
} catch ( error ) {
setOtp ( '' ) ;
showToast ( error instanceof Error ? error . message : 'Неверный код' ) ;
} finally {
setIsSubmitting ( false ) ;
}
}
async function handlePinSubmit ( event : FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
if ( ! pendingSessionId ) return ;
setIsSubmitting ( true ) ;
try {
await completePin ( pendingSessionId , pin ) ;
showToast ( 'Добро пожаловать! ID успешно создан.' ) ;
router . push ( '/' ) ;
} catch ( error ) {
showToast ( error instanceof Error ? error . message : 'Н е удалось проверить PIN-код' ) ;
} finally {
setIsSubmitting ( false ) ;
}
}
function resetToContact() {
setStep ( 'contact' ) ;
setOtp ( '' ) ;
setPin ( '' ) ;
setPendingSessionId ( null ) ;
}
return (
< main className = "flex min-h-screen items-center justify-center bg-[radial-gradient(circle_at_center,#5d6578_0%,#2c2736_48%,#111016_100%)] px-5" >
< section className = "w-full max-w-[430px] rounded-[34px] bg-[#1f2028] px-9 py-10 text-white shadow-2xl" >
< div className = "flex flex-col items-center text-center" >
< BrandLogo size = "lg" variant = "light" / >
< h1 className = "mt-8 text-xl font-bold" > С о з д а й т е ID < / h1 >
< p className = "mt-2 text-sm text-[#b9bdc9]" > В в е д и т е п о ч т у и л и т е л е ф о н . П а р о л ь н е н у ж е н . < / p >
< p className = "mt-2 text-sm text-[#b9bdc9]" >
{ step === 'contact' ? 'Введите почту или телефон. Пароль не нужен.' : 'Подтвердите код — аккаунт создастся автоматически.' }
< / p >
< / div >
< form className = "mt-8 space-y-3" onSubmit = { handleSubmit } >
< Tabs value = { authTab } onValueChange = { ( value ) = > setAuthTab ( value as 'email' | 'phone' ) } className = "w-full" >
< TabsList className = "grid w-full grid-cols-2 ">
< TabsTrigger value = "email" > П о ч т а < / TabsTrigger >
< TabsTrigger value = "phone" > Т е л е ф о н < / TabsTrigger >
< / TabsList >
< TabsContent value = "email" >
< Input
className = "h-[56px] border-[#555762] bg-transparent text-white placeholder:text-[#8f92a0]"
p laceholder = "name@example.com"
type = "email"
value = { email }
onChange = { ( event ) = > setE mail( event . target . value ) }
required = { authTab === 'email' }
/ >
</ TabsContent >
< TabsContent value = "phone" >
< PhoneInput country = { country } value = { phoneNumber } onCountryChange = { setCountry } onValueChange = { setPhoneNumber } / >
< / TabsContent >
< / Tabs >
< Button variant = "white" size = "lg" className = "w-full rounded-[18px]" disabled = { isSubmitting } >
{ isSubmitting ? 'Отправляем...' : 'Получить код' }
{ step !== 'contact' ? (
< button type = "button" onClick = { resetToContact } className = "mt-6 flex items-center gap-1 text-sm text-[#b9bdc9] hover:text-white ">
< ChevronLeft className = "h-4 w-4" / >
И з м е н и т ь п о ч т у и л и т е л е ф о н
< / button >
) : null }
{ step === 'contact' ? (
< form c lassName = "mt-8 space-y-3" onSubmit = { handleSendCode } >
< Tabs value = { authTab } onValueChange = { ( value ) = > setAuthTab ( value as 'email' | 'phone' ) } className = "w-full" >
< TabsList className = "grid w-full grid-cols-2" >
< TabsTrigger value = "e mail" > П о ч т а < / TabsTrigger >
< TabsTrigger value = "phone" > Т е л е ф о н < / TabsTrigger >
< / TabsList >
< TabsContent value = "email" >
< Input
className = "h-[56px] border-[#555762] bg-transparent text-white placeholder:text-[#8f92a0]"
placeholder = "name@example.com"
type = "email"
value = { email }
onChange = { ( event ) = > setEmail ( event . target . value ) }
required = { authTab === 'email' }
/ >
< / TabsContent >
< TabsContent value = "phone" >
< PhoneInput country = { country } value = { phoneNumber } onCountryChange = { setCountry } onValueChange = { setPhoneNumber } / >
< / TabsContent >
< / Tabs >
< Button variant = "white" size = "lg" className = "w-full rounded-[18px]" disabled = { isSubmitting } >
{ isSubmitting ? 'Отправляем...' : 'Получить код' }
< / Button >
< / form >
) : null }
{ step === 'otp' ? (
< div className = "mt-8" >
< div className = "mb-5 flex items-center gap-3 rounded-[20px] bg-[#2a2c36] p-3" >
< div className = "flex h-11 w-11 items-center justify-center rounded-full bg-white/10" >
< UserRound className = "h-5 w-5" / >
< / div >
< div className = "min-w-0 text-left" >
< p className = "text-sm text-[#b9bdc9]" > К о д о т п р а в л е н н а < / p >
< p className = "truncate text-sm font-semibold" > { maskedTarget || recipient } < / p >
< / div >
< / div >
< OtpInput value = { otp } onChange = { setOtp } onComplete = { verifyOtp } disabled = { isSubmitting } / >
{ isSubmitting ? < p className = "mt-3 text-center text-sm text-[#b9bdc9]" > С о з д а ё м в а ш ID . . . < / p > : null }
< / div >
) : null }
{ step === 'pin' && pendingSessionId ? (
< form onSubmit = { handlePinSubmit } className = "mt-8 space-y-3" >
< p className = "text-center text-sm text-[#b9bdc9]" > У с т а н о в и т е PIN д л я з а в е р ш е н и я р е г и с т р а ц и и < / p >
< Input
className = "h-[58px] border-[#555762] bg-transparent text-center text-lg tracking-[0.4em] text-white placeholder:text-[#8f92a0]"
placeholder = "PIN"
value = { pin }
onChange = { ( event ) = > setPin ( event . target . value ) }
required
minLength = { 4 }
maxLength = { 6 }
/ >
< Button variant = "white" size = "lg" className = "w-full rounded-[18px] text-base" disabled = { isSubmitting } >
{ isSubmitting ? 'Проверяем...' : 'Подтвердить PIN' }
< / Button >
< / form >
) : null }
{ step === 'contact' ? (
< Button asChild variant = "ghost" className = "mt-5 w-full text-white hover:bg-[#2a2c36]" >
< Link href = "/auth/login" > У м е н я у ж е е с т ь ID < / Link >
< / Button >
< / form >
< Button asChild variant = "ghost" className = "mt-5 w-full text-white hover:bg-[#2a2c36]" >
< Link href = "/auth/login" > У м е н я у ж е е с т ь ID < / Link >
< / Button >
) : null }
< / section >
< / main >
) ;