Fetch account and relationship with entity store

This commit is contained in:
Chewbacca 2023-04-04 08:11:03 -04:00
parent c045a6630d
commit a486b317d4
10 changed files with 87 additions and 23 deletions

View File

@ -3,4 +3,5 @@ export enum Entities {
GROUPS = 'Groups', GROUPS = 'Groups',
GROUP_RELATIONSHIPS = 'GroupRelationships', GROUP_RELATIONSHIPS = 'GroupRelationships',
GROUP_MEMBERSHIPS = 'GroupMemberships', GROUP_MEMBERSHIPS = 'GroupMemberships',
RELATIONSHIPS = 'Relationships'
} }

View File

@ -21,7 +21,7 @@ function useEntity<TEntity extends Entity>(
entityFn: EntityFn<void>, entityFn: EntityFn<void>,
opts: UseEntityOpts<TEntity> = {}, opts: UseEntityOpts<TEntity> = {},
) { ) {
const [isFetching, setPromise] = useLoading(); const [isFetching, setPromise] = useLoading(true);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [entityType, entityId] = path; const [entityType, entityId] = path;

View File

@ -9,13 +9,14 @@ import DropdownMenu from 'soapbox/components/dropdown-menu/dropdown-menu';
import { HStack } from 'soapbox/components/ui'; import { HStack } from 'soapbox/components/ui';
import { deleteEntities } from 'soapbox/entity-store/actions'; import { deleteEntities } from 'soapbox/entity-store/actions';
import { Entities } from 'soapbox/entity-store/entities'; import { Entities } from 'soapbox/entity-store/entities';
import { useAccount, useAppDispatch, useFeatures } from 'soapbox/hooks'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account';
import { useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api'; import { useAppDispatch, useFeatures } from 'soapbox/hooks';
import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api';
import { GroupRoles } from 'soapbox/schemas/group-member'; import { GroupRoles } from 'soapbox/schemas/group-member';
import toast from 'soapbox/toast'; import toast from 'soapbox/toast';
import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; 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({ const messages = defineMessages({
blockConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Ban' }, blockConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Ban' },
@ -51,7 +52,7 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
const promoteGroupMember = usePromoteGroupMember(group, member); const promoteGroupMember = usePromoteGroupMember(group, member);
const demoteGroupMember = useDemoteGroupMember(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 // Current user role
const isCurrentUserOwner = group.relationship?.role === GroupRoles.OWNER; const isCurrentUserOwner = group.relationship?.role === GroupRoles.OWNER;
@ -64,10 +65,10 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
const handleKickFromGroup = () => { const handleKickFromGroup = () => {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.kickFromGroupMessage, { name: account.username }), message: intl.formatMessage(messages.kickFromGroupMessage, { name: account?.username }),
confirm: intl.formatMessage(messages.kickConfirm), confirm: intl.formatMessage(messages.kickConfirm),
onConfirm: () => dispatch(groupKick(group.id, account.id)).then(() => onConfirm: () => dispatch(groupKick(group.id, account?.id as string)).then(() =>
toast.success(intl.formatMessage(messages.kicked, { name: account.acct })), toast.success(intl.formatMessage(messages.kicked, { name: account?.acct })),
), ),
})); }));
}; };
@ -75,13 +76,13 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
const handleBlockFromGroup = () => { const handleBlockFromGroup = () => {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.blockFromGroupHeading), 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), confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => { onConfirm: () => {
blockGroupMember({ account_ids: [member.account.id] }, { blockGroupMember({ account_ids: [member.account.id] }, {
onSuccess() { onSuccess() {
dispatch(deleteEntities([member.id], Entities.GROUP_MEMBERSHIPS)); 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 = () => { const handleAdminAssignment = () => {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.promoteConfirm), 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), confirm: intl.formatMessage(messages.promoteConfirm),
confirmationTheme: 'primary', confirmationTheme: 'primary',
onConfirm: () => { onConfirm: () => {
promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [account.id] }, { promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [account?.id] }, {
onSuccess() { onSuccess() {
toast.success( 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 = () => { const handleUserAssignment = () => {
demoteGroupMember({ role: GroupRoles.USER, account_ids: [account.id] }, { demoteGroupMember({ role: GroupRoles.USER, account_ids: [account?.id] }, {
onSuccess() { 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; return items;
}, [group, account]); }, [group, account?.id]);
if (isLoading) {
return <PlaceholderAccount />;
}
return ( return (
<HStack alignItems='center' justifyContent='between'> <HStack alignItems='center' justifyContent='between'>

View File

@ -53,11 +53,11 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
</div> </div>
)} )}
> >
{members.map((member) => ( {members.map((member, idx) => (
<GroupMemberListItem <GroupMemberListItem
group={group as Group} group={group as Group}
member={member} member={member}
key={member.account.id} key={idx}
/> />
))} ))}
</ScrollableList> </ScrollableList>

View File

@ -18,4 +18,4 @@ const PlaceholderAccount: React.FC = () => (
</HStack> </HStack>
); );
export default PlaceholderAccount; export default React.memo(PlaceholderAccount);

View File

@ -21,4 +21,4 @@ const PlaceholderDisplayName: React.FC<IPlaceholderDisplayName> = ({ minLength,
); );
}; };
export default PlaceholderDisplayName; export default React.memo(PlaceholderDisplayName);

View File

@ -1,3 +1,8 @@
/**
* Accounts
*/
export { useAccount } from './useAccount';
/** /**
* Groups * Groups
*/ */
@ -12,3 +17,8 @@ export { useJoinGroup } from './groups/useJoinGroup';
export { useLeaveGroup } from './groups/useLeaveGroup'; export { useLeaveGroup } from './groups/useLeaveGroup';
export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; export { usePromoteGroupMember } from './groups/usePromoteGroupMember';
export { useUpdateGroup } from './groups/useUpdateGroup'; export { useUpdateGroup } from './groups/useUpdateGroup';
/**
* Relationships
*/
export { useRelationships } from './useRelationships';

View File

@ -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<Account>(
[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 };

View File

@ -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<Relationship>(
[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 };

View File

@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
function useLoading() { function useLoading(initialState: boolean = false) {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(initialState);
function setPromise<T>(promise: Promise<T>) { function setPromise<T>(promise: Promise<T>) {
setIsLoading(true); setIsLoading(true);