139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
'use client'
|
||
|
||
import React, { useState } from 'react'
|
||
import TextInput from './ui/TextInput'
|
||
import axios from 'axios'
|
||
import { useRouter } from 'next/navigation'
|
||
|
||
interface AddressSelectorProps {
|
||
is_search?: boolean
|
||
}
|
||
|
||
export default function AddressSelector({ is_search }: AddressSelectorProps) {
|
||
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(/\s+/g, '-')
|
||
}
|
||
|
||
const getSearchUrl = async (category: 'mover' | 'customer') => {
|
||
if (!fromAddress.trim() || !toAddress.trim()) {
|
||
return `/search/${category}`
|
||
}
|
||
|
||
const [fromCity, toCity] = await Promise.all([getCityName(fromAddress), getCityName(toAddress)])
|
||
|
||
const from = formatAddress(fromCity)
|
||
const to = formatAddress(toCity)
|
||
|
||
return `/search/${category}/${from}-${to}`
|
||
}
|
||
|
||
const handleSearch = async (category: 'mover' | 'customer') => {
|
||
setError(null)
|
||
|
||
if (!fromAddress.trim() || !toAddress.trim()) {
|
||
router.push(`/search/${category}`)
|
||
return
|
||
}
|
||
|
||
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">
|
||
{is_search && <h2 className="mb-4 text-lg font-bold">Может поищем по городу?</h2>}
|
||
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:gap-3">
|
||
<div className="w-full min-w-0 sm:flex-[3] sm:px-1">
|
||
<TextInput
|
||
placeholder="Минск"
|
||
tooltip="Укажите пункт (Город), откуда необходимо забрать посылку."
|
||
label="Забрать посылку из"
|
||
value={fromAddress}
|
||
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">
|
||
<TextInput
|
||
placeholder="Москва"
|
||
label="Доставить посылку в"
|
||
tooltip="Укажите пункт (Город), куда необходимо доставить посылку."
|
||
value={toAddress}
|
||
handleChange={e => {
|
||
setError(null)
|
||
setToAddress(e.target.value)
|
||
}}
|
||
name="toAddress"
|
||
style="main"
|
||
error={error && !toAddress.trim() ? 'Укажите город назначения' : undefined}
|
||
/>
|
||
</div>
|
||
<button
|
||
onClick={() => handleSearch('mover')}
|
||
disabled={isLoading}
|
||
className={`w-full cursor-pointer 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'
|
||
}`}
|
||
>
|
||
{isLoading ? 'Поиск...' : 'Найти перевозчика'}
|
||
</button>
|
||
<button
|
||
onClick={() => handleSearch('customer')}
|
||
disabled={isLoading}
|
||
className={`w-full cursor-pointer 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'
|
||
}`}
|
||
>
|
||
{isLoading ? 'Поиск...' : 'Найти посылку'}
|
||
</button>
|
||
</div>
|
||
{error && <div className="mt-4 text-center text-sm text-red-500">{error}</div>}
|
||
</div>
|
||
)
|
||
}
|