pl-fe: migrate status interaction lists to tanstack query

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-04 18:49:02 +01:00
parent fac30c2ea9
commit 16ea9f13e2
10 changed files with 83 additions and 356 deletions

View file

@ -2214,11 +2214,8 @@ class PlApiClient {
* Requires features{@link Features['statusDislikes']}.
* @see {@link https://github.com/friendica/friendica/blob/2024.06-rc/doc/API-Friendica.md#get-apifriendicastatusesiddisliked_by}
*/
getDislikedBy: async (statusId: string) => {
const response = await this.request(`/api/friendica/statuses/${statusId}/disliked_by`);
return v.parse(filteredArray(accountSchema), response.json);
},
getDislikedBy: async (statusId: string) =>
this.#paginatedGet(`/api/v1/statuses/${statusId}/disliked_by`, {}, accountSchema),
/**
* Marks the given status as disliked by this user

View file

@ -343,7 +343,6 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
const compose = state.compose[composeId]!;
const status = compose.text;
const media = compose.media_attachments;
const statusId = compose.id;
@ -782,7 +781,6 @@ const deleteComposeLanguage = (composeId: string, value: Language) => ({
value,
});
const addPoll = (composeId: string) => ({
type: COMPOSE_POLL_ADD,
composeId,

View file

@ -6,10 +6,9 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importEntities } from './importer';
import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api';
import type { Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const REBLOG_REQUEST = 'REBLOG_REQUEST' as const;
@ -36,22 +35,6 @@ const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST' as const;
const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS' as const;
const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL' as const;
const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST' as const;
const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS' as const;
const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL' as const;
const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST' as const;
const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS' as const;
const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL' as const;
const DISLIKES_FETCH_REQUEST = 'DISLIKES_FETCH_REQUEST' as const;
const DISLIKES_FETCH_SUCCESS = 'DISLIKES_FETCH_SUCCESS' as const;
const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL' as const;
const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST' as const;
const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS' as const;
const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL' as const;
const PIN_REQUEST = 'PIN_REQUEST' as const;
const PIN_SUCCESS = 'PIN_SUCCESS' as const;
const PIN_FAIL = 'PIN_FAIL' as const;
@ -80,8 +63,6 @@ const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL' as const;
const noOp = () => new Promise(f => f(undefined));
type AccountListLink = () => Promise<PaginatedResponse<Account>>;
const messages = defineMessages({
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
@ -386,175 +367,6 @@ const unbookmarkFail = (statusId: string, error: unknown) => ({
error,
});
const fetchReblogs = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchReblogsRequest(statusId));
return getClient(getState()).statuses.getRebloggedBy(statusId).then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchReblogsFail(statusId, error));
});
};
const fetchReblogsRequest = (statusId: string) => ({
type: REBLOGS_FETCH_REQUEST,
statusId,
});
const fetchReblogsSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
type: REBLOGS_FETCH_SUCCESS,
statusId,
accounts,
next,
});
const fetchReblogsFail = (statusId: string, error: unknown) => ({
type: REBLOGS_FETCH_FAIL,
statusId,
error,
});
const expandReblogs = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch, getState: () => RootState) => {
next().then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandReblogsFail(statusId, error));
});
};
const expandReblogsSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
type: REBLOGS_EXPAND_SUCCESS,
statusId,
accounts,
next,
});
const expandReblogsFail = (statusId: string, error: unknown) => ({
type: REBLOGS_EXPAND_FAIL,
statusId,
error,
});
const fetchFavourites = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchFavouritesRequest(statusId));
return getClient(getState()).statuses.getFavouritedBy(statusId).then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchFavouritesFail(statusId, error));
});
};
const fetchFavouritesRequest = (statusId: string) => ({
type: FAVOURITES_FETCH_REQUEST,
statusId,
});
const fetchFavouritesSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
type: FAVOURITES_FETCH_SUCCESS,
statusId,
accounts,
next,
});
const fetchFavouritesFail = (statusId: string, error: unknown) => ({
type: FAVOURITES_FETCH_FAIL,
statusId,
error,
});
const expandFavourites = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch) => {
next().then(response => {
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandFavouritesFail(statusId, error));
});
};
const expandFavouritesSuccess = (statusId: string, accounts: Array<Account>, next: AccountListLink | null) => ({
type: FAVOURITES_EXPAND_SUCCESS,
statusId,
accounts,
next,
});
const expandFavouritesFail = (statusId: string, error: unknown) => ({
type: FAVOURITES_EXPAND_FAIL,
statusId,
error,
});
const fetchDislikes = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchDislikesRequest(statusId));
return getClient(getState).statuses.getDislikedBy(statusId).then(response => {
dispatch(importEntities({ accounts: response }));
dispatch(fetchRelationships(response.map((item) => item.id)));
dispatch(fetchDislikesSuccess(statusId, response));
}).catch(error => {
dispatch(fetchDislikesFail(statusId, error));
});
};
const fetchDislikesRequest = (statusId: string) => ({
type: DISLIKES_FETCH_REQUEST,
statusId,
});
const fetchDislikesSuccess = (statusId: string, accounts: Array<Account>) => ({
type: DISLIKES_FETCH_SUCCESS,
statusId,
accounts,
});
const fetchDislikesFail = (statusId: string, error: unknown) => ({
type: DISLIKES_FETCH_FAIL,
statusId,
error,
});
const fetchReactions = (statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchReactionsRequest(statusId));
return getClient(getState).statuses.getStatusReactions(statusId).then(response => {
dispatch(importEntities({ accounts: (response).map(({ accounts }) => accounts).flat() }));
dispatch(fetchReactionsSuccess(statusId, response));
}).catch(error => {
dispatch(fetchReactionsFail(statusId, error));
});
};
const fetchReactionsRequest = (statusId: string) => ({
type: REACTIONS_FETCH_REQUEST,
statusId,
});
const fetchReactionsSuccess = (statusId: string, reactions: EmojiReaction[]) => ({
type: REACTIONS_FETCH_SUCCESS,
statusId,
reactions,
});
const fetchReactionsFail = (statusId: string, error: unknown) => ({
type: REACTIONS_FETCH_FAIL,
statusId,
error,
});
const pin = (status: Pick<Status, 'id'>, accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
@ -695,22 +507,6 @@ type InteractionsAction =
| ReturnType<typeof unbookmarkRequest>
| ReturnType<typeof unbookmarkSuccess>
| ReturnType<typeof unbookmarkFail>
| ReturnType<typeof fetchReblogsRequest>
| ReturnType<typeof fetchReblogsSuccess>
| ReturnType<typeof fetchReblogsFail>
| ReturnType<typeof expandReblogsSuccess>
| ReturnType<typeof expandReblogsFail>
| ReturnType<typeof fetchFavouritesRequest>
| ReturnType<typeof fetchFavouritesSuccess>
| ReturnType<typeof fetchFavouritesFail>
| ReturnType<typeof expandFavouritesSuccess>
| ReturnType<typeof expandFavouritesFail>
| ReturnType<typeof fetchDislikesRequest>
| ReturnType<typeof fetchDislikesSuccess>
| ReturnType<typeof fetchDislikesFail>
| ReturnType<typeof fetchReactionsRequest>
| ReturnType<typeof fetchReactionsSuccess>
| ReturnType<typeof fetchReactionsFail>
| ReturnType<typeof pinRequest>
| ReturnType<typeof pinSuccess>
| ReturnType<typeof pinFail>
@ -740,18 +536,6 @@ export {
UNDISLIKE_REQUEST,
UNDISLIKE_SUCCESS,
UNDISLIKE_FAIL,
REBLOGS_FETCH_REQUEST,
REBLOGS_FETCH_SUCCESS,
REBLOGS_FETCH_FAIL,
FAVOURITES_FETCH_REQUEST,
FAVOURITES_FETCH_SUCCESS,
FAVOURITES_FETCH_FAIL,
DISLIKES_FETCH_REQUEST,
DISLIKES_FETCH_SUCCESS,
DISLIKES_FETCH_FAIL,
REACTIONS_FETCH_REQUEST,
REACTIONS_FETCH_SUCCESS,
REACTIONS_FETCH_FAIL,
PIN_REQUEST,
PIN_SUCCESS,
PIN_FAIL,
@ -783,12 +567,6 @@ export {
bookmark,
unbookmark,
toggleBookmark,
fetchReblogs,
expandReblogs,
fetchFavourites,
expandFavourites,
fetchDislikes,
fetchReactions,
pin,
unpin,
togglePin,

View file

@ -0,0 +1,47 @@
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { importEntities } from 'pl-fe/actions/importer';
import { minifyAccountList } from 'pl-fe/api/normalizers/minify-list';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useClient } from 'pl-fe/hooks/use-client';
import type { PaginatedResponse } from 'pl-api';
const useStatusInteractions = (statusId: string, method: 'getDislikedBy' | 'getFavouritedBy' | 'getRebloggedBy') => {
const client = useClient();
const queryKey = {
getDislikedBy: 'statusDislikes',
getFavouritedBy: 'statusFavourites',
getRebloggedBy: 'statusReblogs',
}[method];
return useInfiniteQuery({
queryKey: ['accountsLists', queryKey, statusId],
queryFn: ({ pageParam }) => pageParam.next?.() || client.statuses[method](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(),
});
};
const useStatusDislikes = (statusId: string) => useStatusInteractions(statusId, 'getDislikedBy');
const useStatusFavourites = (statusId: string) => useStatusInteractions(statusId, 'getFavouritedBy');
const useStatusReblogs = (statusId: string) => useStatusInteractions(statusId, 'getRebloggedBy');
const useStatusReactions = (statusId: string, emoji?: string) => {
const client = useClient();
const dispatch = useAppDispatch();
return useQuery({
queryKey: ['accountsLists', 'statusReactions', statusId, emoji],
queryFn: () => client.statuses.getStatusReactions(statusId, emoji).then((reactions) => {
dispatch(importEntities({ accounts: reactions.map(({ accounts }) => accounts).flat() }));
return reactions.map(({ accounts, ...reactions }) => reactions);
}),
placeholderData: (previousData) => previousData?.filter(({ name }) => name === emoji),
});
};
export { useStatusDislikes, useStatusFavourites, useStatusReactions, useStatusReblogs };

View file

@ -1,13 +1,11 @@
import React, { useEffect } from 'react';
import React, { useRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { fetchDislikes } from 'pl-fe/actions/interactions';
import { useStatusDislikes } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
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';
import type { BaseModalProps } from '../modal-root';
@ -16,17 +14,9 @@ interface DislikesModalProps {
}
const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose, statusId }) => {
const dispatch = useAppDispatch();
const modalRef = useRef<HTMLDivElement>(null);
const accountIds = useAppSelector((state) => state.user_lists.disliked_by[statusId]?.items);
const fetchData = () => {
dispatch(fetchDislikes(statusId));
};
useEffect(() => {
fetchData();
}, []);
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusDislikes(statusId);
const onClickClose = () => {
onClose('DISLIKES');
@ -44,7 +34,12 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
emptyMessage={emptyMessage}
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
estimatedSize={42}
parentRef={modalRef}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} />,
@ -57,6 +52,7 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
<Modal
title={<FormattedMessage id='column.dislikes' defaultMessage='Dislikes' />}
onClose={onClickClose}
ref={modalRef}
>
{body}
</Modal>

View file

@ -1,13 +1,11 @@
import React, { useEffect, useRef } from 'react';
import React, { useRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { fetchFavourites, expandFavourites } from 'pl-fe/actions/interactions';
import { useStatusFavourites } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
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';
import type { BaseModalProps } from '../modal-root';
@ -17,29 +15,13 @@ interface FavouritesModalProps {
const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onClose, statusId }) => {
const modalRef = useRef<HTMLDivElement>(null);
const dispatch = useAppDispatch();
const accountIds = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.items);
const next = useAppSelector((state) => state.user_lists.favourited_by[statusId]?.next);
const fetchData = () => {
dispatch(fetchFavourites(statusId));
};
useEffect(() => {
fetchData();
}, []);
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusFavourites(statusId);
const onClickClose = () => {
onClose('FAVOURITES');
};
const handleLoadMore = () => {
if (next) {
dispatch(expandFavourites(statusId, next!));
}
};
let body;
if (!accountIds) {
@ -53,8 +35,9 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
onLoadMore={handleLoadMore}
hasMore={!!next}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
estimatedSize={42}
parentRef={modalRef}
>

View file

@ -1,16 +1,14 @@
import clsx from 'clsx';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { fetchReactions } from 'pl-fe/actions/interactions';
import { useStatusReactions } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Emoji from 'pl-fe/components/ui/emoji';
import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
import Tabs from 'pl-fe/components/ui/tabs';
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';
import type { BaseModalProps } from '../modal-root';
import type { Item } from 'pl-fe/components/ui/tabs';
@ -32,10 +30,10 @@ interface ReactionsModalProps {
const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClose, statusId, reaction: initialReaction }) => {
const modalRef = useRef<HTMLDivElement>(null);
const dispatch = useAppDispatch();
const intl = useIntl();
const [reaction, setReaction] = useState(initialReaction);
const reactions = useAppSelector((state) => state.user_lists.reactions[statusId]?.items);
const { data: reactions, isLoading } = useStatusReactions(statusId);
const onClickClose = () => {
onClose('REACTIONS');
@ -70,16 +68,12 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
if (reaction) {
const reactionRecord = reactions.find(({ name }) => name === reaction);
if (reactionRecord) return reactionRecord.accounts.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined }));
if (reactionRecord) return reactionRecord.account_ids.map(account => ({ id: account, reaction: reaction, reactionUrl: reactionRecord.url || undefined }));
} else {
return reactions.map(({ accounts, name, url }) => accounts.map(account => ({ id: account, reaction: name, reactionUrl: url || undefined }))).flat();
return reactions.map(({ account_ids, name, url }) => account_ids.map(account => ({ id: account, reaction: name, reactionUrl: url || undefined }))).flat();
}
}, [reactions, reaction]);
useEffect(() => {
dispatch(fetchReactions(statusId));
}, []);
let body;
if (!accounts || !reactions) {
@ -96,6 +90,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
})}
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
estimatedSize={42}
parentRef={modalRef}
>

View file

@ -1,14 +1,11 @@
import React, { useEffect, useRef } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import React, { useRef } from 'react';
import { FormattedMessage } from 'react-intl';
import { fetchReblogs, expandReblogs } from 'pl-fe/actions/interactions';
import { fetchStatus } from 'pl-fe/actions/statuses';
import { useStatusReblogs } from 'pl-fe/api/hooks/account-lists/use-status-interactions';
import ScrollableList from 'pl-fe/components/scrollable-list';
import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
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';
import type { BaseModalProps } from '../modal-root';
@ -17,31 +14,14 @@ interface ReblogsModalProps {
}
const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, statusId }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const accountIds = useAppSelector((state) => state.user_lists.reblogged_by[statusId]?.items);
const next = useAppSelector((state) => state.user_lists.reblogged_by[statusId]?.next);
const modalRef = useRef<HTMLDivElement>(null);
const fetchData = () => {
dispatch(fetchReblogs(statusId));
dispatch(fetchStatus(statusId, intl));
};
useEffect(() => {
fetchData();
}, []);
const { data: accountIds, isLoading, hasNextPage, fetchNextPage } = useStatusReblogs(statusId);
const onClickClose = () => {
onClose('REBLOGS');
};
const handleLoadMore = () => {
if (next) {
dispatch(expandReblogs(statusId, next!));
}
};
let body;
if (!accountIds) {
@ -55,8 +35,9 @@ const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, s
listClassName='max-w-full'
itemClassName='pb-3'
style={{ height: 'calc(80vh - 88px)' }}
onLoadMore={handleLoadMore}
hasMore={!!next}
hasMore={hasNextPage}
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
estimatedSize={42}
parentRef={modalRef}
>

View file

@ -13,9 +13,7 @@ describe('user_lists reducer', () => {
follow_requests: { next: null, items: ImmutableOrderedSet(), isLoading: false },
blocks: { next: null, items: ImmutableOrderedSet(), isLoading: false },
mutes: { next: null, items: ImmutableOrderedSet(), isLoading: false },
directory: { next: null, items: ImmutableOrderedSet(), isLoading: true },
pinned: {},
birthday_reminders: {},
familiar_followers: {},
});
});

View file

@ -16,15 +16,6 @@ import {
GROUP_UNBLOCK_SUCCESS,
type GroupsAction,
} from 'pl-fe/actions/groups';
import {
REBLOGS_FETCH_SUCCESS,
REBLOGS_EXPAND_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
FAVOURITES_EXPAND_SUCCESS,
DISLIKES_FETCH_SUCCESS,
REACTIONS_FETCH_SUCCESS,
type InteractionsAction,
} from 'pl-fe/actions/interactions';
import { NOTIFICATIONS_UPDATE, type NotificationsAction } from 'pl-fe/actions/notifications';
import type { Account, NotificationGroup, PaginatedResponse } from 'pl-api';
@ -35,31 +26,12 @@ interface List {
isLoading: boolean;
}
interface Reaction {
accounts: Array<string>;
count: number | null;
name: string;
url: string | undefined;
}
interface ReactionList {
next: (() => Promise<PaginatedResponse<Reaction>>) | null;
items: Array<Reaction>;
isLoading: boolean;
}
type ListKey = 'follow_requests';
type NestedListKey = 'reblogged_by' | 'favourited_by' | 'disliked_by' | 'pinned' | 'familiar_followers' | 'membership_requests' | 'group_blocks';
type NestedListKey = 'pinned' | 'familiar_followers' | 'membership_requests' | 'group_blocks';
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>> & {
reactions: Record<string, ReactionList>;
};
type State = Record<ListKey, List> & Record<NestedListKey, Record<string, List>>;
const initialState: State = {
reblogged_by: {},
favourited_by: {},
disliked_by: {},
reactions: {},
follow_requests: { next: null, items: [], isLoading: false },
pinned: {},
familiar_followers: {},
@ -122,26 +94,8 @@ 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 | FamiliarFollowersAction | GroupsAction | InteractionsAction | NotificationsAction): State => {
const userLists = (state = initialState, action: AccountsAction | FamiliarFollowersAction | GroupsAction | NotificationsAction): State => {
switch (action.type) {
case REBLOGS_FETCH_SUCCESS:
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
case REBLOGS_EXPAND_SUCCESS:
return appendToList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
case FAVOURITES_FETCH_SUCCESS:
return normalizeList(state, ['favourited_by', action.statusId], action.accounts, action.next);
case FAVOURITES_EXPAND_SUCCESS:
return appendToList(state, ['favourited_by', action.statusId], action.accounts, action.next);
case DISLIKES_FETCH_SUCCESS:
return normalizeList(state, ['disliked_by', action.statusId], action.accounts);
case REACTIONS_FETCH_SUCCESS:
return create(state, (draft) => {
draft.reactions[action.statusId] = {
items: action.reactions.map((reaction) => ({ ...reaction, accounts: reaction.accounts.map(({ id }) => id) })),
next: null,
isLoading: false,
};
});
case NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS: