import React, { useCallback, useState } from 'react'; import { AlertCircle, LocateFixed, Loader2, MapPin, Play } from 'lucide-react'; import { runSearch } from '../lib/database'; import type { AppUser } from '../../shared/types'; import { BasicResearchMap } from './BasicResearchMap'; interface SearchSetupProps { user: AppUser; onSelectCreatedJob: (jobId: string) => void; topContent?: React.ReactNode; } export function SearchSetup({ user: _user, onSelectCreatedJob, topContent, }: SearchSetupProps) { const [name, setName] = useState(''); const [radius, setRadius] = useState(5); const [businessType, setBusinessType] = useState(''); const [keywords, setKeywords] = useState(''); const [pin, setPin] = useState(null); const [locationError, setLocationError] = useState(null); const [locationAction, setLocationAction] = useState<'geolocate' | null>(null); const [isSearching, setIsSearching] = useState(false); const [error, setError] = useState(null); const applyPin = useCallback(async (nextPin: google.maps.LatLngLiteral) => { setPin(nextPin); setLocationError(null); }, []); const handleRunSearch = async (e: React.FormEvent) => { e.preventDefault(); setIsSearching(true); setError(null); setLocationError(null); if (!pin) { setLocationError('Drop a pin on the map or use your current location before running research.'); setIsSearching(false); return; } try { const response = await runSearch({ name: name.trim() || undefined, location: `${pin.lat.toFixed(5)}, ${pin.lng.toFixed(5)}`, radiusKm: radius, businessType: businessType.trim(), keywords: keywords.trim() || undefined, lat: pin.lat, lng: pin.lng, }); onSelectCreatedJob(response.job.id); } catch (err) { setError(err instanceof Error ? err.message : 'Research failed.'); } finally { setIsSearching(false); } }; const handleUseMyLocation = () => { setLocationAction('geolocate'); setLocationError(null); navigator.geolocation.getCurrentPosition( async (position) => { try { await applyPin({ lat: position.coords.latitude, lng: position.coords.longitude, }); } catch (err) { setLocationError(err instanceof Error ? err.message : 'Failed to use your current location.'); } finally { setLocationAction(null); } }, (geoError) => { setLocationError(geoError.message || 'Location access was denied.'); setLocationAction(null); }, { enableHighAccuracy: true, timeout: 10000 }, ); }; const hasLocationPin = Boolean(pin); return (
{topContent}

Basic Research

Drop a pin on the map, define the search area, and run standard research before reviewing saved jobs below.

setName(e.target.value)} className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
setBusinessType(e.target.value)} className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
setKeywords(e.target.value)} className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
{locationError && (
{locationError}
)}
setRadius(Number.parseInt(e.target.value, 10) || 1)} className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
Drop a pin directly on the map or use your current location. The map circle always reflects the current area value.
{hasLocationPin && (

Active search center

{pin!.lat.toFixed(5)}, {pin!.lng.toFixed(5)}

)} {error && (
{error}
)}
void applyPin(nextPin)} />

Map controls

Drop a pin directly on the map or use your current location. The circle updates from the Area field and the search runs from the active pin.

); }