pl-fe: migrate polls to tanstack query
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -8,8 +8,9 @@ import { setComposeToStatus } from './compose';
|
||||
import { STATUS_FETCH_SOURCE_FAIL, type StatusesAction } from './statuses';
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
|
||||
import type { PleromaConfig } from 'pl-api';
|
||||
import type { PleromaConfig, Poll } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
|
||||
const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS' as const;
|
||||
|
||||
@ -125,7 +126,7 @@ const redactStatus = (statusId: string) => (dispatch: AppDispatch, getState: ()
|
||||
const state = getState();
|
||||
|
||||
const status = state.statuses[statusId]!;
|
||||
const poll = status.poll_id ? state.polls[status.poll_id] : undefined;
|
||||
const poll = status.poll_id ? queryClient.getQueryData<Poll>(['statuses', 'polls', status.poll_id]) : undefined;
|
||||
|
||||
return getClient(state).statuses.getStatusSource(statusId).then(response => {
|
||||
dispatch(setComposeToStatus(status, poll, response.text, response.spoiler_text, response.content_type, false, undefined, undefined, true));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { importEntities as importEntityStoreEntities } from 'pl-fe/entity-store/actions';
|
||||
import { Entities } from 'pl-fe/entity-store/entities';
|
||||
import { normalizeGroup } from 'pl-fe/normalizers/group';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
|
||||
import type { Account as BaseAccount, Group as BaseGroup, Poll as BasePoll, Relationship as BaseRelationship, Status as BaseStatus } from 'pl-api';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
@ -113,7 +114,11 @@ const importEntities = (entities: {
|
||||
|
||||
if (!isEmpty(accounts)) dispatch(importEntityStoreEntities(Object.values(accounts), Entities.ACCOUNTS));
|
||||
if (!isEmpty(groups)) dispatch(importEntityStoreEntities(Object.values(groups).map(normalizeGroup), Entities.GROUPS));
|
||||
if (!isEmpty(polls)) dispatch<ImportPollAction>(({ type: POLLS_IMPORT, polls: Object.values(polls) }));
|
||||
if (!isEmpty(polls)) {
|
||||
for (const poll of Object.values(polls)) {
|
||||
queryClient.setQueryData<BasePoll>(['statuses', 'polls', poll.id], poll);
|
||||
}
|
||||
}
|
||||
if (!isEmpty(relationships)) dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
|
||||
if (!isEmpty(statuses)) dispatch<ImportStatusesAction>({ type: STATUSES_IMPORT, statuses: Object.values(statuses) });
|
||||
};
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { getClient } from '../api';
|
||||
|
||||
import { importEntities } from './importer';
|
||||
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const vote = (pollId: string, choices: number[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState()).polls.vote(pollId, choices).then((data) => {
|
||||
dispatch(importEntities({ polls: [data] }));
|
||||
});
|
||||
|
||||
const fetchPoll = (pollId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState()).polls.getPoll(pollId).then((data) => {
|
||||
dispatch(importEntities({ polls: [data] }));
|
||||
});
|
||||
|
||||
export {
|
||||
vote,
|
||||
fetchPoll,
|
||||
};
|
||||
@ -11,7 +11,7 @@ import { setComposeToStatus } from './compose';
|
||||
import { importEntities } from './importer';
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
|
||||
import type { CreateStatusParams, Status as BaseStatus, ScheduledStatus, StatusSource } from 'pl-api';
|
||||
import type { CreateStatusParams, Status as BaseStatus, ScheduledStatus, StatusSource, Poll } from 'pl-api';
|
||||
import type { Status } from 'pl-fe/normalizers/status';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
import type { IntlShape } from 'react-intl';
|
||||
@ -91,7 +91,7 @@ const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () =>
|
||||
const state = getState();
|
||||
|
||||
const status = state.statuses[statusId]!;
|
||||
const poll = status.poll_id ? state.polls[status.poll_id] : undefined;
|
||||
const poll = status.poll_id ? queryClient.getQueryData<Poll>(['statuses', 'polls', status.poll_id]) : undefined;
|
||||
|
||||
dispatch<StatusesAction>({ type: STATUS_FETCH_SOURCE_REQUEST });
|
||||
|
||||
@ -123,7 +123,7 @@ const deleteStatus = (statusId: string, groupId?: string, withRedraft = false) =
|
||||
const state = getState();
|
||||
|
||||
const status = state.statuses[statusId]!;
|
||||
const poll = status.poll_id ? state.polls[status.poll_id] : undefined;
|
||||
const poll = status.poll_id ? queryClient.getQueryData<Poll>(['statuses', 'polls', status.poll_id]) : undefined;
|
||||
|
||||
dispatch<StatusesAction>({ type: STATUS_DELETE_REQUEST, params: status });
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchPoll, vote } from 'pl-fe/actions/polls';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import Tooltip from 'pl-fe/components/ui/tooltip';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { usePollQuery, usePollVoteMutation } from 'pl-fe/queries/statuses/use-poll';
|
||||
import { useStatusMetaActions } from 'pl-fe/stores/status-meta';
|
||||
|
||||
import RelativeTimestamp from '../relative-timestamp';
|
||||
@ -28,15 +27,17 @@ interface IPollFooter {
|
||||
}
|
||||
|
||||
const PollFooter: React.FC<IPollFooter> = ({ poll, showResults, selected, statusId }): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const { refetch } = usePollQuery(poll.id);
|
||||
const { mutate: vote } = usePollVoteMutation(poll.id);
|
||||
|
||||
const { toggleShowPollResults } = useStatusMetaActions();
|
||||
|
||||
const handleVote = () => dispatch(vote(poll.id, Object.keys(selected) as any as number[]));
|
||||
const handleVote = () => vote(Object.keys(selected) as any as number[]);
|
||||
|
||||
const handleRefresh: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(fetchPoll(poll.id));
|
||||
refetch();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { vote } from 'pl-fe/actions/polls';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { usePollQuery, usePollVoteMutation } from 'pl-fe/queries/statuses/use-poll';
|
||||
import { useModalsActions } from 'pl-fe/stores/modals';
|
||||
import { useStatusMeta } from 'pl-fe/stores/status-meta';
|
||||
|
||||
@ -25,10 +24,12 @@ interface IPoll {
|
||||
|
||||
const Poll: React.FC<IPoll> = ({ id, status, language, truncate }): JSX.Element | null => {
|
||||
const { openModal } = useModalsActions();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const isLoggedIn = useAppSelector((state) => state.me);
|
||||
const poll = useAppSelector((state) => state.polls[id]);
|
||||
|
||||
const { data: poll } = usePollQuery(id);
|
||||
// TODO: handle pending mutation state
|
||||
const { mutate: vote } = usePollVoteMutation(id);
|
||||
|
||||
const { showPollResults } = useStatusMeta(status.id);
|
||||
|
||||
@ -40,8 +41,6 @@ const Poll: React.FC<IPoll> = ({ id, status, language, truncate }): JSX.Element
|
||||
ap_id: status?.url,
|
||||
});
|
||||
|
||||
const handleVote = (selectedId: number) => dispatch(vote(id, [selectedId]));
|
||||
|
||||
const toggleOption = (value: number) => {
|
||||
if (isLoggedIn) {
|
||||
if (poll?.multiple) {
|
||||
@ -56,7 +55,7 @@ const Poll: React.FC<IPoll> = ({ id, status, language, truncate }): JSX.Element
|
||||
const tmp: Selected = {};
|
||||
tmp[value] = true;
|
||||
setSelected(tmp);
|
||||
handleVote(value);
|
||||
vote([value]);
|
||||
}
|
||||
} else {
|
||||
openUnauthorizedModal();
|
||||
|
||||
29
packages/pl-fe/src/queries/statuses/use-poll.ts
Normal file
29
packages/pl-fe/src/queries/statuses/use-poll.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
|
||||
import type { Poll } from 'pl-api';
|
||||
|
||||
const usePollQuery = (pollId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
return useQuery<Poll>({
|
||||
queryKey: ['statuses', 'polls', pollId],
|
||||
queryFn: () => client.polls.getPoll(pollId),
|
||||
});
|
||||
};
|
||||
|
||||
const usePollVoteMutation = (pollId: string) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['statuses', 'polls', pollId, 'vote'],
|
||||
mutationFn: (choices: number[]) => client.polls.vote(pollId, choices),
|
||||
onSuccess: (poll) => {
|
||||
queryClient.setQueryData<Poll>(['statuses', 'polls', pollId], poll);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { usePollQuery, usePollVoteMutation };
|
||||
@ -17,7 +17,6 @@ import meta from './meta';
|
||||
import notifications from './notifications';
|
||||
import pending_statuses from './pending-statuses';
|
||||
import plfe from './pl-fe';
|
||||
import polls from './polls';
|
||||
import push_notifications from './push-notifications';
|
||||
import statuses from './statuses';
|
||||
import timelines from './timelines';
|
||||
@ -37,7 +36,6 @@ const reducers = {
|
||||
notifications,
|
||||
pending_statuses,
|
||||
plfe,
|
||||
polls,
|
||||
push_notifications,
|
||||
statuses,
|
||||
timelines,
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { POLLS_IMPORT } from 'pl-fe/actions/importer';
|
||||
|
||||
import reducer from './polls';
|
||||
|
||||
describe('polls reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap());
|
||||
});
|
||||
|
||||
describe('POLLS_IMPORT', () => {
|
||||
it('normalizes the poll', () => {
|
||||
const polls = [{ id: '3', options: [{ title: 'Apples' }, { title: 'Oranges' }] }];
|
||||
const action = { type: POLLS_IMPORT, polls };
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
|
||||
const expected = {
|
||||
'3': {
|
||||
options: [
|
||||
{ title: 'Apples', votes_count: 0 },
|
||||
{ title: 'Oranges', votes_count: 0 },
|
||||
],
|
||||
emojis: [],
|
||||
expired: false,
|
||||
multiple: false,
|
||||
voters_count: 0,
|
||||
votes_count: 0,
|
||||
own_votes: null,
|
||||
voted: false,
|
||||
},
|
||||
};
|
||||
|
||||
expect(result.toJS()).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,20 +0,0 @@
|
||||
import { create } from 'mutative';
|
||||
|
||||
import { POLLS_IMPORT, type ImporterAction } from 'pl-fe/actions/importer';
|
||||
|
||||
import type { Poll } from 'pl-api';
|
||||
|
||||
type State = Record<string, Poll>;
|
||||
|
||||
const initialState: State = {};
|
||||
|
||||
const polls = (state: State = initialState, action: ImporterAction): State => {
|
||||
switch (action.type) {
|
||||
case POLLS_IMPORT:
|
||||
return create(state, (draft) => action.polls.forEach(poll => draft[poll.id] = poll));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { polls as default };
|
||||
@ -131,14 +131,13 @@ const makeGetStatus = () => createSelector(
|
||||
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
|
||||
return undefined;
|
||||
},
|
||||
(state: RootState, { id }: APIStatus) => state.polls[id] || null,
|
||||
(_state: RootState, { username }: APIStatus) => username,
|
||||
getFilters,
|
||||
(state: RootState) => state.me,
|
||||
(state: RootState) => state.auth.client.features,
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, poll, username, filters, me, features) => {
|
||||
(statusBase, statusReblog, statusQuote, statusGroup, username, filters, me, features) => {
|
||||
// const locale = getLocale('en');
|
||||
|
||||
if (!statusBase) return null;
|
||||
@ -159,7 +158,6 @@ const makeGetStatus = () => createSelector(
|
||||
reblog: statusReblog || null,
|
||||
quote: statusQuote || null,
|
||||
group: statusGroup || null,
|
||||
poll,
|
||||
filtered,
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user