From dba85273b206c94da8247d57550a2ffb6aac1c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 30 Oct 2024 22:32:10 +0100 Subject: [PATCH] pl-fe: Display interaction requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../statuses/use-interaction-requests.ts | 50 +++++++++++++++++++ .../pl-fe/src/components/sidebar-menu.tsx | 12 +++++ .../src/components/sidebar-navigation.tsx | 12 +++++ .../features/interaction-requests/index.tsx | 11 ++++ packages/pl-fe/src/features/ui/index.tsx | 2 + .../src/features/ui/util/async-components.ts | 1 + 6 files changed, 88 insertions(+) create mode 100644 packages/pl-fe/src/api/hooks/statuses/use-interaction-requests.ts create mode 100644 packages/pl-fe/src/features/interaction-requests/index.tsx diff --git a/packages/pl-fe/src/api/hooks/statuses/use-interaction-requests.ts b/packages/pl-fe/src/api/hooks/statuses/use-interaction-requests.ts new file mode 100644 index 000000000..b77264808 --- /dev/null +++ b/packages/pl-fe/src/api/hooks/statuses/use-interaction-requests.ts @@ -0,0 +1,50 @@ +import { type InfiniteData, useInfiniteQuery } 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 { InteractionRequest, PaginatedResponse } from 'pl-api'; +import type { AppDispatch } from 'pl-fe/store'; + +const minifyInteractionRequest = ({ account, status, reply, ...interactionRequest }: InteractionRequest) => ({ + account_id: account.id, + status: status?.id || null, + reply: reply?.id || null, + ...interactionRequest, +}); + +type MinifiedInteractionRequest = ReturnType; + +const minifyInteractionRequestsList = (dispatch: AppDispatch, { previous, next, items, ...response }: PaginatedResponse): PaginatedResponse => { + dispatch(importEntities({ + statuses: items.map(item => [item.status, item.reply]).flat(), + })); + + return { + ...response, + previous: previous ? () => previous().then(response => minifyInteractionRequestsList(dispatch, response)) : null, + next: next ? () => next().then(response => minifyInteractionRequestsList(dispatch, response)) : null, + items: items.map(minifyInteractionRequest), + }; +}; + +const useInteractionRequests = (select?: (data: InfiniteData>) => T) => { + const client = useClient(); + const features = useFeatures(); + const dispatch = useAppDispatch(); + + return useInfiniteQuery({ + queryKey: ['interaction_requests'], + queryFn: ({ pageParam }) => pageParam.next?.() || client.interactionRequests.getInteractionRequests().then(response => minifyInteractionRequestsList(dispatch, response)), + initialPageParam: { previous: null, next: null, items: [], partial: false } as PaginatedResponse, + getNextPageParam: (page) => page.next ? page : undefined, + enabled: features.interactionRequests, + select, + }); +}; + +const useInteractionRequestsCount = () => useInteractionRequests(data => data.pages.map(({ items }) => items).flat().length); + +export { useInteractionRequests, useInteractionRequestsCount }; diff --git a/packages/pl-fe/src/components/sidebar-menu.tsx b/packages/pl-fe/src/components/sidebar-menu.tsx index 90e51a2b5..f2338dcc1 100644 --- a/packages/pl-fe/src/components/sidebar-menu.tsx +++ b/packages/pl-fe/src/components/sidebar-menu.tsx @@ -6,6 +6,7 @@ import { Link, NavLink } from 'react-router-dom'; import { fetchOwnAccounts, logOut, switchAccount } from 'pl-fe/actions/auth'; import { useAccount } from 'pl-fe/api/hooks/accounts/use-account'; +import { useInteractionRequestsCount } from 'pl-fe/api/hooks/statuses/use-interaction-requests'; import Account from 'pl-fe/components/account'; import Divider from 'pl-fe/components/ui/divider'; import HStack from 'pl-fe/components/ui/hstack'; @@ -42,6 +43,7 @@ const messages = defineMessages({ drafts: { id: 'navigation.drafts', defaultMessage: 'Drafts' }, addAccount: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' }, followRequests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + interactionRequests: { id: 'navigation.interaction_requests', defaultMessage: 'Interaction requests' }, close: { id: 'lightbox.close', defaultMessage: 'Close' }, login: { id: 'account.login', defaultMessage: 'Log in' }, register: { id: 'account.register', defaultMessage: 'Sign up' }, @@ -96,6 +98,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const otherAccounts: ImmutableList = useAppSelector((state) => getOtherAccounts(state)); const { settings } = useSettingsStore(); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); + const interactionRequestsCount = useInteractionRequestsCount().data || 0; const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); const draftCount = useAppSelector((state) => state.draft_statuses.size); // const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); @@ -232,6 +235,15 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { /> )} + {(interactionRequestsCount > 0) && ( + + )} + {features.conversations && ( { const notificationCount = useAppSelector((state) => state.notifications.unread); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); + const interactionRequestsCount = useInteractionRequestsCount().data || 0; const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); const draftCount = useAppSelector((state) => state.draft_statuses.size); @@ -74,6 +77,15 @@ const SidebarNavigation = () => { }); } + if (interactionRequestsCount > 0) { + menu.push({ + to: '/interaction_requests', + text: intl.formatMessage(messages.interactionRequests), + icon: require('@tabler/icons/outline/flag-question.svg'), + count: interactionRequestsCount, + }); + } + if (features.bookmarks) { menu.push({ to: '/bookmarks', diff --git a/packages/pl-fe/src/features/interaction-requests/index.tsx b/packages/pl-fe/src/features/interaction-requests/index.tsx new file mode 100644 index 000000000..97250fed8 --- /dev/null +++ b/packages/pl-fe/src/features/interaction-requests/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +import { useInteractionRequests } from 'pl-fe/api/hooks/statuses/use-interaction-requests'; + +const InteractionRequests = () => { + const interactionRequestsQuery = useInteractionRequests(); + + return <>{JSON.stringify(interactionRequestsQuery.data)}; +}; + +export { InteractionRequests as default }; diff --git a/packages/pl-fe/src/features/ui/index.tsx b/packages/pl-fe/src/features/ui/index.tsx index 02b1e0d54..a9f574d23 100644 --- a/packages/pl-fe/src/features/ui/index.tsx +++ b/packages/pl-fe/src/features/ui/index.tsx @@ -138,6 +138,7 @@ import { Circle, BubbleTimeline, InteractionPolicies, + InteractionRequests, } from './util/async-components'; import GlobalHotkeys from './util/global-hotkeys'; import { WrappedRoute } from './util/react-router-helpers'; @@ -255,6 +256,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {(features.filters || features.filtersV2) && } {(features.filters || features.filtersV2) && } {(features.followedHashtagsList) && } + {features.interactionRequests && } diff --git a/packages/pl-fe/src/features/ui/util/async-components.ts b/packages/pl-fe/src/features/ui/util/async-components.ts index 2c2cddd45..d97ffd6f8 100644 --- a/packages/pl-fe/src/features/ui/util/async-components.ts +++ b/packages/pl-fe/src/features/ui/util/async-components.ts @@ -56,6 +56,7 @@ export const HomeTimeline = lazy(() => import('pl-fe/features/home-timeline')); export const ImportData = lazy(() => import('pl-fe/features/import-data')); export const IntentionalError = lazy(() => import('pl-fe/features/intentional-error')); export const InteractionPolicies = lazy(() => import('pl-fe/features/interaction-policies')); +export const InteractionRequests = lazy(() => import('pl-fe/features/interaction-requests')); export const LandingTimeline = lazy(() => import('pl-fe/features/landing-timeline')); export const Lists = lazy(() => import('pl-fe/features/lists')); export const ListTimeline = lazy(() => import('pl-fe/features/list-timeline'));