From e42e0577f457934633a3d01628967eb5fbd66b3e Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 20 Mar 2023 13:27:22 -0400 Subject: [PATCH] Move Group mutations to entities --- .../entity-store/hooks/useEntityActions.ts | 8 +++ .../group/components/group-action-button.tsx | 38 ++++++++++--- .../components/discover/group-grid-item.tsx | 4 +- .../components/discover/group-list-item.tsx | 4 +- .../api/groups/useCancelMembershipRequest.ts | 21 ++++++++ app/soapbox/hooks/api/groups/useJoinGroup.ts | 20 +++++++ app/soapbox/hooks/api/groups/useLeaveGroup.ts | 18 +++++++ app/soapbox/hooks/api/index.ts | 5 +- app/soapbox/queries/groups.ts | 54 +------------------ 9 files changed, 106 insertions(+), 66 deletions(-) create mode 100644 app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts create mode 100644 app/soapbox/hooks/api/groups/useJoinGroup.ts create mode 100644 app/soapbox/hooks/api/groups/useLeaveGroup.ts diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts index eede5bcb3..2383c69c6 100644 --- a/app/soapbox/entity-store/hooks/useEntityActions.ts +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { z } from 'zod'; import { useApi, useAppDispatch, useGetState } from 'soapbox/hooks'; @@ -42,9 +43,13 @@ function useEntityActions( const getState = useGetState(); const [entityType, listKey] = path; + const [isLoading, setIsLoading] = useState(false); + function createEntity(params: P, callbacks: EntityCallbacks = {}): Promise> { if (!endpoints.post) return Promise.reject(endpoints); + setIsLoading(true); + return api.post(endpoints.post, params).then((response) => { const schema = opts.schema || z.custom(); const entity = schema.parse(response.data); @@ -56,6 +61,8 @@ function useEntityActions( callbacks.onSuccess(entity); } + setIsLoading(false); + return { response, entity, @@ -89,6 +96,7 @@ function useEntityActions( return { createEntity: createEntity, deleteEntity: endpoints.delete ? deleteEntity : undefined, + isLoading, }; } diff --git a/app/soapbox/features/group/components/group-action-button.tsx b/app/soapbox/features/group/components/group-action-button.tsx index 64824866a..ba828c9c1 100644 --- a/app/soapbox/features/group/components/group-action-button.tsx +++ b/app/soapbox/features/group/components/group-action-button.tsx @@ -3,8 +3,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; import { Button } from 'soapbox/components/ui'; +import { deleteEntities } from 'soapbox/entity-store/actions'; +import { Entities } from 'soapbox/entity-store/entities'; import { useAppDispatch } from 'soapbox/hooks'; -import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/queries/groups'; +import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/hooks/api'; +import toast from 'soapbox/toast'; import { Group } from 'soapbox/types/entities'; interface IGroupActionButton { @@ -12,35 +15,54 @@ interface IGroupActionButton { } const messages = defineMessages({ + confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' }, confirmationHeading: { id: 'confirmations.leave_group.heading', defaultMessage: 'Leave group' }, confirmationMessage: { id: 'confirmations.leave_group.message', defaultMessage: 'You are about to leave the group. Do you want to continue?' }, - confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' }, + joinRequestSuccess: { id: 'group.join.request_success', defaultMessage: 'Requested to join the group' }, + joinSuccess: { id: 'group.join.success', defaultMessage: 'Group joined successfully!' }, + leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' }, }); const GroupActionButton = ({ group }: IGroupActionButton) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const joinGroup = useJoinGroup(); - const leaveGroup = useLeaveGroup(); - const cancelRequest = useCancelMembershipRequest(); + const joinGroup = useJoinGroup(group); + const leaveGroup = useLeaveGroup(group); + const cancelRequest = useCancelMembershipRequest(group); const isRequested = group.relationship?.requested; const isNonMember = !group.relationship?.member && !isRequested; const isAdmin = group.relationship?.role === 'owner'; const isBlocked = group.relationship?.blocked_by; - const onJoinGroup = () => joinGroup.mutate(group); + const onJoinGroup = () => joinGroup.mutate({}, { + onSuccess() { + toast.success( + group.locked + ? intl.formatMessage(messages.joinRequestSuccess) + : intl.formatMessage(messages.joinSuccess), + ); + }, + }); const onLeaveGroup = () => dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.confirmationHeading), message: intl.formatMessage(messages.confirmationMessage), confirm: intl.formatMessage(messages.confirmationConfirm), - onConfirm: () => leaveGroup.mutate(group), + onConfirm: () => leaveGroup.mutate({}, { + onSuccess() { + toast.success(intl.formatMessage(messages.leaveSuccess)); + }, + }), })); - const onCancelRequest = () => cancelRequest.mutate(group); + const onCancelRequest = () => cancelRequest.mutate({}, { + onSuccess() { + dispatch(deleteEntities([group.id], Entities.GROUP_RELATIONSHIPS)); + }, + }); if (isBlocked) { return null; diff --git a/app/soapbox/features/groups/components/discover/group-grid-item.tsx b/app/soapbox/features/groups/components/discover/group-grid-item.tsx index 9c743c44f..72bf4dc75 100644 --- a/app/soapbox/features/groups/components/discover/group-grid-item.tsx +++ b/app/soapbox/features/groups/components/discover/group-grid-item.tsx @@ -6,7 +6,7 @@ import GroupAvatar from 'soapbox/components/groups/group-avatar'; import { Button, HStack, Stack, Text } from 'soapbox/components/ui'; import GroupMemberCount from 'soapbox/features/group/components/group-member-count'; import GroupPrivacy from 'soapbox/features/group/components/group-privacy'; -import { useJoinGroup } from 'soapbox/queries/groups'; +import { useJoinGroup } from 'soapbox/hooks/api'; import { Group as GroupEntity } from 'soapbox/types/entities'; interface IGroup { @@ -17,7 +17,7 @@ interface IGroup { const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef) => { const { group, width = 'auto' } = props; - const joinGroup = useJoinGroup(); + const joinGroup = useJoinGroup(group); const onJoinGroup = () => joinGroup.mutate(group); diff --git a/app/soapbox/features/groups/components/discover/group-list-item.tsx b/app/soapbox/features/groups/components/discover/group-list-item.tsx index 8f2f5879c..55190b85e 100644 --- a/app/soapbox/features/groups/components/discover/group-list-item.tsx +++ b/app/soapbox/features/groups/components/discover/group-list-item.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import GroupAvatar from 'soapbox/components/groups/group-avatar'; import { Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; -import { useJoinGroup } from 'soapbox/queries/groups'; +import { useJoinGroup } from 'soapbox/hooks/api'; import { Group as GroupEntity } from 'soapbox/types/entities'; import { shortNumberFormat } from 'soapbox/utils/numbers'; @@ -16,7 +16,7 @@ interface IGroup { const GroupListItem = (props: IGroup) => { const { group, withJoinAction = true } = props; - const joinGroup = useJoinGroup(); + const joinGroup = useJoinGroup(group); const onJoinGroup = () => joinGroup.mutate(group); diff --git a/app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts b/app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts new file mode 100644 index 000000000..3aad33f9c --- /dev/null +++ b/app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts @@ -0,0 +1,21 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityActions } from 'soapbox/entity-store/hooks'; +import { useOwnAccount } from 'soapbox/hooks'; + +import type { Group, GroupRelationship } from 'soapbox/schemas'; + +function useCancelMembershipRequest(group: Group) { + const me = useOwnAccount(); + + const { createEntity, isLoading } = useEntityActions( + [Entities.GROUP_RELATIONSHIPS, group.id], + { post: `/api/v1/groups/${group.id}/membership_requests/${me?.id}/reject` }, + ); + + return { + mutate: createEntity, + isLoading, + }; +} + +export { useCancelMembershipRequest }; diff --git a/app/soapbox/hooks/api/groups/useJoinGroup.ts b/app/soapbox/hooks/api/groups/useJoinGroup.ts new file mode 100644 index 000000000..f30a9c25b --- /dev/null +++ b/app/soapbox/hooks/api/groups/useJoinGroup.ts @@ -0,0 +1,20 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityActions } from 'soapbox/entity-store/hooks'; +import { groupRelationshipSchema } from 'soapbox/schemas'; + +import type { Group, GroupRelationship } from 'soapbox/schemas'; + +function useJoinGroup(group: Group) { + const { createEntity, isLoading } = useEntityActions( + [Entities.GROUP_RELATIONSHIPS, group.id], + { post: `/api/v1/groups/${group.id}/join` }, + { schema: groupRelationshipSchema }, + ); + + return { + mutate: createEntity, + isLoading, + }; +} + +export { useJoinGroup }; \ No newline at end of file diff --git a/app/soapbox/hooks/api/groups/useLeaveGroup.ts b/app/soapbox/hooks/api/groups/useLeaveGroup.ts new file mode 100644 index 000000000..1f6b99f8d --- /dev/null +++ b/app/soapbox/hooks/api/groups/useLeaveGroup.ts @@ -0,0 +1,18 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityActions } from 'soapbox/entity-store/hooks'; +import { Group, GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas'; + +function useLeaveGroup(group: Group) { + const { createEntity, isLoading } = useEntityActions( + [Entities.GROUP_RELATIONSHIPS, group.id], + { post: `/api/v1/groups/${group.id}/leave` }, + { schema: groupRelationshipSchema }, + ); + + return { + mutate: createEntity, + isLoading, + }; +} + +export { useLeaveGroup }; diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/hooks/api/index.ts index 144196a2c..3ec927873 100644 --- a/app/soapbox/hooks/api/index.ts +++ b/app/soapbox/hooks/api/index.ts @@ -2,5 +2,8 @@ * Groups */ export { useBlockGroupMember } from './groups/useBlockGroupMember'; +export { useCancelMembershipRequest } from './groups/useCancelMembershipRequest'; export { useDemoteGroupMember } from './groups/useDemoteGroupMember'; -export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; \ No newline at end of file +export { useJoinGroup } from './groups/useJoinGroup'; +export { useLeaveGroup } from './groups/useLeaveGroup'; +export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; diff --git a/app/soapbox/queries/groups.ts b/app/soapbox/queries/groups.ts index 4ac826b4e..daf39806e 100644 --- a/app/soapbox/queries/groups.ts +++ b/app/soapbox/queries/groups.ts @@ -1,22 +1,12 @@ -import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { AxiosRequestConfig } from 'axios'; -import { defineMessages, useIntl } from 'react-intl'; import { getNextLink } from 'soapbox/api'; import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { normalizeGroup, normalizeGroupRelationship } from 'soapbox/normalizers'; -import toast from 'soapbox/toast'; import { Group, GroupRelationship } from 'soapbox/types/entities'; import { flattenPages, PaginatedResult } from 'soapbox/utils/queries'; -import { queryClient } from './client'; - -const messages = defineMessages({ - joinSuccess: { id: 'group.join.success', defaultMessage: 'Group joined successfully!' }, - joinRequestSuccess: { id: 'group.join.request_success', defaultMessage: 'Requested to join the group' }, - leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' }, -}); - const GroupKeys = { group: (id: string) => ['groups', 'group', id] as const, myGroups: (userId: string) => ['groups', userId] as const, @@ -168,50 +158,8 @@ const useGroup = (id: string) => { }; }; -const useJoinGroup = () => { - const api = useApi(); - const intl = useIntl(); - - return useMutation((group: Group) => api.post(`/api/v1/groups/${group.id}/join`), { - onSuccess(_response, group) { - queryClient.invalidateQueries(['groups']); - toast.success( - group.locked - ? intl.formatMessage(messages.joinRequestSuccess) - : intl.formatMessage(messages.joinSuccess), - ); - }, - }); -}; - -const useLeaveGroup = () => { - const api = useApi(); - const intl = useIntl(); - - return useMutation((group: Group) => api.post(`/api/v1/groups/${group.id}/leave`), { - onSuccess() { - queryClient.invalidateQueries({ queryKey: ['groups'] }); - toast.success(intl.formatMessage(messages.leaveSuccess)); - }, - }); -}; - -const useCancelMembershipRequest = () => { - const api = useApi(); - const me = useOwnAccount(); - - return useMutation((group: Group) => api.post(`/api/v1/groups/${group.id}/membership_requests/${me?.id}/reject`), { - onSuccess() { - queryClient.invalidateQueries({ queryKey: ['groups'] }); - }, - }); -}; - export { - useCancelMembershipRequest, useGroup, useGroups, - useJoinGroup, - useLeaveGroup, usePendingGroups, };