all repos — caroster @ e2d2a77f78f52331a111b5e38389de808e95c571

[Octree] Group carpool to your event https://caroster.io

frontend/containers/PlaceInput/index.tsx (view raw)

  1import {useState} from 'react';
  2import TextField, {TextFieldProps} from '@mui/material/TextField';
  3import InputAdornment from '@mui/material/InputAdornment';
  4import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
  5import Autocomplete from '@mui/material/Autocomplete';
  6import {debounce} from '@mui/material/utils';
  7import {useTranslation} from 'react-i18next';
  8import useLocale from '../../hooks/useLocale';
  9import getPlacesSuggestions from '../../lib/getPlacesSuggestion';
 10
 11interface Props {
 12  place: string;
 13  latitude?: number;
 14  longitude?: number;
 15  onSelect: ({
 16    latitude,
 17    longitude,
 18    place,
 19  }: {
 20    latitude?: number;
 21    longitude?: number;
 22    place: string;
 23  }) => void;
 24  label?: string;
 25  textFieldProps?: TextFieldProps;
 26  disabled?: boolean;
 27}
 28
 29const MAPBOX_CONFIGURED = process.env['MAPBOX_CONFIGURED'] || false;
 30
 31const PlaceInput = ({
 32  place = '',
 33  latitude,
 34  longitude,
 35  onSelect,
 36  label,
 37  textFieldProps,
 38  disabled,
 39}: Props) => {
 40  const {t} = useTranslation();
 41  const {locale} = useLocale();
 42  const [mapboxAvailable, setMapboxAvailable] = useState(MAPBOX_CONFIGURED);
 43  const [noCoordinates, setNoCoordinates] = useState(!latitude || !longitude);
 44  const previousOption = place ? {place_name: place, previous: true} : null;
 45
 46  const [options, setOptions] = useState([] as Array<any>);
 47  const onChange = async (e, selectedOption) => {
 48    if (selectedOption.previous) {
 49      setNoCoordinates(!latitude || !longitude);
 50      onSelect({
 51        place,
 52        latitude,
 53        longitude,
 54      });
 55    } else if (selectedOption.center) {
 56      const [optionLongitude, optionLatitude] = selectedOption.center;
 57      setNoCoordinates(false);
 58      onSelect({
 59        place: selectedOption.place_name,
 60        latitude: optionLatitude,
 61        longitude: optionLongitude,
 62      });
 63    } else {
 64      setNoCoordinates(true);
 65      onSelect({
 66        place: selectedOption.place_name,
 67        latitude: null,
 68        longitude: null,
 69      });
 70    }
 71  };
 72
 73  const updateOptions = debounce(async (e, search: string) => {
 74    if (search !== '') {
 75      getPlacesSuggestions({search, proximity: 'ip', locale}).then(
 76        suggestions => {
 77          let defaultOptions = [];
 78          if (previousOption) {
 79            defaultOptions = [previousOption];
 80          }
 81          if (search && search !== previousOption?.place_name) {
 82            defaultOptions = [...defaultOptions, {place_name: search}];
 83          }
 84          if (suggestions?.length >= 1) {
 85            setMapboxAvailable(true);
 86            const suggestionsWithoutCopies = suggestions.filter(
 87              ({place_name}) =>
 88                place_name !== search &&
 89                place_name !== previousOption?.place_name
 90            );
 91            const uniqueOptions = [
 92              ...defaultOptions,
 93              ...suggestionsWithoutCopies,
 94            ];
 95            setOptions(uniqueOptions);
 96          } else {
 97            setMapboxAvailable(false);
 98            setOptions(defaultOptions);
 99          }
100        }
101      );
102    }
103  }, 400);
104
105  const getHelperText = () => {
106    if (!mapboxAvailable) return t`placeInput.mapboxUnavailable`;
107    else if (noCoordinates) return t`placeInput.noCoordinates`;
108  };
109
110  const handleBlur = e => {
111    onSelect({
112      place: e.target.value,
113      latitude,
114      longitude,
115    });
116  };
117
118  return (
119    <Autocomplete
120      freeSolo
121      disableClearable
122      getOptionLabel={option => option.place_name}
123      options={options}
124      autoComplete
125      defaultValue={previousOption}
126      filterOptions={x => x}
127      noOptionsText={t('autocomplete.noMatch')}
128      onChange={onChange}
129      onInputChange={updateOptions}
130      disabled={disabled}
131      renderInput={params => (
132        <TextField
133          label={label}
134          multiline
135          maxRows={4}
136          helperText={MAPBOX_CONFIGURED && getHelperText()}
137          FormHelperTextProps={{sx: {color: 'warning.main'}}}
138          InputProps={{
139            endAdornment: (
140              <InputAdornment position="end" sx={{mr: -0.5}}>
141                <PlaceOutlinedIcon />
142              </InputAdornment>
143            ),
144          }}
145          {...params}
146          {...textFieldProps}
147          onBlur={handleBlur}
148        />
149      )}
150      renderOption={(props, option) => {
151        return <li {...props}>{option.place_name}</li>;
152      }}
153    />
154  );
155};
156
157export default PlaceInput;