From 89bdc9b4a1ef63d1e968732800928d34ddf8926f Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 17 Mar 2023 14:20:03 -0400 Subject: [PATCH 1/4] Move Promote/Demote admin into entity store --- .../entity-store/hooks/useEntityActions.ts | 10 +- .../components/group-member-list-item.tsx | 118 +++++++++++------- .../hooks/api/groups/useBlockGroupMember.ts | 2 +- .../hooks/api/groups/useDemoteGroupMember.ts | 19 +++ .../hooks/api/groups/usePromoteGroupMember.ts | 19 +++ app/soapbox/hooks/api/index.ts | 6 + app/soapbox/hooks/useGroupRoles.ts | 9 ++ app/soapbox/locales/en.json | 6 +- app/soapbox/utils/features.ts | 7 +- 9 files changed, 143 insertions(+), 53 deletions(-) create mode 100644 app/soapbox/hooks/api/groups/useDemoteGroupMember.ts create mode 100644 app/soapbox/hooks/api/groups/usePromoteGroupMember.ts create mode 100644 app/soapbox/hooks/api/index.ts diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts index 2b307afde..eede5bcb3 100644 --- a/app/soapbox/entity-store/hooks/useEntityActions.ts +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -28,6 +28,10 @@ interface EntityActionEndpoints { delete?: string } +interface EntityCallbacks { + onSuccess?(entity: TEntity): void +} + function useEntityActions( path: EntityPath, endpoints: EntityActionEndpoints, @@ -38,7 +42,7 @@ function useEntityActions( const getState = useGetState(); const [entityType, listKey] = path; - function createEntity(params: P): Promise> { + function createEntity(params: P, callbacks: EntityCallbacks = {}): Promise> { if (!endpoints.post) return Promise.reject(endpoints); return api.post(endpoints.post, params).then((response) => { @@ -48,6 +52,10 @@ function useEntityActions( // TODO: optimistic updating dispatch(importEntities([entity], entityType, listKey)); + if (callbacks.onSuccess) { + callbacks.onSuccess(entity); + } + return { response, entity, diff --git a/app/soapbox/features/group/components/group-member-list-item.tsx b/app/soapbox/features/group/components/group-member-list-item.tsx index af701c7f8..afef35d47 100644 --- a/app/soapbox/features/group/components/group-member-list-item.tsx +++ b/app/soapbox/features/group/components/group-member-list-item.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { groupDemoteAccount, groupKick, groupPromoteAccount } from 'soapbox/actions/groups'; +import { groupKick } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; import Account from 'soapbox/components/account'; import DropdownMenu from 'soapbox/components/dropdown-menu/dropdown-menu'; @@ -10,8 +10,8 @@ import { HStack } from 'soapbox/components/ui'; import { deleteEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { useAccount, useAppDispatch, useFeatures } from 'soapbox/hooks'; -import { useBlockGroupMember } from 'soapbox/hooks/api/groups/useBlockGroupMember'; -import { BaseGroupRoles, useGroupRoles } from 'soapbox/hooks/useGroupRoles'; +import { useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api'; +import { BaseGroupRoles, TruthSocialGroupRoles, useGroupRoles } from 'soapbox/hooks/useGroupRoles'; import toast from 'soapbox/toast'; import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; @@ -24,17 +24,17 @@ const messages = defineMessages({ blocked: { id: 'group.group_mod_block.success', defaultMessage: 'You have successfully blocked @{name} from the group' }, demotedToUser: { id: 'group.group_mod_demote.success', defaultMessage: 'Demoted @{name} to group user' }, groupModBlock: { id: 'group.group_mod_block', defaultMessage: 'Ban from group' }, - groupModDemote: { id: 'group.group_mod_demote', defaultMessage: 'Demote @{name}' }, + groupModDemote: { id: 'group.group_mod_demote', defaultMessage: 'Remove {role} role' }, groupModKick: { id: 'group.group_mod_kick', defaultMessage: 'Kick @{name} from group' }, groupModPromoteAdmin: { id: 'group.group_mod_promote_admin', defaultMessage: 'Promote @{name} to group administrator' }, - groupModPromoteMod: { id: 'group.group_mod_promote_mod', defaultMessage: 'Promote @{name} to group moderator' }, + groupModPromoteMod: { id: 'group.group_mod_promote_mod', defaultMessage: 'Assign {role} role' }, kickConfirm: { id: 'confirmations.kick_from_group.confirm', defaultMessage: 'Kick' }, kickFromGroupMessage: { id: 'confirmations.kick_from_group.message', defaultMessage: 'Are you sure you want to kick @{name} from this group?' }, kicked: { id: 'group.group_mod_kick.success', defaultMessage: 'Kicked @{name} from group' }, promoteConfirm: { id: 'confirmations.promote_in_group.confirm', defaultMessage: 'Promote' }, promoteConfirmMessage: { id: 'confirmations.promote_in_group.message', defaultMessage: 'Are you sure you want to promote @{name}? You will not be able to demote them.' }, promotedToAdmin: { id: 'group.group_mod_promote_admin.success', defaultMessage: 'Promoted @{name} to group administrator' }, - promotedToMod: { id: 'group.group_mod_promote_mod.success', defaultMessage: 'Promoted @{name} to group moderator' }, + promotedToMod: { id: 'group.group_mod_promote_mod.success', defaultMessage: 'You have successfully promoted @{name} to group {role}.' }, }); interface IGroupMemberListItem { @@ -49,8 +49,10 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const features = useFeatures(); const intl = useIntl(); - const { normalizeRole } = useGroupRoles(); + const { roles, isAdminRole, normalizeRole } = useGroupRoles(); const blockGroupMember = useBlockGroupMember(group, member); + const promoteGroupMember = usePromoteGroupMember(group, member); + const demoteGroupMember = useDemoteGroupMember(group, member); const account = useAccount(member.account.id) as AccountEntity; @@ -78,39 +80,59 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { heading: intl.formatMessage(messages.blockFromGroupHeading), message: intl.formatMessage(messages.blockFromGroupMessage, { name: account.username }), confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => blockGroupMember({ account_ids: [member.account.id] }).then(() => { - dispatch(deleteEntities([member.id], Entities.GROUP_MEMBERSHIPS)); - toast.success(intl.formatMessage(messages.blocked, { name: account.acct })); - }), + onConfirm: () => { + blockGroupMember({ account_ids: [member.account.id] }, { + onSuccess() { + dispatch(deleteEntities([member.id], Entities.GROUP_MEMBERSHIPS)); + toast.success(intl.formatMessage(messages.blocked, { name: account.acct })); + }, + }); + }, })); }; - const onPromote = (role: 'admin' | 'moderator', warning?: boolean) => { + const onPromote = (role: TruthSocialGroupRoles | BaseGroupRoles, warning?: boolean) => { if (warning) { return dispatch(openModal('CONFIRM', { message: intl.formatMessage(messages.promoteConfirmMessage, { name: account.username }), confirm: intl.formatMessage(messages.promoteConfirm), - onConfirm: () => dispatch(groupPromoteAccount(group.id, account.id, role)).then(() => - toast.success(intl.formatMessage(role === 'admin' ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct })), - ), + onConfirm: () => { + promoteGroupMember({ role: role, account_ids: [account.id] }, { + onSuccess() { + toast.success( + intl.formatMessage( + isAdminRole(role) ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct, role }, + ), + ); + }, + }); + }, })); } else { - return dispatch(groupPromoteAccount(group.id, account.id, role)).then(() => - toast.success(intl.formatMessage(role === 'admin' ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct })), - ); + promoteGroupMember({ role: role, account_ids: [account.id] }, { + onSuccess() { + toast.success( + intl.formatMessage( + isAdminRole(role) ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct, role }, + ), + ); + }, + }); } }; - const handlePromoteToGroupAdmin = () => onPromote('admin', true); + const handlePromoteToGroupAdmin = () => onPromote(roles.admin, true); - const handlePromoteToGroupMod = () => { - onPromote('moderator', group.relationship!.role === 'moderator'); + const handleAssignModerator = () => { + onPromote(roles.moderator, false); }; const handleDemote = () => { - dispatch(groupDemoteAccount(group.id, account.id, 'user')).then(() => - toast.success(intl.formatMessage(messages.demotedToUser, { name: account.acct })), - ).catch(() => {}); + demoteGroupMember({ role: roles.user, account_ids: [account.id] }, { + onSuccess() { + toast.success(intl.formatMessage(messages.demotedToUser, { name: account.acct })); + }, + }); }; const menu: IMenu = useMemo(() => { @@ -120,6 +142,31 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { return items; } + if (isCurrentUserAdmin && !isMemberAdmin && account.acct === account.username) { + if (isMemberModerator) { + if (features.groupsPromoteToAdmin) { + items.push({ + text: intl.formatMessage(messages.groupModPromoteAdmin, { name: account.username }), + icon: require('@tabler/icons/arrow-up-circle.svg'), + action: handlePromoteToGroupAdmin, + }); + } + + items.push({ + text: intl.formatMessage(messages.groupModDemote, { role: roles.moderator, name: account.username }), + icon: require('@tabler/icons/briefcase.svg'), + action: handleDemote, + destructive: true, + }); + } else if (isMemberUser) { + items.push({ + text: intl.formatMessage(messages.groupModPromoteMod, { role: roles.moderator }), + icon: require('@tabler/icons/briefcase.svg'), + action: handleAssignModerator, + }); + } + } + if ( (isCurrentUserAdmin || isCurrentUserModerator) && (isMemberModerator || isMemberUser) && @@ -141,29 +188,6 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { }); } - if (isCurrentUserAdmin && !isMemberAdmin && account.acct === account.username) { - items.push(null); - - if (isMemberModerator) { - items.push({ - text: intl.formatMessage(messages.groupModPromoteAdmin, { name: account.username }), - icon: require('@tabler/icons/arrow-up-circle.svg'), - action: handlePromoteToGroupAdmin, - }); - items.push({ - text: intl.formatMessage(messages.groupModDemote, { name: account.username }), - icon: require('@tabler/icons/arrow-down-circle.svg'), - action: handleDemote, - }); - } else if (isMemberUser) { - items.push({ - text: intl.formatMessage(messages.groupModPromoteMod, { name: account.username }), - icon: require('@tabler/icons/arrow-up-circle.svg'), - action: handlePromoteToGroupMod, - }); - } - } - return items; }, [group, account]); diff --git a/app/soapbox/hooks/api/groups/useBlockGroupMember.ts b/app/soapbox/hooks/api/groups/useBlockGroupMember.ts index 12af739c9..36f722f27 100644 --- a/app/soapbox/hooks/api/groups/useBlockGroupMember.ts +++ b/app/soapbox/hooks/api/groups/useBlockGroupMember.ts @@ -4,7 +4,7 @@ import { useEntityActions } from 'soapbox/entity-store/hooks'; import type { Group, GroupMember } from 'soapbox/schemas'; function useBlockGroupMember(group: Group, groupMember: GroupMember) { - const { createEntity } = useEntityActions( + const { createEntity } = useEntityActions( [Entities.GROUP_MEMBERSHIPS, groupMember.id], { post: `/api/v1/groups/${group.id}/blocks` }, ); diff --git a/app/soapbox/hooks/api/groups/useDemoteGroupMember.ts b/app/soapbox/hooks/api/groups/useDemoteGroupMember.ts new file mode 100644 index 000000000..db38164b7 --- /dev/null +++ b/app/soapbox/hooks/api/groups/useDemoteGroupMember.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityActions } from 'soapbox/entity-store/hooks'; +import { groupMemberSchema } from 'soapbox/schemas'; + +import type { Group, GroupMember } from 'soapbox/schemas'; + +function useDemoteGroupMember(group: Group, groupMember: GroupMember) { + const { createEntity } = useEntityActions( + [Entities.GROUP_MEMBERSHIPS, groupMember.id], + { post: `/api/v1/groups/${group.id}/demote` }, + { schema: z.array(groupMemberSchema).transform((arr) => arr[0]) }, + ); + + return createEntity; +} + +export { useDemoteGroupMember }; \ No newline at end of file diff --git a/app/soapbox/hooks/api/groups/usePromoteGroupMember.ts b/app/soapbox/hooks/api/groups/usePromoteGroupMember.ts new file mode 100644 index 000000000..148980f0c --- /dev/null +++ b/app/soapbox/hooks/api/groups/usePromoteGroupMember.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntityActions } from 'soapbox/entity-store/hooks'; +import { groupMemberSchema } from 'soapbox/schemas'; + +import type { Group, GroupMember } from 'soapbox/schemas'; + +function usePromoteGroupMember(group: Group, groupMember: GroupMember) { + const { createEntity } = useEntityActions( + [Entities.GROUP_MEMBERSHIPS, groupMember.id], + { post: `/api/v1/groups/${group.id}/promote` }, + { schema: z.array(groupMemberSchema).transform((arr) => arr[0]) }, + ); + + return createEntity; +} + +export { usePromoteGroupMember }; \ No newline at end of file diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/hooks/api/index.ts new file mode 100644 index 000000000..144196a2c --- /dev/null +++ b/app/soapbox/hooks/api/index.ts @@ -0,0 +1,6 @@ +/** + * Groups + */ +export { useBlockGroupMember } from './groups/useBlockGroupMember'; +export { useDemoteGroupMember } from './groups/useDemoteGroupMember'; +export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; \ No newline at end of file diff --git a/app/soapbox/hooks/useGroupRoles.ts b/app/soapbox/hooks/useGroupRoles.ts index dd435ce16..138c8685e 100644 --- a/app/soapbox/hooks/useGroupRoles.ts +++ b/app/soapbox/hooks/useGroupRoles.ts @@ -30,6 +30,14 @@ const useGroupRoles = () => { const isTruthSocial = version.software === TRUTHSOCIAL; const selectedRoles = isTruthSocial ? TruthSocialGroupRoles : BaseGroupRoles; + const isAdminRole = (role: TruthSocialGroupRoles | BaseGroupRoles) => { + if (isTruthSocial) { + return role === TruthSocialGroupRoles.ADMIN; + } + + return role === BaseGroupRoles.ADMIN; + }; + const normalizeRole = (role: TruthSocialGroupRoles) => { if (isTruthSocial) { return roleMap[role]; @@ -39,6 +47,7 @@ const useGroupRoles = () => { }; return { + isAdminRole, normalizeRole, roles: { admin: selectedRoles.ADMIN, diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index f9ce366ae..ef79fb48a 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -772,14 +772,14 @@ "group.group_mod_authorize.success": "Accepted @{name} to group", "group.group_mod_block": "Ban from group", "group.group_mod_block.success": "You have successfully blocked @{name} from the group", - "group.group_mod_demote": "Demote @{name}", + "group.group_mod_demote": "Remove {role} role", "group.group_mod_demote.success": "Demoted @{name} to group user", "group.group_mod_kick": "Kick @{name} from group", "group.group_mod_kick.success": "Kicked @{name} from group", "group.group_mod_promote_admin": "Promote @{name} to group administrator", "group.group_mod_promote_admin.success": "Promoted @{name} to group administrator", - "group.group_mod_promote_mod": "Promote @{name} to group moderator", - "group.group_mod_promote_mod.success": "Promoted @{name} to group moderator", + "group.group_mod_promote_mod": "Assign {role} role", + "group.group_mod_promote_mod.success": "You have successfully promoted @{name} to group {role}.", "group.group_mod_reject": "Reject", "group.group_mod_reject.success": "Rejected @{name} from group", "group.group_mod_unblock": "Unblock", diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index e99d4c921..1a1ae28bc 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -528,9 +528,14 @@ const getInstanceFeatures = (instance: Instance) => { /** * Can query pending Group requests. - */ + */ groupsPending: v.software === TRUTHSOCIAL, + /** + * Can promote members to Admins. + */ + groupsPromoteToAdmin: v.software !== TRUTHSOCIAL, + /** * Can hide follows/followers lists and counts. * @see PATCH /api/v1/accounts/update_credentials From 4985db7deae321b4792e78b094a636fc416e1b13 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 20 Mar 2023 08:53:38 -0400 Subject: [PATCH 2/4] Update group roles to owner/admin/user --- .../components/groups/group-avatar.tsx | 12 +- .../components/group-member-list-item.tsx | 115 +++++++----------- app/soapbox/features/group/group-members.tsx | 16 ++- app/soapbox/hooks/useGroupRoles.ts | 60 --------- app/soapbox/locales/en.json | 6 +- app/soapbox/main.tsx | 10 +- app/soapbox/queries/groups/members.ts | 4 +- app/soapbox/schemas/group-member.ts | 17 +-- 8 files changed, 69 insertions(+), 171 deletions(-) delete mode 100644 app/soapbox/hooks/useGroupRoles.ts diff --git a/app/soapbox/components/groups/group-avatar.tsx b/app/soapbox/components/groups/group-avatar.tsx index b862a92c0..91d6808d2 100644 --- a/app/soapbox/components/groups/group-avatar.tsx +++ b/app/soapbox/components/groups/group-avatar.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import React from 'react'; -import { useGroupRoles } from 'soapbox/hooks/useGroupRoles'; +import { GroupRoles } from 'soapbox/schemas/group-member'; import { Avatar } from '../ui'; @@ -16,17 +16,15 @@ interface IGroupAvatar { const GroupAvatar = (props: IGroupAvatar) => { const { group, size, withRing = false } = props; - const { normalizeRole } = useGroupRoles(); - - const isAdmin = normalizeRole(group.relationship?.role as any) === 'admin'; + const isOwner = group.relationship?.role === GroupRoles.OWNER; return ( { const features = useFeatures(); const intl = useIntl(); - const { roles, isAdminRole, normalizeRole } = useGroupRoles(); const blockGroupMember = useBlockGroupMember(group, member); const promoteGroupMember = usePromoteGroupMember(group, member); const demoteGroupMember = useDemoteGroupMember(group, member); @@ -57,13 +54,13 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const account = useAccount(member.account.id) as AccountEntity; // Current user role - const isCurrentUserAdmin = normalizeRole(group.relationship?.role as any) === BaseGroupRoles.ADMIN; - const isCurrentUserModerator = normalizeRole(group.relationship?.role as any) === BaseGroupRoles.MODERATOR; + const isCurrentUserOwner = group.relationship?.role === GroupRoles.OWNER; + const isCurrentUserAdmin = group.relationship?.role === GroupRoles.ADMIN; // Member role - const isMemberAdmin = normalizeRole(member.role as any) === BaseGroupRoles.ADMIN; - const isMemberModerator = normalizeRole(member.role as any) === BaseGroupRoles.MODERATOR; - const isMemberUser = normalizeRole(member.role as any) === BaseGroupRoles.USER; + const isMemberOwner = member.role === GroupRoles.OWNER; + const isMemberAdmin = member.role === GroupRoles.ADMIN; + const isMemberUser = member.role === GroupRoles.USER; const handleKickFromGroup = () => { dispatch(openModal('CONFIRM', { @@ -91,44 +88,26 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { })); }; - const onPromote = (role: TruthSocialGroupRoles | BaseGroupRoles, warning?: boolean) => { - if (warning) { - return dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.promoteConfirmMessage, { name: account.username }), - confirm: intl.formatMessage(messages.promoteConfirm), - onConfirm: () => { - promoteGroupMember({ role: role, account_ids: [account.id] }, { - onSuccess() { - toast.success( - intl.formatMessage( - isAdminRole(role) ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct, role }, - ), - ); - }, - }); - }, - })); - } else { - promoteGroupMember({ role: role, account_ids: [account.id] }, { - onSuccess() { - toast.success( - intl.formatMessage( - isAdminRole(role) ? messages.promotedToAdmin : messages.promotedToMod, { name: account.acct, role }, - ), - ); - }, - }); - } + const handleAdminAssignment = () => { + dispatch(openModal('CONFIRM', { + heading: intl.formatMessage(messages.promoteConfirm), + message: intl.formatMessage(messages.promoteConfirmMessage, { name: account.username }), + confirm: intl.formatMessage(messages.promoteConfirm), + confirmationTheme: 'primary', + onConfirm: () => { + promoteGroupMember({ role: GroupRoles.ADMIN, account_ids: [account.id] }, { + onSuccess() { + toast.success( + intl.formatMessage(messages.promotedToAdmin, { name: account.acct }), + ); + }, + }); + }, + })); }; - const handlePromoteToGroupAdmin = () => onPromote(roles.admin, true); - - const handleAssignModerator = () => { - onPromote(roles.moderator, false); - }; - - const handleDemote = () => { - demoteGroupMember({ role: roles.user, account_ids: [account.id] }, { + const handleUserAssignment = () => { + demoteGroupMember({ role: GroupRoles.USER, account_ids: [account.id] }, { onSuccess() { toast.success(intl.formatMessage(messages.demotedToUser, { name: account.acct })); }, @@ -142,34 +121,26 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { return items; } - if (isCurrentUserAdmin && !isMemberAdmin && account.acct === account.username) { - if (isMemberModerator) { - if (features.groupsPromoteToAdmin) { - items.push({ - text: intl.formatMessage(messages.groupModPromoteAdmin, { name: account.username }), - icon: require('@tabler/icons/arrow-up-circle.svg'), - action: handlePromoteToGroupAdmin, - }); - } - + if (isCurrentUserOwner) { + if (isMemberUser) { items.push({ - text: intl.formatMessage(messages.groupModDemote, { role: roles.moderator, name: account.username }), + text: intl.formatMessage(messages.groupModPromoteMod, { role: GroupRoles.ADMIN }), icon: require('@tabler/icons/briefcase.svg'), - action: handleDemote, - destructive: true, + action: handleAdminAssignment, }); - } else if (isMemberUser) { + } else if (isMemberAdmin) { items.push({ - text: intl.formatMessage(messages.groupModPromoteMod, { role: roles.moderator }), + text: intl.formatMessage(messages.groupModDemote, { role: GroupRoles.ADMIN, name: account.username }), icon: require('@tabler/icons/briefcase.svg'), - action: handleAssignModerator, + action: handleUserAssignment, + destructive: true, }); } } if ( - (isCurrentUserAdmin || isCurrentUserModerator) && - (isMemberModerator || isMemberUser) && + (isCurrentUserOwner || isCurrentUserAdmin) && + (isMemberAdmin || isMemberUser) && member.role !== group.relationship.role ) { if (features.groupsKick) { @@ -198,12 +169,12 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { - {(isMemberAdmin || isMemberModerator) ? ( + {(isMemberOwner || isMemberAdmin) ? ( diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx index 99ae8c28a..9fa1d135f 100644 --- a/app/soapbox/features/group/group-members.tsx +++ b/app/soapbox/features/group/group-members.tsx @@ -2,8 +2,8 @@ import React, { useMemo } from 'react'; import ScrollableList from 'soapbox/components/scrollable-list'; import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers'; -import { useGroupRoles } from 'soapbox/hooks/useGroupRoles'; import { useGroup } from 'soapbox/queries/groups'; +import { GroupRoles } from 'soapbox/schemas/group-member'; import PlaceholderAccount from '../placeholder/components/placeholder-account'; @@ -16,22 +16,20 @@ interface IGroupMembers { } const GroupMembers: React.FC = (props) => { - const { roles: { admin, moderator, user } } = useGroupRoles(); - const groupId = props.params.id; const { group, isFetching: isFetchingGroup } = useGroup(groupId); - const { groupMembers: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, admin); - const { groupMembers: moderators, isFetching: isFetchingModerators } = useGroupMembers(groupId, moderator); - const { groupMembers: users, isFetching: isFetchingUsers, fetchNextPage, hasNextPage } = useGroupMembers(groupId, user); + const { groupMembers: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER); + const { groupMembers: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, GroupRoles.ADMIN); + const { groupMembers: users, isFetching: isFetchingUsers, fetchNextPage, hasNextPage } = useGroupMembers(groupId, GroupRoles.USER); - const isLoading = isFetchingGroup || isFetchingAdmins || isFetchingModerators || isFetchingUsers; + const isLoading = isFetchingGroup || isFetchingOwners || isFetchingAdmins || isFetchingUsers; const members = useMemo(() => [ + ...owners, ...admins, - ...moderators, ...users, - ], [admins, moderators, users]); + ], [owners, admins, users]); return ( <> diff --git a/app/soapbox/hooks/useGroupRoles.ts b/app/soapbox/hooks/useGroupRoles.ts deleted file mode 100644 index 138c8685e..000000000 --- a/app/soapbox/hooks/useGroupRoles.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { TRUTHSOCIAL } from 'soapbox/utils/features'; - -import { useBackend } from './useBackend'; - -enum TruthSocialGroupRoles { - ADMIN = 'owner', - MODERATOR = 'admin', - USER = 'user' -} - -enum BaseGroupRoles { - ADMIN = 'admin', - MODERATOR = 'moderator', - USER = 'user' -} - -const roleMap = { - [TruthSocialGroupRoles.ADMIN]: BaseGroupRoles.ADMIN, - [TruthSocialGroupRoles.MODERATOR]: BaseGroupRoles.MODERATOR, - [TruthSocialGroupRoles.USER]: BaseGroupRoles.USER, -}; - -/** - * Returns the correct role name depending on the used backend. - * - * @returns Object - */ -const useGroupRoles = () => { - const version = useBackend(); - const isTruthSocial = version.software === TRUTHSOCIAL; - const selectedRoles = isTruthSocial ? TruthSocialGroupRoles : BaseGroupRoles; - - const isAdminRole = (role: TruthSocialGroupRoles | BaseGroupRoles) => { - if (isTruthSocial) { - return role === TruthSocialGroupRoles.ADMIN; - } - - return role === BaseGroupRoles.ADMIN; - }; - - const normalizeRole = (role: TruthSocialGroupRoles) => { - if (isTruthSocial) { - return roleMap[role]; - } - - return role; - }; - - return { - isAdminRole, - normalizeRole, - roles: { - admin: selectedRoles.ADMIN, - moderator: selectedRoles.MODERATOR, - user: selectedRoles.USER, - }, - }; -}; - -export { useGroupRoles, TruthSocialGroupRoles, BaseGroupRoles }; \ No newline at end of file diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index ef79fb48a..5e3bcbae5 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -470,8 +470,8 @@ "confirmations.block.confirm": "Block", "confirmations.block.heading": "Block @{name}", "confirmations.block.message": "Are you sure you want to block {name}?", - "confirmations.block_from_group.confirm": "Block", - "confirmations.block_from_group.heading": "Block group member", + "confirmations.block_from_group.confirm": "Ban User", + "confirmations.block_from_group.heading": "Ban From Group", "confirmations.block_from_group.message": "Are you sure you want to ban @{name} from the group?", "confirmations.cancel.confirm": "Discard", "confirmations.cancel.heading": "Discard post", @@ -771,7 +771,7 @@ "group.group_mod_authorize": "Accept", "group.group_mod_authorize.success": "Accepted @{name} to group", "group.group_mod_block": "Ban from group", - "group.group_mod_block.success": "You have successfully blocked @{name} from the group", + "group.group_mod_block.success": "@{name} is banned", "group.group_mod_demote": "Remove {role} role", "group.group_mod_demote.success": "Demoted @{name} to group user", "group.group_mod_kick": "Kick @{name} from group", diff --git a/app/soapbox/main.tsx b/app/soapbox/main.tsx index f5cf9f0f5..bfc31649e 100644 --- a/app/soapbox/main.tsx +++ b/app/soapbox/main.tsx @@ -41,9 +41,9 @@ ready(() => { root.render(); - if (BuildConfig.NODE_ENV === 'production') { - // avoid offline in dev mode because it's harder to debug - // https://github.com/NekR/offline-plugin/pull/201#issuecomment-285133572 - OfflinePluginRuntime.install(); - } + // if (BuildConfig.NODE_ENV === 'production') { + // avoid offline in dev mode because it's harder to debug + // https://github.com/NekR/offline-plugin/pull/201#issuecomment-285133572 + OfflinePluginRuntime.install(); + // } }); \ No newline at end of file diff --git a/app/soapbox/queries/groups/members.ts b/app/soapbox/queries/groups/members.ts index d1707fb6c..4d0a0bb66 100644 --- a/app/soapbox/queries/groups/members.ts +++ b/app/soapbox/queries/groups/members.ts @@ -1,14 +1,14 @@ import { useQuery } from '@tanstack/react-query'; import { useApi } from 'soapbox/hooks'; -import { useGroupRoles } from 'soapbox/hooks/useGroupRoles'; 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: ReturnType['roles']['admin']) => { +const useGroupMembers = (groupId: string, role: GroupRoles) => { const api = useApi(); const getQuery = async () => { diff --git a/app/soapbox/schemas/group-member.ts b/app/soapbox/schemas/group-member.ts index 73e051588..4521450cb 100644 --- a/app/soapbox/schemas/group-member.ts +++ b/app/soapbox/schemas/group-member.ts @@ -2,27 +2,18 @@ import z from 'zod'; import { accountSchema } from './account'; -enum TruthSocialGroupRoles { - ADMIN = 'owner', - MODERATOR = 'admin', - USER = 'user' -} - -enum BaseGroupRoles { +enum GroupRoles { + OWNER = 'owner', ADMIN = 'admin', - MODERATOR = 'moderator', USER = 'user' } const groupMemberSchema = z.object({ id: z.string(), account: accountSchema, - role: z.union([ - z.nativeEnum(TruthSocialGroupRoles), - z.nativeEnum(BaseGroupRoles), - ]), + role: z.nativeEnum(GroupRoles), }); type GroupMember = z.infer; -export { groupMemberSchema, GroupMember }; \ No newline at end of file +export { groupMemberSchema, GroupMember, GroupRoles }; \ No newline at end of file From 4a6433433f56bd8fe31fdc491a2f0cdd14eaceb2 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 20 Mar 2023 12:48:55 -0400 Subject: [PATCH 3/4] Update translations --- app/soapbox/locales/en.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 5e3bcbae5..0c0b8aff0 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -509,8 +509,6 @@ "confirmations.mute.confirm": "Mute", "confirmations.mute.heading": "Mute @{name}", "confirmations.mute.message": "Are you sure you want to mute {name}?", - "confirmations.promote_in_group.confirm": "Promote", - "confirmations.promote_in_group.message": "Are you sure you want to promote @{name}? You will not be able to demote them.", "confirmations.redraft.confirm": "Delete & redraft", "confirmations.redraft.heading": "Delete & redraft", "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.", @@ -768,18 +766,15 @@ "gdpr.title": "{siteTitle} uses cookies", "getting_started.open_source_notice": "{code_name} is open source software. You can contribute or report issues at {code_link} (v{code_version}).", "group.cancel_request": "Cancel Request", + "group.demote.user.success": "@{name} is now a member", "group.group_mod_authorize": "Accept", "group.group_mod_authorize.success": "Accepted @{name} to group", "group.group_mod_block": "Ban from group", "group.group_mod_block.success": "@{name} is banned", "group.group_mod_demote": "Remove {role} role", - "group.group_mod_demote.success": "Demoted @{name} to group user", "group.group_mod_kick": "Kick @{name} from group", "group.group_mod_kick.success": "Kicked @{name} from group", - "group.group_mod_promote_admin": "Promote @{name} to group administrator", - "group.group_mod_promote_admin.success": "Promoted @{name} to group administrator", "group.group_mod_promote_mod": "Assign {role} role", - "group.group_mod_promote_mod.success": "You have successfully promoted @{name} to group {role}.", "group.group_mod_reject": "Reject", "group.group_mod_reject.success": "Rejected @{name} from group", "group.group_mod_unblock": "Unblock", @@ -798,6 +793,9 @@ "group.privacy.public": "Public", "group.privacy.public.full": "Public Group", "group.privacy.public.info": "Discoverable. Anyone can join.", + "group.promote.admin.confirmation.message": "Are you sure you want to assign the admin role to @{name}?", + "group.promote.admin.confirmation.title": "Assign Admin Role", + "group.promote.admin.success": "@{name} is now an admin", "group.role.admin": "Admin", "group.role.moderator": "Moderator", "group.tabs.all": "All", From 8b478c939a7da8978d53eceab9d26ab70cc4a92e Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 20 Mar 2023 16:08:29 -0400 Subject: [PATCH 4/4] Revert change --- app/soapbox/main.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/soapbox/main.tsx b/app/soapbox/main.tsx index bfc31649e..f5cf9f0f5 100644 --- a/app/soapbox/main.tsx +++ b/app/soapbox/main.tsx @@ -41,9 +41,9 @@ ready(() => { root.render(); - // if (BuildConfig.NODE_ENV === 'production') { - // avoid offline in dev mode because it's harder to debug - // https://github.com/NekR/offline-plugin/pull/201#issuecomment-285133572 - OfflinePluginRuntime.install(); - // } + if (BuildConfig.NODE_ENV === 'production') { + // avoid offline in dev mode because it's harder to debug + // https://github.com/NekR/offline-plugin/pull/201#issuecomment-285133572 + OfflinePluginRuntime.install(); + } }); \ No newline at end of file