diff --git a/packages/pl-fe/src/actions/accounts.test.ts b/packages/pl-fe/src/actions/accounts.test.ts index ef59f6708..5cc66439d 100644 --- a/packages/pl-fe/src/actions/accounts.test.ts +++ b/packages/pl-fe/src/actions/accounts.test.ts @@ -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: '; 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: '; 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); - }); - }); - }); -}); diff --git a/packages/pl-fe/src/actions/accounts.ts b/packages/pl-fe/src/actions/accounts.ts index 7f78a4dd9..4da5c5ee7 100644 --- a/packages/pl-fe/src/actions/accounts.ts +++ b/packages/pl-fe/src/actions/accounts.ts @@ -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, next: (() => Promise>) | 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, next: (() => Promise>) | 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 | ReturnType | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType | 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, diff --git a/packages/pl-fe/src/actions/interactions.ts b/packages/pl-fe/src/actions/interactions.ts index 5c224eebe..1d907baf3 100644 --- a/packages/pl-fe/src/actions/interactions.ts +++ b/packages/pl-fe/src/actions/interactions.ts @@ -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, diff --git a/packages/pl-fe/src/actions/notifications.ts b/packages/pl-fe/src/actions/notifications.ts index 7d4c8d7fa..bb977f607 100644 --- a/packages/pl-fe/src/actions/notifications.ts +++ b/packages/pl-fe/src/actions/notifications.ts @@ -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({ type: NOTIFICATIONS_UPDATE, notification: normalizedNotification, diff --git a/packages/pl-fe/src/api/hooks/account-lists/use-event-participation-requests.ts b/packages/pl-fe/src/api/hooks/account-lists/use-event-participation-requests.ts index da221cbcd..94d138482 100644 --- a/packages/pl-fe/src/api/hooks/account-lists/use-event-participation-requests.ts +++ b/packages/pl-fe/src/api/hooks/account-lists/use-event-participation-requests.ts @@ -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'; diff --git a/packages/pl-fe/src/api/hooks/account-lists/use-follow-requests.ts b/packages/pl-fe/src/api/hooks/account-lists/use-follow-requests.ts new file mode 100644 index 000000000..55c621fc7 --- /dev/null +++ b/packages/pl-fe/src/api/hooks/account-lists/use-follow-requests.ts @@ -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>>(['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>>(['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, + 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, +}); + +export { appendFollowRequest, useFollowRequests, useAcceptFollowRequestMutation, useRejectFollowRequestMutation, prefetchFollowRequests }; diff --git a/packages/pl-fe/src/features/follow-requests/components/account-authorize.tsx b/packages/pl-fe/src/features/follow-requests/components/account-authorize.tsx index 24b298e73..a6446949c 100644 --- a/packages/pl-fe/src/features/follow-requests/components/account-authorize.tsx +++ b/packages/pl-fe/src/features/follow-requests/components/account-authorize.tsx @@ -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 = ({ 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; diff --git a/packages/pl-fe/src/features/follow-requests/index.tsx b/packages/pl-fe/src/features/follow-requests/index.tsx index 01f845858..8e9a2d524 100644 --- a/packages/pl-fe/src/features/follow-requests/index.tsx +++ b/packages/pl-fe/src/features/follow-requests/index.tsx @@ -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 ( handleLoadMore(dispatch)} - hasMore={hasMore} + hasMore={hasNextPage} + isLoading={typeof isLoading === 'boolean' ? isLoading : true} + onLoadMore={() => fetchNextPage({ cancelRefetch: false })} emptyMessage={emptyMessage} > {accountIds.map(id => diff --git a/packages/pl-fe/src/features/ui/components/action-button.tsx b/packages/pl-fe/src/features/ui/components/action-button.tsx index 30c771358..a92c74ef8 100644 --- a/packages/pl-fe/src/features/ui/components/action-button.tsx +++ b/packages/pl-fe/src/features/ui/components/action-button.tsx @@ -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 = ({ 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 = ({ account, actionType, small }) = }; const handleAuthorize = () => { - dispatch(authorizeFollowRequest(account.id)); + authorizeFollowRequest(); }; const handleReject = () => { - dispatch(rejectFollowRequest(account.id)); + rejectFollowRequest(); }; const handleBite = () => { diff --git a/packages/pl-fe/src/features/ui/index.tsx b/packages/pl-fe/src/features/ui/index.tsx index 05b8bc729..31b08a8b6 100644 --- a/packages/pl-fe/src/features/ui/index.tsx +++ b/packages/pl-fe/src/features/ui/index.tsx @@ -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 = ({ 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 = ({ children }) => { setTimeout(() => dispatch(fetchFilters()), 500); if (account.locked) { - setTimeout(() => dispatch(fetchFollowRequests()), 700); + setTimeout(() => prefetchFollowRequests(client), 700); } setTimeout(() => dispatch(fetchScheduledStatuses()), 900); diff --git a/packages/pl-fe/src/reducers/notifications.ts b/packages/pl-fe/src/reducers/notifications.ts index c67733b32..9bdf055f3 100644 --- a/packages/pl-fe/src/reducers/notifications.ts +++ b/packages/pl-fe/src/reducers/notifications.ts @@ -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, type?: string) => - create(state, (draft) => { - const helper = (list: Array) => 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, type?: string) => +// create(state, (draft) => { +// const helper = (list: Array) => 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); diff --git a/packages/pl-fe/src/reducers/user-lists.ts b/packages/pl-fe/src/reducers/user-lists.ts index 6da9da99b..faa26e367 100644 --- a/packages/pl-fe/src/reducers/user-lists.ts +++ b/packages/pl-fe/src/reducers/user-lists.ts @@ -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>) | null; @@ -61,50 +53,8 @@ const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: } }); -const appendToList = (state: State, path: NestedListPath | ListPath, accounts: Array>, 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: