Add ability to update Group tags
This commit is contained in:
parent
2d52c8c3e4
commit
c5c5bd0d62
5 changed files with 105 additions and 59 deletions
|
@ -31,10 +31,14 @@ function useEntityActions<TEntity extends Entity = Entity, Data = any>(
|
||||||
const { createEntity, isSubmitting: createSubmitting } =
|
const { createEntity, isSubmitting: createSubmitting } =
|
||||||
useCreateEntity<TEntity, Data>(path, (data) => api.post(endpoints.post!, data), opts);
|
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 {
|
return {
|
||||||
createEntity,
|
createEntity,
|
||||||
deleteEntity,
|
deleteEntity,
|
||||||
isSubmitting: createSubmitting || deleteSubmitting,
|
updateEntity,
|
||||||
|
isSubmitting: createSubmitting || deleteSubmitting || updateSubmitting,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,11 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { HStack, IconButton, Stack, Text, Tooltip } from 'soapbox/components/ui';
|
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 { useUpdateGroupTag } from 'soapbox/hooks/api';
|
||||||
|
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
|
|
||||||
|
@ -29,15 +33,26 @@ interface IGroupMemberListItem {
|
||||||
|
|
||||||
const GroupTagListItem = (props: IGroupMemberListItem) => {
|
const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
const { group, tag, isPinnable } = props;
|
const { group, tag, isPinnable } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const intl = useIntl();
|
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 = () => {
|
const toggleVisibility = () => {
|
||||||
updateGroupTag({
|
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(
|
toast.success(
|
||||||
entity.visible ?
|
entity.visible ?
|
||||||
intl.formatMessage(messages.visibleSuccess) :
|
intl.formatMessage(messages.visibleSuccess) :
|
||||||
|
@ -49,9 +64,15 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
|
|
||||||
const togglePin = () => {
|
const togglePin = () => {
|
||||||
updateGroupTag({
|
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(
|
toast.success(
|
||||||
entity.pinned ?
|
entity.pinned ?
|
||||||
intl.formatMessage(messages.pinSuccess) :
|
intl.formatMessage(messages.pinSuccess) :
|
||||||
|
@ -73,7 +94,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
transparent
|
theme='transparent'
|
||||||
src={
|
src={
|
||||||
tag.pinned ?
|
tag.pinned ?
|
||||||
require('@tabler/icons/pin-filled.svg') :
|
require('@tabler/icons/pin-filled.svg') :
|
||||||
|
@ -90,7 +111,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
<Tooltip text={intl.formatMessage(messages.unpinTag)}>
|
<Tooltip text={intl.formatMessage(messages.unpinTag)}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
transparent
|
theme='transparent'
|
||||||
src={require('@tabler/icons/pin-filled.svg')}
|
src={require('@tabler/icons/pin-filled.svg')}
|
||||||
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
||||||
/>
|
/>
|
||||||
|
@ -106,12 +127,12 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text
|
<Text
|
||||||
weight='bold'
|
weight='bold'
|
||||||
theme={tag.visible ? 'default' : 'subtle'}
|
theme={(tag.visible || !canEdit) ? 'default' : 'subtle'}
|
||||||
className='group-hover:underline'
|
className='group-hover:underline'
|
||||||
>
|
>
|
||||||
#{tag.name}
|
#{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size='sm' theme={tag.visible ? 'muted' : 'subtle'}>
|
<Text size='sm' theme={(tag.visible || !canEdit) ? 'muted' : 'subtle'}>
|
||||||
{intl.formatMessage(messages.total)}:
|
{intl.formatMessage(messages.total)}:
|
||||||
{' '}
|
{' '}
|
||||||
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
||||||
|
@ -121,30 +142,32 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<HStack alignItems='center' space={2}>
|
{canEdit ? (
|
||||||
{tag.visible ? (
|
<HStack alignItems='center' space={2}>
|
||||||
renderPinIcon()
|
{tag.visible ? (
|
||||||
) : null}
|
renderPinIcon()
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
text={
|
text={
|
||||||
tag.visible ?
|
|
||||||
intl.formatMessage(messages.hideTag) :
|
|
||||||
intl.formatMessage(messages.showTag)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onClick={toggleVisibility}
|
|
||||||
transparent
|
|
||||||
src={
|
|
||||||
tag.visible ?
|
tag.visible ?
|
||||||
require('@tabler/icons/eye.svg') :
|
intl.formatMessage(messages.hideTag) :
|
||||||
require('@tabler/icons/eye-off.svg')
|
intl.formatMessage(messages.showTag)
|
||||||
}
|
}
|
||||||
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
>
|
||||||
/>
|
<IconButton
|
||||||
</Tooltip>
|
onClick={toggleVisibility}
|
||||||
</HStack>
|
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>
|
</HStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||||
|
import { Icon, Stack, Text } from 'soapbox/components/ui';
|
||||||
import { useGroupTags } from 'soapbox/hooks/api';
|
import { useGroupTags } from 'soapbox/hooks/api';
|
||||||
import { useGroup } from 'soapbox/queries/groups';
|
import { useGroup } from 'soapbox/queries/groups';
|
||||||
|
|
||||||
|
@ -26,28 +28,41 @@ const GroupTopics: React.FC<IGroupTopics> = (props) => {
|
||||||
const isPinnable = pinnedTags.length < 3;
|
const isPinnable = pinnedTags.length < 3;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ScrollableList
|
||||||
<ScrollableList
|
scrollKey='group-tags'
|
||||||
scrollKey='group-tags'
|
hasMore={hasNextPage}
|
||||||
hasMore={hasNextPage}
|
onLoadMore={fetchNextPage}
|
||||||
onLoadMore={fetchNextPage}
|
isLoading={isLoading || !group}
|
||||||
isLoading={isLoading || !group}
|
showLoading={!group || isLoading && tags.length === 0}
|
||||||
showLoading={!group || isLoading && tags.length === 0}
|
placeholderComponent={PlaceholderAccount}
|
||||||
placeholderComponent={PlaceholderAccount}
|
placeholderCount={3}
|
||||||
placeholderCount={3}
|
className='divide-y divide-solid divide-gray-300'
|
||||||
className='divide-y divide-solid divide-gray-300'
|
itemClassName='py-3 last:pb-0'
|
||||||
itemClassName='py-3 last:pb-0'
|
emptyMessage={
|
||||||
>
|
<Stack space={4} className='pt-6' justifyContent='center' alignItems='center'>
|
||||||
{tags.map((tag) => (
|
<div className='rounded-full bg-gray-200 p-4 dark:bg-gray-800'>
|
||||||
<GroupTagListItem
|
<Icon
|
||||||
key={tag.id}
|
src={require('@tabler/icons/hash.svg')}
|
||||||
group={group as Group}
|
className='h-6 w-6 text-gray-600'
|
||||||
isPinnable={isPinnable}
|
/>
|
||||||
tag={tag}
|
</div>
|
||||||
/>
|
|
||||||
))}
|
<Text theme='muted'>
|
||||||
</ScrollableList>
|
<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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
import { useEntities } from 'soapbox/entity-store/hooks';
|
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||||
|
import { useApi } from 'soapbox/hooks/useApi';
|
||||||
import { groupTagSchema } from 'soapbox/schemas';
|
import { groupTagSchema } from 'soapbox/schemas';
|
||||||
|
|
||||||
import type { GroupTag } from 'soapbox/schemas';
|
import type { GroupTag } from 'soapbox/schemas';
|
||||||
|
|
||||||
function useGroupTags(groupId: string) {
|
function useGroupTags(groupId: string) {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
const { entities, ...result } = useEntities<GroupTag>(
|
const { entities, ...result } = useEntities<GroupTag>(
|
||||||
[Entities.GROUP_TAGS, groupId],
|
[Entities.GROUP_TAGS, groupId],
|
||||||
'/api/mock/groups/tags', // `api/v1/groups/${groupId}/tags`
|
() => api.get(`api/v1/truth/trends/groups/${groupId}/tags`),
|
||||||
{ schema: groupTagSchema },
|
{ schema: groupTagSchema },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
import { useEntityActions } from 'soapbox/entity-store/hooks';
|
import { useEntityActions } from 'soapbox/entity-store/hooks';
|
||||||
import { groupTagSchema } from 'soapbox/schemas';
|
|
||||||
|
|
||||||
import type { GroupTag } from 'soapbox/schemas';
|
import type { GroupTag } from 'soapbox/schemas';
|
||||||
|
|
||||||
function useUpdateGroupTag(groupId: string, tagId: string) {
|
function useUpdateGroupTag(groupId: string, tagId: string) {
|
||||||
const { updateEntity } = useEntityActions<GroupTag>(
|
const { updateEntity, ...rest } = useEntityActions<GroupTag>(
|
||||||
[Entities.GROUP_TAGS, groupId, tagId],
|
[Entities.GROUP_TAGS, groupId, tagId],
|
||||||
{ patch: `/api/mock/truth/groups/${groupId}/tags/${tagId}` },
|
{ patch: `/api/v1/groups/${groupId}/tags/${tagId}` },
|
||||||
{ schema: groupTagSchema },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return updateEntity;
|
return {
|
||||||
|
updateGroupTag: updateEntity,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useUpdateGroupTag };
|
export { useUpdateGroupTag };
|
Loading…
Reference in a new issue