first commit
This commit is contained in:
175
src/components/auth/AuthModal.tsx
Normal file
175
src/components/auth/AuthModal.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
import React, { useState } from 'react'
|
||||
import { ApolloProvider } from '@apollo/client'
|
||||
import { apolloClient } from '@/lib/apollo'
|
||||
import PhoneInput from './PhoneInput'
|
||||
import CodeVerification from './CodeVerification'
|
||||
import UserRegistration from './UserRegistration'
|
||||
import type { AuthState, AuthStep, ClientAuthResponse, VerificationResponse } from '@/types/auth'
|
||||
|
||||
interface AuthModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSuccess: (client: any, token?: string) => void
|
||||
}
|
||||
|
||||
const AuthModal: React.FC<AuthModalProps> = ({ isOpen, onClose, onSuccess }) => {
|
||||
const [authState, setAuthState] = useState<AuthState>({
|
||||
step: 'phone',
|
||||
phone: '',
|
||||
sessionId: '',
|
||||
isExistingClient: false
|
||||
})
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handlePhoneSuccess = (data: ClientAuthResponse) => {
|
||||
setError('')
|
||||
// Всегда переходим к вводу кода, независимо от того, существует клиент или нет
|
||||
setAuthState(prev => ({
|
||||
...prev,
|
||||
step: 'code',
|
||||
sessionId: data.sessionId,
|
||||
client: data.client,
|
||||
isExistingClient: data.exists
|
||||
}))
|
||||
}
|
||||
|
||||
const handleCodeSuccess = (data: VerificationResponse) => {
|
||||
if (data.success && data.client) {
|
||||
onSuccess(data.client, data.token)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegistrationSuccess = (data: VerificationResponse) => {
|
||||
if (data.success && data.client) {
|
||||
onSuccess(data.client, data.token)
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const handleError = (errorMessage: string) => {
|
||||
setError(errorMessage)
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
setAuthState(prev => ({
|
||||
...prev,
|
||||
step: 'phone'
|
||||
}))
|
||||
setError('')
|
||||
}
|
||||
|
||||
const handleGoToRegistration = () => {
|
||||
setAuthState(prev => ({
|
||||
...prev,
|
||||
step: 'registration'
|
||||
}))
|
||||
setError('')
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAuthState({
|
||||
step: 'phone',
|
||||
phone: '',
|
||||
sessionId: '',
|
||||
isExistingClient: false
|
||||
})
|
||||
setError('')
|
||||
onClose()
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
const renderStep = () => {
|
||||
switch (authState.step) {
|
||||
case 'phone':
|
||||
return (
|
||||
<PhoneInput
|
||||
onSuccess={(data, phone) => {
|
||||
setAuthState(prev => ({
|
||||
...prev,
|
||||
phone: phone,
|
||||
sessionId: data.sessionId,
|
||||
client: data.client,
|
||||
isExistingClient: data.exists
|
||||
}))
|
||||
handlePhoneSuccess(data)
|
||||
}}
|
||||
onError={handleError}
|
||||
onRegister={handleGoToRegistration}
|
||||
/>
|
||||
)
|
||||
case 'code':
|
||||
return (
|
||||
<CodeVerification
|
||||
phone={authState.phone}
|
||||
sessionId={authState.sessionId}
|
||||
isExistingClient={authState.isExistingClient}
|
||||
onSuccess={handleCodeSuccess}
|
||||
onError={handleError}
|
||||
onBack={handleBack}
|
||||
onRegister={handleGoToRegistration}
|
||||
/>
|
||||
)
|
||||
case 'registration':
|
||||
return (
|
||||
<UserRegistration
|
||||
phone={authState.phone}
|
||||
sessionId={authState.sessionId}
|
||||
onSuccess={handleRegistrationSuccess}
|
||||
onError={handleError}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ApolloProvider client={apolloClient}>
|
||||
{/* Overlay */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/10 z-index-40 margin-top-[132px] transition-opacity duration-200"
|
||||
aria-label="Затемнение фона"
|
||||
tabIndex={-1}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
{/* Модальное окно */}
|
||||
<div className="flex relative w-full bg-white mx-auto z-50">
|
||||
<div className="flex relative flex-col gap-4 items-start px-32 py-10 w-full bg-white max-w-[1920px] min-h-[320px] max-md:px-16 max-md:py-8 max-sm:gap-8 max-sm:p-5 mx-auto z-50"
|
||||
style={{ marginTop: 0, position: 'relative' }}
|
||||
>
|
||||
{/* Кнопка закрытия */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-8 top-8 p-2 hover:opacity-70 focus:outline-none"
|
||||
aria-label="Закрыть окно авторизации"
|
||||
tabIndex={0}
|
||||
>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none">
|
||||
<path d="M8 23.75L6.25 22L13.25 15L6.25 8L8 6.25L15 13.25L22 6.25L23.75 8L16.75 15L23.75 22L22 23.75L15 16.75L8 23.75Z" fill="#000814"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/* Заголовок */}
|
||||
<div className="flex relative justify-between items-start w-full max-sm:flex-col max-sm:gap-5">
|
||||
<div className="relative text-5xl font-bold uppercase leading-[62.4px] text-gray-950 max-md:text-5xl max-sm:self-start max-sm:text-3xl">
|
||||
ВХОД
|
||||
</div>
|
||||
</div>
|
||||
{/* Ошибка */}
|
||||
{error && (
|
||||
<div className="mb-4 px-4 py-3 bg-red-50 border border-red-200 rounded">
|
||||
<p className="text-red-800 m-0">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
{/* Контент */}
|
||||
<div className="flex relative flex-col gap-5 items-start self-stretch w-full">
|
||||
{renderStep()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthModal
|
159
src/components/auth/CodeVerification.tsx
Normal file
159
src/components/auth/CodeVerification.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { SEND_SMS_CODE, VERIFY_CODE } from '@/lib/graphql'
|
||||
import type { SMSCodeResponse, VerificationResponse } from '@/types/auth'
|
||||
|
||||
interface CodeVerificationProps {
|
||||
phone: string
|
||||
sessionId: string
|
||||
isExistingClient: boolean
|
||||
onSuccess: (data: VerificationResponse) => void
|
||||
onError: (error: string) => void
|
||||
onBack: () => void
|
||||
onRegister: () => void
|
||||
}
|
||||
|
||||
const CodeVerification: React.FC<CodeVerificationProps> = ({
|
||||
phone,
|
||||
sessionId,
|
||||
isExistingClient,
|
||||
onSuccess,
|
||||
onError,
|
||||
onBack,
|
||||
onRegister
|
||||
}) => {
|
||||
const [code, setCode] = useState(['', '', '', '', ''])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [smsCode, setSmsCode] = useState('')
|
||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([])
|
||||
|
||||
const [sendSMS] = useMutation<{ sendSMSCode: SMSCodeResponse }>(SEND_SMS_CODE)
|
||||
const [verifyCode] = useMutation<{ verifyCode: VerificationResponse }>(VERIFY_CODE)
|
||||
|
||||
// SMS код уже отправлен в PhoneInput, здесь только показываем
|
||||
useEffect(() => {
|
||||
console.log('CodeVerification mounted for', isExistingClient ? 'existing' : 'new', 'client')
|
||||
}, [])
|
||||
|
||||
const handleCodeChange = (index: number, value: string) => {
|
||||
if (!/^\d*$/.test(value)) return
|
||||
|
||||
const newCode = [...code]
|
||||
newCode[index] = value.slice(-1)
|
||||
setCode(newCode)
|
||||
|
||||
// Автоматически переходим к следующему полю
|
||||
if (value && index < 4) {
|
||||
inputRefs.current[index + 1]?.focus()
|
||||
}
|
||||
|
||||
// Если все поля заполнены, отправляем код
|
||||
if (newCode.every(digit => digit !== '') && !isLoading) {
|
||||
handleVerify(newCode.join(''))
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Backspace' && !code[index] && index > 0) {
|
||||
inputRefs.current[index - 1]?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
const handleVerify = async (codeString?: string) => {
|
||||
const finalCode = codeString || code.join('')
|
||||
|
||||
if (finalCode.length !== 5) {
|
||||
onError('Введите полный код')
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const { data } = await verifyCode({
|
||||
variables: {
|
||||
phone,
|
||||
code: finalCode,
|
||||
sessionId
|
||||
}
|
||||
})
|
||||
|
||||
if (data?.verifyCode?.success) {
|
||||
if (data.verifyCode.client) {
|
||||
// Если клиент существует - авторизуем
|
||||
onSuccess(data.verifyCode)
|
||||
} else {
|
||||
// Если клиент новый - переходим к регистрации
|
||||
onRegister()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка верификации:', error)
|
||||
onError('Неверный код')
|
||||
setCode(['', '', '', '', ''])
|
||||
inputRefs.current[0]?.focus()
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5 w-full">
|
||||
<label className="text-2xl leading-8 text-gray-950 mb-2 font-normal font-[Onest,sans-serif]">Введите код из СМС</label>
|
||||
<div className="flex gap-5 items-center w-full max-md:flex-col max-md:gap-4 max-sm:gap-3">
|
||||
{/* 5 полей для цифр */}
|
||||
<div className="flex gap-3">
|
||||
{code.map((digit, index) => (
|
||||
<input
|
||||
key={index}
|
||||
ref={el => { inputRefs.current[index] = el }}
|
||||
type="text"
|
||||
value={digit}
|
||||
onChange={(e) => handleCodeChange(index, e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(index, e)}
|
||||
className="w-[62px] h-[62px] px-4 py-3 text-[18px] leading-[1.4] font-normal font-[Onest,sans-serif] text-neutral-500 bg-white border border-stone-300 rounded focus:outline-none text-center"
|
||||
maxLength={1}
|
||||
disabled={isLoading}
|
||||
aria-label={`Цифра ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Кнопка "Войти" */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleVerify()}
|
||||
disabled={isLoading || code.some(digit => digit === '')}
|
||||
style={{ color: 'white' }}
|
||||
className="flex items-center justify-center flex-shrink-0 bg-red-600 rounded-xl px-8 py-5 text-lg font-medium leading-5 text-white disabled:opacity-50 disabled:cursor-not-allowed h-[62px] max-sm:px-6 max-sm:py-4"
|
||||
aria-label="Войти"
|
||||
tabIndex={0}
|
||||
>
|
||||
{isLoading ? 'Проверяем...' : 'Войти'}
|
||||
</button>
|
||||
</div>
|
||||
{/* Кнопка "Ввести другой номер" под вводом кода */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="flex gap-3 items-center hover:opacity-70 mt-2"
|
||||
aria-label="Ввести другой номер"
|
||||
tabIndex={0}
|
||||
>
|
||||
<svg width="40" height="13" viewBox="0 0 40 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.469669 5.96967C0.176777 6.26256 0.176777 6.73743 0.469669 7.03033L5.24264 11.8033C5.53553 12.0962 6.01041 12.0962 6.3033 11.8033C6.59619 11.5104 6.59619 11.0355 6.3033 10.7426L2.06066 6.5L6.3033 2.25736C6.5962 1.96446 6.5962 1.48959 6.3033 1.1967C6.01041 0.903803 5.53553 0.903803 5.24264 1.1967L0.469669 5.96967ZM40 5.75L1 5.75L1 7.25L40 7.25L40 5.75Z" fill="#424F60"/>
|
||||
</svg>
|
||||
<span className="text-lg leading-[1.4] font-normal font-[Onest,sans-serif] text-[#424F60]">Ввести другой номер</span>
|
||||
</button>
|
||||
{/* Отладочная информация */}
|
||||
{smsCode && (
|
||||
<div className="mt-4 px-4 py-3 bg-blue-50 border border-blue-200 rounded">
|
||||
<p className="text-sm text-blue-800 m-0 font-[Onest,sans-serif]">
|
||||
<strong>Код для тестирования:</strong> {smsCode}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CodeVerification
|
142
src/components/auth/PhoneInput.tsx
Normal file
142
src/components/auth/PhoneInput.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { CHECK_CLIENT_BY_PHONE, SEND_SMS_CODE } from '@/lib/graphql'
|
||||
import type { ClientAuthResponse, SMSCodeResponse } from '@/types/auth'
|
||||
|
||||
interface PhoneInputProps {
|
||||
onSuccess: (data: ClientAuthResponse, phone: string) => void
|
||||
onError: (error: string) => void
|
||||
onRegister: () => void
|
||||
}
|
||||
|
||||
const PhoneInput: React.FC<PhoneInputProps> = ({ onSuccess, onError, onRegister }) => {
|
||||
const [phone, setPhone] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const [checkClient] = useMutation<{ checkClientByPhone: ClientAuthResponse }>(CHECK_CLIENT_BY_PHONE)
|
||||
const [sendSMSCode] = useMutation<{ sendSMSCode: SMSCodeResponse }>(SEND_SMS_CODE)
|
||||
|
||||
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let value = e.target.value
|
||||
|
||||
// Убираем все кроме цифр
|
||||
let digitsOnly = value.replace(/\D/g, '')
|
||||
|
||||
// Если начинается с 7, убираем её
|
||||
if (digitsOnly.startsWith('7')) {
|
||||
digitsOnly = digitsOnly.substring(1)
|
||||
}
|
||||
|
||||
// Ограничиваем до 10 цифр
|
||||
if (digitsOnly.length <= 10) {
|
||||
// Форматируем номер
|
||||
let formatted = digitsOnly
|
||||
if (digitsOnly.length >= 1) {
|
||||
formatted = digitsOnly.replace(/(\d{1,3})(\d{0,3})(\d{0,2})(\d{0,2})/, (match, p1, p2, p3, p4) => {
|
||||
let result = `(${p1}`
|
||||
if (p2) result += `) ${p2}`
|
||||
if (p3) result += `-${p3}`
|
||||
if (p4) result += `-${p4}`
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
setPhone(formatted)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const cleanPhone = '+7' + phone.replace(/\D/g, '')
|
||||
|
||||
if (phone.replace(/\D/g, '').length !== 10) {
|
||||
onError('Введите корректный номер телефона')
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
// Сначала проверяем существует ли клиент
|
||||
const { data: clientData } = await checkClient({
|
||||
variables: { phone: cleanPhone }
|
||||
})
|
||||
|
||||
if (clientData?.checkClientByPhone) {
|
||||
// Затем отправляем SMS код
|
||||
const { data: smsData } = await sendSMSCode({
|
||||
variables: {
|
||||
phone: cleanPhone,
|
||||
sessionId: clientData.checkClientByPhone.sessionId
|
||||
}
|
||||
})
|
||||
|
||||
if (smsData?.sendSMSCode?.success) {
|
||||
console.log('SMS код отправлен! Код:', smsData.sendSMSCode.code)
|
||||
onSuccess(clientData.checkClientByPhone, cleanPhone)
|
||||
} else {
|
||||
onError('Не удалось отправить SMS код')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка проверки телефона:', error)
|
||||
onError('Произошла ошибка при проверке номера')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5 w-full">
|
||||
<label className="text-2xl leading-8 text-gray-950 mb-2 font-normal font-[Onest,sans-serif] "
|
||||
style={{
|
||||
fontSize: '22px',
|
||||
lineHeight: '1.4',
|
||||
fontWeight: 400,
|
||||
fontFamily: 'Onest, sans-serif',
|
||||
color: '#000814'
|
||||
}}>Введите номер телефона</label>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-5 items-center w-full max-md:flex-col max-md:gap-4 max-sm:gap-3">
|
||||
<input
|
||||
type="tel"
|
||||
value={`+7 ${phone}`}
|
||||
onChange={handlePhoneChange}
|
||||
placeholder="+7 (999) 999-99-99"
|
||||
className="max-w-[360px] w-full h-[70px] px-[30px] py-[20px] text-[20px] leading-[1.4] font-[Onest,sans-serif] text-neutral-500 bg-white border border-stone-300 rounded focus:outline-none min-w-0 max-md:w-[300px] max-sm:w-full"
|
||||
disabled={isLoading}
|
||||
required
|
||||
aria-label="Введите номер телефона"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || phone.replace(/\D/g, '').length !== 10}
|
||||
className="flex items-center justify-center flex-shrink-0 bg-red-600 rounded-xl px-8 py-5 text-lg font-medium leading-5 text-white disabled:opacity-50 disabled:cursor-not-allowed h-[70px] max-sm:px-6 max-sm:py-4"
|
||||
style={{ color: 'white' }}
|
||||
aria-label="Получить код"
|
||||
tabIndex={0}
|
||||
>
|
||||
{isLoading ? 'Проверяем...' : 'Получить код'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/* <button
|
||||
type="button"
|
||||
onClick={onRegister}
|
||||
className="flex gap-5 justify-center items-center px-7 py-5 w-80 rounded-xl border border-red-700 border-solid cursor-pointer max-md:self-center max-md:px-6 max-md:py-5 max-md:w-[280px] max-sm:px-5 max-sm:py-4 max-sm:w-full"
|
||||
aria-label="Зарегистрироваться"
|
||||
tabIndex={0}
|
||||
>
|
||||
<span className="text-xl font-medium leading-7 text-center text-gray-950 max-md:text-lg max-sm:text-base">Зарегистрироваться</span>
|
||||
<span aria-hidden="true">
|
||||
<svg width="31" height="16" viewBox="0 0 31 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="arrow-icon" style={{width:'30px',height:'16px',flexShrink:0}}>
|
||||
<path d="M30.7071 8.70711C31.0976 8.31659 31.0976 7.68342 30.7071 7.2929L24.3431 0.928936C23.9526 0.538412 23.3195 0.538412 22.9289 0.928936C22.5384 1.31946 22.5384 1.95263 22.9289 2.34315L28.5858 8L22.9289 13.6569C22.5384 14.0474 22.5384 14.6805 22.9289 15.0711C23.3195 15.4616 23.9526 15.4616 24.3431 15.0711L30.7071 8.70711ZM0 8L-1.74846e-07 9L30 9.00001L30 8.00001L30 7.00001L1.74846e-07 7L0 8Z" fill="#000814"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PhoneInput
|
105
src/components/auth/UserRegistration.tsx
Normal file
105
src/components/auth/UserRegistration.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { REGISTER_NEW_CLIENT } from '@/lib/graphql'
|
||||
import type { VerificationResponse } from '@/types/auth'
|
||||
|
||||
interface UserRegistrationProps {
|
||||
phone: string
|
||||
sessionId: string
|
||||
onSuccess: (data: VerificationResponse) => void
|
||||
onError: (error: string) => void
|
||||
}
|
||||
|
||||
const UserRegistration: React.FC<UserRegistrationProps> = ({
|
||||
phone,
|
||||
sessionId,
|
||||
onSuccess,
|
||||
onError
|
||||
}) => {
|
||||
const [firstName, setFirstName] = useState('')
|
||||
const [lastName, setLastName] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const [registerClient] = useMutation<{ registerNewClient: VerificationResponse }>(REGISTER_NEW_CLIENT)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!firstName.trim()) {
|
||||
onError('Введите имя')
|
||||
return
|
||||
}
|
||||
if (!lastName.trim()) {
|
||||
onError('Введите фамилию')
|
||||
return
|
||||
}
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const fullName = `${firstName.trim()} ${lastName.trim()}`
|
||||
const { data } = await registerClient({
|
||||
variables: {
|
||||
phone,
|
||||
name: fullName,
|
||||
sessionId
|
||||
}
|
||||
})
|
||||
if (data?.registerNewClient) {
|
||||
onSuccess(data.registerNewClient)
|
||||
}
|
||||
} catch (error) {
|
||||
onError('Не удалось зарегистрировать пользователя')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5 w-full">
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-5 items-end w-full max-md:flex-col max-md:gap-4 max-sm:gap-3">
|
||||
{/* Имя */}
|
||||
<div className="flex flex-col gap-3 max-w-[360px] w-full">
|
||||
<label className="text-2xl leading-8 text-gray-950 mb-2 font-normal font-[Onest,sans-serif]">Введите имя</label>
|
||||
<input
|
||||
type="text"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
placeholder="Иван"
|
||||
className="max-w-[360px] w-full h-[62px] px-6 py-4 text-[18px] leading-[1.4] font-normal font-[Onest,sans-serif] text-neutral-500 bg-white border border-stone-300 rounded focus:outline-none"
|
||||
disabled={isLoading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/* Фамилия */}
|
||||
<div className="flex flex-col gap-3 max-w-[360px] w-full">
|
||||
<label className="text-2xl leading-8 text-gray-950 mb-2 font-normal font-[Onest,sans-serif]">Фамилию</label>
|
||||
<input
|
||||
type="text"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
placeholder="Иванов"
|
||||
className="max-w-[360px] w-full h-[62px] px-6 py-4 text-[18px] leading-[1.4] font-normal font-[Onest,sans-serif] text-neutral-500 bg-white border border-stone-300 rounded focus:outline-none"
|
||||
disabled={isLoading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/* Кнопка */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || !firstName.trim() || !lastName.trim()}
|
||||
className="flex items-center justify-center flex-shrink-0 bg-red-600 rounded-xl px-8 py-5 text-lg font-medium leading-5 text-white disabled:opacity-50 disabled:cursor-not-allowed h-[70px] max-sm:px-6 max-sm:py-4"
|
||||
style={{
|
||||
color: 'white'
|
||||
}}
|
||||
aria-label="Сохранить"
|
||||
tabIndex={0}
|
||||
>
|
||||
{isLoading ? 'Сохраняем...' : 'Сохранить'}
|
||||
{/* <img src="/images/Arrow_right.svg" alt="" className="ml-2 w-6 h-6" /> */}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserRegistration
|
Reference in New Issue
Block a user