Allow group admins to moderate new members
Closes #881 Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -23,29 +23,6 @@
|
||||
</ul>
|
||||
</nav>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<invitations
|
||||
v-if="isCurrentActorAnInvitedGroupMember"
|
||||
:invitations="[groupMember]"
|
||||
@acceptInvitation="acceptInvitation"
|
||||
@reject-invitation="rejectInvitation"
|
||||
/>
|
||||
<b-message v-if="isCurrentActorARejectedGroupMember" type="is-danger">
|
||||
{{ $t("You have been removed from this group's members.") }}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
isCurrentActorAGroupMember &&
|
||||
isCurrentActorARecentMember &&
|
||||
isCurrentActorOnADifferentDomainThanGroup
|
||||
"
|
||||
type="is-info"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"Since you are a new member, private content can take a few minutes to appear."
|
||||
)
|
||||
}}
|
||||
</b-message>
|
||||
<header class="block-container presentation" v-if="group">
|
||||
<div class="banner-container">
|
||||
<lazy-image-wrapper :picture="group.banner" />
|
||||
@@ -137,7 +114,7 @@
|
||||
<b-tooltip
|
||||
v-if="
|
||||
(!isCurrentActorAGroupMember || previewPublic) &&
|
||||
group.openness !== Openness.OPEN
|
||||
group.openness === Openness.INVITE_ONLY
|
||||
"
|
||||
:label="$t('This group is invite-only')"
|
||||
position="is-bottom"
|
||||
@@ -148,7 +125,9 @@
|
||||
>
|
||||
<b-button
|
||||
v-else-if="
|
||||
(!isCurrentActorAGroupMember || previewPublic) &&
|
||||
((!isCurrentActorAGroupMember &&
|
||||
!isCurrentActorAPendingGroupMember) ||
|
||||
previewPublic) &&
|
||||
currentActor.id
|
||||
"
|
||||
@click="joinGroup"
|
||||
@@ -157,6 +136,14 @@
|
||||
:disabled="previewPublic"
|
||||
>{{ $t("Join group") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
outlined
|
||||
v-else-if="isCurrentActorAPendingGroupMember"
|
||||
@click="leaveGroup"
|
||||
@keyup.enter="leaveGroup"
|
||||
type="is-primary"
|
||||
>{{ $t("Cancel membership request") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{
|
||||
@@ -310,6 +297,49 @@
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<invitations
|
||||
v-if="isCurrentActorAnInvitedGroupMember"
|
||||
:invitations="[groupMember]"
|
||||
/>
|
||||
<b-message v-if="isCurrentActorARejectedGroupMember" type="is-danger">
|
||||
{{ $t("You have been removed from this group's members.") }}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
isCurrentActorAGroupMember &&
|
||||
isCurrentActorARecentMember &&
|
||||
isCurrentActorOnADifferentDomainThanGroup
|
||||
"
|
||||
type="is-info"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"Since you are a new member, private content can take a few minutes to appear."
|
||||
)
|
||||
}}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
!isCurrentActorAGroupMember &&
|
||||
!isCurrentActorAPendingGroupMember &&
|
||||
!isCurrentActorPendingFollow &&
|
||||
!isCurrentActorFollowing
|
||||
"
|
||||
type="is-info"
|
||||
has-icon
|
||||
class="m-3"
|
||||
>
|
||||
<i18n
|
||||
path="Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts."
|
||||
>
|
||||
<b slot="group_upcoming_public_events">{{
|
||||
$t("group's upcoming public events")
|
||||
}}</b>
|
||||
<b slot="access_to_group_private_content_as_well">{{
|
||||
$t("access to the group's private content as well")
|
||||
}}</b>
|
||||
</i18n>
|
||||
</b-message>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
@@ -893,31 +923,6 @@ export default class Group extends mixins(GroupMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
acceptInvitation(): void {
|
||||
if (this.groupMember) {
|
||||
const index = this.person.memberships.elements.findIndex(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
({ id }: IMember) => id === this.groupMember.id
|
||||
);
|
||||
const member = this.groupMember;
|
||||
member.role = MemberRole.MEMBER;
|
||||
this.person.memberships.elements.splice(index, 1, member);
|
||||
this.$apollo.queries.group.refetch();
|
||||
}
|
||||
}
|
||||
|
||||
rejectInvitation({ id: memberId }: { id: string }): void {
|
||||
const index = this.person.memberships.elements.findIndex(
|
||||
(membership) =>
|
||||
membership.role === MemberRole.INVITED && membership.id === memberId
|
||||
);
|
||||
if (index > -1) {
|
||||
this.person.memberships.elements.splice(index, 1);
|
||||
this.person.memberships.total -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
async reportGroup(content: string, forward: boolean): Promise<void> {
|
||||
this.isReportModalActive = false;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -195,6 +195,20 @@
|
||||
</b-table-column>
|
||||
<b-table-column field="actions" :label="$t('Actions')" v-slot="props">
|
||||
<div class="buttons" v-if="props.row.actor.id !== currentActor.id">
|
||||
<b-button
|
||||
type="is-success"
|
||||
v-if="props.row.role === MemberRole.NOT_APPROVED"
|
||||
@click="approveMember(props.row)"
|
||||
icon-left="check"
|
||||
>{{ $t("Approve member") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
type="is-danger"
|
||||
v-if="props.row.role === MemberRole.NOT_APPROVED"
|
||||
@click="rejectMember(props.row)"
|
||||
icon-left="exit-to-app"
|
||||
>{{ $t("Reject member") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="
|
||||
[MemberRole.MEMBER, MemberRole.MODERATOR].includes(
|
||||
@@ -217,7 +231,7 @@
|
||||
>
|
||||
<b-button
|
||||
v-if="props.row.role === MemberRole.MEMBER"
|
||||
@click="removeMember(props.row.id)"
|
||||
@click="removeMember(props.row)"
|
||||
type="is-danger"
|
||||
icon-left="exit-to-app"
|
||||
>{{ $t("Remove") }}</b-button
|
||||
@@ -250,8 +264,9 @@ import {
|
||||
GROUP_MEMBERS,
|
||||
REMOVE_MEMBER,
|
||||
UPDATE_MEMBER,
|
||||
APPROVE_MEMBER,
|
||||
} from "../../graphql/member";
|
||||
import { usernameWithDomain } from "../../types/actor";
|
||||
import { usernameWithDomain, displayName } from "../../types/actor";
|
||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||
|
||||
@Component({
|
||||
@@ -332,7 +347,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
this.$notifier.success(
|
||||
this.$t("{username} was invited to {group}", {
|
||||
username: this.newMemberUsername,
|
||||
group: this.group.name || usernameWithDomain(this.group),
|
||||
group: displayName(this.group),
|
||||
}) as string
|
||||
);
|
||||
this.newMemberUsername = "";
|
||||
@@ -375,7 +390,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
async removeMember(memberId: string): Promise<void> {
|
||||
async removeMember(oldMember: IMember): Promise<void> {
|
||||
const { roles, MEMBERS_PER_PAGE, group, page } = this;
|
||||
const variables = {
|
||||
name: usernameWithDomain(group),
|
||||
@@ -388,7 +403,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
mutation: REMOVE_MEMBER,
|
||||
variables: {
|
||||
groupId: this.group.id,
|
||||
memberId,
|
||||
memberId: oldMember.id,
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
@@ -397,12 +412,18 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
},
|
||||
],
|
||||
});
|
||||
this.$notifier.success(
|
||||
this.$t("The member was removed from the group {group}", {
|
||||
username: this.newMemberUsername,
|
||||
group: this.group.name || usernameWithDomain(this.group),
|
||||
}) as string
|
||||
);
|
||||
let message = this.$t("The member was removed from the group {group}", {
|
||||
group: displayName(this.group),
|
||||
}) as string;
|
||||
if (oldMember.role === MemberRole.NOT_APPROVED) {
|
||||
message = this.$t(
|
||||
"The membership request from {profile} was rejected",
|
||||
{
|
||||
group: displayName(oldMember.actor),
|
||||
}
|
||||
) as string;
|
||||
}
|
||||
this.$notifier.success(message);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
@@ -414,29 +435,49 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
promoteMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
if (member.role === MemberRole.MODERATOR) {
|
||||
this.updateMember(member.id, MemberRole.ADMINISTRATOR);
|
||||
this.updateMember(member, MemberRole.ADMINISTRATOR);
|
||||
}
|
||||
if (member.role === MemberRole.MEMBER) {
|
||||
this.updateMember(member.id, MemberRole.MODERATOR);
|
||||
this.updateMember(member, MemberRole.MODERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
demoteMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
if (member.role === MemberRole.MODERATOR) {
|
||||
this.updateMember(member.id, MemberRole.MEMBER);
|
||||
this.updateMember(member, MemberRole.MEMBER);
|
||||
}
|
||||
if (member.role === MemberRole.ADMINISTRATOR) {
|
||||
this.updateMember(member.id, MemberRole.MODERATOR);
|
||||
this.updateMember(member, MemberRole.MODERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMember(memberId: string, role: MemberRole): Promise<void> {
|
||||
async approveMember(member: IMember): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate<{ approveMember: IMember }>({
|
||||
mutation: APPROVE_MEMBER,
|
||||
variables: { memberId: member.id },
|
||||
});
|
||||
this.$notifier.success(this.$t("The member was approved") as string);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
this.$notifier.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rejectMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
this.removeMember(member);
|
||||
}
|
||||
|
||||
async updateMember(oldMember: IMember, role: MemberRole): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate<{ updateMember: IMember }>({
|
||||
mutation: UPDATE_MEMBER,
|
||||
variables: {
|
||||
memberId,
|
||||
memberId: oldMember.id,
|
||||
role,
|
||||
},
|
||||
refetchQueries: [
|
||||
@@ -455,8 +496,14 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
successMessage = "The member role was updated to administrator";
|
||||
break;
|
||||
case MemberRole.MEMBER:
|
||||
if (oldMember.role === MemberRole.NOT_APPROVED) {
|
||||
successMessage = "The member was approved";
|
||||
} else {
|
||||
successMessage = "The member role was updated to simple member";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
successMessage = "The member role was updated to simple member";
|
||||
successMessage = "The member role was updated";
|
||||
}
|
||||
this.$notifier.success(this.$t(successMessage) as string);
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -128,6 +128,19 @@
|
||||
}}</small>
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio
|
||||
v-model="editableGroup.openness"
|
||||
name="groupOpenness"
|
||||
:native-value="Openness.MODERATED"
|
||||
>{{ $t("Moderate new members") }}<br />
|
||||
<small>{{
|
||||
$t(
|
||||
"Anyone can request being a member, but an administrator needs to approve the membership."
|
||||
)
|
||||
}}</small>
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio
|
||||
v-model="editableGroup.openness"
|
||||
|
||||
Reference in New Issue
Block a user