pl-fe: Display interaction requests

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-30 22:32:10 +01:00
parent 3b0cd07e1d
commit dba85273b2
6 changed files with 88 additions and 0 deletions

View file

@ -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<typeof minifyInteractionRequest>;
const minifyInteractionRequestsList = (dispatch: AppDispatch, { previous, next, items, ...response }: PaginatedResponse<InteractionRequest>): PaginatedResponse<MinifiedInteractionRequest> => {
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 = <T>(select?: (data: InfiniteData<PaginatedResponse<MinifiedInteractionRequest>>) => 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<MinifiedInteractionRequest>,
getNextPageParam: (page) => page.next ? page : undefined,
enabled: features.interactionRequests,
select,
});
};
const useInteractionRequestsCount = () => useInteractionRequests(data => data.pages.map(({ items }) => items).flat().length);
export { useInteractionRequests, useInteractionRequestsCount };

View file

@ -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<AccountEntity> = 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) && (
<SidebarLink
to='/interaction_requests'
icon={require('@tabler/icons/outline/flag-question.svg')}
text={intl.formatMessage(messages.interactionRequests)}
onClick={closeSidebar}
/>
)}
{features.conversations && (
<SidebarLink
to='/conversations'

View file

@ -1,6 +1,7 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useInteractionRequestsCount } from 'pl-fe/api/hooks/statuses/use-interaction-requests';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import { useStatContext } from 'pl-fe/contexts/stat-context';
@ -31,6 +32,7 @@ const messages = defineMessages({
scheduledStatuses: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled posts' },
drafts: { id: 'navigation.drafts', defaultMessage: 'Drafts' },
conversations: { id: 'navigation.direct_messages', defaultMessage: 'Direct messages' },
interactionRequests: { id: 'navigation.interaction_requests', defaultMessage: 'Interaction requests' },
});
/** Desktop sidebar with links to different views in the app. */
@ -47,6 +49,7 @@ const SidebarNavigation = () => {
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',

View file

@ -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 };

View file

@ -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<ISwitchingColumnsArea> = ({ children }) =>
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters/:id' layout={DefaultLayout} component={EditFilter} content={children} />}
{(features.filters || features.filtersV2) && <WrappedRoute path='/filters' layout={DefaultLayout} component={Filters} content={children} />}
{(features.followedHashtagsList) && <WrappedRoute path='/followed_tags' layout={DefaultLayout} component={FollowedTags} content={children} />}
{features.interactionRequests && <WrappedRoute path='/interaction_requests' layout={DefaultLayout} component={InteractionRequests} content={children} />}
<WrappedRoute path='/@:username' publicRoute exact layout={ProfileLayout} component={AccountTimeline} content={children} />
<WrappedRoute path='/@:username/with_replies' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/@:username/followers' publicRoute={!authenticatedProfile} layout={ProfileLayout} component={Followers} content={children} />

View file

@ -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'));