diff --git a/packages/pl-fe/src/actions/compose.ts b/packages/pl-fe/src/actions/compose.ts index 72fc4f506..b506a5f93 100644 --- a/packages/pl-fe/src/actions/compose.ts +++ b/packages/pl-fe/src/actions/compose.ts @@ -679,7 +679,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str (dispatch: AppDispatch, getState: () => RootState) => { let completion = '', startPosition = position; - if (typeof suggestion === 'object' && suggestion.id) { + if (typeof suggestion === 'object' && 'id' in suggestion) { completion = isNativeEmoji(suggestion) ? suggestion.native : suggestion.colons; startPosition = position - 1; diff --git a/packages/pl-fe/src/actions/events.ts b/packages/pl-fe/src/actions/events.ts index 9e07e7f48..466cc90ec 100644 --- a/packages/pl-fe/src/actions/events.ts +++ b/packages/pl-fe/src/actions/events.ts @@ -9,10 +9,6 @@ import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOU import type { Account, CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'pl-fe/store'; -const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST' as const; -const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS' as const; -const LOCATION_SEARCH_FAIL = 'LOCATION_SEARCH_FAIL' as const; - const EVENT_SUBMIT_REQUEST = 'EVENT_SUBMIT_REQUEST' as const; const EVENT_SUBMIT_SUCCESS = 'EVENT_SUBMIT_SUCCESS' as const; const EVENT_SUBMIT_FAIL = 'EVENT_SUBMIT_FAIL' as const; @@ -73,18 +69,6 @@ const messages = defineMessages({ rejected: { id: 'compose_event.participation_requests.reject_success', defaultMessage: 'User rejected' }, }); -const locationSearch = (query: string, signal?: AbortSignal) => - (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: LOCATION_SEARCH_REQUEST, query }); - return getClient(getState).search.searchLocation(query, { signal }).then((locations) => { - dispatch({ type: LOCATION_SEARCH_SUCCESS, locations }); - return locations; - }).catch(error => { - dispatch({ type: LOCATION_SEARCH_FAIL }); - throw error; - }); - }; - const submitEvent = ({ statusId, name, @@ -509,9 +493,6 @@ type EventsAction = | EventFormSetAction; export { - LOCATION_SEARCH_REQUEST, - LOCATION_SEARCH_SUCCESS, - LOCATION_SEARCH_FAIL, EVENT_SUBMIT_REQUEST, EVENT_SUBMIT_SUCCESS, EVENT_SUBMIT_FAIL, @@ -547,7 +528,6 @@ export { JOINED_EVENTS_FETCH_REQUEST, JOINED_EVENTS_FETCH_SUCCESS, JOINED_EVENTS_FETCH_FAIL, - locationSearch, submitEvent, submitEventRequest, submitEventSuccess, diff --git a/packages/pl-fe/src/api/hooks/search/use-search-location.ts b/packages/pl-fe/src/api/hooks/search/use-search-location.ts new file mode 100644 index 000000000..d28648016 --- /dev/null +++ b/packages/pl-fe/src/api/hooks/search/use-search-location.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; + +import { useClient } from 'pl-fe/hooks/use-client'; + +const useSearchLocation = (query: string) => { + const client = useClient(); + + return useQuery({ + queryKey: ['search', 'location', query], + queryFn: ({ signal }) => client.search.searchLocation(query, { signal }), + gcTime: 60 * 1000, + enabled: !!query.trim(), + }); +}; + +export { useSearchLocation }; diff --git a/packages/pl-fe/src/components/autosuggest-input.tsx b/packages/pl-fe/src/components/autosuggest-input.tsx index 992dd532e..e015a503c 100644 --- a/packages/pl-fe/src/components/autosuggest-input.tsx +++ b/packages/pl-fe/src/components/autosuggest-input.tsx @@ -8,15 +8,18 @@ import Portal from 'pl-fe/components/ui/portal'; import AutosuggestAccount from 'pl-fe/features/compose/components/autosuggest-account'; import { textAtCursorMatchesToken } from 'pl-fe/utils/suggestions'; +import AutosuggestLocation from './autosuggest-location'; + +import type { Location } from 'pl-api'; import type { Menu, MenuItem } from 'pl-fe/components/dropdown-menu'; import type { InputThemes } from 'pl-fe/components/ui/input'; import type { Emoji } from 'pl-fe/features/emoji'; -type AutoSuggestion = string | Emoji; +type AutoSuggestion = string | Emoji | Location; interface IAutosuggestInput extends Pick, 'onChange' | 'onKeyUp' | 'onKeyDown'> { value: string; - suggestions: Array; + suggestions: Array; disabled?: boolean; placeholder?: string; onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void; @@ -29,7 +32,6 @@ interface IAutosuggestInput extends Pick, searchTokens?: string[]; maxLength?: number; menu?: Menu; - renderSuggestion?: React.FC<{ id: string }>; hidePortal?: boolean; theme?: InputThemes; } @@ -165,16 +167,12 @@ const AutosuggestInput: React.FC = ({ const renderSuggestion = (suggestion: AutoSuggestion, i: number) => { let inner, key; - if (props.renderSuggestion && typeof suggestion === 'string') { - const RenderSuggestion = props.renderSuggestion; - inner = ; - key = suggestion; + if (typeof suggestion === 'object' && 'origin_id' in suggestion) { + inner = ; + key = suggestion.origin_id; } else if (typeof suggestion === 'object') { inner = ; key = suggestion.id; - } else if (suggestion[0] === '#') { - inner = suggestion; - key = suggestion; } else { inner = ; key = suggestion; diff --git a/packages/pl-fe/src/components/autosuggest-location.tsx b/packages/pl-fe/src/components/autosuggest-location.tsx index 77f9f30ec..22e692fbf 100644 --- a/packages/pl-fe/src/components/autosuggest-location.tsx +++ b/packages/pl-fe/src/components/autosuggest-location.tsx @@ -4,7 +4,8 @@ import HStack from 'pl-fe/components/ui/hstack'; import Icon from 'pl-fe/components/ui/icon'; import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; + +import type { Location } from 'pl-api'; const buildingCommunityIcon = require('@tabler/icons/outline/building-community.svg'); const homeIcon = require('@tabler/icons/outline/home-2.svg'); @@ -21,12 +22,10 @@ const ADDRESS_ICONS: Record = { }; interface IAutosuggestLocation { - id: string; + location: Location; } -const AutosuggestLocation: React.FC = ({ id }) => { - const location = useAppSelector((state) => state.locations.get(id)); - +const AutosuggestLocation: React.FC = ({ location }) => { if (!location) return null; return ( diff --git a/packages/pl-fe/src/components/location-search.tsx b/packages/pl-fe/src/components/location-search.tsx index 0716b1a96..7e1a19530 100644 --- a/packages/pl-fe/src/components/location-search.tsx +++ b/packages/pl-fe/src/components/location-search.tsx @@ -1,14 +1,13 @@ +import { useDebounce } from '@uidotdev/usehooks'; import clsx from 'clsx'; -import throttle from 'lodash/throttle'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { locationSearch } from 'pl-fe/actions/events'; +import { useSearchLocation } from 'pl-fe/api/hooks/search/use-search-location'; import AutosuggestInput, { AutoSuggestion } from 'pl-fe/components/autosuggest-input'; import Icon from 'pl-fe/components/icon'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import AutosuggestLocation from './autosuggest-location'; +import type { Location } from 'pl-api'; const noOp = () => {}; @@ -17,27 +16,24 @@ const messages = defineMessages({ }); interface ILocationSearch { - onSelected: (locationId: string) => void; + onSelected: (location: Location) => void; } const LocationSearch: React.FC = ({ onSelected }) => { const intl = useIntl(); - const dispatch = useAppDispatch(); - const [locationIds, setLocationIds] = useState>([]); - const controller = useRef(new AbortController()); const [value, setValue] = useState(''); + const debouncedValue = useDebounce(value, 400); + const locationsQuery = useSearchLocation(debouncedValue); const empty = !(value.length > 0); const handleChange: React.ChangeEventHandler = ({ target }) => { - refreshCancelToken(); - handleLocationSearch(target.value); setValue(target.value); }; const handleSelected = (_tokenStart: number, _lastToken: string | null, suggestion: AutoSuggestion) => { - if (typeof suggestion === 'string') { + if (typeof suggestion === 'object' && 'origin_id' in suggestion) { onSelected(suggestion); } }; @@ -56,30 +52,6 @@ const LocationSearch: React.FC = ({ onSelected }) => { } }; - const refreshCancelToken = () => { - controller.current.abort(); - controller.current = new AbortController(); - }; - - const clearResults = () => { - setLocationIds([]); - }; - - const handleLocationSearch = useCallback(throttle(q => { - dispatch(locationSearch(q, controller.current.signal)) - .then((locations: { origin_id: string }[]) => { - const locationIds = locations.map(location => location.origin_id); - setLocationIds(locationIds); - }) - .catch(noOp); - }, 900, { leading: true, trailing: true }), []); - - useEffect(() => { - if (value === '') { - clearResults(); - } - }, [value]); - return (
= ({ onSelected }) => { placeholder={intl.formatMessage(messages.placeholder)} value={value} onChange={handleChange} - suggestions={locationIds} + suggestions={locationsQuery.data || []} onSuggestionsFetchRequested={noOp} onSuggestionsClearRequested={noOp} onSuggestionSelected={handleSelected} searchTokens={[]} onKeyDown={handleKeyDown} - renderSuggestion={AutosuggestLocation} />
diff --git a/packages/pl-fe/src/features/compose-event/tabs/edit-event.tsx b/packages/pl-fe/src/features/compose-event/tabs/edit-event.tsx index 66a6e5627..4531f2fa2 100644 --- a/packages/pl-fe/src/features/compose-event/tabs/edit-event.tsx +++ b/packages/pl-fe/src/features/compose-event/tabs/edit-event.tsx @@ -93,16 +93,8 @@ const EditEvent: React.FC = ({ statusId }) => { setApprovalRequired(target.checked); }; - const onChangeLocation = (value: string | null) => { - dispatch((_, getState) => { - let location = null; - - if (value) { - location = getState().locations.get(value, null); - } - - setLocation(location); - }); + const onChangeLocation = (location: Location | null) => { + setLocation(location); }; const handleFiles = (files: FileList) => { diff --git a/packages/pl-fe/src/features/compose/editor/plugins/autosuggest-plugin.tsx b/packages/pl-fe/src/features/compose/editor/plugins/autosuggest-plugin.tsx index 3e8f29028..e7f78756b 100644 --- a/packages/pl-fe/src/features/compose/editor/plugins/autosuggest-plugin.tsx +++ b/packages/pl-fe/src/features/compose/editor/plugins/autosuggest-plugin.tsx @@ -44,7 +44,9 @@ import AutosuggestAccount from '../../components/autosuggest-account'; import { $createEmojiNode } from '../nodes/emoji-node'; import { $createMentionNode } from '../nodes/mention-node'; -import type { AutoSuggestion } from 'pl-fe/components/autosuggest-input'; +import type { Emoji } from 'pl-fe/features/emoji'; + +type AutoSuggestion = string | Emoji; type QueryMatch = { leadOffset: number; diff --git a/packages/pl-fe/src/reducers/index.ts b/packages/pl-fe/src/reducers/index.ts index 002b38567..21212ae71 100644 --- a/packages/pl-fe/src/reducers/index.ts +++ b/packages/pl-fe/src/reducers/index.ts @@ -23,7 +23,6 @@ import instance from './instance'; import listAdder from './list-adder'; import listEditor from './list-editor'; import lists from './lists'; -import locations from './locations'; import me from './me'; import meta from './meta'; import notifications from './notifications'; @@ -65,7 +64,6 @@ const reducers = { listAdder, listEditor, lists, - locations, me, meta, notifications, diff --git a/packages/pl-fe/src/reducers/locations.ts b/packages/pl-fe/src/reducers/locations.ts deleted file mode 100644 index ae6fbeed4..000000000 --- a/packages/pl-fe/src/reducers/locations.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Map as ImmutableMap } from 'immutable'; -import { Location } from 'pl-api'; -import { AnyAction } from 'redux'; - -import { LOCATION_SEARCH_SUCCESS } from 'pl-fe/actions/events'; - -type State = ImmutableMap; - -const initialState: State = ImmutableMap(); - -const normalizeLocations = (state: State, locations: Array) => - locations.reduce( - (state: State, location: Location) => state.set(location.origin_id, location), - state, - ); - -const locations = (state: State = initialState, action: AnyAction): State => { - switch (action.type) { - case LOCATION_SEARCH_SUCCESS: - return normalizeLocations(state, action.locations); - default: - return state; - } -}; - -export { locations as default };