pl-fe: migrate follow requests to tanstack query

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-04 19:37:59 +01:00
parent 0218fa445f
commit f6e2f7c6c6
12 changed files with 103 additions and 499 deletions

View file

@ -7,7 +7,6 @@ import { normalizeAccount } from 'pl-fe/normalizers/account';
import { ListRecord, ReducerRecord } from 'pl-fe/reducers/user-lists';
import {
authorizeFollowRequest,
blockAccount,
createAccount,
expandFollowRequests,
@ -940,240 +939,3 @@ describe('fetchRelationships()', () => {
});
});
});
describe('fetchFollowRequests()', () => {
describe('when logged out', () => {
beforeEach(() => {
const state = { ...rootState, me: null };
store = mockStore(state);
});
it('should do nothing', async() => {
await store.dispatch(fetchFollowRequests());
const actions = store.getActions();
expect(actions).toEqual([]);
});
});
describe('when logged in', () => {
beforeEach(() => {
const state = { ...rootState, me: '123' };
store = mockStore(state);
});
describe('with a successful API request', () => {
beforeEach(() => {
const state = {
...rootState,
me: '123',
relationships: ImmutableMap(),
};
store = mockStore(state);
__stub((mock) => {
mock.onGet('/api/v1/follow_requests').reply(200, [], {
link: '<https://example.com/api/v1/follow_requests?since_id=1>; rel=\'prev\'',
});
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUESTS_FETCH_REQUEST' },
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
{
type: 'FOLLOW_REQUESTS_FETCH_SUCCESS',
accounts: [],
next: null,
},
];
await store.dispatch(fetchFollowRequests());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
describe('with an unsuccessful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onGet('/api/v1/follow_requests').networkError();
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUESTS_FETCH_REQUEST' },
{ type: 'FOLLOW_REQUESTS_FETCH_FAIL', error: new Error('Network Error') },
];
await store.dispatch(fetchFollowRequests());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
});
});
describe('expandFollowRequests()', () => {
describe('when logged out', () => {
beforeEach(() => {
const state = { ...rootState, me: null };
store = mockStore(state);
});
it('should do nothing', async() => {
await store.dispatch(expandFollowRequests());
const actions = store.getActions();
expect(actions).toEqual([]);
});
});
describe('when logged in', () => {
beforeEach(() => {
const state = {
...rootState,
me: '123',
user_lists: ReducerRecord({
follow_requests: ListRecord({
next: 'next_url',
}),
}),
};
store = mockStore(state);
});
describe('when the url is null', () => {
beforeEach(() => {
const state = {
...rootState,
me: '123',
user_lists: ReducerRecord({
follow_requests: ListRecord({
next: null,
}),
}),
};
store = mockStore(state);
});
it('should do nothing', async() => {
await store.dispatch(expandFollowRequests());
const actions = store.getActions();
expect(actions).toEqual([]);
});
});
describe('with a successful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onGet('next_url').reply(200, [], {
link: '<next_url>; rel=\'prev\'',
});
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUESTS_EXPAND_REQUEST' },
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
{
type: 'FOLLOW_REQUESTS_EXPAND_SUCCESS',
accounts: [],
next: null,
},
];
await store.dispatch(expandFollowRequests());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
describe('with an unsuccessful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onGet('next_url').networkError();
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUESTS_EXPAND_REQUEST' },
{ type: 'FOLLOW_REQUESTS_EXPAND_FAIL', error: new Error('Network Error') },
];
await store.dispatch(expandFollowRequests());
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
});
});
describe('authorizeFollowRequest()', () => {
const id = '1';
describe('when logged out', () => {
beforeEach(() => {
const state = { ...rootState, me: null };
store = mockStore(state);
});
it('should do nothing', async() => {
await store.dispatch(authorizeFollowRequest(id));
const actions = store.getActions();
expect(actions).toEqual([]);
});
});
describe('when logged in', () => {
beforeEach(() => {
const state = { ...rootState, me: '123' };
store = mockStore(state);
});
describe('with a successful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onPost(`/api/v1/follow_requests/${id}/authorize`).reply(200);
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUEST_AUTHORIZE_REQUEST', id },
{ type: 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS', id },
];
await store.dispatch(authorizeFollowRequest(id));
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
describe('with an unsuccessful API request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onPost(`/api/v1/follow_requests/${id}/authorize`).networkError();
});
});
it('should dispatch the correct actions', async() => {
const expectedActions = [
{ type: 'FOLLOW_REQUEST_AUTHORIZE_REQUEST', id },
{ type: 'FOLLOW_REQUEST_AUTHORIZE_FAIL', id, error: new Error('Network Error') },
];
await store.dispatch(authorizeFollowRequest(id));
const actions = store.getActions();
expect(actions).toEqual(expectedActions);
});
});
});
});

View file

@ -3,7 +3,6 @@ import {
type UpdateNotificationSettingsParams,
type Account,
type CreateAccountParams,
type PaginatedResponse,
type PlApiClient,
type Relationship,
type Token,
@ -51,22 +50,6 @@ const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST' as const;
const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS' as const;
const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL' as const;
const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST' as const;
const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS' as const;
const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL' as const;
const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST' as const;
const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS' as const;
const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL' as const;
const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST' as const;
const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS' as const;
const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL' as const;
const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST' as const;
const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS' as const;
const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL' as const;
const NOTIFICATION_SETTINGS_REQUEST = 'NOTIFICATION_SETTINGS_REQUEST' as const;
const NOTIFICATION_SETTINGS_SUCCESS = 'NOTIFICATION_SETTINGS_SUCCESS' as const;
const NOTIFICATION_SETTINGS_FAIL = 'NOTIFICATION_SETTINGS_FAIL' as const;
@ -313,120 +296,6 @@ const fetchRelationships = (accountIds: string[]) =>
.then(response => dispatch(importEntities({ relationships: response })));
};
const fetchFollowRequests = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
dispatch(fetchFollowRequestsRequest());
return getClient(getState()).myAccount.getFollowRequests()
.then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchFollowRequestsSuccess(response.items, response.next));
})
.catch(error => dispatch(fetchFollowRequestsFail(error)));
};
const fetchFollowRequestsRequest = () => ({
type: FOLLOW_REQUESTS_FETCH_REQUEST,
});
const fetchFollowRequestsSuccess = (accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
type: FOLLOW_REQUESTS_FETCH_SUCCESS,
accounts,
next,
});
const fetchFollowRequestsFail = (error: unknown) => ({
type: FOLLOW_REQUESTS_FETCH_FAIL,
error,
});
const expandFollowRequests = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
const next = getState().user_lists.follow_requests.next;
if (next === null) return null;
dispatch(expandFollowRequestsRequest());
return next().then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(expandFollowRequestsSuccess(response.items, response.next));
}).catch(error => dispatch(expandFollowRequestsFail(error)));
};
const expandFollowRequestsRequest = () => ({
type: FOLLOW_REQUESTS_EXPAND_REQUEST,
});
const expandFollowRequestsSuccess = (accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
type: FOLLOW_REQUESTS_EXPAND_SUCCESS,
accounts,
next,
});
const expandFollowRequestsFail = (error: unknown) => ({
type: FOLLOW_REQUESTS_EXPAND_FAIL,
error,
});
const authorizeFollowRequest = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
dispatch(authorizeFollowRequestRequest(accountId));
return getClient(getState()).myAccount.acceptFollowRequest(accountId)
.then(() => dispatch(authorizeFollowRequestSuccess(accountId)))
.catch(error => dispatch(authorizeFollowRequestFail(accountId, error)));
};
const authorizeFollowRequestRequest = (accountId: string) => ({
type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
accountId,
});
const authorizeFollowRequestSuccess = (accountId: string) => ({
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
accountId,
});
const authorizeFollowRequestFail = (accountId: string, error: unknown) => ({
type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
accountId,
error,
});
const rejectFollowRequest = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
dispatch(rejectFollowRequestRequest(accountId));
return getClient(getState()).myAccount.rejectFollowRequest(accountId)
.then(() => dispatch(rejectFollowRequestSuccess(accountId)))
.catch(error => dispatch(rejectFollowRequestFail(accountId, error)));
};
const rejectFollowRequestRequest = (accountId: string) => ({
type: FOLLOW_REQUEST_REJECT_REQUEST,
accountId,
});
const rejectFollowRequestSuccess = (accountId: string) => ({
type: FOLLOW_REQUEST_REJECT_SUCCESS,
accountId,
});
const rejectFollowRequestFail = (accountId: string, error: unknown) => ({
type: FOLLOW_REQUEST_REJECT_FAIL,
accountId,
error,
});
const pinAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return dispatch(noOp);
@ -580,18 +449,6 @@ type AccountsAction =
| ReturnType<typeof muteAccountRequest>
| ReturnType<typeof muteAccountSuccess>
| ReturnType<typeof muteAccountFail>
| ReturnType<typeof fetchFollowRequestsRequest>
| ReturnType<typeof fetchFollowRequestsSuccess>
| ReturnType<typeof fetchFollowRequestsFail>
| ReturnType<typeof expandFollowRequestsRequest>
| ReturnType<typeof expandFollowRequestsSuccess>
| ReturnType<typeof expandFollowRequestsFail>
| ReturnType<typeof authorizeFollowRequestRequest>
| ReturnType<typeof authorizeFollowRequestSuccess>
| ReturnType<typeof authorizeFollowRequestFail>
| ReturnType<typeof rejectFollowRequestRequest>
| ReturnType<typeof rejectFollowRequestSuccess>
| ReturnType<typeof rejectFollowRequestFail>
| NotificationSettingsRequestAction
| NotificationSettingsSuccessAction
| NotificationSettingsFailAction
@ -627,18 +484,6 @@ export {
ACCOUNT_LOOKUP_REQUEST,
ACCOUNT_LOOKUP_SUCCESS,
ACCOUNT_LOOKUP_FAIL,
FOLLOW_REQUESTS_FETCH_REQUEST,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_FETCH_FAIL,
FOLLOW_REQUESTS_EXPAND_REQUEST,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
FOLLOW_REQUESTS_EXPAND_FAIL,
FOLLOW_REQUEST_AUTHORIZE_REQUEST,
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
FOLLOW_REQUEST_AUTHORIZE_FAIL,
FOLLOW_REQUEST_REJECT_REQUEST,
FOLLOW_REQUEST_REJECT_SUCCESS,
FOLLOW_REQUEST_REJECT_FAIL,
NOTIFICATION_SETTINGS_REQUEST,
NOTIFICATION_SETTINGS_SUCCESS,
NOTIFICATION_SETTINGS_FAIL,
@ -651,10 +496,6 @@ export {
unmuteAccount,
removeFromFollowers,
fetchRelationships,
fetchFollowRequests,
expandFollowRequests,
authorizeFollowRequest,
rejectFollowRequest,
pinAccount,
unpinAccount,
updateNotificationSettings,

View file

@ -55,12 +55,6 @@ const REMOTE_INTERACTION_REQUEST = 'REMOTE_INTERACTION_REQUEST' as const;
const REMOTE_INTERACTION_SUCCESS = 'REMOTE_INTERACTION_SUCCESS' as const;
const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL' as const;
const FAVOURITES_EXPAND_SUCCESS = 'FAVOURITES_EXPAND_SUCCESS' as const;
const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL' as const;
const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS' as const;
const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL' as const;
const noOp = () => new Promise(f => f(undefined));
const messages = defineMessages({
@ -551,10 +545,6 @@ export {
REMOTE_INTERACTION_REQUEST,
REMOTE_INTERACTION_SUCCESS,
REMOTE_INTERACTION_FAIL,
FAVOURITES_EXPAND_SUCCESS,
FAVOURITES_EXPAND_FAIL,
REBLOGS_EXPAND_SUCCESS,
REBLOGS_EXPAND_FAIL,
reblog,
unreblog,
toggleReblog,

View file

@ -3,6 +3,7 @@ import 'intl-pluralrules';
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { appendFollowRequest } from 'pl-fe/api/hooks/account-lists/use-follow-requests';
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import { normalizeNotification } from 'pl-fe/normalizers/notification';
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
@ -74,9 +75,14 @@ const updateNotifications = (notification: BaseNotification) =>
statuses: [getNotificationStatus(notification) as any],
}));
if (showInColumn) {
const normalizedNotification = normalizeNotification(notification);
if (normalizedNotification.type === 'follow_request') {
normalizedNotification.sample_account_ids.forEach(appendFollowRequest);
}
dispatch<NotificationsUpdateAction>({
type: NOTIFICATIONS_UPDATE,
notification: normalizedNotification,

View file

@ -1,5 +1,5 @@
import { InfiniteData, useInfiniteQuery, useMutation } from '@tanstack/react-query';
import { type InfiniteData, useInfiniteQuery, useMutation } from '@tanstack/react-query';
import { importEntities } from 'pl-fe/actions/importer';
import { minifyList } from 'pl-fe/api/normalizers/minify-list';

View file

@ -0,0 +1,64 @@
import { useInfiniteQuery, useMutation, type InfiniteData } from '@tanstack/react-query';
import { minifyAccountList } from 'pl-fe/api/normalizers/minify-list';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from 'pl-fe/queries/client';
import type { PaginatedResponse, PlApiClient } from 'pl-api';
const appendFollowRequest = (accountId: string) =>
queryClient.setQueryData<InfiniteData<ReturnType<typeof minifyAccountList>>>(['accountsLists', 'followRequests'], (data) => {
if (!data || data.pages.some(page => page.items.includes(accountId))) return data;
return {
...data,
pages: data.pages.map((page, index) => index === 0 ? ({ ...page, items: [accountId, ...page.items] }) : page),
};
});
const removeFollowRequest = (accountId: string) =>
queryClient.setQueryData<InfiniteData<ReturnType<typeof minifyAccountList>>>(['accountsLists', 'followRequests'], (data) => data ? {
...data,
pages: data.pages.map(({ items, ...page }) => ({ ...page, items: items.filter((id) => id !== accountId) })),
} : undefined);
const useFollowRequests = () => {
const client = useClient();
return useInfiniteQuery({
queryKey: ['accountsLists', 'followRequests'],
queryFn: ({ pageParam }) => pageParam.next?.() || client.myAccount.getFollowRequests().then(minifyAccountList),
initialPageParam: { previous: null, next: null, items: [], partial: false } as PaginatedResponse<string>,
getNextPageParam: (page) => page.next ? page : undefined,
select: (data) => data.pages.map(page => page.items).flat(),
});
};
const useAcceptFollowRequestMutation = (accountId: string) => {
const client = useClient();
return useMutation({
mutationKey: ['accountsLists', 'followRequests', accountId],
mutationFn: () => client.myAccount.acceptFollowRequest(accountId),
onSettled: () => removeFollowRequest(accountId),
});
};
const useRejectFollowRequestMutation = (accountId: string) => {
const client = useClient();
return useMutation({
mutationKey: ['accountsLists', 'followRequests', accountId],
mutationFn: () => client.myAccount.rejectFollowRequest(accountId),
onSettled: () => removeFollowRequest(accountId),
});
};
const prefetchFollowRequests = (client: PlApiClient) => queryClient.prefetchInfiniteQuery({
queryKey: ['accountsLists', 'followRequests'],
queryFn: ({ pageParam }) => pageParam.next?.() || client.myAccount.getFollowRequests().then(minifyAccountList),
initialPageParam: { previous: null, next: null, items: [], partial: false } as PaginatedResponse<string>,
});
export { appendFollowRequest, useFollowRequests, useAcceptFollowRequestMutation, useRejectFollowRequestMutation, prefetchFollowRequests };

View file

@ -1,21 +1,22 @@
import React from 'react';
import { authorizeFollowRequest, rejectFollowRequest } from 'pl-fe/actions/accounts';
import { useAcceptFollowRequestMutation, useRejectFollowRequestMutation } from 'pl-fe/api/hooks/account-lists/use-follow-requests';
import { useAccount } from 'pl-fe/api/hooks/accounts/use-account';
import Account from 'pl-fe/components/account';
import { AuthorizeRejectButtons } from 'pl-fe/components/authorize-reject-buttons';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
interface IAccountAuthorize {
id: string;
}
const AccountAuthorize: React.FC<IAccountAuthorize> = ({ id }) => {
const dispatch = useAppDispatch();
const { account } = useAccount(id);
const onAuthorize = () => dispatch(authorizeFollowRequest(id));
const onReject = () => dispatch(rejectFollowRequest(id));
const { mutate: authorizeFollowRequest } = useAcceptFollowRequestMutation(id);
const { mutate: rejectFollowRequest } = useRejectFollowRequestMutation(id);
const onAuthorize = () => authorizeFollowRequest();
const onReject = () => rejectFollowRequest();
if (!account) return null;

View file

@ -1,13 +1,10 @@
import debounce from 'lodash/debounce';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { fetchFollowRequests, expandFollowRequests } from 'pl-fe/actions/accounts';
import { useFollowRequests } from 'pl-fe/api/hooks/account-lists/use-follow-requests';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Column from 'pl-fe/components/ui/column';
import Spinner from 'pl-fe/components/ui/spinner';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import AccountAuthorize from './components/account-authorize';
@ -15,20 +12,10 @@ const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
});
const handleLoadMore = debounce((dispatch) => {
dispatch(expandFollowRequests());
}, 300, { leading: true });
const FollowRequests: React.FC = () => {
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector((state) => state.user_lists.follow_requests.items);
const hasMore = useAppSelector((state) => !!state.user_lists.follow_requests.next);
React.useEffect(() => {
dispatch(fetchFollowRequests());
}, []);
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useFollowRequests();
if (!accountIds) {
return (
@ -43,8 +30,9 @@ const FollowRequests: React.FC = () => {
return (
<Column label={intl.formatMessage(messages.heading)}>
<ScrollableList
onLoadMore={() => handleLoadMore(dispatch)}
hasMore={hasMore}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
emptyMessage={emptyMessage}
>
{accountIds.map(id =>

View file

@ -6,10 +6,9 @@ import {
unblockAccount,
muteAccount,
unmuteAccount,
authorizeFollowRequest,
rejectFollowRequest,
biteAccount,
} from 'pl-fe/actions/accounts';
import { useAcceptFollowRequestMutation, useRejectFollowRequestMutation } from 'pl-fe/api/hooks/account-lists/use-follow-requests';
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
import Button from 'pl-fe/components/ui/button';
import HStack from 'pl-fe/components/ui/hstack';
@ -63,6 +62,9 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
const { isLoggedIn, me } = useLoggedIn();
const { follow, unfollow } = useFollow();
const { mutate: authorizeFollowRequest } = useAcceptFollowRequestMutation(account.id);
const { mutate: rejectFollowRequest } = useRejectFollowRequestMutation(account.id);
const handleFollow = () => {
if (account.relationship?.following || account.relationship?.requested) {
unfollow(account.id);
@ -88,11 +90,11 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
};
const handleAuthorize = () => {
dispatch(authorizeFollowRequest(account.id));
authorizeFollowRequest();
};
const handleReject = () => {
dispatch(rejectFollowRequest(account.id));
rejectFollowRequest();
};
const handleBite = () => {

View file

@ -2,7 +2,6 @@ import clsx from 'clsx';
import React, { Suspense, lazy, useEffect, useRef } from 'react';
import { Redirect, Switch, useHistory, useLocation } from 'react-router-dom';
import { fetchFollowRequests } from 'pl-fe/actions/accounts';
import { fetchConfig, fetchReports, fetchUsers } from 'pl-fe/actions/admin';
import { fetchCustomEmojis } from 'pl-fe/actions/custom-emojis';
import { fetchDraftStatuses } from 'pl-fe/actions/draft-statuses';
@ -12,12 +11,14 @@ import { expandNotifications } from 'pl-fe/actions/notifications';
import { register as registerPushNotifications } from 'pl-fe/actions/push-notifications/registerer';
import { fetchScheduledStatuses } from 'pl-fe/actions/scheduled-statuses';
import { fetchHomeTimeline } from 'pl-fe/actions/timelines';
import { prefetchFollowRequests } from 'pl-fe/api/hooks/account-lists/use-follow-requests';
import { useUserStream } from 'pl-fe/api/hooks/streaming/use-user-stream';
import SidebarNavigation from 'pl-fe/components/sidebar-navigation';
import ThumbNavigation from 'pl-fe/components/thumb-navigation';
import Layout from 'pl-fe/components/ui/layout';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { useClient } from 'pl-fe/hooks/use-client';
import { useDraggedFiles } from 'pl-fe/hooks/use-dragged-files';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useInstance } from 'pl-fe/hooks/use-instance';
@ -362,6 +363,7 @@ const UI: React.FC<IUI> = ({ children }) => {
const { account } = useOwnAccount();
const features = useFeatures();
const vapidKey = useAppSelector(state => getVapidKey(state));
const client = useClient();
const { isDropdownMenuOpen } = useUiStore();
const standalone = useAppSelector(isStandalone);
@ -409,7 +411,7 @@ const UI: React.FC<IUI> = ({ children }) => {
setTimeout(() => dispatch(fetchFilters()), 500);
if (account.locked) {
setTimeout(() => dispatch(fetchFollowRequests()), 700);
setTimeout(() => prefetchFollowRequests(client), 700);
}
setTimeout(() => dispatch(fetchScheduledStatuses()), 900);

View file

@ -3,8 +3,6 @@ import { create } from 'mutative';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
FOLLOW_REQUEST_REJECT_SUCCESS,
type AccountsAction,
} from '../actions/accounts';
import {
@ -87,11 +85,11 @@ const filterNotifications = (state: State, relationship: Relationship) =>
draft.items = draft.items.filter(item => !item.sample_account_ids.includes(relationship.id));
});
const filterNotificationIds = (state: State, accountIds: Array<string>, type?: string) =>
create(state, (draft) => {
const helper = (list: Array<NotificationGroup>) => list.filter(item => !(accountIds.includes(item.sample_account_ids[0]) && (type === undefined || type === item.type)));
draft.items = helper(draft.items);
});
// const filterNotificationIds = (state: State, accountIds: Array<string>, type?: string) =>
// create(state, (draft) => {
// const helper = (list: Array<NotificationGroup>) => list.filter(item => !(accountIds.includes(item.sample_account_ids[0]) && (type === undefined || type === item.type)));
// draft.items = helper(draft.items);
// });
const updateTop = (state: State, top: boolean) =>
create(state, (draft) => {
@ -147,9 +145,9 @@ const notifications = (state: State = initialState, action: AccountsAction | Mar
return filterNotifications(state, action.relationship);
case ACCOUNT_MUTE_SUCCESS:
return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state;
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
case FOLLOW_REQUEST_REJECT_SUCCESS:
return filterNotificationIds(state, [action.accountId], 'follow_request');
// case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
// case FOLLOW_REQUEST_REJECT_SUCCESS:
// return filterNotificationIds(state, [action.accountId], 'follow_request');
case MARKER_FETCH_SUCCESS:
case MARKER_SAVE_SUCCESS:
return importMarker(state, action.marker);

View file

@ -1,13 +1,6 @@
import { create } from 'mutative';
import {
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
FOLLOW_REQUEST_REJECT_SUCCESS,
PINNED_ACCOUNTS_FETCH_SUCCESS,
type AccountsAction,
} from 'pl-fe/actions/accounts';
import { PINNED_ACCOUNTS_FETCH_SUCCESS, type AccountsAction } from 'pl-fe/actions/accounts';
import { FAMILIAR_FOLLOWERS_FETCH_SUCCESS, type FamiliarFollowersAction } from 'pl-fe/actions/familiar-followers';
import {
GROUP_BLOCKS_FETCH_REQUEST,
@ -16,9 +9,8 @@ import {
GROUP_UNBLOCK_SUCCESS,
type GroupsAction,
} from 'pl-fe/actions/groups';
import { NOTIFICATIONS_UPDATE, type NotificationsAction } from 'pl-fe/actions/notifications';
import type { Account, NotificationGroup, PaginatedResponse } from 'pl-api';
import type { Account, PaginatedResponse } from 'pl-api';
interface List {
next: (() => Promise<PaginatedResponse<Account>>) | null;
@ -61,50 +53,8 @@ const normalizeList = (state: State, path: NestedListPath | ListPath, accounts:
}
});
const appendToList = (state: State, path: NestedListPath | ListPath, accounts: Array<Pick<Account, 'id'>>, next: (() => any) | null = null) =>
create(state, (draft) => {
let list: List;
if (path.length === 1) {
list = draft[path[0]];
} else {
list = draft[path[0]][path[1]];
}
list.next = next;
list.isLoading = false;
list.items = [...new Set([...list.items, ...accounts.map(item => item.id)])];
});
const removeFromList = (state: State, path: NestedListPath | ListPath, accountId: string) =>
create(state, (draft) => {
let list: List;
if (path.length === 1) {
list = draft[path[0]];
} else {
list = draft[path[0]][path[1]];
}
list.items = list.items.filter(item => item !== accountId);
});
const normalizeFollowRequest = (state: State, notification: NotificationGroup) =>
create(state, (draft) => {
draft.follow_requests.items = [...new Set([...notification.sample_account_ids, ...draft.follow_requests.items])];
});
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction | NotificationsAction): State => {
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction): State => {
switch (action.type) {
case NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return normalizeList(state, ['follow_requests'], action.accounts, action.next);
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
return appendToList(state, ['follow_requests'], action.accounts, action.next);
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
case FOLLOW_REQUEST_REJECT_SUCCESS:
return removeFromList(state, ['follow_requests'], action.accountId);
case PINNED_ACCOUNTS_FETCH_SUCCESS:
return normalizeList(state, ['pinned', action.accountId], action.accounts, action.next);
case FAMILIAR_FOLLOWERS_FETCH_SUCCESS: