|
1 | | -import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/react' |
| 1 | +import { Autocomplete, useJsApiLoader } from '@react-google-maps/api' |
| 2 | +import { useRef, useEffect } from 'react' |
2 | 3 | import { Icon } from '../Icon/Icon' |
3 | | -import { useEffect, useState } from 'react' |
4 | | -import PlacesAutocomplete, { geocodeByAddress, getLatLng } from 'react-places-autocomplete' |
5 | 4 |
|
6 | | -export interface LocationPickerProps { |
7 | | - posCallback?: (pos: { name: string; id: string; position: object }[]) => void |
8 | | - centerCallback?: (center: { lat: number; lng: number }) => void |
9 | | - singleMarker?: boolean |
10 | | - singleMarkerCallback?: (marker: { coordinates: { lat: number; lng: number }; address: string }) => void |
11 | | - eventLatitude?: number |
12 | | - eventLongitude?: number |
13 | | - eventAddress?: string |
| 5 | +const libraries: 'places'[] = ['places'] |
| 6 | + |
| 7 | +export interface SingleMarkerInterface { |
| 8 | + address: string |
| 9 | + coordinates: { lat: number; lng: number } |
14 | 10 | } |
15 | 11 |
|
16 | | -export const LocationPicker = ({ |
17 | | - posCallback, |
18 | | - centerCallback, |
19 | | - singleMarker, |
20 | | - singleMarkerCallback, |
21 | | - eventAddress, |
22 | | - eventLatitude, |
23 | | - eventLongitude, |
24 | | -}: LocationPickerProps) => { |
25 | | - const [address, setAddress] = useState(eventAddress ? eventAddress : '') |
26 | | - const [center, setCenter] = useState<{ lat: number; lng: number }>({ |
27 | | - lat: 45.3850225, |
28 | | - lng: -75.6946679, |
29 | | - }) |
30 | | - const [pos, setPos] = useState<{ name: string; id: string; position: object }[]>([]) |
31 | | - const [coordinates, setCoordinates] = useState({ |
32 | | - lat: eventLatitude ? eventLatitude : 45.3850225, |
33 | | - lng: eventLongitude ? eventLongitude : -75.6946679, |
| 12 | +interface ILocationPickerProps { |
| 13 | + address?: string |
| 14 | + markerCallback?: (coordinates: SingleMarkerInterface) => void |
| 15 | +} |
| 16 | + |
| 17 | +export function LocationPicker({ address, markerCallback }: ILocationPickerProps) { |
| 18 | + // eslint-disable-next-line no-undef |
| 19 | + const autocompleteRef = useRef<google.maps.places.Autocomplete | null>(null) |
| 20 | + const inputRef = useRef<HTMLInputElement | null>(null) |
| 21 | + |
| 22 | + const { isLoaded } = useJsApiLoader({ |
| 23 | + googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY, |
| 24 | + libraries, |
34 | 25 | }) |
35 | 26 |
|
36 | | - const handleSelect = async (value: string) => { |
37 | | - const results = await geocodeByAddress(value) |
38 | | - const latLng = await getLatLng(results[0]) |
39 | | - const placeID = results[0].place_id |
40 | | - setAddress(value) |
41 | | - setCenter({ lat: latLng.lat, lng: latLng.lng }) |
42 | | - setPos([...pos, { name: value, id: placeID, position: latLng }]) |
43 | | - setCoordinates(latLng) |
| 27 | + useEffect(() => { |
| 28 | + if (address && inputRef.current) { |
| 29 | + inputRef.current.value = address |
| 30 | + } |
| 31 | + }, [address]) |
| 32 | + |
| 33 | + // eslint-disable-next-line no-undef |
| 34 | + const onLoad = (autocomplete: google.maps.places.Autocomplete) => { |
| 35 | + autocompleteRef.current = autocomplete |
44 | 36 | } |
45 | 37 |
|
46 | | - useEffect(() => { |
47 | | - if (posCallback) posCallback(pos) |
48 | | - }, [pos, posCallback]) |
| 38 | + const onPlaceChanged = () => { |
| 39 | + const place = autocompleteRef.current?.getPlace() |
| 40 | + if (place?.geometry?.location) { |
| 41 | + const newAddress = place.formatted_address || '' |
| 42 | + const newLat = place.geometry.location.lat() |
| 43 | + const newLng = place.geometry.location.lng() |
49 | 44 |
|
50 | | - useEffect(() => { |
51 | | - if (centerCallback) centerCallback(center) |
52 | | - }, [center, centerCallback]) |
| 45 | + markerCallback?.({ |
| 46 | + coordinates: { lat: newLat, lng: newLng }, |
| 47 | + address: newAddress, |
| 48 | + }) |
| 49 | + } |
| 50 | + } |
53 | 51 |
|
54 | | - useEffect(() => { |
55 | | - if (singleMarker && singleMarkerCallback) singleMarkerCallback({ coordinates, address: address }) |
56 | | - }, [coordinates, address, singleMarkerCallback, singleMarker]) |
| 52 | + if (!isLoaded) return null |
57 | 53 |
|
58 | 54 | return ( |
59 | | - <div className="cu-locationpicker not-prose"> |
60 | | - <PlacesAutocomplete value={address} onChange={setAddress} onSelect={handleSelect}> |
61 | | - {({ getInputProps, suggestions, getSuggestionItemProps }) => ( |
62 | | - <Combobox value={address} onChange={handleSelect}> |
63 | | - <div className="relative"> |
64 | | - <Icon |
65 | | - name="magnifying-glass" |
66 | | - size={20} |
67 | | - className="pointer-events-none absolute left-3.5 top-3.5" |
68 | | - color="#9CA3AF" |
69 | | - /> |
70 | | - <ComboboxInput |
71 | | - className="w-full h-12 pl-10 pr-4 bg-white border rounded-lg border-cu-black-200 text-cu-black-800 placeholder-cu-black-400 focus:border-cu-black-400 focus:outline-none focus:ring-0 sm:text-sm" |
72 | | - {...getInputProps({ placeholder: 'Type address' })} |
73 | | - /> |
74 | | - {address && ( |
75 | | - <span |
76 | | - className="absolute right-3.5 top-2.5 cursor-pointer text-2xl select-none" |
77 | | - style={{ lineHeight: '1', color: '#9CA3AF' }} |
78 | | - onClick={() => setAddress('')} |
79 | | - role="button" |
80 | | - tabIndex={0} |
81 | | - aria-label="Clear address" |
82 | | - > |
83 | | - × |
84 | | - </span> |
85 | | - )} |
86 | | - </div> |
87 | | - <ComboboxOptions className="mt-3 max-h-80 divide-y divide-cu-black-100 overflow-y-auto bg-white px-1.5 text-sm text-cu-black-800"> |
88 | | - {suggestions.map((suggestion) => { |
89 | | - return ( |
90 | | - <ComboboxOption key={suggestion.index} value={suggestion}> |
91 | | - {({ active }) => ( |
92 | | - <ul> |
93 | | - <li |
94 | | - {...getSuggestionItemProps(suggestion)} |
95 | | - className={`p-4 text-cu-black-600 hover:cursor-pointer ${ |
96 | | - active ? 'bg-cu-black-50 text-cu-black-900' : 'bg-white' |
97 | | - }`} |
98 | | - key={suggestion.index} |
99 | | - > |
100 | | - {suggestion.description} |
101 | | - </li> |
102 | | - </ul> |
103 | | - )} |
104 | | - </ComboboxOption> |
105 | | - ) |
106 | | - })} |
107 | | - </ComboboxOptions> |
108 | | - </Combobox> |
109 | | - )} |
110 | | - </PlacesAutocomplete> |
| 55 | + <div className="relative"> |
| 56 | + <Autocomplete onLoad={onLoad} onPlaceChanged={onPlaceChanged}> |
| 57 | + <input |
| 58 | + ref={inputRef} |
| 59 | + defaultValue={address} |
| 60 | + placeholder="Search for a location" |
| 61 | + style={{ |
| 62 | + width: '100%', |
| 63 | + padding: '8px 8px 8px 40px', |
| 64 | + border: '1px solid #ccc', |
| 65 | + borderRadius: '4px', |
| 66 | + }} |
| 67 | + /> |
| 68 | + </Autocomplete> |
| 69 | + <Icon |
| 70 | + name="magnifying-glass" |
| 71 | + size={16} |
| 72 | + className="pointer-events-none absolute left-3.5 top-3.5" |
| 73 | + color="#999999" |
| 74 | + /> |
111 | 75 | </div> |
112 | 76 | ) |
113 | 77 | } |
0 commit comments