Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| import { useState, useEffect, useRef } from 'react' | |
| import { MapPinIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline' | |
| import { stateNameToCode } from '../utils/stateMapping' | |
| import { useLocation as useLocationContext } from '../contexts/LocationContext' | |
| interface LocationData { | |
| address: string | |
| state: string | |
| county: string | |
| city: string | |
| latitude?: number | |
| longitude?: number | |
| } | |
| interface AddressLookupProps { | |
| onLocationFound: (location: LocationData) => void | |
| initialAddress?: string | |
| compact?: boolean | |
| } | |
| export default function AddressLookup({ onLocationFound, initialAddress = '', compact = false }: AddressLookupProps) { | |
| const { clearLocation } = useLocationContext() | |
| const [address, setAddress] = useState(initialAddress) | |
| const [isLoading, setIsLoading] = useState(false) | |
| const [error, setError] = useState<string | null>(null) | |
| const [suggestions, setSuggestions] = useState<any[]>([]) | |
| const [foundLocation, setFoundLocation] = useState<LocationData | null>(null) | |
| const [showSuggestions, setShowSuggestions] = useState(false) | |
| const [selectedIndex, setSelectedIndex] = useState(-1) | |
| const debounceTimer = useRef<number | null>(null) | |
| const inputRef = useRef<HTMLInputElement>(null) | |
| // Fetch suggestions as user types | |
| const fetchSuggestions = async (query: string) => { | |
| if (query.trim().length < 3) { | |
| setSuggestions([]) | |
| setShowSuggestions(false) | |
| return | |
| } | |
| try { | |
| const response = await fetch( | |
| `https://nominatim.openstreetmap.org/search?` + | |
| `q=${encodeURIComponent(query)}&` + | |
| `format=json&` + | |
| `addressdetails=1&` + | |
| `countrycodes=us&` + | |
| `limit=5`, | |
| { | |
| headers: { | |
| 'User-Agent': 'CommunityOne-Navigator/1.0' | |
| } | |
| } | |
| ) | |
| if (!response.ok) { | |
| return | |
| } | |
| const data = await response.json() | |
| // Deduplicate results using OSM unique IDs | |
| const uniqueResults = data.reduce((acc: any[], current: any) => { | |
| const osmKey = `${current.osm_type}_${current.osm_id}` | |
| const exists = acc.some((item) => { | |
| const itemKey = `${item.osm_type}_${item.osm_id}` | |
| return itemKey === osmKey | |
| }) | |
| if (!exists) { | |
| acc.push(current) | |
| } | |
| return acc | |
| }, []) | |
| setSuggestions(uniqueResults) | |
| setShowSuggestions(uniqueResults.length > 0) | |
| setSelectedIndex(-1) | |
| } catch (err) { | |
| console.error('Autocomplete error:', err) | |
| } | |
| } | |
| // Handle address input change with debouncing | |
| const handleAddressChange = (value: string) => { | |
| setAddress(value) | |
| setError(null) | |
| // Clear previous timer | |
| if (debounceTimer.current) { | |
| clearTimeout(debounceTimer.current) | |
| } | |
| // Set new timer | |
| debounceTimer.current = setTimeout(() => { | |
| fetchSuggestions(value) | |
| }, 300) | |
| } | |
| // Cleanup timer on unmount | |
| useEffect(() => { | |
| return () => { | |
| if (debounceTimer.current) { | |
| clearTimeout(debounceTimer.current) | |
| } | |
| } | |
| }, []) | |
| const lookupAddress = async (addressToLookup: string) => { | |
| if (!addressToLookup.trim()) { | |
| setError('Please enter an address') | |
| return | |
| } | |
| setIsLoading(true) | |
| setError(null) | |
| setSuggestions([]) | |
| setShowSuggestions(false) | |
| try { | |
| // Use Nominatim (OpenStreetMap) geocoding service | |
| const response = await fetch( | |
| `https://nominatim.openstreetmap.org/search?` + | |
| `q=${encodeURIComponent(addressToLookup)}&` + | |
| `format=json&` + | |
| `addressdetails=1&` + | |
| `countrycodes=us&` + | |
| `limit=5`, | |
| { | |
| headers: { | |
| 'User-Agent': 'CommunityOne-Navigator/1.0' | |
| } | |
| } | |
| ) | |
| if (!response.ok) { | |
| throw new Error('Failed to lookup address') | |
| } | |
| const data = await response.json() | |
| if (data.length === 0) { | |
| setError('Address not found. Please try a different address or be more specific.') | |
| return | |
| } | |
| // Deduplicate results using OSM unique IDs | |
| const uniqueResults = data.reduce((acc: any[], current: any) => { | |
| // Use OSM type + ID as unique key (most reliable) | |
| const osmKey = `${current.osm_type}_${current.osm_id}` | |
| const exists = acc.some((item) => { | |
| const itemKey = `${item.osm_type}_${item.osm_id}` | |
| return itemKey === osmKey | |
| }) | |
| if (!exists) { | |
| acc.push(current) | |
| } | |
| return acc | |
| }, []) | |
| // If we have multiple unique results, show suggestions | |
| if (uniqueResults.length > 1) { | |
| setSuggestions(uniqueResults) | |
| setShowSuggestions(true) | |
| return | |
| } | |
| // Single result - process it | |
| processResult(uniqueResults[0]) | |
| } catch (err) { | |
| console.error('Address lookup error:', err) | |
| setError('Failed to lookup address. Please try again.') | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| } | |
| const processResult = (result: any) => { | |
| const addr = result.address | |
| // Convert state name to 2-letter code | |
| const stateName = addr.state || '' | |
| const stateCode = stateNameToCode(stateName) | |
| console.log(`๐บ๏ธ [AddressLookup] State conversion: "${stateName}" โ "${stateCode}"`) | |
| const locationData: LocationData = { | |
| address: result.display_name, | |
| state: stateCode, | |
| county: addr.county || '', | |
| city: addr.city || addr.town || addr.village || addr.municipality || '', | |
| latitude: parseFloat(result.lat), | |
| longitude: parseFloat(result.lon), | |
| } | |
| // Validate we got the essential data | |
| if (!locationData.state || !locationData.city) { | |
| setError('Could not determine city and state from this address. Please be more specific.') | |
| setSuggestions([]) | |
| setShowSuggestions(false) | |
| return | |
| } | |
| console.log('๐ [AddressLookup] Location found:', locationData) | |
| setSuggestions([]) | |
| setShowSuggestions(false) | |
| setFoundLocation(locationData) | |
| onLocationFound(locationData) | |
| } | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault() | |
| // If a suggestion is selected, use that | |
| if (selectedIndex >= 0 && suggestions[selectedIndex]) { | |
| processResult(suggestions[selectedIndex]) | |
| } else { | |
| lookupAddress(address) | |
| } | |
| } | |
| const handleSuggestionClick = (suggestion: any) => { | |
| setAddress(suggestion.display_name) | |
| processResult(suggestion) | |
| } | |
| const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | |
| if (!showSuggestions || suggestions.length === 0) return | |
| switch (e.key) { | |
| case 'ArrowDown': | |
| e.preventDefault() | |
| setSelectedIndex(prev => | |
| prev < suggestions.length - 1 ? prev + 1 : prev | |
| ) | |
| break | |
| case 'ArrowUp': | |
| e.preventDefault() | |
| setSelectedIndex(prev => prev > 0 ? prev - 1 : -1) | |
| break | |
| case 'Enter': | |
| if (selectedIndex >= 0) { | |
| e.preventDefault() | |
| processResult(suggestions[selectedIndex]) | |
| } | |
| break | |
| case 'Escape': | |
| setShowSuggestions(false) | |
| setSelectedIndex(-1) | |
| break | |
| } | |
| } | |
| const useMyLocation = () => { | |
| if (!navigator.geolocation) { | |
| setError('Geolocation is not supported by your browser') | |
| return | |
| } | |
| setIsLoading(true) | |
| setError(null) | |
| setSuggestions([]) | |
| navigator.geolocation.getCurrentPosition( | |
| async (position) => { | |
| const { latitude, longitude } = position.coords | |
| try { | |
| // Reverse geocode using Nominatim | |
| const response = await fetch( | |
| `https://nominatim.openstreetmap.org/reverse?` + | |
| `lat=${latitude}&` + | |
| `lon=${longitude}&` + | |
| `format=json&` + | |
| `addressdetails=1`, | |
| { | |
| headers: { | |
| 'User-Agent': 'CommunityOne-Navigator/1.0' | |
| } | |
| } | |
| ) | |
| if (!response.ok) { | |
| throw new Error('Failed to reverse geocode location') | |
| } | |
| const data = await response.json() | |
| // Update the address input field | |
| setAddress(data.display_name) | |
| // Process the result | |
| processResult(data) | |
| } catch (err) { | |
| console.error('Reverse geocoding error:', err) | |
| setError('Failed to determine your location. Please enter your address manually.') | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| }, | |
| (error) => { | |
| console.error('Geolocation error:', error) | |
| setIsLoading(false) | |
| switch (error.code) { | |
| case error.PERMISSION_DENIED: | |
| setError('Location access denied. Please enter your address manually or enable location permissions.') | |
| break | |
| case error.POSITION_UNAVAILABLE: | |
| setError('Location information unavailable. Please enter your address manually.') | |
| break | |
| case error.TIMEOUT: | |
| setError('Location request timed out. Please try again or enter your address manually.') | |
| break | |
| default: | |
| setError('An error occurred while getting your location. Please enter your address manually.') | |
| } | |
| }, | |
| { | |
| enableHighAccuracy: false, // Use fast network-based location instead of GPS | |
| timeout: 5000, // Reduced timeout since network location is faster | |
| maximumAge: 30000 // Allow 30s cached location for faster response | |
| } | |
| ) | |
| } | |
| if (compact) { | |
| return ( | |
| <form onSubmit={handleSubmit} className="w-full"> | |
| <div className="relative"> | |
| <input | |
| key="address-input-compact" | |
| ref={inputRef} | |
| type="text" | |
| value={address} | |
| onChange={(e) => handleAddressChange(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder="Enter your address..." | |
| className="w-full px-4 py-2 pl-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-900" | |
| disabled={isLoading} | |
| autoComplete="off" | |
| /> | |
| <MapPinIcon className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" /> | |
| <button | |
| type="submit" | |
| disabled={isLoading} | |
| className="absolute right-2 top-1.5 px-3 py-1 text-white rounded-md transition-colors text-sm disabled:opacity-50" | |
| style={{ backgroundColor: '#354F52' }} | |
| onMouseEnter={(e) => !isLoading && (e.currentTarget.style.backgroundColor = '#2e4346')} | |
| onMouseLeave={(e) => !isLoading && (e.currentTarget.style.backgroundColor = '#354F52')} | |
| > | |
| {isLoading ? 'Finding...' : 'Find'} | |
| </button> | |
| {/* Autocomplete suggestions dropdown */} | |
| {showSuggestions && suggestions.length > 0 && ( | |
| <div className="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto"> | |
| {suggestions.map((suggestion, index) => { | |
| const addr = suggestion.address | |
| const locationName = addr.city || addr.town || addr.village || addr.county || 'Unknown' | |
| return ( | |
| <button | |
| key={`${suggestion.osm_type}_${suggestion.osm_id}`} | |
| type="button" | |
| onClick={() => handleSuggestionClick(suggestion)} | |
| className={`w-full px-4 py-2 text-left hover:bg-gray-100 transition-colors ${ | |
| index === selectedIndex ? 'bg-gray-100' : '' | |
| }`} | |
| > | |
| <p className="text-sm font-medium text-gray-900"> | |
| {suggestion.display_name} | |
| </p> | |
| <p className="text-xs text-gray-500"> | |
| {locationName}, {addr.state} | |
| </p> | |
| </button> | |
| ) | |
| })} | |
| </div> | |
| )} | |
| </div> | |
| {error && ( | |
| <p className="mt-2 text-sm text-red-600">{error}</p> | |
| )} | |
| </form> | |
| ) | |
| } | |
| return ( | |
| <div className="w-full"> | |
| <form onSubmit={handleSubmit} className="space-y-4"> | |
| <div> | |
| <label htmlFor="address" className="block text-sm font-medium text-gray-700 mb-2"> | |
| <span className="flex items-center gap-2"> | |
| <MapPinIcon className="h-5 w-5" /> | |
| Enter Your Address | |
| </span> | |
| </label> | |
| <div className="relative"> | |
| <input | |
| key="address-input" | |
| ref={inputRef} | |
| type="text" | |
| id="address" | |
| name="addresslookup" | |
| value={address} | |
| onChange={(e) => handleAddressChange(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder="123 Main St, Los Angeles, CA 90001" | |
| className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-base text-gray-900" | |
| disabled={isLoading} | |
| autoComplete="off" | |
| /> | |
| {/* Autocomplete suggestions dropdown */} | |
| {showSuggestions && suggestions.length > 0 && ( | |
| <div className="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto"> | |
| {suggestions.map((suggestion, index) => { | |
| const addr = suggestion.address | |
| const locationName = addr.city || addr.town || addr.village || addr.county || 'Unknown' | |
| return ( | |
| <button | |
| key={`${suggestion.osm_type}_${suggestion.osm_id}`} | |
| type="button" | |
| onClick={() => handleSuggestionClick(suggestion)} | |
| className={`w-full px-4 py-3 text-left hover:bg-gray-100 transition-colors border-b border-gray-100 last:border-b-0 ${ | |
| index === selectedIndex ? 'bg-gray-100' : '' | |
| }`} | |
| > | |
| <p className="text-sm font-medium text-gray-900"> | |
| {suggestion.display_name} | |
| </p> | |
| <p className="text-xs text-gray-500 mt-1"> | |
| {locationName}, {addr.state} | |
| </p> | |
| </button> | |
| ) | |
| })} | |
| </div> | |
| )} | |
| </div> | |
| <p className="mt-1 text-xs text-gray-500"> | |
| We'll find your local organizations based on your address | |
| </p> | |
| {/* Use My Location Button */} | |
| <div className="mt-3"> | |
| <button | |
| type="button" | |
| onClick={useMyLocation} | |
| disabled={isLoading} | |
| className="w-full px-4 py-2 bg-white border-2 border-primary-300 text-primary-700 rounded-lg hover:bg-primary-50 hover:border-primary-500 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" | |
| > | |
| <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /> | |
| </svg> | |
| <span>Use My Current Location</span> | |
| </button> | |
| </div> | |
| {/* Divider */} | |
| <div className="relative my-4"> | |
| <div className="absolute inset-0 flex items-center"> | |
| <div className="w-full border-t border-gray-300"></div> | |
| </div> | |
| <div className="relative flex justify-center text-xs"> | |
| <span className="px-2 bg-white text-gray-500">or enter manually</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button | |
| type="submit" | |
| disabled={isLoading} | |
| className="w-full px-6 py-3 text-white rounded-lg transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" | |
| style={{ backgroundColor: '#354F52' }} | |
| onMouseEnter={(e) => !isLoading && (e.currentTarget.style.backgroundColor = '#2e4346')} | |
| onMouseLeave={(e) => !isLoading && (e.currentTarget.style.backgroundColor = '#354F52')} | |
| > | |
| {isLoading ? ( | |
| <> | |
| <div className="animate-spin h-5 w-5 border-2 border-white border-t-transparent rounded-full"></div> | |
| <span>Looking up address...</span> | |
| </> | |
| ) : ( | |
| <> | |
| <MagnifyingGlassIcon className="h-5 w-5" /> | |
| <span>Find My Community</span> | |
| </> | |
| )} | |
| </button> | |
| </form> | |
| {/* Error Message */} | |
| {error && ( | |
| <div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg"> | |
| <p className="text-sm text-red-800">{error}</p> | |
| </div> | |
| )} | |
| {/* Note: Suggestions now appear as autocomplete dropdown above */} | |
| {/* Location Results */} | |
| {foundLocation && !compact && ( | |
| <div className="mt-6 border-2 border-primary-200 rounded-lg overflow-hidden bg-primary-50"> | |
| <div className="bg-primary-600 px-4 py-3 flex items-center justify-between"> | |
| <h3 className="text-lg font-semibold text-white flex items-center gap-2"> | |
| <MapPinIcon className="h-5 w-5" /> | |
| Your Local Community | |
| </h3> | |
| <button | |
| onClick={() => window.location.href = '/'} | |
| className="text-sm text-white hover:text-primary-100 underline font-medium" | |
| > | |
| โ Back to Home | |
| </button> | |
| </div> | |
| <div className="p-6 space-y-4"> | |
| <p className="text-sm text-gray-700 mb-4"> | |
| Select a jurisdiction level below to explore organizations, meeting minutes, and contacts: | |
| </p> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| {/* City */} | |
| {foundLocation.city && ( | |
| <button | |
| onClick={() => { | |
| window.location.href = `/?scope=city` | |
| }} | |
| className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-blue-500 transition-all text-left w-full group" | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="p-2 bg-blue-100 rounded-lg group-hover:bg-blue-200 transition-colors"> | |
| <svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" /> | |
| </svg> | |
| </div> | |
| <div className="flex-1"> | |
| <p className="text-xs font-medium text-gray-500 uppercase tracking-wider">City</p> | |
| <p className="text-lg font-semibold text-gray-900 mt-1 group-hover:text-blue-600">{foundLocation.city}</p> | |
| <p className="text-sm text-gray-600 mt-1">City Council</p> | |
| <p className="text-xs text-blue-600 mt-2 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Click to explore โ | |
| </p> | |
| </div> | |
| </div> | |
| </button> | |
| )} | |
| {/* County */} | |
| {foundLocation.county && ( | |
| <button | |
| onClick={() => { | |
| window.location.href = `/?scope=county` | |
| }} | |
| className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-green-500 transition-all text-left w-full group" | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="p-2 bg-green-100 rounded-lg group-hover:bg-green-200 transition-colors"> | |
| <svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" /> | |
| </svg> | |
| </div> | |
| <div className="flex-1"> | |
| <p className="text-xs font-medium text-gray-500 uppercase tracking-wider">County</p> | |
| <p className="text-lg font-semibold text-gray-900 mt-1 group-hover:text-green-600">{foundLocation.county}</p> | |
| <p className="text-sm text-gray-600 mt-1">County Board</p> | |
| <p className="text-xs text-green-600 mt-2 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Click to explore โ | |
| </p> | |
| </div> | |
| </div> | |
| </button> | |
| )} | |
| {/* State */} | |
| {foundLocation.state && ( | |
| <button | |
| onClick={() => { | |
| window.location.href = `/?scope=state` | |
| }} | |
| className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-purple-500 transition-all text-left w-full group" | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="p-2 bg-purple-100 rounded-lg group-hover:bg-purple-200 transition-colors"> | |
| <svg className="h-6 w-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 21v-4m0 0V5a2 2 0 012-2h6.5l1 1H21l-3 6 3 6h-8.5l-1-1H5a2 2 0 00-2 2zm9-13.5V9" /> | |
| </svg> | |
| </div> | |
| <div className="flex-1"> | |
| <p className="text-xs font-medium text-gray-500 uppercase tracking-wider">State</p> | |
| <p className="text-lg font-semibold text-gray-900 mt-1 group-hover:text-purple-600">{foundLocation.state}</p> | |
| <p className="text-sm text-gray-600 mt-1">State Legislature</p> | |
| <p className="text-xs text-purple-600 mt-2 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Click to explore โ | |
| </p> | |
| </div> | |
| </div> | |
| </button> | |
| )} | |
| {/* School District */} | |
| {foundLocation.city && ( | |
| <button | |
| onClick={() => { | |
| window.location.href = `/?scope=community` | |
| }} | |
| className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-amber-500 transition-all text-left w-full group" | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="p-2 bg-amber-100 rounded-lg group-hover:bg-amber-200 transition-colors"> | |
| <svg className="h-6 w-6 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> | |
| </svg> | |
| </div> | |
| <div className="flex-1"> | |
| <p className="text-xs font-medium text-gray-500 uppercase tracking-wider">School District</p> | |
| <p className="text-lg font-semibold text-gray-900 mt-1 group-hover:text-amber-600">{foundLocation.city} Unified</p> | |
| <p className="text-sm text-gray-600 mt-1">School Board</p> | |
| <p className="text-xs text-amber-600 mt-2 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Click to explore โ | |
| </p> | |
| </div> | |
| </div> | |
| </button> | |
| )} | |
| </div> | |
| {/* Action Buttons */} | |
| <div className="pt-4 border-t border-primary-200"> | |
| <p className="text-sm text-gray-600 mb-3">Quick access to all local resources:</p> | |
| <div className="flex flex-wrap gap-3"> | |
| <button | |
| onClick={() => { | |
| window.location.href = `/documents?state=${foundLocation.state}&city=${foundLocation.city}` | |
| }} | |
| className="flex-1 min-w-[200px] px-4 py-2 bg-white border-2 border-primary-600 text-primary-700 rounded-lg hover:bg-primary-50 transition-colors font-medium" | |
| > | |
| ๐ All Meeting Minutes | |
| </button> | |
| <button | |
| onClick={() => { | |
| window.location.href = `/nonprofits?state=${foundLocation.state}&city=${foundLocation.city}` | |
| }} | |
| className="flex-1 min-w-[200px] px-4 py-2 bg-white border-2 border-primary-600 text-primary-700 rounded-lg hover:bg-primary-50 transition-colors font-medium" | |
| > | |
| ๐ข All Local Organizations | |
| </button> | |
| </div> | |
| </div> | |
| {/* Start Over */} | |
| <div className="text-center pt-2"> | |
| <button | |
| onClick={() => { | |
| setFoundLocation(null) | |
| setAddress('') | |
| setError(null) | |
| clearLocation() // Clear the global location context | |
| }} | |
| className="text-sm text-primary-600 hover:text-primary-700 font-medium underline" | |
| > | |
| Search Different Address | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |