sozsoft-platform/ui/src/components/orders/TenantForm.tsx
2026-07-07 00:53:45 +03:00

875 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { getTenantByNameDetail } from '@/services/tenant.service'
import { CustomTenantDto } from '@/proxy/config/models'
import {
FaArrowLeft,
FaArrowRight,
FaDollarSign,
FaMoneyBillWave,
FaBuilding,
FaGlobe,
FaEnvelope,
FaMap,
FaMapPin,
FaPhone,
FaSearch,
FaUser,
FaUserPlus,
} from 'react-icons/fa'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { Button, Select } from '@/components/ui'
import {
CityDto,
CountryDto,
DistrictDto,
getCities,
getCountries,
getDistricts,
} from '@/services/location.service'
interface LocationOption<T> {
label: string
value: string
data: T
}
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
const DEFAULT_PHONE_FORMAT = '000 000 000 000 000'
const TURKEY_PHONE_FORMAT = '(000) 000-0000'
const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
'organizationName',
'founder',
'country',
'city',
'district',
'postalCode',
'address1',
'phoneNumber',
'email',
'taxOffice',
'vknTckn',
]
const REQUIRED_EXISTING_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
'id',
'name',
'organizationName',
'founder',
'country',
'city',
'district',
'address1',
'email',
]
const getPhoneFormat = (country?: CountryDto) =>
country?.phoneNumberFormat ||
(country?.phoneCode === 90 ? TURKEY_PHONE_FORMAT : DEFAULT_PHONE_FORMAT)
const getPhoneMaxLength = (country?: CountryDto) =>
country?.phoneNumberMaxLength ?? getPhoneFormat(country).replace(/[^0]/g, '').length
const getPhoneMinLength = (country?: CountryDto) =>
country?.phoneNumberMinLength ?? getPhoneMaxLength(country)
const getNationalPhoneDigits = (value: string, country?: CountryDto) => {
let digits = value.replace(/\D/g, '')
if (!country) return digits
const code = String(country.phoneCode)
if (value.trimStart().startsWith('+') && digits.startsWith(code)) {
digits = digits.slice(code.length)
}
return digits
}
const getPhoneValidationError = (value: string, country?: CountryDto) => {
if (!country) return 'Lütfen önce ülke seçin.'
const length = getNationalPhoneDigits(value, country).length
const minLength = getPhoneMinLength(country)
const maxLength = getPhoneMaxLength(country)
if (length >= minLength && length <= maxLength) return ''
return minLength === maxLength
? `Telefon numarası ${minLength} haneli olmalıdır.`
: `Telefon numarası ${minLength}-${maxLength} hane arasında olmalıdır.`
}
const formatPhoneNumber = (value: string, country?: CountryDto) => {
if (!country) return value.replace(/\D/g, '').slice(0, 15)
const maxLength = getPhoneMaxLength(country)
const digits = getNationalPhoneDigits(value, country).slice(0, maxLength)
const format = getPhoneFormat(country)
let digitIndex = 0
let nationalNumber = ''
for (const character of format) {
if (character === '0') {
if (digitIndex >= digits.length) break
nationalNumber += digits[digitIndex]
digitIndex += 1
} else if (digitIndex === 0) {
if (digits.length > 0) nationalNumber += character
} else if (digitIndex < digits.length || character === ')') {
nationalNumber += character
} else {
break
}
}
return `+${country.phoneCode}${nationalNumber ? ` ${nationalNumber}` : ''}`
}
const isBlankValue = (value: unknown) =>
value === undefined || value === null || String(value).trim() === ''
const hasRequiredFields = (
tenant: Partial<CustomTenantDto>,
fields: Array<keyof CustomTenantDto>,
) => fields.every((field) => !isBlankValue(tenant[field]))
const normalizeCode = (value?: string) => value?.trim().toLowerCase() || ''
interface TenantFormProps {
onSubmit: (tenant: CustomTenantDto) => void
onBack: () => void
}
export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
const [isExisting, setIsExisting] = useState<boolean>(true)
const [formData, setFormData] = useState<Partial<CustomTenantDto>>({})
const [countries, setCountries] = useState<CountryDto[]>([])
const [cities, setCities] = useState<CityDto[]>([])
const [districts, setDistricts] = useState<DistrictDto[]>([])
const [isLoadingCountries, setIsLoadingCountries] = useState(false)
const [isLoadingCities, setIsLoadingCities] = useState(false)
const [isLoadingDistricts, setIsLoadingDistricts] = useState(false)
const [isLoadingTenant, setIsLoadingTenant] = useState(false)
const [emailError, setEmailError] = useState('')
const [phoneError, setPhoneError] = useState('')
const [existingTenantError, setExistingTenantError] = useState('')
const [formError, setFormError] = useState('')
const [foundTenantName, setFoundTenantName] = useState('')
const navigate = useNavigate()
const { translate } = useLocalization()
useEffect(() => {
const loadCountries = async () => {
setIsLoadingCountries(true)
try {
const response = await getCountries()
setCountries(response.data)
const defaultCountry = response.data.find((country) => country.name === 'Türkiye')
if (defaultCountry) {
setFormData((prev) =>
prev.country
? prev
: {
...prev,
country: defaultCountry.name,
city: '',
district: '',
phoneNumber: `+${defaultCountry.phoneCode}`,
},
)
setIsLoadingCities(true)
try {
const cityResponse = await getCities(defaultCountry.name)
setCities(cityResponse.data)
} catch (error) {
console.error('Şehirler alınırken hata:', error)
} finally {
setIsLoadingCities(false)
}
}
} catch (error) {
console.error('Ülkeler alınırken hata:', error)
} finally {
setIsLoadingCountries(false)
}
}
void loadCountries()
}, [])
const selectedCountry = countries.find((country) => country.name === formData.country)
const phoneMask = selectedCountry
? `+${selectedCountry.phoneCode} ${getPhoneFormat(selectedCountry)}`
: DEFAULT_PHONE_FORMAT
const canSubmitExistingTenant =
Boolean(
formData.id &&
formData.organizationName &&
normalizeCode(foundTenantName) === normalizeCode(formData.name),
) && !isLoadingTenant
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
let hasValidationError = false
setFormError('')
if (isExisting) {
if (!formData.name?.trim()) {
setExistingTenantError('Lütfen önce organization code girin.')
return
}
if (
!formData.id ||
normalizeCode(foundTenantName) !== normalizeCode(formData.name) ||
!formData.organizationName
) {
setExistingTenantError('Devam etmek için önce organization code ile kurum bilgisini bulun.')
return
}
if (!hasRequiredFields(formData, REQUIRED_EXISTING_CUSTOMER_FIELDS)) {
setExistingTenantError('Kurum bilgileri eksik geldi. Lütfen kurum kodunu tekrar aratın.')
return
}
onSubmit({ ...formData, isExisting: true } as CustomTenantDto)
return
}
if (!hasRequiredFields(formData, REQUIRED_NEW_CUSTOMER_FIELDS)) {
setFormError('Lütfen zorunlu alanları eksiksiz doldurun.')
hasValidationError = true
}
const nextPhoneError = getPhoneValidationError(formData.phoneNumber || '', selectedCountry)
setPhoneError(nextPhoneError)
if (nextPhoneError) {
hasValidationError = true
}
if (!isExisting && !EMAIL_PATTERN.test(formData.email || '')) {
setEmailError('Lütfen geçerli bir e-posta adresi girin.')
hasValidationError = true
}
if (hasValidationError) return
onSubmit({ ...formData, isExisting: false } as CustomTenantDto)
}
const handleInputChange = (field: keyof CustomTenantDto, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}
const resetExistingTenantDetails = (name: string) => {
setFoundTenantName('')
setExistingTenantError('')
setFormError('')
setFormData({ name })
}
const handleCountryChange = async (option: LocationOption<CountryDto> | null) => {
const country = option?.data
setFormData((prev) => ({
...prev,
country: country?.name || '',
city: '',
district: '',
phoneNumber: country ? `+${country.phoneCode}` : '',
}))
setCities([])
setDistricts([])
setPhoneError('')
if (!country) return
setIsLoadingCities(true)
try {
const response = await getCities(country.name)
setCities(response.data)
} catch (error) {
console.error('Şehirler alınırken hata:', error)
} finally {
setIsLoadingCities(false)
}
}
const handleCityChange = async (option: LocationOption<CityDto> | null) => {
const city = option?.data
setFormData((prev) => ({ ...prev, city: city?.name || '', district: '' }))
setDistricts([])
if (!city || !formData.country) return
setIsLoadingDistricts(true)
try {
const response = await getDistricts(formData.country, city.name)
setDistricts(response.data)
} catch (error) {
console.error('İlçeler alınırken hata:', error)
} finally {
setIsLoadingDistricts(false)
}
}
const countryOptions: LocationOption<CountryDto>[] = countries.map((country) => ({
label: country.name,
value: country.name,
data: country,
}))
const cityOptions: LocationOption<CityDto>[] = cities.map((city) => ({
label: city.name,
value: city.name,
data: city,
}))
const districtOptions: LocationOption<DistrictDto>[] = districts.map((district) => ({
label: district.name,
value: district.name,
data: district,
}))
const getTenantInfo = async () => {
const tenantName = formData.name?.trim()
if (!tenantName) {
setExistingTenantError('Lütfen önce organization code girin.')
return
}
setIsLoadingTenant(true)
setExistingTenantError('')
setFormError('')
try {
const tenant = await getTenantByNameDetail(tenantName)
setFormData((prev) => ({
...prev,
id: tenant.data.id,
name: tenant.data.name,
isActive: tenant.data.isActive,
organizationName: tenant.data.organizationName,
founder: tenant.data.founder,
vknTckn: tenant.data.vknTckn,
taxOffice: tenant.data.taxOffice,
address1: tenant.data.address1,
address2: tenant.data.address2,
district: tenant.data.district,
country: tenant.data.country,
city: tenant.data.city,
postalCode: tenant.data.postalCode,
phoneNumber: tenant.data.phoneNumber,
mobileNumber: tenant.data.mobileNumber,
faxNumber: tenant.data.faxNumber,
email: tenant.data.email,
website: tenant.data.website,
menuGroup: tenant.data.menuGroup,
}))
setFoundTenantName(tenant.data.name)
} catch (error) {
setFoundTenantName('')
setExistingTenantError('Kurum bulunamadı. Lütfen organization code bilgisini kontrol edin.')
console.error('Kurum bilgisi alınırken hata:', error)
} finally {
setIsLoadingTenant(false)
}
}
return (
<div className="flex flex-col lg:flex-row gap-6 mb-6">
<div className="w-full lg:w-1/3 bg-white rounded-xl shadow-lg border border-gray-200 p-6 dark:border-gray-700 dark:bg-gray-900 dark:shadow-gray-950/40">
<div className="mb-6">
<h2 className="text-lg font-semibold mb-4 flex items-center">
<FaUser className="w-5 h-5 text-green-600 mr-2" />{' '}
{translate('::Public.payment.customerInfo')}
</h2>
</div>
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4">
<Button
onClick={() => setIsExisting(true)}
variant="plain"
className={`block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none ${
isExisting === true
? 'border-blue-500 bg-blue-50 shadow-md scale-[1.02] dark:bg-blue-950/30'
: 'border-gray-200 hover:border-blue-300 dark:border-gray-700 dark:hover:border-blue-500'
}`}
>
<div className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
{translate('::Public.products.tenantForm.existing.title')}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{translate('::Public.products.tenantForm.existing.desc')}
</div>
</Button>
<Button
onClick={() => setIsExisting(false)}
variant="plain"
className={`block h-auto w-full p-4 border-2 rounded-xl transition-all text-left select-none ${
isExisting === false
? 'border-blue-500 bg-blue-50 shadow-md scale-[1.02] dark:bg-blue-950/30'
: 'border-gray-200 hover:border-blue-300 dark:border-gray-700 dark:hover:border-blue-500'
}`}
>
<div className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
{translate('::Public.products.tenantForm.new.title')}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{translate('::Public.products.tenantForm.new.desc')}
</div>
</Button>
</div>
</div>
</div>
<div className="w-full lg:w-2/3 bg-white rounded-xl shadow-lg border border-gray-200 p-6 dark:border-gray-700 dark:bg-gray-900 dark:shadow-gray-950/40">
{isExisting !== null && (
<form onSubmit={handleSubmit} className="space-y-6">
{isExisting ? (
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.code')}
</label>
<div className="relative flex flex-col sm:flex-row sm:items-stretch gap-3">
<div className="relative w-full">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
autoFocus
placeholder="Enter your organization code"
value={formData.name || ''}
onChange={(e) => resetExistingTenantDetails(e.target.value)}
onKeyDown={async (e) => {
if (e.key === 'Enter') {
e.preventDefault()
await getTenantInfo()
}
}}
className="h-[40px] w-full pl-10 pr-4 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 sm:rounded-l-lg sm:rounded-r-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
/>
</div>
<Button
type="button"
disabled={isLoadingTenant || !formData.name?.trim()}
onClick={getTenantInfo}
icon={<FaSearch className="w-4 h-4" />}
variant="solid"
size="sm"
className="!h-[40px] min-w-[180px] sm:mr-2 sm:rounded-r-lg sm:rounded-l-none [&>span]:gap-2"
>
{isLoadingTenant
? 'Aranıyor...'
: translate('::Public.products.tenantForm.searchOrg')}
</Button>
</div>
{existingTenantError && (
<p className="mt-2 text-sm text-red-600">{existingTenantError}</p>
)}
{!existingTenantError &&
formData.name &&
!canSubmitExistingTenant &&
!isLoadingTenant && (
<p className="mt-2 text-sm text-amber-600">
Devam etmek için önce Find Organization ile kurum bilgilerini getirin.
</p>
)}
{formData.organizationName && (
<div className="grid grid-cols-1 gap-y-3 text-sm text-gray-700 dark:text-gray-300 p-3">
<div className="flex items-center gap-2">
<FaUser className="w-4 h-4 text-gray-500" />
<span className="font-medium">{translate('::LoginPanel.Profil')}</span>
<span>{formData.founder}</span>
</div>
<div className="flex items-center gap-2">
<FaBuilding className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.payment.customer.company')}
</span>
<span>{formData.organizationName}</span>
</div>
<div className="flex items-center gap-2">
<FaEnvelope className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Abp.Account.EmailAddress')}:
</span>
<span>{formData.email}</span>
</div>
<div className="flex items-center gap-2">
<FaPhone className="w-4 h-4 text-gray-500" />
<span className="font-medium">{translate('::blog.categories.mobile')}:</span>
<span>{formData.mobileNumber}</span>
</div>
<div className="flex items-start gap-2">
<FaMapPin className="w-4 h-4 text-gray-500 mt-0.5" />
<div>
<span className="font-medium">{translate('::App.Address')}:</span>
<div>{formData.address1}</div>
</div>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.payment.customer.country')}:
</span>
<span>{formData.country}</span>
</div>
<div className="flex items-center gap-2">
<FaGlobe className="w-4 h-4 text-gray-500" />
<span className="font-medium">{translate('::Public.common.city')}:</span>
<span>{formData.city}</span>
</div>
<div className="flex items-center gap-2">
<FaMap className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.payment.customer.district')}:
</span>
<span>{formData.district}</span>
</div>
<div className="flex items-center gap-2">
<FaMapPin className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.payment.customer.postalCode')}:
</span>
<span>{formData.postalCode}</span>
</div>
<div className="flex items-center gap-2">
<FaMoneyBillWave className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.contact.taxOffice')}:
</span>
<span>{formData.taxOffice}</span>
</div>
<div className="flex items-center gap-2">
<FaDollarSign className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.contact.taxNumber')}:
</span>
<span>{formData.vknTckn}</span>
</div>
{formData.reference && (
<div className="flex items-center gap-2">
<FaUserPlus className="w-4 h-4 text-gray-500" />
<span className="font-medium">
{translate('::Public.payment.customer.reference')}:
</span>
<span>{formData.reference}</span>
</div>
)}
</div>
)}
</div>
) : (
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.products.tenantForm.orgName')}
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
autoFocus
placeholder="Enter your organization name"
value={formData.organizationName || ''}
onChange={(e) => handleInputChange('organizationName', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::LoginPanel.Profil')}
</label>
<div className="relative">
<FaUser className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="Enter founder's name"
value={formData.founder || ''}
onChange={(e) => handleInputChange('founder', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.country')}
</label>
<div className="relative">
<Select<LocationOption<CountryDto>>
required
isClearable
isLoading={isLoadingCountries}
options={countryOptions}
value={
countryOptions.find((option) => option.value === formData.country) || null
}
onChange={handleCountryChange}
placeholder="Ülke seçin"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.common.city')}
</label>
<div className="relative">
<Select<LocationOption<CityDto>>
required
isClearable
isDisabled={!formData.country}
isLoading={isLoadingCities}
options={cityOptions}
value={cityOptions.find((option) => option.value === formData.city) || null}
onChange={handleCityChange}
placeholder={formData.country ? 'Şehir seçin' : 'Önce ülke seçin'}
/>
</div>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.district')}
</label>
<div className="relative">
<Select<LocationOption<DistrictDto>>
required
isClearable
isDisabled={!formData.city}
isLoading={isLoadingDistricts}
options={districtOptions}
value={
districtOptions.find((option) => option.value === formData.district) ||
null
}
onChange={(option) =>
handleInputChange('district', option?.data.name || '')
}
placeholder={formData.city ? 'İlçe seçin' : 'Önce şehir seçin'}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.postalCode')}
</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="34782"
value={formData.postalCode || ''}
onChange={(e) => handleInputChange('postalCode', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::App.Address')}
</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-3 text-gray-400 w-5 h-5" />
<textarea
required
placeholder="Enter your address"
value={formData.address1 || ''}
onChange={(e) => handleInputChange('address1', e.target.value)}
rows={3}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}
</label>
<div className="relative">
<FaPhone className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="tel"
required
inputMode="numeric"
placeholder={phoneMask.replace(/0/g, '_')}
aria-label={`Telefon numarası, format: ${phoneMask}`}
value={formData.phoneNumber || ''}
onChange={(e) => {
handleInputChange(
'phoneNumber',
formatPhoneNumber(e.target.value, selectedCountry),
)
if (phoneError) setPhoneError('')
}}
onBlur={() =>
setPhoneError(
getPhoneValidationError(formData.phoneNumber || '', selectedCountry),
)
}
aria-invalid={Boolean(phoneError)}
aria-describedby={phoneError ? 'tenant-phone-error' : undefined}
title={`Beklenen format: ${phoneMask}`}
className={`w-full pl-10 pr-4 py-2 border rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
phoneError
? 'border-red-500 dark:border-red-500'
: 'border-gray-300 dark:border-gray-700'
}`}
/>
</div>
{phoneError && (
<p id="tenant-phone-error" className="mt-1 text-sm text-red-600">
{phoneError}
</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Abp.Account.EmailAddress')}
</label>
<div className="relative">
<FaEnvelope className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="email"
required
placeholder="sample@email.com"
value={formData.email || ''}
onChange={(e) => {
handleInputChange('email', e.target.value)
if (emailError) {
setEmailError(
EMAIL_PATTERN.test(e.target.value)
? ''
: 'Lütfen geçerli bir e-posta adresi girin.',
)
}
}}
onBlur={() =>
setEmailError(
EMAIL_PATTERN.test(formData.email || '')
? ''
: 'Lütfen geçerli bir e-posta adresi girin.',
)
}
aria-invalid={Boolean(emailError)}
aria-describedby={emailError ? 'tenant-email-error' : undefined}
className={`w-full pl-10 pr-4 py-2 border rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
emailError
? 'border-red-500 dark:border-red-500'
: 'border-gray-300 dark:border-gray-700'
}`}
/>
</div>
{emailError && (
<p id="tenant-email-error" className="mt-1 text-sm text-red-600">
{emailError}
</p>
)}
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxOffice')}
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="Kozyatağı"
value={formData.taxOffice}
onChange={(e) => handleInputChange('taxOffice', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.contact.taxNumber')}
</label>
<div className="relative">
<FaDollarSign className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder="1234567890"
value={formData.vknTckn}
onChange={(e) => handleInputChange('vknTckn', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.reference')}
</label>
<div className="relative">
<FaUserPlus className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder={translate('::Public.payment.customer.reference')}
value={formData.reference || ''}
onChange={(e) => handleInputChange('reference', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
)}
{formError && <p className="mt-2 text-sm text-red-600">{formError}</p>}
<div className="flex justify-between items-center mt-6">
<Button
type="button"
onClick={() => navigate(ROUTES_ENUM.public.products)}
icon={<FaArrowLeft className="w-4 h-4" />}
variant="default"
size="sm"
>
{translate('::Back')}
</Button>
<Button
type="submit"
disabled={isExisting && !canSubmitExistingTenant}
icon={<FaArrowRight className="w-5 h-5" />}
variant="solid"
color="green-600"
size="sm"
>
{translate('::Abp.Account.ResetPassword.Continue')}
</Button>
</div>
</form>
)}
</div>
</div>
)
}