diff --git a/packages/pl-fe/src/actions/accounts.ts b/packages/pl-fe/src/actions/accounts.ts index c98dbe871..b11d75e25 100644 --- a/packages/pl-fe/src/actions/accounts.ts +++ b/packages/pl-fe/src/actions/accounts.ts @@ -1,6 +1,16 @@ -import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship, Token, PlApiClient } from 'pl-api'; +import { + PLEROMA, + type UpdateNotificationSettingsParams, + type Account, + type CreateAccountParams, + type PaginatedResponse, + type PlApiClient, + type Relationship, + type Token, +} from 'pl-api'; import { Entities } from 'pl-fe/entity-store/entities'; +import { queryClient } from 'pl-fe/queries/client'; import { selectAccount } from 'pl-fe/selectors'; import { isLoggedIn } from 'pl-fe/utils/auth'; @@ -8,6 +18,7 @@ import { getClient, type PlfeResponse } from '../api'; import { importEntities } from './importer'; +import type { MinifiedSuggestion } from 'pl-fe/api/hooks/trends/use-suggested-accounts'; import type { MinifiedStatus } from 'pl-fe/reducers/statuses'; import type { AppDispatch, RootState } from 'pl-fe/store'; import type { History } from 'pl-fe/types/history'; @@ -187,6 +198,11 @@ const blockAccount = (accountId: string) => return getClient(getState).filtering.blockAccount(accountId) .then(response => { dispatch(importEntities({ relationships: [response] })); + + queryClient.setQueryData>(['suggestions'], suggestions => suggestions + ? suggestions.filter((suggestion) => suggestion.account_id !== accountId) + : undefined); + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers return dispatch(blockAccountSuccess(response, getState().statuses)); }).catch(error => dispatch(blockAccountFail(error))); @@ -243,6 +259,11 @@ const muteAccount = (accountId: string, notifications?: boolean, duration = 0) = return client.filtering.muteAccount(accountId, params) .then(response => { dispatch(importEntities({ relationships: [response] })); + + queryClient.setQueryData>(['suggestions'], suggestions => suggestions + ? suggestions.filter((suggestion) => suggestion.account_id !== accountId) + : undefined); + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers return dispatch(muteAccountSuccess(response, getState().statuses)); }) diff --git a/packages/pl-fe/src/actions/domain-blocks.ts b/packages/pl-fe/src/actions/domain-blocks.ts index a943b8098..80007cce7 100644 --- a/packages/pl-fe/src/actions/domain-blocks.ts +++ b/packages/pl-fe/src/actions/domain-blocks.ts @@ -1,9 +1,11 @@ import { Entities } from 'pl-fe/entity-store/entities'; +import { queryClient } from 'pl-fe/queries/client'; import { isLoggedIn } from 'pl-fe/utils/auth'; import { getClient } from '../api'; import type { PaginatedResponse } from 'pl-api'; +import type { MinifiedSuggestion } from 'pl-fe/api/hooks/trends/use-suggested-accounts'; import type { EntityStore } from 'pl-fe/entity-store/types'; import type { Account } from 'pl-fe/normalizers/account'; import type { AppDispatch, RootState } from 'pl-fe/store'; @@ -35,6 +37,10 @@ const blockDomain = (domain: string) => const accounts = selectAccountsByDomain(getState(), domain); if (!accounts) return; dispatch(blockDomainSuccess(domain, accounts)); + + queryClient.setQueryData>(['suggestions'], suggestions => suggestions + ? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id)) + : undefined); }).catch(err => { dispatch(blockDomainFail(domain, err)); }); diff --git a/packages/pl-fe/src/actions/suggestions.ts b/packages/pl-fe/src/actions/suggestions.ts deleted file mode 100644 index 4d407eeae..000000000 --- a/packages/pl-fe/src/actions/suggestions.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { getClient } from '../api'; - -import { fetchRelationships } from './accounts'; -import { importEntities } from './importer'; -import { insertSuggestionsIntoTimeline } from './timelines'; - -import type { Suggestion } from 'pl-api'; -import type { AppDispatch, RootState } from 'pl-fe/store'; - -const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST' as const; -const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS' as const; -const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL' as const; - -interface SuggestionsFetchRequestAction { - type: typeof SUGGESTIONS_FETCH_REQUEST; -} - -interface SuggestionsFetchSuccessAction { - type: typeof SUGGESTIONS_FETCH_SUCCESS; - suggestions: Array; -} - -interface SuggestionsFetchFailAction { - type: typeof SUGGESTIONS_FETCH_FAIL; - error: any; - skipAlert: true; -} - -const fetchSuggestions = (limit = 50) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const client = getClient(state); - const me = state.me; - - if (!me) return null; - - if (client.features.suggestions) { - dispatch({ type: SUGGESTIONS_FETCH_REQUEST }); - - return getClient(getState).myAccount.getSuggestions(limit).then((suggestions) => { - const accounts = suggestions.map(({ account }) => account); - - dispatch(importEntities({ accounts })); - dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, suggestions }); - - dispatch(fetchRelationships(accounts.map(({ id }) => id))); - return suggestions; - }).catch(error => { - dispatch({ type: SUGGESTIONS_FETCH_FAIL, error, skipAlert: true }); - throw error; - }); - } else { - // Do nothing - return null; - } - }; - -const fetchSuggestionsForTimeline = () => (dispatch: AppDispatch) => { - dispatch(fetchSuggestions(20))?.then(() => dispatch(insertSuggestionsIntoTimeline())); -}; - -type SuggestionsAction = - | SuggestionsFetchRequestAction - | SuggestionsFetchSuccessAction - | SuggestionsFetchFailAction; - -export { - SUGGESTIONS_FETCH_REQUEST, - SUGGESTIONS_FETCH_SUCCESS, - SUGGESTIONS_FETCH_FAIL, - fetchSuggestions, - fetchSuggestionsForTimeline, - type SuggestionsAction, -}; diff --git a/packages/pl-fe/src/api/hooks/trends/use-suggested-accounts.ts b/packages/pl-fe/src/api/hooks/trends/use-suggested-accounts.ts new file mode 100644 index 000000000..a7cbf2d9c --- /dev/null +++ b/packages/pl-fe/src/api/hooks/trends/use-suggested-accounts.ts @@ -0,0 +1,27 @@ +import { useQuery } from '@tanstack/react-query'; + +import { importEntities } from 'pl-fe/actions/importer'; +import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; +import { useClient } from 'pl-fe/hooks/use-client'; +import { useFeatures } from 'pl-fe/hooks/use-features'; + +import type { Suggestion } from 'pl-api'; + +type MinifiedSuggestion = Omit & { account_id: string }; + +const useSuggestedAccounts = () => { + const client = useClient(); + const dispatch = useAppDispatch(); + const features = useFeatures(); + + return useQuery({ + queryKey: ['suggestions'], + queryFn: () => client.myAccount.getSuggestions().then((suggestions) => { + dispatch(importEntities({ accounts: suggestions.map(({ account }) => account) })); + return suggestions.map(({ account, ...suggestion }): MinifiedSuggestion => ({ account_id: account.id, ...suggestion })); + }), + enabled: features.suggestions || features.suggestionsV2, + }); +}; + +export { useSuggestedAccounts, type MinifiedSuggestion }; diff --git a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx index 2d6bff48c..5b4e32126 100644 --- a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx +++ b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx @@ -3,12 +3,12 @@ import { defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { useAccount } from 'pl-fe/api/hooks/accounts/use-account'; +import { useSuggestedAccounts } from 'pl-fe/api/hooks/trends/use-suggested-accounts'; import Card, { CardBody, CardTitle } from 'pl-fe/components/ui/card'; import HStack from 'pl-fe/components/ui/hstack'; import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import Emojify from '../emoji/emojify'; import ActionButton from '../ui/components/action-button'; @@ -69,10 +69,9 @@ interface IFeedSuggesetions { const FeedSuggestions: React.FC = ({ statusId, onMoveUp, onMoveDown }) => { const intl = useIntl(); - const suggestedProfiles = useAppSelector((state) => state.suggestions.items); - const isLoading = useAppSelector((state) => state.suggestions.isLoading); + const { data: suggestedProfiles, isLoading } = useSuggestedAccounts(); - if (!isLoading && suggestedProfiles.length === 0) return null; + if (!isLoading && suggestedProfiles?.length === 0) return null; const handleHotkeyMoveUp = (e?: KeyboardEvent): void => { if (onMoveUp) { @@ -107,7 +106,7 @@ const FeedSuggestions: React.FC = ({ statusId, onMoveUp, onMo - {suggestedProfiles.slice(0, 4).map((suggestedProfile) => ( + {suggestedProfiles?.slice(0, 4).map((suggestedProfile) => ( ))} diff --git a/packages/pl-fe/src/features/search/components/search-results.tsx b/packages/pl-fe/src/features/search/components/search-results.tsx index 664038909..f38c9c062 100644 --- a/packages/pl-fe/src/features/search/components/search-results.tsx +++ b/packages/pl-fe/src/features/search/components/search-results.tsx @@ -5,6 +5,7 @@ import { useSearchParams } from 'react-router-dom-v5-compat'; import { useAccount } from 'pl-fe/api/hooks/accounts/use-account'; import { useSearchAccounts, useSearchHashtags, useSearchStatuses } from 'pl-fe/api/hooks/search/use-search'; +import { useSuggestedAccounts } from 'pl-fe/api/hooks/trends/use-suggested-accounts'; import { useTrendingLinks } from 'pl-fe/api/hooks/trends/use-trending-links'; import { useTrendingStatuses } from 'pl-fe/api/hooks/trends/use-trending-statuses'; import Hashtag from 'pl-fe/components/hashtag'; @@ -19,7 +20,6 @@ import StatusContainer from 'pl-fe/containers/status-container'; import PlaceholderAccount from 'pl-fe/features/placeholder/components/placeholder-account'; import PlaceholderHashtag from 'pl-fe/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; import useTrends from 'pl-fe/queries/trends'; @@ -65,7 +65,7 @@ const SearchResults = () => { else setParams(params => ({ ...Object.fromEntries(params.entries()), type: newActiveFilter })); }; - const suggestions = useAppSelector((state) => state.suggestions.items); + const { data: suggestions } = useSuggestedAccounts(); const { data: trendingTags } = useTrends(); const { data: trendingStatuses } = useTrendingStatuses(); const { data: trendingLinks } = useTrendingLinks(); diff --git a/packages/pl-fe/src/features/ui/index.tsx b/packages/pl-fe/src/features/ui/index.tsx index b408afa98..3e996cada 100644 --- a/packages/pl-fe/src/features/ui/index.tsx +++ b/packages/pl-fe/src/features/ui/index.tsx @@ -11,7 +11,6 @@ import { fetchMarker } from 'pl-fe/actions/markers'; import { expandNotifications } from 'pl-fe/actions/notifications'; import { register as registerPushNotifications } from 'pl-fe/actions/push-notifications'; import { fetchScheduledStatuses } from 'pl-fe/actions/scheduled-statuses'; -import { fetchSuggestionsForTimeline } from 'pl-fe/actions/suggestions'; import { fetchHomeTimeline } from 'pl-fe/actions/timelines'; import { useUserStream } from 'pl-fe/api/hooks/streaming/use-user-stream'; import SidebarNavigation from 'pl-fe/components/sidebar-navigation'; @@ -388,9 +387,7 @@ const UI: React.FC = ({ children }) => { dispatch(fetchDraftStatuses()); - dispatch(fetchHomeTimeline(false, () => { - dispatch(fetchSuggestionsForTimeline()); - })); + dispatch(fetchHomeTimeline()); dispatch(expandNotifications()) // @ts-ignore diff --git a/packages/pl-fe/src/reducers/index.ts b/packages/pl-fe/src/reducers/index.ts index 935333036..1eba73eb7 100644 --- a/packages/pl-fe/src/reducers/index.ts +++ b/packages/pl-fe/src/reducers/index.ts @@ -35,7 +35,6 @@ import scheduled_statuses from './scheduled-statuses'; import security from './security'; import status_lists from './status-lists'; import statuses from './statuses'; -import suggestions from './suggestions'; import tags from './tags'; import timelines from './timelines'; import trending_statuses from './trending-statuses'; @@ -74,7 +73,6 @@ const reducers = { security, status_lists, statuses, - suggestions, tags, timelines, trending_statuses, diff --git a/packages/pl-fe/src/reducers/suggestions.test.ts b/packages/pl-fe/src/reducers/suggestions.test.ts deleted file mode 100644 index e875132e4..000000000 --- a/packages/pl-fe/src/reducers/suggestions.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import reducer from './suggestions'; - -describe('suggestions reducer', () => { - it('should return the initial state', () => { - expect(reducer(undefined, {} as any).toJS()).toEqual({ - items: [], - next: null, - isLoading: false, - }); - }); -}); diff --git a/packages/pl-fe/src/reducers/suggestions.ts b/packages/pl-fe/src/reducers/suggestions.ts deleted file mode 100644 index e33f767f0..000000000 --- a/packages/pl-fe/src/reducers/suggestions.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Record as ImmutableRecord } from 'immutable'; - -import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, type AccountsAction } from 'pl-fe/actions/accounts'; -import { DOMAIN_BLOCK_SUCCESS, type DomainBlocksAction } from 'pl-fe/actions/domain-blocks'; -import { - SUGGESTIONS_FETCH_REQUEST, - SUGGESTIONS_FETCH_SUCCESS, - SUGGESTIONS_FETCH_FAIL, - type SuggestionsAction, -} from 'pl-fe/actions/suggestions'; - -import type { Suggestion as SuggestionEntity } from 'pl-api'; - -const ReducerRecord = ImmutableRecord({ - items: Array(), - isLoading: false, -}); - -type State = ReturnType; - -const minifySuggestion = ({ account, ...suggestion }: SuggestionEntity) => ({ - ...suggestion, - account_id: account.id, -}); - -type MinifiedSuggestion = ReturnType; - -const importSuggestions = (state: State, suggestions: SuggestionEntity[]) => - state.withMutations(state => { - state.update('items', items => items.concat(suggestions.map(minifySuggestion))); - state.set('isLoading', false); - }); - -const dismissAccount = (state: State, accountId: string) => - state.update('items', items => items.filter(item => item.account_id !== accountId)); - -const dismissAccounts = (state: State, accountIds: string[]) => - state.update('items', items => items.filter(item => !accountIds.includes(item.account_id))); - -const suggestionsReducer = (state: State = ReducerRecord(), action: AccountsAction | DomainBlocksAction | SuggestionsAction) => { - switch (action.type) { - case SUGGESTIONS_FETCH_REQUEST: - return state.set('isLoading', true); - case SUGGESTIONS_FETCH_SUCCESS: - return importSuggestions(state, action.suggestions); - case SUGGESTIONS_FETCH_FAIL: - return state.set('isLoading', false); - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_MUTE_SUCCESS: - return dismissAccount(state, action.relationship.id); - case DOMAIN_BLOCK_SUCCESS: - return dismissAccounts(state, action.accounts); - default: - return state; - } -}; - -export { suggestionsReducer as default }; diff --git a/packages/pl-fe/src/reducers/user-lists.ts b/packages/pl-fe/src/reducers/user-lists.ts index 669dd451c..f0d323a0a 100644 --- a/packages/pl-fe/src/reducers/user-lists.ts +++ b/packages/pl-fe/src/reducers/user-lists.ts @@ -203,7 +203,7 @@ const userLists = (state = initialState, action: AccountsAction | DirectoryActio case PINNED_ACCOUNTS_FETCH_SUCCESS: return normalizeList(state, ['pinned', action.accountId], action.accounts, action.next); case BIRTHDAY_REMINDERS_FETCH_SUCCESS: - return normalizeList(state, ['birthday_reminders', action.accountId], action.accounts, action.next); + return normalizeList(state, ['birthday_reminders', action.accountId], action.accounts); case FAMILIAR_FOLLOWERS_FETCH_SUCCESS: return normalizeList(state, ['familiar_followers', action.accountId], action.accounts, action.next); case EVENT_PARTICIPATIONS_FETCH_SUCCESS: