pl-fe: migrate event participation requests to tanstack query

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-04 17:58:20 +01:00
parent 90f17de808
commit 443e15c591
6 changed files with 79 additions and 263 deletions

View file

@ -6,7 +6,7 @@ import toast from 'pl-fe/toast';
import { importEntities } from './importer';
import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOURCE_SUCCESS } from './statuses';
import type { Account, CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api';
import type { CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const EVENT_SUBMIT_REQUEST = 'EVENT_SUBMIT_REQUEST' as const;
@ -21,22 +21,6 @@ const EVENT_LEAVE_REQUEST = 'EVENT_LEAVE_REQUEST' as const;
const EVENT_LEAVE_SUCCESS = 'EVENT_LEAVE_SUCCESS' as const;
const EVENT_LEAVE_FAIL = 'EVENT_LEAVE_FAIL' as const;
const EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST = 'EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST' as const;
const EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS = 'EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS' as const;
const EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL = 'EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL' as const;
const EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST' as const;
const EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS' as const;
const EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL' as const;
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST' as const;
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS' as const;
const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL' as const;
const EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST = 'EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST' as const;
const EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS' as const;
const EVENT_PARTICIPATION_REQUEST_REJECT_FAIL = 'EVENT_PARTICIPATION_REQUEST_REJECT_FAIL' as const;
const EVENT_COMPOSE_CANCEL = 'EVENT_COMPOSE_CANCEL' as const;
const EVENT_FORM_SET = 'EVENT_FORM_SET' as const;
@ -213,136 +197,6 @@ const leaveEventFail = (error: unknown, statusId: string, previousState: Exclude
previousState,
});
const fetchEventParticipationRequests = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchEventParticipationRequestsRequest(statusId));
return getClient(getState).events.getEventParticipationRequests(statusId).then(response => {
dispatch(importEntities({ accounts: response.items.map(({ account }) => account) }));
return dispatch(fetchEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchEventParticipationRequestsFail(statusId, error));
});
};
const fetchEventParticipationRequestsRequest = (statusId: string) => ({
type: EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST,
statusId,
});
const fetchEventParticipationRequestsSuccess = (statusId: string, participations: Array<{
account: Account;
participation_message: string;
}>, next: (() => Promise<PaginatedResponse<{ account: Account }>>) | null) => ({
type: EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS,
statusId,
participations,
next,
});
const fetchEventParticipationRequestsFail = (statusId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL,
statusId,
error,
});
const expandEventParticipationRequests = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const next = getState().user_lists.event_participation_requests[statusId]?.next || null;
if (next === null) {
return dispatch(noOp);
}
dispatch(expandEventParticipationRequestsRequest(statusId));
return next().then(response => {
dispatch(importEntities({ accounts: response.items.map(({ account }) => account) }));
return dispatch(expandEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandEventParticipationRequestsFail(statusId, error));
});
};
const expandEventParticipationRequestsRequest = (statusId: string) => ({
type: EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST,
statusId,
});
const expandEventParticipationRequestsSuccess = (statusId: string, participations: Array<{
account: Account;
participation_message: string;
}>, next: (() => Promise<PaginatedResponse<{ account: Account }>>) | null) => ({
type: EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS,
statusId,
participations,
next,
});
const expandEventParticipationRequestsFail = (statusId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL,
statusId,
error,
});
const authorizeEventParticipationRequest = (statusId: string, accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(authorizeEventParticipationRequestRequest(statusId, accountId));
return getClient(getState).events.acceptEventParticipationRequest(statusId, accountId).then(() => {
dispatch(authorizeEventParticipationRequestSuccess(statusId, accountId));
toast.success(messages.authorized);
}).catch(error => dispatch(authorizeEventParticipationRequestFail(statusId, accountId, error)));
};
const authorizeEventParticipationRequestRequest = (statusId: string, accountId: string) => ({
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST,
statusId,
accountId,
});
const authorizeEventParticipationRequestSuccess = (statusId: string, accountId: string) => ({
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
statusId,
accountId,
});
const authorizeEventParticipationRequestFail = (statusId: string, accountId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL,
statusId,
accountId,
error,
});
const rejectEventParticipationRequest = (statusId: string, accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(rejectEventParticipationRequestRequest(statusId, accountId));
return getClient(getState).events.rejectEventParticipationRequest(statusId, accountId).then(() => {
dispatch(rejectEventParticipationRequestSuccess(statusId, accountId));
toast.success(messages.rejected);
}).catch(error => dispatch(rejectEventParticipationRequestFail(statusId, accountId, error)));
};
const rejectEventParticipationRequestRequest = (statusId: string, accountId: string) => ({
type: EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST,
statusId,
accountId,
});
const rejectEventParticipationRequestSuccess = (statusId: string, accountId: string) => ({
type: EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
statusId,
accountId,
});
const rejectEventParticipationRequestFail = (statusId: string, accountId: string, error: unknown) => ({
type: EVENT_PARTICIPATION_REQUEST_REJECT_FAIL,
statusId,
accountId,
error,
});
const fetchEventIcs = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) =>
getClient(getState).events.getEventIcs(statusId);
@ -425,20 +279,6 @@ type EventsAction =
| ReturnType<typeof leaveEventRequest>
| ReturnType<typeof leaveEventSuccess>
| ReturnType<typeof leaveEventFail>
| ReturnType<typeof fetchEventParticipationRequestsRequest>
| ReturnType<typeof fetchEventParticipationRequestsSuccess>
| ReturnType<typeof fetchEventParticipationRequestsFail>
| ReturnType<typeof expandEventParticipationRequestsRequest>
| ReturnType<typeof expandEventParticipationRequestsSuccess>
| ReturnType<typeof expandEventParticipationRequestsFail>
| ReturnType<typeof expandEventParticipationRequestsSuccess>
| ReturnType<typeof expandEventParticipationRequestsFail>
| ReturnType<typeof authorizeEventParticipationRequestRequest>
| ReturnType<typeof authorizeEventParticipationRequestSuccess>
| ReturnType<typeof authorizeEventParticipationRequestFail>
| ReturnType<typeof rejectEventParticipationRequestRequest>
| ReturnType<typeof rejectEventParticipationRequestSuccess>
| ReturnType<typeof rejectEventParticipationRequestFail>
| ReturnType<typeof cancelEventCompose>
| EventFormSetAction
| { type: typeof RECENT_EVENTS_FETCH_REQUEST }
@ -458,18 +298,6 @@ export {
EVENT_LEAVE_REQUEST,
EVENT_LEAVE_SUCCESS,
EVENT_LEAVE_FAIL,
EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST,
EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS,
EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL,
EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST,
EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS,
EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL,
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST,
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL,
EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST,
EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
EVENT_PARTICIPATION_REQUEST_REJECT_FAIL,
EVENT_COMPOSE_CANCEL,
EVENT_FORM_SET,
RECENT_EVENTS_FETCH_REQUEST,
@ -481,10 +309,6 @@ export {
submitEvent,
joinEvent,
leaveEvent,
fetchEventParticipationRequests,
expandEventParticipationRequests,
authorizeEventParticipationRequest,
rejectEventParticipationRequest,
fetchEventIcs,
cancelEventCompose,
initEventEdit,

View file

@ -33,7 +33,7 @@ type ExportDataAction = {
| typeof EXPORT_BLOCKS_SUCCESS
| typeof EXPORT_MUTES_SUCCESS;
} | {
type: typeof EXPORT_FOLLOWS_FAIL
type: typeof EXPORT_FOLLOWS_FAIL
| typeof EXPORT_BLOCKS_FAIL
| typeof EXPORT_MUTES_FAIL;
error?: unknown;

View file

@ -0,0 +1,62 @@
import { InfiniteData, useInfiniteQuery, useMutation } from '@tanstack/react-query';
import { importEntities } from 'pl-fe/actions/importer';
import { minifyList } from 'pl-fe/api/normalizers/minify-list';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from 'pl-fe/queries/client';
import { store } from 'pl-fe/store';
import type { PlApiClient } from 'pl-api';
const minifyRequestList = (response: Awaited<ReturnType<(InstanceType<typeof PlApiClient>)['events']['getEventParticipationRequests']>>) =>
minifyList(
response,
({ account, participation_message }) => ({ account_id: account.id, participation_message }),
(requests) => store.dispatch(importEntities({ accounts: requests.map(request => request.account) }) as any),
);
type MinifiedRequestList = ReturnType<typeof minifyRequestList>
const removeRequest = (statusId: string, accountId: string) =>
queryClient.setQueryData<InfiniteData<MinifiedRequestList>>(['accountsLists', 'eventParticipationRequests', statusId], (data) => data ? {
...data,
pages: data.pages.map(({ items, ...page }) => ({ ...page, items: items.filter(({ account_id }) => account_id !== accountId) })),
} : undefined);
const useEventParticipationRequests = (statusId: string) => {
const client = useClient();
return useInfiniteQuery({
queryKey: ['accountsLists', 'eventParticipationRequests', statusId],
queryFn: ({ pageParam }) => pageParam.next?.() || client.events.getEventParticipationRequests(statusId).then(minifyRequestList),
initialPageParam: { previous: null, next: null, items: [], partial: false } as MinifiedRequestList,
getNextPageParam: (page) => page.next ? page : undefined,
select: (data) => data.pages.map(page => page.items).flat(),
});
};
const useAcceptEventParticipationRequestMutation = (statusId: string, accountId: string) => {
const client = useClient();
return useMutation({
mutationKey: ['accountsLists', 'eventParticipationRequests', statusId, accountId],
mutationFn: () => client.events.acceptEventParticipationRequest(statusId, accountId),
onSettled: () => removeRequest(statusId, accountId),
});
};
const useRejectEventParticipationRequestMutation = (statusId: string, accountId: string) => {
const client = useClient();
return useMutation({
mutationKey: ['accountsLists', 'eventParticipationRequests', statusId, accountId],
mutationFn: () => client.events.rejectEventParticipationRequest(statusId, accountId),
onSettled: () => removeRequest(statusId, accountId),
});
};
export {
useEventParticipationRequests,
useAcceptEventParticipationRequestMutation,
useRejectEventParticipationRequestMutation,
};

View file

@ -11,7 +11,7 @@ const useEventParticipations = (statusId: string) => {
return useInfiniteQuery({
queryKey: ['accountsLists', 'eventParticipations', statusId],
queryFn: ({ pageParam }) => pageParam.next?.() || client.events.getEventParticipations(statusId).then(minifyAccountList),
queryFn: ({ pageParam }) => pageParam.next?.() || client.events.getEventParticipations(statusId).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(),

View file

@ -1,20 +1,13 @@
import React, { useEffect } from 'react';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import {
fetchEventParticipationRequests,
rejectEventParticipationRequest,
authorizeEventParticipationRequest,
cancelEventCompose,
} from 'pl-fe/actions/events';
import { useAcceptEventParticipationRequestMutation, useEventParticipationRequests } from 'pl-fe/api/hooks/account-lists/use-event-participation-requests';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Button from 'pl-fe/components/ui/button';
import HStack from 'pl-fe/components/ui/hstack';
import Spinner from 'pl-fe/components/ui/spinner';
import Stack from 'pl-fe/components/ui/stack';
import AccountContainer from 'pl-fe/containers/account-container';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
const messages = defineMessages({
authorize: { id: 'compose_event.participation_requests.authorize', defaultMessage: 'Authorize' },
@ -29,15 +22,9 @@ interface IAccount {
const Account: React.FC<IAccount> = ({ eventId, id, participationMessage }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const handleAuthorize = () => {
dispatch(authorizeEventParticipationRequest(eventId, id));
};
const handleReject = () => {
dispatch(rejectEventParticipationRequest(eventId, id));
};
const { mutate: acceptEventParticipationRequest } = useAcceptEventParticipationRequestMutation(eventId, id);
const { mutate: rejectEventParticipationRequest } = useAcceptEventParticipationRequestMutation(eventId, id);
return (
<AccountContainer
@ -49,13 +36,13 @@ const Account: React.FC<IAccount> = ({ eventId, id, participationMessage }) => {
theme='secondary'
size='sm'
text={intl.formatMessage(messages.authorize)}
onClick={handleAuthorize}
onClick={() => acceptEventParticipationRequest()}
/>
<Button
theme='danger'
size='sm'
text={intl.formatMessage(messages.reject)}
onClick={handleReject}
onClick={() => rejectEventParticipationRequest()}
/>
</HStack>
}
@ -68,27 +55,18 @@ interface IManagePendingParticipants {
}
const ManagePendingParticipants: React.FC<IManagePendingParticipants> = ({ statusId }) => {
const dispatch = useAppDispatch();
const accounts = useAppSelector((state) => state.user_lists.event_participation_requests[statusId]?.items);
useEffect(() => {
if (statusId) dispatch(fetchEventParticipationRequests(statusId));
return () => {
dispatch(cancelEventCompose());
};
}, [statusId]);
const { data: accounts, isLoading, hasNextPage, fetchNextPage } = useEventParticipationRequests(statusId);
return accounts ? (
<Stack space={3}>
<ScrollableList
isLoading={!accounts}
showLoading={!accounts}
emptyMessage={<FormattedMessage id='empty_column.event_participant_requests' defaultMessage='There are no pending event participation requests.' />}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
>
{accounts.map(({ account, participation_message }) =>
<Account key={account} eventId={statusId!} id={account} participationMessage={participation_message} />,
{accounts.map(({ account_id, participation_message }) =>
<Account key={account_id} eventId={statusId!} id={account_id} participationMessage={participation_message} />,
)}
</ScrollableList>
</Stack>

View file

@ -9,13 +9,6 @@ import {
BIRTHDAY_REMINDERS_FETCH_SUCCESS,
type AccountsAction,
} from 'pl-fe/actions/accounts';
import {
EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS,
EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS,
EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS,
EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS,
type EventsAction,
} from 'pl-fe/actions/events';
import { FAMILIAR_FOLLOWERS_FETCH_SUCCESS, type FamiliarFollowersAction } from 'pl-fe/actions/familiar-followers';
import {
GROUP_BLOCKS_FETCH_REQUEST,
@ -56,23 +49,11 @@ interface ReactionList {
isLoading: boolean;
}
interface ParticipationRequest {
account: string;
participation_message: string | null;
}
interface ParticipationRequestList {
next: (() => Promise<PaginatedResponse<any>>) | null;
items: Array<ParticipationRequest>;
isLoading: boolean;
}
type ListKey = 'follow_requests';
type NestedListKey = 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'event_participations' | 'membership_requests' | 'group_blocks';
type NestedListKey = 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'pinned' | 'birthday_reminders' | 'familiar_followers' | 'membership_requests' | 'group_blocks';
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>> & {
reactions: Record<string, ReactionList>;
event_participation_requests: Record<string, ParticipationRequestList>;
};
const initialState: State = {
@ -84,8 +65,6 @@ const initialState: State = {
pinned: {},
birthday_reminders: {},
familiar_followers: {},
event_participations: {},
event_participation_requests: {},
membership_requests: {},
group_blocks: {},
};
@ -145,7 +124,7 @@ const normalizeFollowRequest = (state: State, notification: NotificationGroup) =
draft.follow_requests.items = [...new Set([...notification.sample_account_ids, ...draft.follow_requests.items])];
});
const userLists = (state = initialState, action: AccountsAction | EventsAction | FamiliarFollowersAction | GroupsAction | InteractionsAction | NotificationsAction): State => {
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction | InteractionsAction | NotificationsAction): State => {
switch (action.type) {
case REBLOGS_FETCH_SUCCESS:
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
@ -180,33 +159,6 @@ const userLists = (state = initialState, action: AccountsAction | EventsAction |
return normalizeList(state, ['birthday_reminders', action.accountId], action.accounts);
case FAMILIAR_FOLLOWERS_FETCH_SUCCESS:
return normalizeList(state, ['familiar_followers', action.accountId], action.accounts);
case EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS:
return create(state, (draft) => {
draft.event_participation_requests[action.statusId] = {
next: action.next,
items: action.participations.map(({ account, participation_message }) => ({
account: account.id,
participation_message,
})),
isLoading: false,
};
});
case EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS:
return create(state, (draft) => {
const list = draft.event_participation_requests[action.statusId];
list.next = action.next;
list.items = [...list.items, ...action.participations.map(({ account, participation_message }) => ({
account: account.id,
participation_message,
}))];
list.isLoading = false;
});
case EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS:
case EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS:
return create(state, (draft) => {
const list = draft.event_participation_requests[action.statusId];
if (list.items) list.items = list.items.filter(item => item.account !== action.accountId);
});
case GROUP_BLOCKS_FETCH_SUCCESS:
return normalizeList(state, ['group_blocks', action.groupId], action.accounts, action.next);
case GROUP_BLOCKS_FETCH_REQUEST: