pl-fe: migrate group members

Signed-off-by: mkljczk <git@mkljczk.pl>
This commit is contained in:
mkljczk 2024-12-05 10:49:43 +01:00
parent 05bb838577
commit 72523df008
15 changed files with 52 additions and 107 deletions

View file

@ -27,4 +27,4 @@ const groupMemberSchema = v.object({
*/
type GroupMember = v.InferOutput<typeof groupMemberSchema>;
export { groupMemberSchema, type GroupMember, GroupRoles, type GroupRole };
export { groupMemberSchema, type GroupMember, type GroupRoles, type GroupRole };

View file

@ -1,46 +0,0 @@
import { GroupRoles } from 'pl-api';
import { __stub } from 'pl-fe/api';
import { buildGroupMember } from 'pl-fe/jest/factory';
import { renderHook, waitFor } from 'pl-fe/jest/test-helpers';
import { useGroupMembers } from './use-group-members';
const groupMember = buildGroupMember();
const groupId = '1';
describe('useGroupMembers hook', () => {
describe('with a successful request', () => {
beforeEach(() => {
__stub((mock) => {
mock.onGet(`/api/v1/groups/${groupId}/memberships?role=${GroupRoles.ADMIN}`).reply(200, [groupMember]);
});
});
it('is successful', async () => {
const { result } = renderHook(() => useGroupMembers(groupId, GroupRoles.ADMIN));
await waitFor(() => expect(result.current.isFetching).toBe(false));
expect(result.current.groupMembers.length).toBe(1);
expect(result.current.groupMembers[0].id).toBe(groupMember.id);
});
});
describe('with an unsuccessful query', () => {
beforeEach(() => {
__stub((mock) => {
mock.onGet(`/api/v1/groups/${groupId}/memberships?role=${GroupRoles.ADMIN}`).networkError();
});
});
it('is has error state', async() => {
const { result } = renderHook(() => useGroupMembers(groupId, GroupRoles.ADMIN));
await waitFor(() => expect(result.current.isFetching).toBe(false));
expect(result.current.groupMembers.length).toBe(0);
expect(result.current.isError).toBeTruthy();
});
});
});

View file

@ -1,23 +0,0 @@
import { Entities } from 'pl-fe/entity-store/entities';
import { useEntities } from 'pl-fe/entity-store/hooks/use-entities';
import { useClient } from 'pl-fe/hooks/use-client';
import { normalizeGroupMember, type GroupMember } from 'pl-fe/normalizers/group-member';
import type { GroupMember as BaseGroupMember, GroupRoles } from 'pl-api';
const useGroupMembers = (groupId: string, role: GroupRoles) => {
const client = useClient();
const { entities, ...result } = useEntities<BaseGroupMember, GroupMember>(
[Entities.GROUP_MEMBERSHIPS, groupId, role],
() => client.experimental.groups.getGroupMemberships(groupId, role),
{ transform: normalizeGroupMember },
);
return {
...result,
groupMembers: entities,
};
};
export { useGroupMembers };

View file

@ -20,7 +20,7 @@ import toast from 'pl-fe/toast';
import type { Menu as IMenu } from 'pl-fe/components/dropdown-menu';
import type { Group } from 'pl-fe/normalizers/group';
import type { GroupMember } from 'pl-fe/normalizers/group-member';
import type { MinifiedGroupMember } from 'pl-fe/queries/groups/use-group-members';
const messages = defineMessages({
adminLimitTitle: { id: 'group.member.admin.limit.title', defaultMessage: 'Admin limit reached' },
@ -44,7 +44,7 @@ const messages = defineMessages({
});
interface IGroupMemberListItem {
member: GroupMember;
member: MinifiedGroupMember;
group: Pick<Group, 'id' | 'relationship'>;
}
@ -53,11 +53,11 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
const intl = useIntl();
const { openModal } = useModalsStore();
const { mutate: blockGroupMember } = useBlockGroupUserMutation(group.id, member.account.id);
const { mutate: blockGroupMember } = useBlockGroupUserMutation(group.id, member.account_id);
const promoteGroupMember = usePromoteGroupMember(group, member);
const demoteGroupMember = useDemoteGroupMember(group, member);
const { account, isLoading } = useAccount(member.account.id);
const { account, isLoading } = useAccount(member.account_id);
// Current user role
const isCurrentUserOwner = group.relationship?.role === GroupRoles.OWNER;
@ -102,7 +102,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
confirm: intl.formatMessage(messages.promoteConfirm),
confirmationTheme: 'primary',
onConfirm: () => {
promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [member.account.id] }, {
promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [member.account_id] }, {
onSuccess() {
toast.success(
intl.formatMessage(messages.promotedToAdmin, { name: account?.acct }),
@ -114,7 +114,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
};
const handleUserAssignment = () => {
demoteGroupMember({ role: GroupRoles.USER, account_ids: [member.account.id] }, {
demoteGroupMember({ role: GroupRoles.USER, account_ids: [member.account_id] }, {
onSuccess() {
toast.success(intl.formatMessage(messages.demotedToUser, { name: account?.acct }));
},
@ -167,7 +167,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
return items;
}, [group, account?.id]);
if (isLoading) {
if (isLoading || !account) {
return <PlaceholderAccount />;
}
@ -178,7 +178,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
data-testid='group-member-list-item'
>
<div className='w-full'>
<Account account={member.account} withRelationship={false} />
<Account account={account} withRelationship={false} />
</div>
<HStack alignItems='center' space={2}>

View file

@ -3,10 +3,10 @@ import { GroupRoles } from 'pl-api';
import React, { useMemo } from 'react';
import { useGroup } from 'pl-fe/api/hooks/groups/use-group';
import { useGroupMembers } from 'pl-fe/api/hooks/groups/use-group-members';
import { useGroupMembershipRequests } from 'pl-fe/api/hooks/groups/use-group-membership-requests';
import { PendingItemsRow } from 'pl-fe/components/pending-items-row';
import ScrollableList from 'pl-fe/components/scrollable-list';
import { useGroupMembers } from 'pl-fe/queries/groups/use-group-members';
import PlaceholderAccount from '../placeholder/components/placeholder-account';
@ -20,17 +20,17 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
const { groupId } = props.params;
const { group, isFetching: isFetchingGroup } = useGroup(groupId);
const { groupMembers: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER);
const { groupMembers: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, GroupRoles.ADMIN);
const { groupMembers: users, isFetching: isFetchingUsers, fetchNextPage, hasNextPage } = useGroupMembers(groupId, GroupRoles.USER);
const { data: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER);
const { data: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, GroupRoles.ADMIN);
const { data: users, isFetching: isFetchingUsers, fetchNextPage, hasNextPage } = useGroupMembers(groupId, GroupRoles.USER);
const { isFetching: isFetchingPending, count: pendingCount } = useGroupMembershipRequests(groupId);
const isLoading = isFetchingGroup || isFetchingOwners || isFetchingAdmins || isFetchingUsers || isFetchingPending;
const members = useMemo(() => [
...owners,
...admins,
...users,
...(owners || []),
...(admins || []),
...(users || []),
], [owners, admins, users]);
return (
@ -57,7 +57,7 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
<GroupMemberListItem
group={group!}
member={member}
key={member.account.id}
key={member.account_id}
/>
))}
</ScrollableList>

View file

@ -1,9 +1,7 @@
import { GroupRoles } from 'pl-api';
import React, { useEffect } from 'react';
import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useGroup } from 'pl-fe/api/hooks/groups/use-group';
import { useGroupMembers } from 'pl-fe/api/hooks/groups/use-group-members';
import { useGroupMembershipRequests } from 'pl-fe/api/hooks/groups/use-group-membership-requests';
import Account from 'pl-fe/components/account';
import { AuthorizeRejectButtons } from 'pl-fe/components/authorize-reject-buttons';
@ -64,11 +62,6 @@ const GroupMembershipRequests: React.FC<IGroupMembershipRequests> = ({ params })
const { group } = useGroup(groupId);
const { accounts, authorize, reject, refetch, isLoading } = useGroupMembershipRequests(groupId);
const { invalidate } = useGroupMembers(groupId, GroupRoles.USER);
useEffect(() => () => {
invalidate();
}, []);
if (!group || !group.relationship || isLoading) {
return (

View file

@ -1,9 +1,9 @@
import { useMutation, type InfiniteData } from '@tanstack/react-query';
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/api/utils/minify-list';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from 'pl-fe/queries/client';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/queries/utils/minify-list';
import type { PaginatedResponse, PlApiClient } from 'pl-api';

View file

@ -2,8 +2,8 @@
import { type InfiniteData, useMutation } from '@tanstack/react-query';
import { importEntities } from 'pl-fe/actions/importer';
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyList } from 'pl-fe/api/utils/minify-list';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyList } from 'pl-fe/queries/utils/minify-list';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from 'pl-fe/queries/client';
import { store } from 'pl-fe/store';

View file

@ -1,5 +1,5 @@
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/api/utils/minify-list';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/queries/utils/minify-list';
const useEventParticipations = makePaginatedResponseQuery(
(statusId: string) => ['accountsLists', 'eventParticipations', statusId],

View file

@ -1,7 +1,7 @@
import { useMutation, type InfiniteData } from '@tanstack/react-query';
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/api/utils/minify-list';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/queries/utils/minify-list';
import { useClient } from 'pl-fe/hooks/use-client';
import { queryClient } from 'pl-fe/queries/client';

View file

@ -0,0 +1,21 @@
import { GroupMember, GroupRole, PaginatedResponse } from 'pl-api';
import { importEntities } from 'pl-fe/actions/importer';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { store } from 'pl-fe/store';
import { minifyList } from '../utils/minify-list';
const minifyGroupMembersList = (response: PaginatedResponse<GroupMember>): PaginatedResponse<Omit<GroupMember, 'account'> & { account_id: string }> =>
minifyList(response, ({ account, ...groupMember }) => ({ ...groupMember, account_id: account.id }), (groupMembers) => {
store.dispatch(importEntities({ accounts: groupMembers.map(({ account }) => account) }) as any);
});
const useGroupMembers = makePaginatedResponseQuery(
(groupId: string, role?: GroupRole) => ['accountsLists', 'groupMembers', groupId, role],
(client, [groupId, role]) => client.experimental.groups.getGroupMemberships(groupId, role).then(minifyGroupMembersList),
);
type MinifiedGroupMember = ReturnType<typeof minifyGroupMembersList>['items'][0];
export { useGroupMembers, type MinifiedGroupMember };

View file

@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { importEntities } from 'pl-fe/actions/importer';
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/api/utils/minify-list';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyAccountList } from 'pl-fe/queries/utils/minify-list';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useClient } from 'pl-fe/hooks/use-client';

View file

@ -1,5 +1,5 @@
import { makePaginatedResponseQuery } from 'pl-fe/api/utils/make-paginated-response-query';
import { minifyStatusList } from 'pl-fe/api/utils/minify-list';
import { makePaginatedResponseQuery } from 'pl-fe/queries/utils/make-paginated-response-query';
import { minifyStatusList } from 'pl-fe/queries/utils/minify-list';
const useStatusQuotes = makePaginatedResponseQuery(
(statusId: string) => ['statusLists', 'quotes', statusId],

View file

@ -5,7 +5,7 @@ import { useClient } from 'pl-fe/hooks/use-client';
import type { PaginatedResponse, PlApiClient } from 'pl-api';
const makePaginatedResponseQuery = <T1 extends Array<any>, T2, T3 = Array<T2>>(
queryKey: (...params: T1) => string[],
queryKey: (...params: T1) => Array<string | undefined>,
queryFn: (client: PlApiClient, params: T1) => Promise<PaginatedResponse<T2>>,
select?: (data: InfiniteData<PaginatedResponse<T2>>) => T3,
) => (...params: T1) => {