pl-fe: migrate pinned statuses list

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-09-20 12:01:37 +02:00
parent bc013ccc39
commit c56df66c4c
7 changed files with 24 additions and 142 deletions

View File

@ -1,35 +0,0 @@
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importEntities } from './importer';
import type { PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS' as const;
const fetchPinnedStatuses = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
const me = getState().me;
return getClient(getState()).accounts.getAccountStatuses(me as string, { pinned: true }).then(response => {
dispatch(importEntities({ statuses: response.items }));
dispatch(fetchPinnedStatusesSuccess(response.items, response.next));
});
};
const fetchPinnedStatusesSuccess = (statuses: Array<Status>, next: (() => Promise<PaginatedResponse<Status>>) | null) => ({
type: PINNED_STATUSES_FETCH_SUCCESS,
statuses,
next,
});
type PinStatusesAction = ReturnType<typeof fetchPinnedStatusesSuccess>;
export {
PINNED_STATUSES_FETCH_SUCCESS,
fetchPinnedStatuses,
type PinStatusesAction,
};

View File

@ -1,14 +1,12 @@
import React, { useEffect } from 'react';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useParams } from 'react-router-dom';
import { fetchPinnedStatuses } from 'pl-fe/actions/pin-statuses';
import MissingIndicator from 'pl-fe/components/missing-indicator';
import StatusList from 'pl-fe/components/status-list';
import Column from 'pl-fe/components/ui/column';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
import { selectOwnAccount } from 'pl-fe/selectors';
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
import { usePinnedStatuses } from 'pl-fe/queries/status-lists/use-pinned-statuses';
const messages = defineMessages({
heading: { id: 'column.pins', defaultMessage: 'Pinned posts' },
@ -16,19 +14,11 @@ const messages = defineMessages({
const PinnedStatusesPage = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const { username } = useParams<{ username: string }>();
const meUsername = useAppSelector((state) => selectOwnAccount(state)?.username || '');
const statusIds = useAppSelector((state) => state.status_lists.pins!.items);
const isLoading = useAppSelector((state) => !!state.status_lists.pins!.isLoading);
const hasMore = useAppSelector((state) => !!state.status_lists.pins!.next);
const isMyAccount = username.toLowerCase() === meUsername.toLowerCase();
useEffect(() => {
dispatch(fetchPinnedStatuses());
}, []);
const { account } = useOwnAccount();
const { data: statusIds = [], isFetching: isLoading, hasNextPage: hasMore } = usePinnedStatuses(account?.id || '');
const isMyAccount = username.toLowerCase() === account?.username.toLowerCase();
if (!isMyAccount) {
return (

View File

@ -0,0 +1,11 @@
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyStatusList } from 'pl-fe/queries/utils/minify-list';
const usePinnedStatuses = makePaginatedResponseQuery(
(accountId: string) => ['statusLists', 'pins', accountId],
(client, [accountId]) => client.accounts.getAccountStatuses(accountId as string, { pinned: true }).then(minifyStatusList),
undefined,
(accountId: string) => !!accountId,
);
export { usePinnedStatuses };

View File

@ -6,6 +6,7 @@ import { PIN_SUCCESS, UNPIN_SUCCESS, type InteractionsAction } from 'pl-fe/actio
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useClient } from 'pl-fe/hooks/use-client';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/queries/utils/minify-list';
import { useModalsStore } from 'pl-fe/stores/modals';
@ -237,12 +238,15 @@ const useUnbookmarkStatus = (statusId: string) => {
const usePinStatus = (statusId: string) => {
const client = useClient();
const dispatch = useAppDispatch();
const queryClient = useQueryClient();
const { me } = useLoggedIn();
return useMutation({
mutationKey: ['statuses', 'pin', statusId],
mutationFn: () => client.statuses.pinStatus(statusId),
onSuccess: (status) => {
dispatch(importEntities({ statuses: [status] }));
queryClient.invalidateQueries({ queryKey: ['statusLists', 'pins', me] });
dispatch<InteractionsAction>({ type: PIN_SUCCESS, statusId: status.id, accountId: status.account.id });
},
});
@ -251,12 +255,15 @@ const usePinStatus = (statusId: string) => {
const useUnpinStatus = (statusId: string) => {
const client = useClient();
const dispatch = useAppDispatch();
const queryClient = useQueryClient();
const { me } = useLoggedIn();
return useMutation({
mutationKey: ['statuses', 'unpin', statusId],
mutationFn: () => client.statuses.unpinStatus(statusId),
onSuccess: (status) => {
dispatch(importEntities({ statuses: [status] }));
queryClient.setQueryData(['statusLists', 'pins', me], filterById(statusId));
dispatch<InteractionsAction>({ type: UNPIN_SUCCESS, statusId: status.id, accountId: status.account.id });
},
});

View File

@ -21,7 +21,6 @@ import plfe from './pl-fe';
import polls from './polls';
import push_notifications from './push-notifications';
import shoutbox from './shoutbox';
import status_lists from './status-lists';
import statuses from './statuses';
import timelines from './timelines';
@ -44,7 +43,6 @@ const reducers = {
polls,
push_notifications,
shoutbox,
status_lists,
statuses,
timelines,
};

View File

@ -1,14 +0,0 @@
import reducer from './status-lists';
describe('status_lists reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any).toJS()).toEqual({
pins: {
next: null,
loaded: false,
isLoading: null,
items: [],
},
});
});
});

View File

@ -1,75 +0,0 @@
import { create } from 'mutative';
import {
PIN_SUCCESS,
UNPIN_SUCCESS,
type InteractionsAction,
} from 'pl-fe/actions/interactions';
import { PINNED_STATUSES_FETCH_SUCCESS, type PinStatusesAction } from 'pl-fe/actions/pin-statuses';
import type { PaginatedResponse, Status } from 'pl-api';
interface StatusList {
next: (() => Promise<PaginatedResponse<Status>>) | null;
loaded: boolean;
isLoading: boolean | null;
items: Array<string>;
}
const newStatusList = (): StatusList => ({
next: null,
loaded: false,
isLoading: null,
items: [],
});
type State = Record<string, StatusList>;
const initialState: State = {
favourites: newStatusList(),
pins: newStatusList(),
};
const getStatusId = (status: string | Pick<Status, 'id'>) => typeof status === 'string' ? status : status.id;
const getStatusIds = (statuses: Array<string | Pick<Status, 'id'>> = []) => statuses.map(getStatusId);
const normalizeList = (state: State, listType: string, statuses: Array<string | Pick<Status, 'id'>>, next: (() => Promise<PaginatedResponse<Status>>) | null) => {
const list = state[listType] = state[listType] || newStatusList();
list.next = next;
list.loaded = true;
list.isLoading = false;
list.items = getStatusIds(statuses);
};
const prependOneToList = (state: State, listType: string, status: string | Pick<Status, 'id'>) => {
const statusId = getStatusId(status);
const list = state[listType] = state[listType] || newStatusList();
list.items = [...new Set([statusId, ...list.items])];
};
const removeOneFromList = (state: State, listType: string, status: string | Pick<Status, 'id'>) => {
const statusId = getStatusId(status);
const list = state[listType] = state[listType] || newStatusList();
list.items = list.items.filter(id => id !== statusId);
};
const statusLists = (state = initialState, action: InteractionsAction | PinStatusesAction): State => {
switch (action.type) {
case PINNED_STATUSES_FETCH_SUCCESS:
return create(state, draft => normalizeList(draft, 'pins', action.statuses, action.next));
case PIN_SUCCESS:
return create(state, draft => prependOneToList(draft, 'pins', action.statusId));
case UNPIN_SUCCESS:
return create(state, draft => removeOneFromList(draft, 'pins', action.statusId));
default:
return state;
}
};
export {
statusLists as default,
};