feat: add location autocomplete functionality to HeroSection search input

This commit is contained in:
pradeepkumar
2026-04-20 13:36:59 +05:30
parent 07b19e3a7c
commit a292bdc694

View File

@@ -20,6 +20,7 @@ export function HeroSection({ content }: { content?: HeroContent }) {
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
const [specializationOptions, setSpecializationOptions] = useState<{ id: string; name: string }[]>([]);
const [specializationSlug, setSpecializationSlug] = useState<string>('');
const [locationOptions, setLocationOptions] = useState<{ label: string; value: string }[]>([]);
const [searchParams, setSearchParams] = useState({
specialization: '',
type: '',
@@ -47,6 +48,30 @@ export function HeroSection({ content }: { content?: HeroContent }) {
setSpecializationSlug(specField.slug);
setSpecializationOptions(specField.options.map(o => ({ id: o.value, name: o.label })));
}
// Collect city + state options for location autocomplete.
// Cities in the system may carry a state prefix (e.g. "ne_adams") — strip it for display.
const formatCity = (label: string) => label.replace(/^[A-Z]{2}\s*[-–—]\s*/, '').trim();
const locs: { label: string; value: string }[] = [];
const seen = new Set<string>();
for (const f of fields) {
if (f.slug === 'state') {
for (const opt of f.options) {
const key = `state:${opt.label.toLowerCase()}`;
if (seen.has(key)) continue;
seen.add(key);
locs.push({ label: opt.label, value: opt.label });
}
} else if (f.slug === 'city') {
for (const opt of f.options) {
const clean = formatCity(opt.label);
const key = `city:${clean.toLowerCase()}`;
if (seen.has(key)) continue;
seen.add(key);
locs.push({ label: clean, value: clean });
}
}
}
setLocationOptions(locs);
} catch (error) {
console.error('Failed to fetch specializations:', error);
}
@@ -55,6 +80,10 @@ export function HeroSection({ content }: { content?: HeroContent }) {
fetchSpecializations();
}, []);
const filteredLocationOptions = searchParams.location.trim()
? locationOptions.filter(o => o.label.toLowerCase().includes(searchParams.location.trim().toLowerCase())).slice(0, 8)
: [];
const handleSearch = () => {
// Validate that at least one field has a value
const hasType = searchParams.type.trim() !== '';
@@ -182,15 +211,39 @@ export function HeroSection({ content }: { content?: HeroContent }) {
/>
</div>
{/* Location Input */}
<div className="flex-1 md:min-w-[140px]">
{/* Location Input with autocomplete */}
<div className="relative flex-1 md:min-w-[140px]">
<input
type="text"
placeholder="Location"
value={searchParams.location}
onChange={(e) => handleInputChange('location', e.target.value)}
onChange={(e) => {
handleInputChange('location', e.target.value);
setOpenDropdown('location');
}}
onFocus={() => setOpenDropdown('location')}
onBlur={() => setTimeout(() => setOpenDropdown((cur) => (cur === 'location' ? null : cur)), 150)}
autoComplete="off"
className="w-full h-[42px] px-4 rounded-[7px] border border-[#00293d]/10 bg-white font-serif text-sm text-[#00293d] placeholder:text-[#00293d] focus:outline-none"
/>
{openDropdown === 'location' && filteredLocationOptions.length > 0 && (
<div className="absolute top-full left-0 right-0 mt-0 bg-white rounded-[7px] border border-[#00293d]/10 shadow-lg z-20 overflow-hidden max-h-[240px] overflow-y-auto">
{filteredLocationOptions.map((option, index, arr) => (
<button
key={option.value}
onMouseDown={(e) => {
e.preventDefault();
setSearchParams((prev) => ({ ...prev, location: option.value }));
setOpenDropdown(null);
setValidationError(null);
}}
className={`w-full px-4 py-2.5 text-left font-serif text-sm text-[#00293d] hover:bg-[#00293d]/5 ${index < arr.length - 1 ? 'border-b border-[#00293d]/10' : ''}`}
>
{option.label}
</button>
))}
</div>
)}
</div>
{/* Specialization Select */}