Cap Group Admins at 5
This commit is contained in:
parent
b704e476eb
commit
5fa8a21403
6 changed files with 65 additions and 27 deletions
|
@ -8,6 +8,8 @@ import { ToastText, ToastType } from 'soapbox/toast';
|
|||
|
||||
import HStack from '../hstack/hstack';
|
||||
import Icon from '../icon/icon';
|
||||
import Stack from '../stack/stack';
|
||||
import Text from '../text/text';
|
||||
|
||||
const renderText = (text: ToastText) => {
|
||||
if (typeof text === 'string') {
|
||||
|
@ -24,13 +26,14 @@ interface IToast {
|
|||
action?(): void
|
||||
actionLink?: string
|
||||
actionLabel?: ToastText
|
||||
summary?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizable Toasts for in-app notifications.
|
||||
*/
|
||||
const Toast = (props: IToast) => {
|
||||
const { t, message, type, action, actionLink, actionLabel } = props;
|
||||
const { t, message, type, action, actionLink, actionLabel, summary } = props;
|
||||
|
||||
const dismissToast = () => toast.dismiss(t.id);
|
||||
|
||||
|
@ -109,35 +112,41 @@ const Toast = (props: IToast) => {
|
|||
})
|
||||
}
|
||||
>
|
||||
<HStack space={4} alignItems='start'>
|
||||
<HStack space={3} justifyContent='between' alignItems='start' className='w-0 flex-1'>
|
||||
<HStack space={3} alignItems='start' className='w-0 flex-1'>
|
||||
<div className='shrink-0'>
|
||||
{renderIcon()}
|
||||
</div>
|
||||
<Stack space={2}>
|
||||
<HStack space={4} alignItems='start'>
|
||||
<HStack space={3} justifyContent='between' alignItems='start' className='w-0 flex-1'>
|
||||
<HStack space={3} alignItems='start' className='w-0 flex-1'>
|
||||
<div className='shrink-0'>
|
||||
{renderIcon()}
|
||||
</div>
|
||||
|
||||
<p className='pt-0.5 text-sm text-gray-900 dark:text-gray-100' data-testid='toast-message'>
|
||||
{renderText(message)}
|
||||
</p>
|
||||
<Text size='sm' data-testid='toast-message' className='pt-0.5'>
|
||||
{renderText(message)}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
{/* Action */}
|
||||
{renderAction()}
|
||||
</HStack>
|
||||
|
||||
{/* Action */}
|
||||
{renderAction()}
|
||||
{/* Dismiss Button */}
|
||||
<div className='flex shrink-0 pt-0.5'>
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex rounded-md text-gray-600 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:text-gray-600 dark:hover:text-gray-500'
|
||||
onClick={dismissToast}
|
||||
data-testid='toast-dismiss'
|
||||
>
|
||||
<span className='sr-only'>Close</span>
|
||||
<Icon src={require('@tabler/icons/x.svg')} className='h-5 w-5' />
|
||||
</button>
|
||||
</div>
|
||||
</HStack>
|
||||
|
||||
{/* Dismiss Button */}
|
||||
<div className='flex shrink-0 pt-0.5'>
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex rounded-md text-gray-600 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:text-gray-600 dark:hover:text-gray-500'
|
||||
onClick={dismissToast}
|
||||
data-testid='toast-dismiss'
|
||||
>
|
||||
<span className='sr-only'>Close</span>
|
||||
<Icon src={require('@tabler/icons/x.svg')} className='h-5 w-5' />
|
||||
</button>
|
||||
</div>
|
||||
</HStack>
|
||||
{summary ? (
|
||||
<Text theme='muted' size='sm'>{summary}</Text>
|
||||
) : null}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -15,10 +15,14 @@ import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupM
|
|||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import { MAX_ADMIN_COUNT } from '../group-members';
|
||||
|
||||
import type { Menu as IMenu } from 'soapbox/components/dropdown-menu';
|
||||
import type { Group, GroupMember } from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
adminLimitTitle: { id: 'group.member.admin.limit.title', defaultMessage: 'Admin limit reached' },
|
||||
adminLimitSummary: { id: 'group.member.admin.limit.summary', defaultMessage: 'You can assign up to {count} admins for the group at this time.' },
|
||||
blockConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Ban' },
|
||||
blockFromGroupHeading: { id: 'confirmations.block_from_group.heading', defaultMessage: 'Ban From Group' },
|
||||
blockFromGroupMessage: { id: 'confirmations.block_from_group.message', defaultMessage: 'Are you sure you want to ban @{name} from the group?' },
|
||||
|
@ -39,10 +43,11 @@ const messages = defineMessages({
|
|||
interface IGroupMemberListItem {
|
||||
member: GroupMember
|
||||
group: Group
|
||||
canPromoteToAdmin: boolean
|
||||
}
|
||||
|
||||
const GroupMemberListItem = (props: IGroupMemberListItem) => {
|
||||
const { member, group } = props;
|
||||
const { canPromoteToAdmin, member, group } = props;
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
@ -90,6 +95,13 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => {
|
|||
};
|
||||
|
||||
const handleAdminAssignment = () => {
|
||||
if (!canPromoteToAdmin) {
|
||||
toast.error(intl.formatMessage(messages.adminLimitTitle), {
|
||||
summary: intl.formatMessage(messages.adminLimitSummary, { count: MAX_ADMIN_COUNT }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.promoteConfirm),
|
||||
message: intl.formatMessage(messages.promoteConfirmMessage, { name: account?.username }),
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { useMemo } from 'react';
|
|||
|
||||
import { PendingItemsRow } from 'soapbox/components/pending-items-row';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
import { useGroup } from 'soapbox/hooks/api';
|
||||
import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests';
|
||||
import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers';
|
||||
|
@ -18,9 +19,13 @@ interface IGroupMembers {
|
|||
params: { id: string }
|
||||
}
|
||||
|
||||
export const MAX_ADMIN_COUNT = 5;
|
||||
|
||||
const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
||||
const groupId = props.params.id;
|
||||
|
||||
const features = useFeatures();
|
||||
|
||||
const { group, isFetching: isFetchingGroup } = useGroup(groupId);
|
||||
const { groupMembers: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER);
|
||||
const { groupMembers: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, GroupRoles.ADMIN);
|
||||
|
@ -35,6 +40,10 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
|||
...users,
|
||||
], [owners, admins, users]);
|
||||
|
||||
const canPromoteToAdmin = features.groupsAdminMax
|
||||
? members.filter((member) => member.role === GroupRoles.ADMIN).length < MAX_ADMIN_COUNT
|
||||
: true;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollableList
|
||||
|
@ -58,6 +67,7 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
|||
group={group as Group}
|
||||
member={member}
|
||||
key={member.account.id}
|
||||
canPromoteToAdmin={canPromoteToAdmin}
|
||||
/>
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { GroupMember, groupMemberSchema } from 'soapbox/schemas';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
function useGroupMembers(groupId: string, role: string) {
|
||||
function useGroupMembers(groupId: string, role: GroupRoles) {
|
||||
const api = useApi();
|
||||
|
||||
const { entities, ...result } = useEntities<GroupMember>(
|
||||
|
|
|
@ -14,6 +14,7 @@ interface IToastOptions {
|
|||
actionLink?: string
|
||||
actionLabel?: ToastText
|
||||
duration?: number
|
||||
summary?: string
|
||||
}
|
||||
|
||||
const DEFAULT_DURATION = 4000;
|
||||
|
|
|
@ -529,6 +529,11 @@ const getInstanceFeatures = (instance: Instance) => {
|
|||
*/
|
||||
groups: v.build === UNRELEASED,
|
||||
|
||||
/**
|
||||
* Cap # of Group Admins to 5
|
||||
*/
|
||||
groupsAdminMax: v.software === TRUTHSOCIAL,
|
||||
|
||||
/**
|
||||
* Can see trending/suggested Groups.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue