all repos — caroster @ 8f8d1b858152fdaf4b7594c0f9ebc84e7518a035

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

feat: ✨ Rollback to Mapbox places API
Simon Mulquin simon@octree.ch
Tue, 04 Jun 2024 11:49:27 +0000
commit

8f8d1b858152fdaf4b7594c0f9ebc84e7518a035

parent

d6aab0312acbe808a9a7daeaaed71fbd73ab05d1

M frontend/containers/PlaceInput/index.tsxfrontend/containers/PlaceInput/index.tsx

@@ -1,16 +1,14 @@

import {useState} from 'react'; import TextField, {TextFieldProps} from '@mui/material/TextField'; import InputAdornment from '@mui/material/InputAdornment'; -import ListItem from '@mui/material/ListItem'; -import ListItemText from '@mui/material/ListItemText'; import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined'; import Autocomplete from '@mui/material/Autocomplete'; import {debounce} from '@mui/material/utils'; -import {SessionToken} from '@mapbox/search-js-core'; import {useTranslation} from 'react-i18next'; import useLocale from '../../hooks/useLocale'; -import {MapboxSuggestion} from '../../pages/api/mapbox/searchbox/suggest'; -import {GeocodedOption} from '../../pages/api/mapbox/searchbox/retrieve'; +import getPlacesSuggestions from '../../lib/getPlacesSuggestion'; +import ListItemText from '@mui/material/ListItemText'; +import ListItem from '@mui/material/ListItem'; interface Props { place: string;

@@ -30,14 +28,12 @@ textFieldProps?: TextFieldProps;

disabled?: boolean; } -type Option = MapboxSuggestion | {name: String; previous?: Boolean}; - const MAPBOX_CONFIGURED = process.env['MAPBOX_CONFIGURED'] || false; const PlaceInput = ({ + place = '', latitude, longitude, - place = '', onSelect, label, textFieldProps,

@@ -47,43 +43,22 @@ const {t} = useTranslation();

const {locale} = useLocale(); const [mapboxAvailable, setMapboxAvailable] = useState(MAPBOX_CONFIGURED); const [noCoordinates, setNoCoordinates] = useState(!latitude || !longitude); - const previousOption = place ? {name: place, previous: true} : null; - const sessionToken = new SessionToken(); - - const [options, setOptions] = useState([] as Array<Option>); - - const getOptionDecorators = option => { - if (option.mapbox_id) { - return {secondary: option.address || option.place_formatted}; - } else { - return { - secondary: t`placeInput.item.noCoordinates`, - color: 'warning.main', - }; - } - }; + const previousOption = place ? {place_name: place, previous: true} : null; + const [options, setOptions] = useState([] as Array<any>); const onChange = async (e, selectedOption) => { - if (selectedOption.mapbox_id) { - const geocodedFeature: GeocodedOption = await fetch( - '/api/mapbox/searchbox/retrieve?' + - new URLSearchParams({ - id: selectedOption.mapbox_id, - sessionToken: String(sessionToken), - locale, - }) - ).then(response => response.json()); - const {longitude, latitude} = geocodedFeature.coordinates; + if (selectedOption.center) { + const [optionLongitude, optionLatitude] = selectedOption.center; setNoCoordinates(false); onSelect({ - place: geocodedFeature.name, - latitude, - longitude, + place: selectedOption.place_name, + latitude: optionLatitude, + longitude: optionLongitude, }); } else { setNoCoordinates(true); onSelect({ - place: selectedOption.name, + place: selectedOption.place_name, latitude: null, longitude: null, });

@@ -92,21 +67,33 @@ };

const updateOptions = debounce(async (e, search: string) => { if (search !== '') { - try { - await fetch( - '/api/mapbox/searchbox/suggest?' + - new URLSearchParams({ - search, - sessionToken, - locale, - }) - ) - .then(response => response.json()) - .then(suggestions => setOptions([{name: search}, ...suggestions])); - } catch (err) { - console.warn(err); - setMapboxAvailable(false); - } + getPlacesSuggestions({search, proximity: 'ip', locale}).then( + suggestions => { + let defaultOptions = []; + if (previousOption) { + defaultOptions = [previousOption]; + } + if (search && search !== previousOption?.place_name) { + defaultOptions = [...defaultOptions, {place_name: search}]; + } + if (suggestions?.length >= 1) { + setMapboxAvailable(true); + const suggestionsWithoutCopies = suggestions.filter( + ({place_name}) => + place_name !== search && + place_name !== previousOption?.place_name + ); + const uniqueOptions = [ + ...defaultOptions, + ...suggestionsWithoutCopies, + ]; + setOptions(uniqueOptions); + } else { + setMapboxAvailable(false); + setOptions(defaultOptions); + } + } + ); } }, 400);

@@ -127,10 +114,10 @@ return (

<Autocomplete freeSolo disableClearable - getOptionLabel={option => option?.name || place} + getOptionLabel={option => option.place_name} options={options} - defaultValue={previousOption} autoComplete + defaultValue={previousOption} filterOptions={x => x} noOptionsText={t('autocomplete.noMatch')} onChange={onChange}

@@ -157,14 +144,16 @@ onBlur={handleBlur}

/> )} renderOption={(props, option) => { - const {color, secondary} = getOptionDecorators(option); if (option.previous) return null; + return ( - <ListItem key={option.mapbox_id || 'text'} {...props}> + <ListItem key={option.id || 'text'} {...props}> <ListItemText - primary={option.name} - secondary={secondary} - secondaryTypographyProps={{color}} + primary={option.place_name} + secondary={!option.center && t`placeInput.item.noCoordinates`} + secondaryTypographyProps={{ + color: option.center ? 'inherit' : 'warning.main', + }} /> </ListItem> );
M frontend/locales/nl.jsonfrontend/locales/nl.json

@@ -174,6 +174,7 @@ "passenger.success.goToTravels": "Ga naar reizen",

"passenger.title": "Wachtlijst", "placeInput.mapboxUnavailable": "Er kunnen momenteel geen locaties worden voorgesteld", "placeInput.noCoordinates": "Deze locatie is niet aangetroffen en verschijnt daarom niet op de kaart", + "placeInput.item.noCoordinates": "Geen coördinaten", "profile.actions.cancel": "Annuleren", "profile.actions.change_password": "Wachtwoord wijzigen", "profile.actions.edit": "Bewerken",
A frontend/pages/api/mapbox/places.ts

@@ -0,0 +1,33 @@

+import type {NextApiRequest, NextApiResponse} from 'next'; + +type ResponseData = Array<{place_name: string; center: [number, number]}>; + +const {MAPBOX_TOKEN, MAPBOX_URL} = process.env; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse<ResponseData> +) { + const {search, proximity = 'ip', locale} = req.query; + if (!search) return res.status(400).send(null); + else if (!MAPBOX_TOKEN) return res.status(500).send(null); + + const url = `${MAPBOX_URL}geocoding/v5/mapbox.places/${search}.json?proximity=${proximity}&access_token=${MAPBOX_TOKEN}&language=${locale}`; + + const mapBoxResult = await fetch(url) + .then(response => { + if (response.status === 429) { + throw 'MAPBOX_RATE_LIMIT_EXCEEDED'; + } + return response.json(); + }) + .catch(error => { + console.error(error); + }); + + if (mapBoxResult?.features) { + res.status(200).send(mapBoxResult.features); + return; + } + res.status(500).send(null); +}
D frontend/pages/api/mapbox/searchbox/retrieve.ts

@@ -1,33 +0,0 @@

-import type {NextApiRequest, NextApiResponse} from 'next'; - -export type GeocodedOption = {name: string, coordinates: {latitude, longitude}}; - -const {MAPBOX_TOKEN, MAPBOX_URL} = process.env; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse<GeocodedOption> -) { - const {id, sessionToken = 'ip', locale} = req.query; - if (!id) return res.status(400).send(null); - else if (!MAPBOX_TOKEN) return res.status(500).send(null); - - const url = `${MAPBOX_URL}search/searchbox/v1/retrieve/${id}?language=${locale}&access_token=${MAPBOX_TOKEN}&session_token=${sessionToken}`; - - const mapBoxResult = await fetch(url) - .then(response => { - if (response.status === 429) { - throw 'MAPBOX_RATE_LIMIT_EXCEEDED'; - } - return response.json(); - }) - .catch(error => { - console.error(error); - }); - - if (mapBoxResult?.features?.length > 0) { - res.status(200).send(mapBoxResult.features[0].properties); - return; - } - res.status(500).send(null); -}
D frontend/pages/api/mapbox/searchbox/suggest.ts

@@ -1,34 +0,0 @@

-import type {NextApiRequest, NextApiResponse} from 'next'; - -export type MapboxSuggestion = {name: string; mapbox_id: string, address: string; place_formatted: string}; - -const {MAPBOX_TOKEN, MAPBOX_URL} = process.env; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse<Array<MapboxSuggestion>> -) { - const {search, proximity = 'ip', sessionToken, locale} = req.query; - if (!search) return res.status(400).send(null); - else if (!MAPBOX_TOKEN) return res.status(500).send(null); - - const url = `${MAPBOX_URL}search/searchbox/v1/suggest?q=${search}&proximity=${proximity}&language=${locale}&access_token=${MAPBOX_TOKEN}&session_token=${sessionToken}`; - - const mapBoxResult = await fetch(url) - .then(response => { - if (response.status === 429) { - throw 'MAPBOX_RATE_LIMIT_EXCEEDED'; - } - return response.json(); - }) - .catch(error => { - console.error(error); - }); - - if (mapBoxResult?.suggestions) { - console.log(url) - res.status(200).send(mapBoxResult.suggestions); - return; - } - res.send([]); -}