diff --git a/app/soapbox/features/compose/components/search_results.tsx b/app/soapbox/features/compose/components/search_results.tsx index 1dc244920..d24c7631d 100644 --- a/app/soapbox/features/compose/components/search_results.tsx +++ b/app/soapbox/features/compose/components/search_results.tsx @@ -3,19 +3,23 @@ import React, { useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl'; +import { expandSearch, setFilter } from 'soapbox/actions/search'; import { fetchTrendingStatuses } from 'soapbox/actions/trending_statuses'; import ScrollableList from 'soapbox/components/scrollable_list'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account'; import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status'; -import { useAppDispatch } from 'soapbox/hooks'; +import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import Hashtag from '../../../components/hashtag'; import { Tabs } from '../../../components/ui'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; -import type { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import type { + Map as ImmutableMap, + List as ImmutableList, +} from 'immutable'; const messages = defineMessages({ accounts: { id: 'search_results.accounts', defaultMessage: 'People' }, @@ -38,22 +42,20 @@ interface ISearchResults { } /** Displays search results depending on the active tab. */ -const SearchResults: React.FC = ({ - value, - results, - submitted, - expandSearch, - selectedFilter, - selectFilter, - suggestions, - trendingStatuses, - trends, -}) => { +const SearchResults: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const handleLoadMore = () => expandSearch(selectedFilter); - const handleSelectFilter = (newActiveFilter: SearchFilter) => selectFilter(newActiveFilter); + const value = useAppSelector(state => state.search.get('submittedValue')); + const results = useAppSelector(state => state.search.get('results')); + const suggestions = useAppSelector(state => state.suggestions.items); + const trendingStatuses = useAppSelector(state => state.trending_statuses.items); + const trends = useAppSelector(state => state.trends.items); + const submitted = useAppSelector(state => state.search.get('submitted')); + const selectedFilter = useAppSelector(state => state.search.get('filter')); + + const handleLoadMore = () => dispatch(expandSearch(selectedFilter)); + const handleSelectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter)); useEffect(() => { dispatch(fetchTrendingStatuses()); diff --git a/app/soapbox/features/follow_recommendations/components/follow_recommendations_list.tsx b/app/soapbox/features/follow_recommendations/components/follow_recommendations_list.tsx index 4ca838072..109febdf4 100644 --- a/app/soapbox/features/follow_recommendations/components/follow_recommendations_list.tsx +++ b/app/soapbox/features/follow_recommendations/components/follow_recommendations_list.tsx @@ -31,8 +31,8 @@ const FollowRecommendationsList: React.FC = () => { return (
- {suggestions.size > 0 ? suggestions.map((suggestion: { account: string }) => ( - + {suggestions.size > 0 ? suggestions.map(suggestion => ( + )) : (
diff --git a/app/soapbox/reducers/suggestions.js b/app/soapbox/reducers/suggestions.ts similarity index 54% rename from app/soapbox/reducers/suggestions.js rename to app/soapbox/reducers/suggestions.ts index ac00eecf1..bca64c9e2 100644 --- a/app/soapbox/reducers/suggestions.js +++ b/app/soapbox/reducers/suggestions.ts @@ -1,4 +1,8 @@ -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { + Map as ImmutableMap, + List as ImmutableList, + Record as ImmutableRecord, +} from 'immutable'; import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'soapbox/actions/accounts'; import { DOMAIN_BLOCK_SUCCESS } from 'soapbox/actions/domain_blocks'; @@ -13,42 +17,64 @@ import { SUGGESTIONS_V2_FETCH_FAIL, } from '../actions/suggestions'; -const initialState = ImmutableMap({ - items: ImmutableList(), +import type { AnyAction } from 'redux'; + +type SuggestionSource = 'past_interactions' | 'staff' | 'global'; + +type ReducerSuggestion = { + source: SuggestionSource, + account: string, +} + +type SuggestionAccount = { + id: string, +} + +type Suggestion = { + source: SuggestionSource, + account: SuggestionAccount, +} + +const ReducerRecord = ImmutableRecord({ + items: ImmutableList>(), isLoading: false, }); -// Convert a v1 account into a v2 suggestion -const accountToSuggestion = account => { +type State = ReturnType; + +/** Convert a v1 account into a v2 suggestion. */ +const accountToSuggestion = (account: SuggestionAccount): ReducerSuggestion => { return { source: 'past_interactions', account: account.id, }; }; -const importAccounts = (state, accounts) => { +/** Import plain accounts into the reducer (legacy). */ +const importAccounts = (state: State, accounts: SuggestionAccount[]): State => { return state.withMutations(state => { - state.set('items', fromJS(accounts.map(accountToSuggestion))); + state.set('items', ImmutableList(accounts.map(account => ImmutableMap(accountToSuggestion(account))))); state.set('isLoading', false); }); }; -const importSuggestions = (state, suggestions) => { +/** Import full suggestion objects. */ +const importSuggestions = (state: State, suggestions: Suggestion[]): State => { return state.withMutations(state => { - state.set('items', fromJS(suggestions.map(x => ({ ...x, account: x.account.id })))); + state.set('items', ImmutableList(suggestions.map(x => ImmutableMap({ ...x, account: x.account.id })))); state.set('isLoading', false); }); }; -const dismissAccount = (state, accountId) => { +const dismissAccount = (state: State, accountId: string): State => { return state.update('items', items => items.filterNot(item => item.get('account') === accountId)); }; -const dismissAccounts = (state, accountIds) => { +const dismissAccounts = (state: State, accountIds: string[]): State => { return state.update('items', items => items.filterNot(item => accountIds.includes(item.get('account')))); }; -export default function suggestionsReducer(state = initialState, action) { +export default function suggestionsReducer(state = ReducerRecord(), action: AnyAction) { switch (action.type) { case SUGGESTIONS_FETCH_REQUEST: case SUGGESTIONS_V2_FETCH_REQUEST: diff --git a/app/soapbox/reducers/trending_statuses.js b/app/soapbox/reducers/trending_statuses.js deleted file mode 100644 index ee4fa234b..000000000 --- a/app/soapbox/reducers/trending_statuses.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; - -import { - TRENDING_STATUSES_FETCH_REQUEST, - TRENDING_STATUSES_FETCH_SUCCESS, -} from 'soapbox/actions/trending_statuses'; - -const initialState = ImmutableMap({ - items: ImmutableOrderedSet(), - isLoading: false, -}); - -const toIds = items => ImmutableOrderedSet(items.map(item => item.id)); - -const importStatuses = (state, statuses) => { - return state.withMutations(state => { - state.set('items', toIds(statuses)); - state.set('isLoading', false); - }); -}; - -export default function trending_statuses(state = initialState, action) { - switch (action.type) { - case TRENDING_STATUSES_FETCH_REQUEST: - return state.set('isLoading', true); - case TRENDING_STATUSES_FETCH_SUCCESS: - return importStatuses(state, action.statuses); - default: - return state; - } -} diff --git a/app/soapbox/reducers/trending_statuses.ts b/app/soapbox/reducers/trending_statuses.ts new file mode 100644 index 000000000..afaf5d9af --- /dev/null +++ b/app/soapbox/reducers/trending_statuses.ts @@ -0,0 +1,37 @@ +import { Record as ImmutableRecord, OrderedSet as ImmutableOrderedSet } from 'immutable'; + +import { + TRENDING_STATUSES_FETCH_REQUEST, + TRENDING_STATUSES_FETCH_SUCCESS, +} from 'soapbox/actions/trending_statuses'; + +import type { AnyAction } from 'redux'; + +const ReducerRecord = ImmutableRecord({ + items: ImmutableOrderedSet(), + isLoading: false, +}); + +type State = ReturnType; + +type IdEntity = { id: string }; + +const toIds = (items: IdEntity[]) => ImmutableOrderedSet(items.map(item => item.id)); + +const importStatuses = (state: State, statuses: IdEntity[]): State => { + return state.withMutations(state => { + state.set('items', toIds(statuses)); + state.set('isLoading', false); + }); +}; + +export default function trending_statuses(state = ReducerRecord(), action: AnyAction) { + switch (action.type) { + case TRENDING_STATUSES_FETCH_REQUEST: + return state.set('isLoading', true); + case TRENDING_STATUSES_FETCH_SUCCESS: + return importStatuses(state, action.statuses); + default: + return state; + } +} diff --git a/app/soapbox/reducers/trends.js b/app/soapbox/reducers/trends.ts similarity index 53% rename from app/soapbox/reducers/trends.js rename to app/soapbox/reducers/trends.ts index eb7df59bf..cb061c7b7 100644 --- a/app/soapbox/reducers/trends.js +++ b/app/soapbox/reducers/trends.ts @@ -1,4 +1,8 @@ -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { + Map as ImmutableMap, + Record as ImmutableRecord, + List as ImmutableList, +} from 'immutable'; import { TRENDS_FETCH_REQUEST, @@ -6,18 +10,20 @@ import { TRENDS_FETCH_FAIL, } from '../actions/trends'; -const initialState = ImmutableMap({ - items: ImmutableList(), +import type { AnyAction } from 'redux'; + +const ReducerRecord = ImmutableRecord({ + items: ImmutableList>(), isLoading: false, }); -export default function trendsReducer(state = initialState, action) { +export default function trendsReducer(state = ReducerRecord(), action: AnyAction) { switch (action.type) { case TRENDS_FETCH_REQUEST: return state.set('isLoading', true); case TRENDS_FETCH_SUCCESS: return state.withMutations(map => { - map.set('items', fromJS(action.tags.map((x => x)))); + map.set('items', ImmutableList(action.tags.map(ImmutableMap))); map.set('isLoading', false); }); case TRENDS_FETCH_FAIL: