all repos — caroster @ 1844621d9cf386a3759850b1ab830c79319c5838

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