Use react-query and zod for announcements

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-04-09 23:38:09 +02:00
parent d4beb15d71
commit 161db37ba0
28 changed files with 391 additions and 905 deletions

View File

@ -0,0 +1,89 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { useApi } from 'soapbox/hooks';
import { queryClient } from 'soapbox/queries/client';
import { adminAnnouncementSchema, type AdminAnnouncement } from 'soapbox/schemas';
import { useAnnouncements as useUserAnnouncements } from '../announcements';
import type { AxiosResponse } from 'axios';
interface CreateAnnouncementParams {
content: string;
starts_at?: string | null;
ends_at?: string | null;
all_day?: boolean;
}
interface UpdateAnnouncementParams extends CreateAnnouncementParams {
id: string;
}
const useAnnouncements = () => {
const api = useApi();
const userAnnouncements = useUserAnnouncements();
const getAnnouncements = async () => {
const { data } = await api.get<AdminAnnouncement[]>('/api/v1/pleroma/admin/announcements');
const normalizedData = data.map((announcement) => adminAnnouncementSchema.parse(announcement));
return normalizedData;
};
const result = useQuery<ReadonlyArray<AdminAnnouncement>>({
queryKey: ['admin', 'announcements'],
queryFn: getAnnouncements,
placeholderData: [],
});
const {
mutate: createAnnouncement,
isPending: isCreating,
} = useMutation({
mutationFn: (params: CreateAnnouncementParams) => api.post('/api/v1/pleroma/admin/announcements', params),
retry: false,
onSuccess: ({ data }: AxiosResponse) =>
queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
[...prevResult, adminAnnouncementSchema.parse(data)],
),
onSettled: () => userAnnouncements.refetch(),
});
const {
mutate: updateAnnouncement,
isPending: isUpdating,
} = useMutation({
mutationFn: ({ id, ...params }: UpdateAnnouncementParams) => api.patch(`/api/v1/pleroma/admin/announcements/${id}`, params),
retry: false,
onSuccess: ({ data }: AxiosResponse) =>
queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
prevResult.map((announcement) => announcement.id === data.id ? adminAnnouncementSchema.parse(data) : announcement),
),
onSettled: () => userAnnouncements.refetch(),
});
const {
mutate: deleteAnnouncement,
isPending: isDeleting,
} = useMutation({
mutationFn: (id: string) => api.delete(`/api/v1/pleroma/admin/announcements/${id}`),
retry: false,
onSuccess: (_, id) =>
queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray<AdminAnnouncement>) =>
prevResult.filter(({ id: announcementId }) => announcementId !== id),
),
onSettled: () => userAnnouncements.refetch(),
});
return {
...result,
createAnnouncement,
isCreating,
updateAnnouncement,
isUpdating,
deleteAnnouncement,
isDeleting,
};
};
export { useAnnouncements };

View File

@ -0,0 +1 @@
export { useAnnouncements } from './useAnnouncements';

View File

@ -0,0 +1,95 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { useApi } from 'soapbox/hooks';
import { queryClient } from 'soapbox/queries/client';
import { announcementReactionSchema, announcementSchema, type Announcement, type AnnouncementReaction } from 'soapbox/schemas';
const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({
...reaction,
me: typeof me === 'boolean' ? me : reaction.me,
count: overwrite ? count : (reaction.count + count),
});
export const updateReactions = (reactions: AnnouncementReaction[], name: string, count: number, me?: boolean, overwrite?: boolean) => {
const idx = reactions.findIndex(reaction => reaction.name === name);
if (idx > -1) {
reactions = reactions.map(reaction => reaction.name === name ? updateReaction(reaction, count, me, overwrite) : reaction);
}
return [...reactions, updateReaction(announcementReactionSchema.parse({ name }), count, me, overwrite)];
};
const useAnnouncements = () => {
const api = useApi();
const getAnnouncements = async () => {
const { data } = await api.get<Announcement[]>('/api/v1/announcements');
const normalizedData = data?.map((announcement) => announcementSchema.parse(announcement));
return normalizedData;
};
const { data, ...result } = useQuery<ReadonlyArray<Announcement>>({
queryKey: ['announcements'],
queryFn: getAnnouncements,
placeholderData: [],
});
const {
mutate: addReaction,
} = useMutation({
mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) =>
api.put<Announcement>(`/api/v1/announcements/${announcementId}/reactions/${name}`),
retry: false,
onMutate: ({ announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
...value,
reactions: updateReactions(value.reactions, name, 1, true),
})),
);
},
onError: (_, { announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
...value,
reactions: updateReactions(value.reactions, name, -1, false),
})),
);
},
});
const {
mutate: removeReaction,
} = useMutation({
mutationFn: ({ announcementId, name }: { announcementId: string; name: string }) =>
api.delete<Announcement>(`/api/v1/announcements/${announcementId}/reactions/${name}`),
retry: false,
onMutate: ({ announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
...value,
reactions: updateReactions(value.reactions, name, -1, false),
})),
);
},
onError: (_, { announcementId: id, name }) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => value.id !== id ? value : announcementSchema.parse({
...value,
reactions: updateReactions(value.reactions, name, 1, true),
})),
);
},
});
return {
data: data?.toSorted((a, b) => new Date(a.starts_at || a.published_at).getDate() - new Date(b.starts_at || b.published_at).getDate()),
...result,
addReaction,
removeReaction,
};
};
export { useAnnouncements };

View File

@ -1,4 +1,3 @@
import { fetchAnnouncements } from 'soapbox/actions/announcements';
import { expandNotifications } from 'soapbox/actions/notifications';
import { expandHomeTimeline } from 'soapbox/actions/timelines';
import { useStatContext } from 'soapbox/contexts/stat-context';
@ -24,8 +23,7 @@ function useUserStream() {
/** Refresh home timeline and notifications. */
function refresh(dispatch: AppDispatch, done?: () => void) {
return dispatch(expandHomeTimeline({}, () =>
dispatch(expandNotifications({}, () =>
dispatch(fetchAnnouncements(done))))));
dispatch(expandNotifications({}, done))));
}
export { useUserStream };