all repos — caroster @ c45e87f1213a8a980ac0fc9fe510f124f5e1a225

[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 [firstSuggestion, ...otherSuggestions] = suggestions;
 85            let uniqueOptions = [...defaultOptions, ...otherSuggestions];
 86            if (
 87              firstSuggestion.place_name !== search ||
 88              firstSuggestion.place_name !== previousOption?.place_name
 89            )
 90              uniqueOptions = [
 91                ...defaultOptions,
 92                firstSuggestion,
 93                ...otherSuggestions,
 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) {
107      return t`placeInput.mapboxUnavailable`;
108    }
109    if (noCoordinates) {
110      return t`placeInput.noCoordinates`;
111    }
112    return null;
113  };
114
115  return (
116    <Autocomplete
117      freeSolo
118      disableClearable
119      getOptionLabel={option => option.place_name}
120      options={options}
121      autoComplete
122      defaultValue={previousOption}
123      filterOptions={x => x}
124      noOptionsText={t('autocomplete.noMatch')}
125      onChange={onChange}
126      onInputChange={updateOptions}
127      renderInput={params => (
128        <TextField
129          label={label}
130          multiline
131          maxRows={4}
132          helperText={MAPBOX_CONFIGURED && getHelperText()}
133          FormHelperTextProps={{sx: {color: 'warning.main'}}}
134          InputProps={{
135            endAdornment: (
136              <InputAdornment position="end" sx={{mr: -0.5}}>
137                <PlaceOutlinedIcon />
138              </InputAdornment>
139            ),
140          }}
141          {...params}
142          {...textFieldProps}
143        />
144      )}
145      renderOption={(props, option) => {
146        return <li {...props}>{option.place_name}</li>;
147      }}
148    />
149  );
150};
151
152export default PlaceInput;