From 82b2c51e4e7fa143fcf9cb631c89fc9130c6a8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:35:15 +0000 Subject: [PATCH] feat: Split search into Business Name + Location fields - Split single search input into two fields: Business Name (required) and Location (auto-detected from IP geolocation) - Auto-fill location field with city/country from IP on page load - Add click overlay on map iframe to prevent interaction - Add warning modal when user clicks map, directing them to use search - Update test URLs to use split format - Make Validate button full-width for better UX Co-Authored-By: Claude Opus 4.5 --- web/components/ScraperTest.tsx | 212 +++++++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 50 deletions(-) diff --git a/web/components/ScraperTest.tsx b/web/components/ScraperTest.tsx index 0b40ed6..b969637 100644 --- a/web/components/ScraperTest.tsx +++ b/web/components/ScraperTest.tsx @@ -41,8 +41,12 @@ interface ScraperTestProps { } export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTestProps = {}) { - const [searchQuery, setSearchQuery] = useState(''); + // Split search fields + const [businessNameQuery, setBusinessNameQuery] = useState(''); + const [locationQuery, setLocationQuery] = useState(''); + const [detectedLocation, setDetectedLocation] = useState<{ city: string; country: string } | null>(null); const [searchedQuery, setSearchedQuery] = useState(''); + const [showMapClickModal, setShowMapClickModal] = useState(false); const [jobs, setJobs] = useState>(new Map()); const [activeJobId, setActiveJobId] = useState(null); const [reviews, setReviews] = useState([]); @@ -105,6 +109,15 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe lat: data.latitude, lng: data.longitude }; + // Store detected city and country for location field + if (data.city && data.country_name) { + setDetectedLocation({ + city: data.city, + country: data.country_name + }); + // Auto-fill location field + setLocationQuery(`${data.city}, ${data.country_name}`); + } console.log('IP location:', data.city, data.country_name); } } @@ -122,15 +135,28 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe const pollingIntervals = useRef>(new Map()); const abortControllerRef = useRef(null); + // Build full search query from business name + location + const buildSearchQuery = () => { + const name = businessNameQuery.trim(); + const location = locationQuery.trim(); + if (name && location) { + return `"${name}" ${location}`; + } else if (name) { + return name; + } + return ''; + }; + // Debounce: update map preview as user types (500ms after stopping) useEffect(() => { - if (searchQuery.trim().length >= 2) { + const query = buildSearchQuery(); + if (query.length >= 2) { if (debounceRef.current) { clearTimeout(debounceRef.current); } debounceRef.current = setTimeout(() => { - setSearchedQuery(searchQuery.trim()); + setSearchedQuery(query); }, 500); return () => { @@ -139,12 +165,13 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe } }; } - }, [searchQuery]); + }, [businessNameQuery, locationQuery]); // Clear validation results when user starts typing a new search useEffect(() => { - // If searchQuery is different from searchedQuery, clear results - if (searchQuery.trim() !== searchedQuery && searchedQuery) { + const currentQuery = buildSearchQuery(); + // If current query is different from searchedQuery, clear results + if (currentQuery !== searchedQuery && searchedQuery) { // Abort any pending validation request if (abortControllerRef.current) { abortControllerRef.current.abort(); @@ -157,7 +184,7 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe setBusinessImage(null); setBusinessCategory(null); } - }, [searchQuery, searchedQuery]); + }, [businessNameQuery, locationQuery, searchedQuery]); // Notify parent when jobs change useEffect(() => { @@ -296,9 +323,8 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe }, []); const handleSearch = () => { - if (searchQuery.trim().length < 2) return; - - const query = searchQuery.trim(); + const query = buildSearchQuery(); + if (query.length < 2 || !businessNameQuery.trim()) return; // Clear any pending debounce if (debounceRef.current) { @@ -435,12 +461,12 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe const searchInputRef = useRef(null); - // Test URLs at different scales + // Test URLs at different scales (split into business name + location) const testUrls = [ - { name: 'πŸͺ Small (~79)', query: 'R. Fleitas Peluqueros Gran Canaria' }, - { name: 'πŸš— Medium (~589)', query: 'ClickRent Gran Canaria' }, - { name: 'πŸ₯ Large (~2000+)', query: 'Hospital Universitario Doctor NegrΓ­n Las Palmas' }, - { name: 'πŸ›’ Alcampo', query: 'Alcampo Hipermarket Las Palmas' }, + { name: 'πŸͺ Small (~79)', businessName: 'R. Fleitas Peluqueros', location: 'Las Palmas, Spain' }, + { name: 'πŸš— Medium (~589)', businessName: 'ClickRent', location: 'Gran Canaria, Spain' }, + { name: 'πŸ₯ Large (~2000+)', businessName: 'Hospital Universitario Doctor NegrΓ­n', location: 'Las Palmas, Spain' }, + { name: 'πŸ›’ Alcampo', businessName: 'Alcampo Hipermarket', location: 'Las Palmas, Spain' }, ]; return ( @@ -455,9 +481,11 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe @@ -540,18 +609,24 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe src={embedUrl} width="100%" height="300" - style={{ border: 0 }} + style={{ border: 0, pointerEvents: 'none' }} allowFullScreen loading="lazy" referrerPolicy="no-referrer-when-downgrade" title="Google Maps Preview" /> + {/* Click overlay to prevent iframe interaction */} +
setShowMapClickModal(true)} + /> {/* Open in Google Maps overlay button */} e.stopPropagation()} + className="absolute bottom-3 right-3 inline-flex items-center gap-2 px-3 py-1.5 bg-white/90 backdrop-blur border border-gray-300 rounded-lg text-xs font-medium text-gray-700 hover:bg-white hover:border-blue-500 transition-all shadow-md z-10" > @@ -561,6 +636,43 @@ export default function ScraperTest({ onJobsChange, onSelectReviews }: ScraperTe + {/* Map Click Warning Modal */} + {showMapClickModal && ( +
setShowMapClickModal(false)} + > +
e.stopPropagation()} + > +
+
πŸ”
+

Use the Search Fields

+

+ Due to technical limitations, please use the Business Name and Location fields above to find your business. +

+
+
+

+ Tip: Be specific with the business name and include the city for accurate results. +

+
+ +
+
+ )} ) : (