Add ability to update Group tags

This commit is contained in:
Chewbacca 2023-04-14 15:15:34 -04:00
parent 2d52c8c3e4
commit c5c5bd0d62
5 changed files with 105 additions and 59 deletions

View file

@ -31,10 +31,14 @@ function useEntityActions<TEntity extends Entity = Entity, Data = any>(
const { createEntity, isSubmitting: createSubmitting } =
useCreateEntity<TEntity, Data>(path, (data) => api.post(endpoints.post!, data), opts);
const { createEntity: updateEntity, isSubmitting: updateSubmitting } =
useCreateEntity<TEntity, Data>(path, (data) => api.patch(endpoints.patch!, data), opts);
return {
createEntity,
deleteEntity,
isSubmitting: createSubmitting || deleteSubmitting,
updateEntity,
isSubmitting: createSubmitting || deleteSubmitting || updateSubmitting,
};
}

View file

@ -3,7 +3,11 @@ import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { HStack, IconButton, Stack, Text, Tooltip } from 'soapbox/components/ui';
import { importEntities } from 'soapbox/entity-store/actions';
import { Entities } from 'soapbox/entity-store/entities';
import { useAppDispatch } from 'soapbox/hooks';
import { useUpdateGroupTag } from 'soapbox/hooks/api';
import { GroupRoles } from 'soapbox/schemas/group-member';
import toast from 'soapbox/toast';
import { shortNumberFormat } from 'soapbox/utils/numbers';
@ -29,15 +33,26 @@ interface IGroupMemberListItem {
const GroupTagListItem = (props: IGroupMemberListItem) => {
const { group, tag, isPinnable } = props;
const dispatch = useAppDispatch();
const intl = useIntl();
const updateGroupTag = useUpdateGroupTag(group.id, tag.id);
const { updateGroupTag } = useUpdateGroupTag(group.id, tag.id);
const isOwner = group.relationship?.role === GroupRoles.OWNER;
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
const canEdit = isOwner || isAdmin;
const toggleVisibility = () => {
updateGroupTag({
pinned: !tag.visible,
group_tag_type: tag.visible ? 'hidden' : 'normal',
}, {
onSuccess(entity: GroupTag) {
onSuccess() {
const entity = {
...tag,
visible: !tag.visible,
};
dispatch(importEntities([entity], Entities.GROUP_TAGS));
toast.success(
entity.visible ?
intl.formatMessage(messages.visibleSuccess) :
@ -49,9 +64,15 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
const togglePin = () => {
updateGroupTag({
pinned: !tag.pinned,
group_tag_type: tag.pinned ? 'normal' : 'pinned',
}, {
onSuccess(entity: GroupTag) {
onSuccess() {
const entity = {
...tag,
pinned: !tag.pinned,
};
dispatch(importEntities([entity], Entities.GROUP_TAGS));
toast.success(
entity.pinned ?
intl.formatMessage(messages.pinSuccess) :
@ -73,7 +94,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
>
<IconButton
onClick={togglePin}
transparent
theme='transparent'
src={
tag.pinned ?
require('@tabler/icons/pin-filled.svg') :
@ -90,7 +111,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
<Tooltip text={intl.formatMessage(messages.unpinTag)}>
<IconButton
onClick={togglePin}
transparent
theme='transparent'
src={require('@tabler/icons/pin-filled.svg')}
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
/>
@ -106,12 +127,12 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
<Stack>
<Text
weight='bold'
theme={tag.visible ? 'default' : 'subtle'}
theme={(tag.visible || !canEdit) ? 'default' : 'subtle'}
className='group-hover:underline'
>
#{tag.name}
</Text>
<Text size='sm' theme={tag.visible ? 'muted' : 'subtle'}>
<Text size='sm' theme={(tag.visible || !canEdit) ? 'muted' : 'subtle'}>
{intl.formatMessage(messages.total)}:
{' '}
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
@ -121,30 +142,32 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
</Stack>
</Link>
<HStack alignItems='center' space={2}>
{tag.visible ? (
renderPinIcon()
) : null}
{canEdit ? (
<HStack alignItems='center' space={2}>
{tag.visible ? (
renderPinIcon()
) : null}
<Tooltip
text={
tag.visible ?
intl.formatMessage(messages.hideTag) :
intl.formatMessage(messages.showTag)
}
>
<IconButton
onClick={toggleVisibility}
transparent
src={
<Tooltip
text={
tag.visible ?
require('@tabler/icons/eye.svg') :
require('@tabler/icons/eye-off.svg')
intl.formatMessage(messages.hideTag) :
intl.formatMessage(messages.showTag)
}
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
/>
</Tooltip>
</HStack>
>
<IconButton
onClick={toggleVisibility}
theme='transparent'
src={
tag.visible ?
require('@tabler/icons/eye.svg') :
require('@tabler/icons/eye-off.svg')
}
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
/>
</Tooltip>
</HStack>
) : null}
</HStack>
);
};

View file

@ -1,6 +1,8 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import ScrollableList from 'soapbox/components/scrollable-list';
import { Icon, Stack, Text } from 'soapbox/components/ui';
import { useGroupTags } from 'soapbox/hooks/api';
import { useGroup } from 'soapbox/queries/groups';
@ -26,28 +28,41 @@ const GroupTopics: React.FC<IGroupTopics> = (props) => {
const isPinnable = pinnedTags.length < 3;
return (
<>
<ScrollableList
scrollKey='group-tags'
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
isLoading={isLoading || !group}
showLoading={!group || isLoading && tags.length === 0}
placeholderComponent={PlaceholderAccount}
placeholderCount={3}
className='divide-y divide-solid divide-gray-300'
itemClassName='py-3 last:pb-0'
>
{tags.map((tag) => (
<GroupTagListItem
key={tag.id}
group={group as Group}
isPinnable={isPinnable}
tag={tag}
/>
))}
</ScrollableList>
</>
<ScrollableList
scrollKey='group-tags'
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
isLoading={isLoading || !group}
showLoading={!group || isLoading && tags.length === 0}
placeholderComponent={PlaceholderAccount}
placeholderCount={3}
className='divide-y divide-solid divide-gray-300'
itemClassName='py-3 last:pb-0'
emptyMessage={
<Stack space={4} className='pt-6' justifyContent='center' alignItems='center'>
<div className='rounded-full bg-gray-200 p-4 dark:bg-gray-800'>
<Icon
src={require('@tabler/icons/hash.svg')}
className='h-6 w-6 text-gray-600'
/>
</div>
<Text theme='muted'>
<FormattedMessage id='group.tags.empty' defaultMessage='There are no topics in this group yet.' />
</Text>
</Stack>
}
emptyMessageCard={false}
>
{tags.map((tag) => (
<GroupTagListItem
key={tag.id}
group={group as Group}
isPinnable={isPinnable}
tag={tag}
/>
))}
</ScrollableList>
);
};

View file

@ -1,13 +1,16 @@
import { Entities } from 'soapbox/entity-store/entities';
import { useEntities } from 'soapbox/entity-store/hooks';
import { useApi } from 'soapbox/hooks/useApi';
import { groupTagSchema } from 'soapbox/schemas';
import type { GroupTag } from 'soapbox/schemas';
function useGroupTags(groupId: string) {
const api = useApi();
const { entities, ...result } = useEntities<GroupTag>(
[Entities.GROUP_TAGS, groupId],
'/api/mock/groups/tags', // `api/v1/groups/${groupId}/tags`
() => api.get(`api/v1/truth/trends/groups/${groupId}/tags`),
{ schema: groupTagSchema },
);

View file

@ -1,17 +1,18 @@
import { Entities } from 'soapbox/entity-store/entities';
import { useEntityActions } from 'soapbox/entity-store/hooks';
import { groupTagSchema } from 'soapbox/schemas';
import type { GroupTag } from 'soapbox/schemas';
function useUpdateGroupTag(groupId: string, tagId: string) {
const { updateEntity } = useEntityActions<GroupTag>(
const { updateEntity, ...rest } = useEntityActions<GroupTag>(
[Entities.GROUP_TAGS, groupId, tagId],
{ patch: `/api/mock/truth/groups/${groupId}/tags/${tagId}` },
{ schema: groupTagSchema },
{ patch: `/api/v1/groups/${groupId}/tags/${tagId}` },
);
return updateEntity;
return {
updateGroupTag: updateEntity,
...rest,
};
}
export { useUpdateGroupTag };