diff --git a/src/features/ui/components/latest-accounts-panel.tsx b/src/features/ui/components/latest-accounts-panel.tsx new file mode 100644 index 000000000..c1221defe --- /dev/null +++ b/src/features/ui/components/latest-accounts-panel.tsx @@ -0,0 +1,57 @@ +import xIcon from '@tabler/icons/outline/x.svg'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import Widget from 'soapbox/components/ui/widget.tsx'; +import AccountContainer from 'soapbox/containers/account-container.tsx'; +import PlaceholderSidebarSuggestions from 'soapbox/features/placeholder/components/placeholder-sidebar-suggestions.tsx'; +import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts'; +import { useDismissSuggestion, useLocalSuggestions } from 'soapbox/queries/suggestions.ts'; + +import type { Account as AccountEntity } from 'soapbox/types/entities.ts'; + +const messages = defineMessages({ + dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, +}); + +interface ILatestAccountsPanel { + limit: number; +} + +const LatestAccountsPanel: React.FC = ({ limit }) => { + const intl = useIntl(); + + const { account } = useOwnAccount(); + const { data: suggestions, isFetching } = useLocalSuggestions(); + const dismissSuggestion = useDismissSuggestion(); + + const suggestionsToRender = suggestions.slice(0, limit); + + const handleDismiss = (account: AccountEntity) => { + dismissSuggestion.mutate(account.id); + }; + + if (!isFetching && !suggestions.length) { + return null; + } + + return ( + }> + {isFetching ? ( + + ) : ( + suggestionsToRender.map((suggestion: any) => ( + , but it isn't + id={suggestion.account} + actionIcon={xIcon} + actionTitle={intl.formatMessage(messages.dismissSuggestion)} + onActionClick={account ? handleDismiss : undefined} + /> + )) + )} + + ); +}; + +export default LatestAccountsPanel; diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 5654b246b..ff6b05aca 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -93,7 +93,7 @@ export const ProfileFieldsPanel = lazy(() => import('soapbox/features/ui/compone export const PinnedAccountsPanel = lazy(() => import('soapbox/features/ui/components/pinned-accounts-panel.tsx')); export const InstanceInfoPanel = lazy(() => import('soapbox/features/ui/components/instance-info-panel.tsx')); export const InstanceModerationPanel = lazy(() => import('soapbox/features/ui/components/instance-moderation-panel.tsx')); -export const LatestAccountsPanel = lazy(() => import('soapbox/features/admin/components/latest-accounts-panel.tsx')); +export const LatestAdminAccountsPanel = lazy(() => import('soapbox/features/admin/components/latest-accounts-panel.tsx')); export const SidebarMenu = lazy(() => import('soapbox/components/sidebar-menu.tsx')); export const ModalContainer = lazy(() => import('soapbox/features/ui/containers/modal-container.ts')); export const ProfileHoverCard = lazy(() => import('soapbox/components/profile-hover-card.tsx')); @@ -109,6 +109,7 @@ export const FederationRestrictions = lazy(() => import('soapbox/features/federa export const Aliases = lazy(() => import('soapbox/features/aliases/index.tsx')); export const Migration = lazy(() => import('soapbox/features/migration/index.tsx')); export const WhoToFollowPanel = lazy(() => import('soapbox/features/ui/components/who-to-follow-panel.tsx')); +export const LatestAccountsPanel = lazy(() => import('soapbox/features/ui/components/latest-accounts-panel.tsx')); export const FollowRecommendations = lazy(() => import('soapbox/features/follow-recommendations/index.tsx')); export const Directory = lazy(() => import('soapbox/features/directory/index.tsx')); export const RegisterInvite = lazy(() => import('soapbox/features/register-invite/index.tsx')); diff --git a/src/pages/admin-page.tsx b/src/pages/admin-page.tsx index 73b923ff6..facc4caff 100644 --- a/src/pages/admin-page.tsx +++ b/src/pages/admin-page.tsx @@ -1,7 +1,5 @@ import Layout from 'soapbox/components/ui/layout.tsx'; -import { - LatestAccountsPanel, -} from 'soapbox/features/ui/util/async-components.ts'; +import { LatestAdminAccountsPanel } from 'soapbox/features/ui/util/async-components.ts'; import LinkFooter from '../features/ui/components/link-footer.tsx'; @@ -17,7 +15,7 @@ const AdminPage: React.FC = ({ children }) => { - + diff --git a/src/pages/home-page.tsx b/src/pages/home-page.tsx index dafb49e15..8db870b76 100644 --- a/src/pages/home-page.tsx +++ b/src/pages/home-page.tsx @@ -20,6 +20,7 @@ import { BirthdayPanel, CtaBanner, AnnouncementsPanel, + LatestAccountsPanel, } from 'soapbox/features/ui/util/async-components.ts'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts'; @@ -62,6 +63,14 @@ const HomePage: React.FC = ({ children }) => { const acct = account ? account.acct : ''; const avatar = account ? account.avatar : ''; + const renderSuggestions = () => { + if (features.suggestionsLocal && pathname !== '/timeline/global') { + return ; + } else if (features.suggestions) { + return ; + } + }; + return ( <> @@ -120,9 +129,7 @@ const HomePage: React.FC = ({ children }) => { {features.trends && ( )} - {features.suggestions && ( - - )} + {renderSuggestions()} {features.birthdays && ( )} diff --git a/src/queries/suggestions.ts b/src/queries/suggestions.ts index 1a973410b..c8e1624f4 100644 --- a/src/queries/suggestions.ts +++ b/src/queries/suggestions.ts @@ -24,6 +24,7 @@ type PageParam = { const SuggestionKeys = { suggestions: ['suggestions'] as const, + localSuggestions: ['suggestions', 'local'] as const, }; const useSuggestions = () => { @@ -133,4 +134,53 @@ function useOnboardingSuggestions() { }; } -export { useOnboardingSuggestions, useSuggestions, useDismissSuggestion }; \ No newline at end of file +const useLocalSuggestions = () => { + const api = useApi(); + const dispatch = useAppDispatch(); + + const getLocalSuggestions = async (pageParam: PageParam): Promise> => { + 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( + (prev: any, curr: any) => [...prev, ...curr.result], + [], + ); + + return { + ...result, + data: data || [], + }; +}; + + +export { useOnboardingSuggestions, useSuggestions, useDismissSuggestion, useLocalSuggestions }; \ No newline at end of file diff --git a/src/utils/features.ts b/src/utils/features.ts index 061326bf4..ccad8d361 100644 --- a/src/utils/features.ts +++ b/src/utils/features.ts @@ -1027,6 +1027,8 @@ const getInstanceFeatures = (instance: InstanceV1 | InstanceV2) => { features.includes('v2_suggestions'), ]), + suggestionsLocal: v.software === DITTO, + /** * Supports V2 suggested accounts. * @see GET /api/v2/suggestions