kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Simplify/consolidate latest accounts code, fix suggestions pagination
rodzic
742e339c53
commit
2d973c9cd0
|
@ -1,41 +1,33 @@
|
||||||
import { debounce } from 'es-toolkit';
|
import { debounce } from 'es-toolkit';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { fetchSuggestions } from 'soapbox/actions/suggestions.ts';
|
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
|
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
|
||||||
import { Column } from 'soapbox/components/ui/column.tsx';
|
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||||
import Text from 'soapbox/components/ui/text.tsx';
|
import Text from 'soapbox/components/ui/text.tsx';
|
||||||
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
||||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
import { useSuggestions } from 'soapbox/queries/suggestions.ts';
|
||||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'follow_recommendations.heading', defaultMessage: 'Suggested Profiles' },
|
heading: { id: 'follow_recommendations.heading', defaultMessage: 'Suggested Profiles' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const FollowRecommendations: React.FC = () => {
|
interface IFollowRecommendations {
|
||||||
const dispatch = useAppDispatch();
|
local?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FollowRecommendations: React.FC<IFollowRecommendations> = ({ local = false }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const suggestions = useAppSelector((state) => state.suggestions.items);
|
const { data: suggestions, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = useSuggestions({ local });
|
||||||
const hasMore = useAppSelector((state) => !!state.suggestions.next);
|
|
||||||
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
|
||||||
|
|
||||||
const handleLoadMore = debounce(() => {
|
const handleLoadMore = debounce(() => {
|
||||||
if (isLoading) {
|
if (hasNextPage && !isFetchingNextPage) {
|
||||||
return null;
|
fetchNextPage();
|
||||||
}
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return dispatch(fetchSuggestions({ limit: 20 }));
|
if (suggestions.length === 0 && !isLoading) {
|
||||||
}, 300);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchSuggestions({ limit: 20 }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (suggestions.size === 0 && !isLoading) {
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.heading)}>
|
<Column label={intl.formatMessage(messages.heading)}>
|
||||||
<Text align='center'>
|
<Text align='center'>
|
||||||
|
@ -52,7 +44,7 @@ const FollowRecommendations: React.FC = () => {
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
scrollKey='suggestions'
|
scrollKey='suggestions'
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
hasMore={hasMore}
|
hasMore={hasNextPage}
|
||||||
itemClassName='pb-4'
|
itemClassName='pb-4'
|
||||||
>
|
>
|
||||||
{suggestions.map((suggestion) => (
|
{suggestions.map((suggestion) => (
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import xIcon from '@tabler/icons/outline/x.svg';
|
import xIcon from '@tabler/icons/outline/x.svg';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Text from 'soapbox/components/ui/text.tsx';
|
||||||
import Widget from 'soapbox/components/ui/widget.tsx';
|
import Widget from 'soapbox/components/ui/widget.tsx';
|
||||||
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
||||||
import PlaceholderSidebarSuggestions from 'soapbox/features/placeholder/components/placeholder-sidebar-suggestions.tsx';
|
import PlaceholderSidebarSuggestions from 'soapbox/features/placeholder/components/placeholder-sidebar-suggestions.tsx';
|
||||||
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
|
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
|
||||||
import { useDismissSuggestion, useLocalSuggestions } from 'soapbox/queries/suggestions.ts';
|
import { useDismissSuggestion, useSuggestions } from 'soapbox/queries/suggestions.ts';
|
||||||
|
|
||||||
import type { Account as AccountEntity } from 'soapbox/types/entities.ts';
|
import type { Account as AccountEntity } from 'soapbox/types/entities.ts';
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ const LatestAccountsPanel: React.FC<ILatestAccountsPanel> = ({ limit }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const { data: suggestions, isFetching } = useLocalSuggestions();
|
const { data: suggestions, isFetching } = useSuggestions({ local: true });
|
||||||
const dismissSuggestion = useDismissSuggestion();
|
const dismissSuggestion = useDismissSuggestion();
|
||||||
|
|
||||||
const suggestionsToRender = suggestions.slice(0, limit);
|
const suggestionsToRender = suggestions.slice(0, limit);
|
||||||
|
@ -35,7 +37,16 @@ const LatestAccountsPanel: React.FC<ILatestAccountsPanel> = ({ limit }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget title={<FormattedMessage id='latest_accounts.title' defaultMessage='Latest Accounts' />}>
|
<Widget
|
||||||
|
title={<FormattedMessage id='latest_accounts.title' defaultMessage='Latest Accounts' />}
|
||||||
|
action={
|
||||||
|
<Link className='text-right' to='/suggestions/local'>
|
||||||
|
<Text tag='span' theme='primary' size='sm' className='hover:underline'>
|
||||||
|
<FormattedMessage id='feed_suggestions.view_all' defaultMessage='View all' />
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
<PlaceholderSidebarSuggestions limit={limit} />
|
<PlaceholderSidebarSuggestions limit={limit} />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -262,8 +262,9 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
|
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/search' page={SearchPage} component={Search} content={children} publicRoute />
|
<WrappedRoute path='/search' page={SearchPage} component={Search} content={children} publicRoute />
|
||||||
{features.suggestions && <WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />}
|
{features.suggestionsLocal && <WrappedRoute path='/suggestions/local' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} componentParams={{ local: true }} />}
|
||||||
{features.profileDirectory && <WrappedRoute path='/directory' publicRoute page={DefaultPage} component={Directory} content={children} />}
|
{features.suggestions && <WrappedRoute path='/suggestions' exact publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />}
|
||||||
|
{features.profileDirectory && <WrappedRoute path='/directory' exact publicRoute page={DefaultPage} component={Directory} content={children} />}
|
||||||
{features.events && <WrappedRoute path='/events' page={EventsPage} component={Events} content={children} />}
|
{features.events && <WrappedRoute path='/events' page={EventsPage} component={Events} content={children} />}
|
||||||
|
|
||||||
{features.chats && <WrappedRoute path='/chats' exact page={ChatsPage} component={ChatIndex} content={children} />}
|
{features.chats && <WrappedRoute path='/chats' exact page={ChatsPage} component={ChatIndex} content={children} />}
|
||||||
|
|
|
@ -7,14 +7,15 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||||
|
|
||||||
import { PaginatedResult, removePageItem } from '../utils/queries.ts';
|
import { PaginatedResult, removePageItem } from '../utils/queries.ts';
|
||||||
|
|
||||||
import type { IAccount } from './accounts.ts';
|
import type { Account } from 'soapbox/schemas/account.ts';
|
||||||
|
|
||||||
type Suggestion = {
|
type Suggestion = {
|
||||||
source: 'staff';
|
source: string;
|
||||||
account: IAccount;
|
account: Account;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
|
source: string;
|
||||||
account: string;
|
account: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,45 +28,48 @@ const SuggestionKeys = {
|
||||||
localSuggestions: ['suggestions', 'local'] as const,
|
localSuggestions: ['suggestions', 'local'] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
const useSuggestions = () => {
|
interface UseSuggestionsOpts {
|
||||||
|
local?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSuggestions = (opts?: UseSuggestionsOpts) => {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const local = opts?.local ?? false;
|
||||||
|
|
||||||
const getV2Suggestions = async (pageParam: PageParam): Promise<PaginatedResult<Result>> => {
|
const getV2Suggestions = async (pageParam?: PageParam): Promise<PaginatedResult<Result>> => {
|
||||||
const endpoint = pageParam?.link || '/api/v2/suggestions';
|
const endpoint = pageParam?.link || (local ? '/api/v2/ditto/suggestions/local' : '/api/v2/suggestions');
|
||||||
const response = await api.get(endpoint);
|
const response = await api.get(endpoint);
|
||||||
const next = response.next();
|
const next = response.next();
|
||||||
const hasMore = !!next;
|
|
||||||
|
|
||||||
const data: Suggestion[] = await response.json();
|
const data: Suggestion[] = await response.json();
|
||||||
const accounts = data.map(({ account }) => account);
|
const accounts = data.map(({ account }) => account);
|
||||||
const accountIds = accounts.map((account) => account.id);
|
const accountIds = accounts.map((account) => account.id);
|
||||||
|
|
||||||
dispatch(importFetchedAccounts(accounts));
|
dispatch(importFetchedAccounts(accounts));
|
||||||
dispatch(fetchRelationships(accountIds));
|
dispatch(fetchRelationships(accountIds));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: data.map(x => ({ ...x, account: x.account.id })),
|
result: data.map(x => ({ ...x, account: x.account.id })),
|
||||||
link: next ?? undefined,
|
link: next ?? undefined,
|
||||||
hasMore,
|
hasMore: !!next,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = useInfiniteQuery({
|
const result = useInfiniteQuery({
|
||||||
queryKey: SuggestionKeys.suggestions,
|
queryKey: local ? SuggestionKeys.localSuggestions : SuggestionKeys.suggestions,
|
||||||
queryFn: ({ pageParam }: any) => getV2Suggestions(pageParam),
|
queryFn: ({ pageParam }) => getV2Suggestions(pageParam),
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
initialPageParam: { nextLink: undefined },
|
initialPageParam: undefined as PageParam | undefined,
|
||||||
getNextPageParam: (config) => {
|
getNextPageParam: (config): PageParam | undefined => {
|
||||||
if (config?.hasMore) {
|
if (config?.hasMore) {
|
||||||
return { nextLink: config?.link };
|
return { link: config?.link };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: any = result.data?.pages.reduce<Suggestion[]>(
|
const data = result.data?.pages.reduce<Result[]>(
|
||||||
(prev: any, curr: any) => [...prev, ...curr.result],
|
(prev, curr) => [...prev, ...curr.result],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -134,53 +138,5 @@ function useOnboardingSuggestions() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const useLocalSuggestions = () => {
|
|
||||||
const api = useApi();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const getLocalSuggestions = async (pageParam: PageParam): Promise<PaginatedResult<Result>> => {
|
export { useOnboardingSuggestions, useSuggestions, useDismissSuggestion };
|
||||||
const endpoint = pageParam?.link || '/api/v2/ditto/suggestions/local';
|
|
||||||
const response = await api.get(endpoint);
|
|
||||||
const next = response.next();
|
|
||||||
const hasMore = !!next;
|
|
||||||
|
|
||||||
const data: Suggestion[] = await response.json();
|
|
||||||
const accounts = data.map(({ account }) => account);
|
|
||||||
const accountIds = accounts.map((account) => account.id);
|
|
||||||
dispatch(importFetchedAccounts(accounts));
|
|
||||||
dispatch(fetchRelationships(accountIds));
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: data.map(x => ({ ...x, account: x.account.id })),
|
|
||||||
link: next ?? undefined,
|
|
||||||
hasMore,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = useInfiniteQuery({
|
|
||||||
queryKey: SuggestionKeys.localSuggestions,
|
|
||||||
queryFn: ({ pageParam }: any) => getLocalSuggestions(pageParam),
|
|
||||||
placeholderData: keepPreviousData,
|
|
||||||
initialPageParam: { nextLink: undefined },
|
|
||||||
getNextPageParam: (config) => {
|
|
||||||
if (config?.hasMore) {
|
|
||||||
return { nextLink: config?.link };
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data: any = result.data?.pages.reduce<Suggestion[]>(
|
|
||||||
(prev: any, curr: any) => [...prev, ...curr.result],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
data: data || [],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export { useOnboardingSuggestions, useSuggestions, useDismissSuggestion, useLocalSuggestions };
|
|
Ładowanie…
Reference in New Issue