From a486b317d453da5e88175e780c773b8122946a38 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 4 Apr 2023 08:11:03 -0400 Subject: [PATCH] Fetch account and relationship with entity store --- app/soapbox/entity-store/entities.ts | 1 + app/soapbox/entity-store/hooks/useEntity.ts | 2 +- .../components/group-member-list-item.tsx | 35 +++++++++++-------- app/soapbox/features/group/group-members.tsx | 4 +-- .../components/placeholder-account.tsx | 2 +- .../components/placeholder-display-name.tsx | 2 +- app/soapbox/hooks/api/index.ts | 12 ++++++- app/soapbox/hooks/api/useAccount.ts | 26 ++++++++++++++ app/soapbox/hooks/api/useRelationships.ts | 22 ++++++++++++ app/soapbox/hooks/useLoading.ts | 4 +-- 10 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 app/soapbox/hooks/api/useAccount.ts create mode 100644 app/soapbox/hooks/api/useRelationships.ts diff --git a/app/soapbox/entity-store/entities.ts b/app/soapbox/entity-store/entities.ts index 44f2db3c9..7f9f84e2a 100644 --- a/app/soapbox/entity-store/entities.ts +++ b/app/soapbox/entity-store/entities.ts @@ -3,4 +3,5 @@ export enum Entities { GROUPS = 'Groups', GROUP_RELATIONSHIPS = 'GroupRelationships', GROUP_MEMBERSHIPS = 'GroupMemberships', + RELATIONSHIPS = 'Relationships' } \ No newline at end of file diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index f30c9a18a..37027f73f 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -21,7 +21,7 @@ function useEntity( entityFn: EntityFn, opts: UseEntityOpts = {}, ) { - const [isFetching, setPromise] = useLoading(); + const [isFetching, setPromise] = useLoading(true); const dispatch = useAppDispatch(); const [entityType, entityId] = path; diff --git a/app/soapbox/features/group/components/group-member-list-item.tsx b/app/soapbox/features/group/components/group-member-list-item.tsx index 10ad4e0cb..4fa3ccb7a 100644 --- a/app/soapbox/features/group/components/group-member-list-item.tsx +++ b/app/soapbox/features/group/components/group-member-list-item.tsx @@ -9,13 +9,14 @@ import DropdownMenu from 'soapbox/components/dropdown-menu/dropdown-menu'; import { HStack } from 'soapbox/components/ui'; import { deleteEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; -import { useAccount, useAppDispatch, useFeatures } from 'soapbox/hooks'; -import { useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api'; +import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account'; +import { useAppDispatch, useFeatures } from 'soapbox/hooks'; +import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; -import type { Account as AccountEntity, Group, GroupMember } from 'soapbox/types/entities'; +import type { Group, GroupMember } from 'soapbox/types/entities'; const messages = defineMessages({ blockConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Ban' }, @@ -51,7 +52,7 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const promoteGroupMember = usePromoteGroupMember(group, member); const demoteGroupMember = useDemoteGroupMember(group, member); - const account = useAccount(member.account.id) as AccountEntity; + const { account, isLoading } = useAccount(member.account.id); // Current user role const isCurrentUserOwner = group.relationship?.role === GroupRoles.OWNER; @@ -64,10 +65,10 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const handleKickFromGroup = () => { dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.kickFromGroupMessage, { name: account.username }), + message: intl.formatMessage(messages.kickFromGroupMessage, { name: account?.username }), confirm: intl.formatMessage(messages.kickConfirm), - onConfirm: () => dispatch(groupKick(group.id, account.id)).then(() => - toast.success(intl.formatMessage(messages.kicked, { name: account.acct })), + onConfirm: () => dispatch(groupKick(group.id, account?.id as string)).then(() => + toast.success(intl.formatMessage(messages.kicked, { name: account?.acct })), ), })); }; @@ -75,13 +76,13 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const handleBlockFromGroup = () => { dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.blockFromGroupHeading), - message: intl.formatMessage(messages.blockFromGroupMessage, { name: account.username }), + message: intl.formatMessage(messages.blockFromGroupMessage, { name: account?.username }), confirm: intl.formatMessage(messages.blockConfirm), onConfirm: () => { blockGroupMember({ account_ids: [member.account.id] }, { onSuccess() { dispatch(deleteEntities([member.id], Entities.GROUP_MEMBERSHIPS)); - toast.success(intl.formatMessage(messages.blocked, { name: account.acct })); + toast.success(intl.formatMessage(messages.blocked, { name: account?.acct })); }, }); }, @@ -91,14 +92,14 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const handleAdminAssignment = () => { dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.promoteConfirm), - message: intl.formatMessage(messages.promoteConfirmMessage, { name: account.username }), + message: intl.formatMessage(messages.promoteConfirmMessage, { name: account?.username }), confirm: intl.formatMessage(messages.promoteConfirm), confirmationTheme: 'primary', onConfirm: () => { - promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [account.id] }, { + promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [account?.id] }, { onSuccess() { toast.success( - intl.formatMessage(messages.promotedToAdmin, { name: account.acct }), + intl.formatMessage(messages.promotedToAdmin, { name: account?.acct }), ); }, }); @@ -107,9 +108,9 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { }; const handleUserAssignment = () => { - demoteGroupMember({ role: GroupRoles.USER, account_ids: [account.id] }, { + demoteGroupMember({ role: GroupRoles.USER, account_ids: [account?.id] }, { onSuccess() { - toast.success(intl.formatMessage(messages.demotedToUser, { name: account.acct })); + toast.success(intl.formatMessage(messages.demotedToUser, { name: account?.acct })); }, }); }; @@ -160,7 +161,11 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { } return items; - }, [group, account]); + }, [group, account?.id]); + + if (isLoading) { + return ; + } return ( diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx index 2facf912f..be4185d2b 100644 --- a/app/soapbox/features/group/group-members.tsx +++ b/app/soapbox/features/group/group-members.tsx @@ -53,11 +53,11 @@ const GroupMembers: React.FC = (props) => { )} > - {members.map((member) => ( + {members.map((member, idx) => ( ))} diff --git a/app/soapbox/features/placeholder/components/placeholder-account.tsx b/app/soapbox/features/placeholder/components/placeholder-account.tsx index 28f04adb4..c887903fd 100644 --- a/app/soapbox/features/placeholder/components/placeholder-account.tsx +++ b/app/soapbox/features/placeholder/components/placeholder-account.tsx @@ -18,4 +18,4 @@ const PlaceholderAccount: React.FC = () => ( ); -export default PlaceholderAccount; +export default React.memo(PlaceholderAccount); diff --git a/app/soapbox/features/placeholder/components/placeholder-display-name.tsx b/app/soapbox/features/placeholder/components/placeholder-display-name.tsx index e09e73439..3a2e0e641 100644 --- a/app/soapbox/features/placeholder/components/placeholder-display-name.tsx +++ b/app/soapbox/features/placeholder/components/placeholder-display-name.tsx @@ -21,4 +21,4 @@ const PlaceholderDisplayName: React.FC = ({ minLength, ); }; -export default PlaceholderDisplayName; +export default React.memo(PlaceholderDisplayName); diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/hooks/api/index.ts index f3bd400e1..f3addf56b 100644 --- a/app/soapbox/hooks/api/index.ts +++ b/app/soapbox/hooks/api/index.ts @@ -1,3 +1,8 @@ +/** + * Accounts + */ +export { useAccount } from './useAccount'; + /** * Groups */ @@ -11,4 +16,9 @@ export { useGroupValidation } from './groups/useGroupValidation'; export { useJoinGroup } from './groups/useJoinGroup'; export { useLeaveGroup } from './groups/useLeaveGroup'; export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; -export { useUpdateGroup } from './groups/useUpdateGroup'; \ No newline at end of file +export { useUpdateGroup } from './groups/useUpdateGroup'; + +/** + * Relationships + */ +export { useRelationships } from './useRelationships'; \ No newline at end of file diff --git a/app/soapbox/hooks/api/useAccount.ts b/app/soapbox/hooks/api/useAccount.ts new file mode 100644 index 000000000..a222d8de4 --- /dev/null +++ b/app/soapbox/hooks/api/useAccount.ts @@ -0,0 +1,26 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntity } from 'soapbox/entity-store/hooks'; +import { type Account, accountSchema } from 'soapbox/schemas'; + +import { useApi } from '../useApi'; + +import { useRelationships } from './useRelationships'; + +function useAccount(id: string) { + const api = useApi(); + + const { entity: account, ...result } = useEntity( + [Entities.ACCOUNTS, id], + () => api.get(`/api/v1/accounts/${id}`), + { schema: accountSchema }, + ); + const { relationships, isLoading } = useRelationships([account?.id as string]); + + return { + ...result, + isLoading: result.isLoading || isLoading, + account: account ? { ...account, relationship: relationships[0] || null } : undefined, + }; +} + +export { useAccount }; \ No newline at end of file diff --git a/app/soapbox/hooks/api/useRelationships.ts b/app/soapbox/hooks/api/useRelationships.ts new file mode 100644 index 000000000..fde5f1017 --- /dev/null +++ b/app/soapbox/hooks/api/useRelationships.ts @@ -0,0 +1,22 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntities } from 'soapbox/entity-store/hooks'; +import { type Relationship, relationshipSchema } from 'soapbox/schemas'; + +import { useApi } from '../useApi'; + +function useRelationships(ids: string[]) { + const api = useApi(); + + const { entities: relationships, ...result } = useEntities( + [Entities.RELATIONSHIPS], + () => api.get(`/api/v1/accounts/relationships?${ids.map(id => `id[]=${id}`).join('&')}`), + { schema: relationshipSchema, enabled: ids.filter(Boolean).length > 0 }, + ); + + return { + ...result, + relationships, + }; +} + +export { useRelationships }; \ No newline at end of file diff --git a/app/soapbox/hooks/useLoading.ts b/app/soapbox/hooks/useLoading.ts index 51f15e521..a8c56c0f6 100644 --- a/app/soapbox/hooks/useLoading.ts +++ b/app/soapbox/hooks/useLoading.ts @@ -1,7 +1,7 @@ import { useState } from 'react'; -function useLoading() { - const [isLoading, setIsLoading] = useState(false); +function useLoading(initialState: boolean = false) { + const [isLoading, setIsLoading] = useState(initialState); function setPromise(promise: Promise) { setIsLoading(true);