Merge branch 'max-admins' into 'develop'

Cap Group Admins at 5

See merge request soapbox-pub/soapbox!2415
environments/review-develop-3zknud/deployments/3099
Chewbacca 1 year ago
commit 535579676b

@ -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,46 @@ 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>
<p className='pt-0.5 text-sm text-gray-900 dark:text-gray-100' data-testid='toast-message'>
{renderText(message)}
</p>
<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>
<Text
size='sm'
data-testid='toast-message'
className='pt-0.5'
weight={typeof summary === 'undefined' ? 'normal' : 'medium'}
>
{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>(

@ -789,6 +789,8 @@
"group.leave.label": "Leave",
"group.leave.success": "Left the group",
"group.manage": "Manage Group",
"group.member.admin.limit.summary": "You can assign up to {count} admins for the group at this time.",
"group.member.admin.limit.title": "Admin limit reached",
"group.popover.action": "View Group",
"group.popover.summary": "You must be a member of the group in order to reply to this status.",
"group.popover.title": "Membership required",

@ -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…
Cancel
Save