use russian cities names for search params

This commit is contained in:
2025-05-27 11:51:51 +03:00
parent 46cd984395
commit bc3ef3fb57
11 changed files with 241 additions and 37 deletions

View File

@@ -2,28 +2,81 @@
import React, { useState } from 'react'
import TextInput from './ui/TextInput'
import Link from 'next/link'
import axios from 'axios'
import { useRouter } from 'next/navigation'
export default function AddressSelector() {
const router = useRouter()
const [fromAddress, setFromAddress] = useState('')
const [toAddress, setToAddress] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const API_URL = process.env.NEXT_PUBLIC_API_URL
const getCityName = async (searchText: string): Promise<string> => {
try {
const encodedSearch = encodeURIComponent(searchText)
const url = `${API_URL}/cities/?search=${encodedSearch}&russian_name=${encodedSearch}`
const response = await axios.get(url)
if (response.data && response.data.length > 0) {
return response.data[0].value
}
throw new Error(`Город "${searchText}" не найден`)
} catch (error) {
if (error instanceof Error) {
throw new Error(`Ошибка при поиске города "${searchText}": ${error.message}`)
}
throw error
}
}
const formatAddress = (address: string) => {
return address
.toLowerCase()
.trim()
.replace(/[^a-zа-я0-9\s-]/gi, '')
.replace(/[^a-zаё0-9\s-]/gi, '')
.replace(/\s+/g, '-')
}
const getSearchUrl = (category: 'mover' | 'customer') => {
if (!fromAddress || !toAddress) return `/search/${category}`
const validateInputs = () => {
if (!fromAddress.trim()) {
throw new Error('Укажите город отправления')
}
if (!toAddress.trim()) {
throw new Error('Укажите город назначения')
}
if (fromAddress.trim() === toAddress.trim()) {
throw new Error('Города отправления и назначения должны различаться')
}
}
const getSearchUrl = async (category: 'mover' | 'customer') => {
validateInputs()
const [fromCity, toCity] = await Promise.all([getCityName(fromAddress), getCityName(toAddress)])
const from = formatAddress(fromCity)
const to = formatAddress(toCity)
const from = formatAddress(fromAddress)
const to = formatAddress(toAddress)
return `/search/${category}/${from}-${to}`
}
const handleSearch = async (category: 'mover' | 'customer') => {
setError(null)
setIsLoading(true)
try {
const url = await getSearchUrl(category)
router.push(url)
} catch (err) {
setError(err instanceof Error ? err.message : 'Произошла ошибка при поиске')
} finally {
setIsLoading(false)
}
}
return (
<div className="my-2 w-full rounded-xl bg-white p-4 shadow-lg sm:my-4 sm:p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:gap-3">
@@ -33,9 +86,13 @@ export default function AddressSelector() {
tooltip="Укажите пункт (Город/Страна), откуда необходимо забрать посылку."
label="Забрать посылку из"
value={fromAddress}
handleChange={e => setFromAddress(e.target.value)}
handleChange={e => {
setError(null)
setFromAddress(e.target.value)
}}
name="fromAddress"
style="main"
error={error && !fromAddress.trim() ? 'Укажите город отправления' : undefined}
/>
</div>
<div className="w-full min-w-0 sm:flex-[3] sm:px-1">
@@ -44,24 +101,37 @@ export default function AddressSelector() {
label="Доставить посылку в"
tooltip="Укажите пункт (Город/Страна), куда необходимо доставить посылку."
value={toAddress}
handleChange={e => setToAddress(e.target.value)}
handleChange={e => {
setError(null)
setToAddress(e.target.value)
}}
name="toAddress"
style="main"
error={error && !toAddress.trim() ? 'Укажите город назначения' : undefined}
/>
</div>
<Link
href={getSearchUrl('mover')}
className="bg-orange hover:bg-orange/80 w-full rounded-2xl p-4 text-center whitespace-nowrap text-white sm:w-auto sm:flex-1"
<button
onClick={() => handleSearch('mover')}
disabled={isLoading}
className={`w-full rounded-2xl p-4 text-center whitespace-nowrap text-white sm:w-auto sm:flex-1 ${
isLoading ? 'bg-orange/50 cursor-not-allowed' : 'bg-orange hover:bg-orange/80'
}`}
>
Найти перевозчика
</Link>
<Link
href={getSearchUrl('customer')}
className="w-full rounded-2xl bg-gray-100 p-4 text-center whitespace-nowrap text-gray-800 hover:bg-gray-200 sm:w-auto sm:flex-1"
{isLoading ? 'Поиск...' : 'Найти перевозчика'}
</button>
<button
onClick={() => handleSearch('customer')}
disabled={isLoading}
className={`w-full rounded-2xl p-4 text-center whitespace-nowrap sm:w-auto sm:flex-1 ${
isLoading
? 'cursor-not-allowed bg-gray-100 text-gray-400'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
}`}
>
Найти посылку
</Link>
{isLoading ? 'Поиск...' : 'Найти посылку'}
</button>
</div>
{error && <div className="mt-4 text-center text-sm text-red-500">{error}</div>}
</div>
)
}