sforkowany z mirror/soapbox
Merge branch 'group-improvements' into 'develop'
Group improvements See merge request soapbox-pub/soapbox!2385develop^2
commit
89ab02224f
|
@ -39,7 +39,7 @@ const useButtonStyles = ({
|
|||
size,
|
||||
}: IButtonStyles) => {
|
||||
const buttonStyle = clsx({
|
||||
'inline-flex items-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all': true,
|
||||
'inline-flex items-center place-content-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all': true,
|
||||
'select-none disabled:opacity-75 disabled:cursor-default': disabled,
|
||||
[`${themes[theme]}`]: true,
|
||||
[`${sizes[size]}`]: true,
|
||||
|
|
|
@ -40,6 +40,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
|
||||
const onJoinGroup = () => joinGroup.mutate({}, {
|
||||
onSuccess() {
|
||||
joinGroup.invalidate();
|
||||
|
||||
toast.success(
|
||||
group.locked
|
||||
? intl.formatMessage(messages.joinRequestSuccess)
|
||||
|
@ -53,8 +55,9 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
heading: intl.formatMessage(messages.confirmationHeading),
|
||||
message: intl.formatMessage(messages.confirmationMessage),
|
||||
confirm: intl.formatMessage(messages.confirmationConfirm),
|
||||
onConfirm: () => leaveGroup.mutate({}, {
|
||||
onConfirm: () => leaveGroup.mutate(group.relationship?.id as string, {
|
||||
onSuccess() {
|
||||
leaveGroup.invalidate();
|
||||
toast.success(intl.formatMessage(messages.leaveSuccess));
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { HStack, Icon, Text } from 'soapbox/components/ui';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
import { Group } from 'soapbox/types/entities';
|
||||
|
||||
interface IGroupRelationship {
|
||||
|
@ -9,10 +10,10 @@ interface IGroupRelationship {
|
|||
}
|
||||
|
||||
const GroupRelationship = ({ group }: IGroupRelationship) => {
|
||||
const isAdmin = group.relationship?.role === 'admin';
|
||||
const isModerator = group.relationship?.role === 'moderator';
|
||||
const isOwner = group.relationship?.role === GroupRoles.OWNER;
|
||||
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
|
||||
|
||||
if (!isAdmin || !isModerator) {
|
||||
if (!isOwner || !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -21,14 +22,14 @@ const GroupRelationship = ({ group }: IGroupRelationship) => {
|
|||
<Icon
|
||||
className='h-4 w-4'
|
||||
src={
|
||||
isAdmin
|
||||
isOwner
|
||||
? require('@tabler/icons/users.svg')
|
||||
: require('@tabler/icons/gavel.svg')
|
||||
}
|
||||
/>
|
||||
|
||||
<Text tag='span' weight='medium' size='sm' theme='inherit'>
|
||||
{isAdmin
|
||||
{isOwner
|
||||
? <FormattedMessage id='group.role.admin' defaultMessage='Admin' />
|
||||
: <FormattedMessage id='group.role.moderator' defaultMessage='Moderator' />}
|
||||
</Text>
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import GroupAvatar from 'soapbox/components/groups/group-avatar';
|
||||
import { Button, HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import GroupActionButton from 'soapbox/features/group/components/group-action-button';
|
||||
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
|
||||
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
|
||||
import { useJoinGroup } from 'soapbox/hooks/api';
|
||||
import { Group as GroupEntity } from 'soapbox/types/entities';
|
||||
|
||||
import type { Group } from 'soapbox/schemas';
|
||||
|
||||
interface IGroup {
|
||||
group: GroupEntity
|
||||
group: Group
|
||||
width?: number
|
||||
}
|
||||
|
||||
const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
const { group, width = 'auto' } = props;
|
||||
|
||||
const joinGroup = useJoinGroup(group);
|
||||
|
||||
const onJoinGroup = () => joinGroup.mutate(group);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.id}
|
||||
|
@ -71,16 +67,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
|||
</Stack>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
theme='primary'
|
||||
block
|
||||
onClick={onJoinGroup}
|
||||
disabled={joinGroup.isLoading}
|
||||
>
|
||||
{group.locked
|
||||
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
|
||||
: <FormattedMessage id='group.join.public' defaultMessage='Join Group' />}
|
||||
</Button>
|
||||
<GroupActionButton group={group} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import GroupAvatar from 'soapbox/components/groups/group-avatar';
|
||||
import { Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useJoinGroup } from 'soapbox/hooks/api';
|
||||
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import GroupActionButton from 'soapbox/features/group/components/group-action-button';
|
||||
import { Group as GroupEntity } from 'soapbox/types/entities';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
|
@ -16,10 +16,6 @@ interface IGroup {
|
|||
const GroupListItem = (props: IGroup) => {
|
||||
const { group, withJoinAction = true } = props;
|
||||
|
||||
const joinGroup = useJoinGroup(group);
|
||||
|
||||
const onJoinGroup = () => joinGroup.mutate(group);
|
||||
|
||||
return (
|
||||
<HStack
|
||||
alignItems='center'
|
||||
|
@ -74,11 +70,7 @@ const GroupListItem = (props: IGroup) => {
|
|||
</Link>
|
||||
|
||||
{withJoinAction && (
|
||||
<Button theme='primary' onClick={onJoinGroup} disabled={joinGroup.isLoading}>
|
||||
{group.locked
|
||||
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
|
||||
: <FormattedMessage id='group.join.public' defaultMessage='Join Group' />}
|
||||
</Button>
|
||||
<GroupActionButton group={group} />
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso';
|
||||
|
||||
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useGroupSearch } from 'soapbox/queries/groups/search';
|
||||
import { useGroupSearch } from 'soapbox/hooks/api';
|
||||
import { Group } from 'soapbox/types/entities';
|
||||
|
||||
import GroupGridItem from '../group-grid-item';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { Stack } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search';
|
||||
import { useDebounce, useOwnAccount } from 'soapbox/hooks';
|
||||
import { useGroupSearch } from 'soapbox/queries/groups/search';
|
||||
import { useGroupSearch } from 'soapbox/hooks/api';
|
||||
import { saveGroupSearch } from 'soapbox/utils/groups';
|
||||
|
||||
import Blankslate from './blankslate';
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { groupSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useApi } from '../../useApi';
|
||||
import { useFeatures } from '../../useFeatures';
|
||||
|
||||
import { useGroupRelationships } from './useGroups';
|
||||
|
||||
import type { Group } from 'soapbox/schemas';
|
||||
|
||||
function useGroupSearch(search: string) {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, 'discover', 'search', search],
|
||||
() => api.get('/api/v1/groups/search', {
|
||||
params: {
|
||||
q: search,
|
||||
},
|
||||
}),
|
||||
{ enabled: features.groupsDiscovery && !!search, schema: groupSchema },
|
||||
);
|
||||
|
||||
const { relationships } = useGroupRelationships(entities.map(entity => entity.id));
|
||||
|
||||
const groups = entities.map((group) => ({
|
||||
...group,
|
||||
relationship: relationships[group.id] || null,
|
||||
}));
|
||||
|
||||
return {
|
||||
...result,
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
export { useGroupSearch };
|
|
@ -2,9 +2,13 @@ import { Entities } from 'soapbox/entity-store/entities';
|
|||
import { useEntityActions } from 'soapbox/entity-store/hooks';
|
||||
import { groupRelationshipSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useGroups } from './useGroups';
|
||||
|
||||
import type { Group, GroupRelationship } from 'soapbox/schemas';
|
||||
|
||||
function useJoinGroup(group: Group) {
|
||||
const { invalidate } = useGroups();
|
||||
|
||||
const { createEntity, isLoading } = useEntityActions<GroupRelationship>(
|
||||
[Entities.GROUP_RELATIONSHIPS, group.id],
|
||||
{ post: `/api/v1/groups/${group.id}/join` },
|
||||
|
@ -14,6 +18,7 @@ function useJoinGroup(group: Group) {
|
|||
return {
|
||||
mutate: createEntity,
|
||||
isLoading,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntityActions } from 'soapbox/entity-store/hooks';
|
||||
import { Group, GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas';
|
||||
import { groupRelationshipSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useGroups } from './useGroups';
|
||||
|
||||
import type { Group, GroupRelationship } from 'soapbox/schemas';
|
||||
|
||||
function useLeaveGroup(group: Group) {
|
||||
const { invalidate } = useGroups();
|
||||
|
||||
const { createEntity, isLoading } = useEntityActions<GroupRelationship>(
|
||||
[Entities.GROUP_RELATIONSHIPS, group.id],
|
||||
{ post: `/api/v1/groups/${group.id}/leave` },
|
||||
|
@ -12,6 +18,7 @@ function useLeaveGroup(group: Group) {
|
|||
return {
|
||||
mutate: createEntity,
|
||||
isLoading,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ export { useCancelMembershipRequest } from './groups/useCancelMembershipRequest'
|
|||
export { useDeleteGroup } from './groups/useDeleteGroup';
|
||||
export { useDemoteGroupMember } from './groups/useDemoteGroupMember';
|
||||
export { useGroup, useGroups } from './groups/useGroups';
|
||||
export { useGroupSearch } from './groups/useGroupSearch';
|
||||
export { useJoinGroup } from './groups/useJoinGroup';
|
||||
export { useLeaveGroup } from './groups/useLeaveGroup';
|
||||
export { usePromoteGroupMember } from './groups/usePromoteGroupMember';
|
||||
|
|
|
@ -8,13 +8,15 @@ import {
|
|||
fromJS,
|
||||
} from 'immutable';
|
||||
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
|
||||
export const GroupRelationshipRecord = ImmutableRecord({
|
||||
id: '',
|
||||
blocked_by: false,
|
||||
member: false,
|
||||
notifying: null,
|
||||
requested: false,
|
||||
role: null as 'admin' | 'moderator' | 'user' | null,
|
||||
role: 'user' as GroupRoles,
|
||||
});
|
||||
|
||||
export const normalizeGroupRelationship = (relationship: Record<string, any>) => {
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
|
||||
const GroupMemberKeys = {
|
||||
members: (id: string, role: string) => ['group', id, role] as const,
|
||||
};
|
||||
|
||||
const useGroupMembers = (groupId: string, role: GroupRoles) => {
|
||||
const api = useApi();
|
||||
|
||||
const getQuery = async () => {
|
||||
const { data } = await api.get(`/api/v1/groups/${groupId}/memberships`, {
|
||||
params: {
|
||||
role,
|
||||
},
|
||||
});
|
||||
|
||||
const result = data.map((member: any) => {
|
||||
return {
|
||||
...member,
|
||||
account: normalizeAccount(member.account),
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return useQuery(
|
||||
GroupMemberKeys.members(groupId, role),
|
||||
getQuery,
|
||||
{
|
||||
placeholderData: [],
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export { useGroupMembers };
|
|
@ -1,10 +1,12 @@
|
|||
import z from 'zod';
|
||||
|
||||
import { GroupRoles } from './group-member';
|
||||
|
||||
const groupRelationshipSchema = z.object({
|
||||
id: z.string(),
|
||||
member: z.boolean().catch(false),
|
||||
requested: z.boolean().catch(false),
|
||||
role: z.string().nullish().catch(null),
|
||||
role: z.nativeEnum(GroupRoles).catch(GroupRoles.USER),
|
||||
blocked_by: z.boolean().catch(false),
|
||||
notifying: z.boolean().nullable().catch(null),
|
||||
});
|
||||
|
|
Ładowanie…
Reference in New Issue