From 69588dbf4ce2f80cc5829a841135042fa73eb4fe Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 31 Aug 2023 17:08:55 +0200 Subject: [PATCH] feat(reports): allow to suspend a profile or a user account directly from the report view Signed-off-by: Thomas Citharel --- js/src/graphql/report.ts | 5 + js/src/i18n/en_US.json | 11 ++- js/src/i18n/fr_FR.json | 11 ++- js/src/views/Moderation/ReportView.vue | 121 ++++++++++++++++++++++++- js/src/vue-apollo.ts | 29 +++++- 5 files changed, 172 insertions(+), 5 deletions(-) diff --git a/js/src/graphql/report.ts b/js/src/graphql/report.ts index c3d3b2bca..eac2e1502 100644 --- a/js/src/graphql/report.ts +++ b/js/src/graphql/report.ts @@ -42,6 +42,11 @@ const REPORT_FRAGMENT = gql` id reported { ...ActorFragment + ... on Person { + user { + id + } + } } reporter { ...ActorFragment diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 4003abde3..74cde1814 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -1592,5 +1592,14 @@ "Event deleted and report resolved": "Event deleted and report resolved", "Event deleted": "Event deleted", "Comment deleted and report resolved": "Comment deleted and report resolved", - "Comment under event {eventTitle}": "Comment under event {eventTitle}" + "Comment under event {eventTitle}": "Comment under event {eventTitle}", + "Suspend profile": "Suspend profile", + "Do you really want to suspend this profile? All of the profiles content will be deleted.": "Do you really want to suspend this profile? All of the profiles content will be deleted.", + "There will be no way to restore the profile's data!": "There will be no way to restore the profile's data!", + "Suspend the profile": "Suspend the profile", + "The following user's profiles will be deleted, with all their data:": "The following user's profiles will be deleted, with all their data:", + "Do you really want to suspend the account « {emailAccount} » ?": "Do you really want to suspend the account « {emailAccount} » ?", + "There will be no way to restore the user's data!": "There will be no way to restore the user's data!", + "User suspended and report resolved": "User suspended and report resolved", + "Profile suspended and report resolved": "Profile suspended and report resolved" } \ No newline at end of file diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 81b4b84ec..281bd276e 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -1590,5 +1590,14 @@ "Event deleted and report resolved": "Événement supprimé et signalement résolu", "Event deleted": "Événement supprimé", "Comment deleted and report resolved": "Commentaire supprimé et signalement résolu", - "Comment under event {eventTitle}": "Commentaire sous l'événement {eventTitle}" + "Comment under event {eventTitle}": "Commentaire sous l'événement {eventTitle}", + "Suspend the profile?": "Suspendre le profil ?", + "Do you really want to suspend this profile? All of the profiles content will be deleted.": "Voulez-vous vraiment suspendre ce profil ? Tout le contenu du profil sera supprimé.", + "There will be no way to restore the profile's data!": "Il n'y aura aucun moyen de restorer les données du profil !", + "Suspend the profile": "Suspendre le profil", + "The following user's profiles will be deleted, with all their data:": "Les profils suivants de l'utilisateur·ice seront supprimés, avec toutes leurs données :", + "Do you really want to suspend the account « {emailAccount} » ?": "Voulez-vous vraiment suspendre le compte « {emailAccount} » ?", + "There will be no way to restore the user's data!": "Il n'y aura aucun moyen de restorer les données de l'utilisateur·ice !", + "User suspended and report resolved": "Utilisateur suspendu et signalement résolu", + "Profile suspended and report resolved": "Profil suspendu et signalement résolu" } diff --git a/js/src/views/Moderation/ReportView.vue b/js/src/views/Moderation/ReportView.vue index 347a74ec9..9d4c01113 100644 --- a/js/src/views/Moderation/ReportView.vue +++ b/js/src/views/Moderation/ReportView.vue @@ -88,7 +88,7 @@ {{ t("Reported identity") }} - + {{ t("Suspend the profile") }} + {{ t("Suspend the account") }} @@ -333,7 +347,7 @@ import { ActorType, AntiSpamFeedback, ReportStatusEnum } from "@/types/enums"; import RouteName from "@/router/name"; import { GraphQLError } from "graphql"; import { ApolloCache, FetchResult } from "@apollo/client/core"; -import { useMutation, useQuery } from "@vue/apollo-composable"; +import { useLazyQuery, useMutation, useQuery } from "@vue/apollo-composable"; import { useCurrentActorClient } from "@/composition/apollo/actor"; import { useHead } from "@vueuse/head"; import { useI18n } from "vue-i18n"; @@ -348,6 +362,10 @@ import { useFeatures } from "@/composition/apollo/config"; import { IEvent } from "@/types/event.model"; import EmptyContent from "@/components/Utils/EmptyContent.vue"; import EventComment from "@/components/Comment/EventComment.vue"; +import { SUSPEND_PROFILE } from "@/graphql/actor"; +import { GET_USER, SUSPEND_USER } from "@/graphql/user"; +import { IUser } from "@/types/current-user.model"; +import { waitApolloQuery } from "@/vue-apollo"; const router = useRouter(); @@ -663,6 +681,105 @@ const reportToAntispam = (spam: boolean) => { }, }); }; + +const { mutate: doSuspendProfile, onDone: onSuspendProfileDone } = useMutation< + { + suspendProfile: { id: string }; + }, + { id: string } +>(SUSPEND_PROFILE); + +const { mutate: doSuspendUser, onDone: onSuspendUserDone } = useMutation< + { suspendProfile: { id: string } }, + { userId: string } +>(SUSPEND_USER); + +const userLazyQuery = useLazyQuery<{ user: IUser }, { id: string }>(GET_USER); + +const suspendProfile = async (actorId: string): Promise => { + dialog?.confirm({ + title: t("Suspend the profile?"), + message: + t( + "Do you really want to suspend this profile? All of the profiles content will be deleted." + ) + + `

` + + t("There will be no way to restore the profile's data!") + + `

`, + confirmText: t("Suspend the profile"), + cancelText: t("Cancel"), + variant: "danger", + onConfirm: async () => { + doSuspendProfile({ + id: actorId, + }); + return router.push({ name: RouteName.USERS }); + }, + }); +}; + +const userSuspendedProfilesMessages = (user: IUser) => { + return ( + t("The following user's profiles will be deleted, with all their data:") + + `
    ` + + user.actors + .map((person) => `
  • ${displayNameAndUsername(person)}
  • `) + .join("") + + `
` + ); +}; + +const cachedReportedUser = ref(); + +const suspendUser = async (user: IUser): Promise => { + try { + if (!cachedReportedUser.value) { + userLazyQuery.load(GET_USER, { id: user.id }); + + const userLazyQueryResult = await waitApolloQuery< + { user: IUser }, + { id: string } + >(userLazyQuery); + console.debug("data", userLazyQueryResult); + + cachedReportedUser.value = userLazyQueryResult.data.user; + } + + dialog?.confirm({ + title: t("Suspend the account?"), + message: + t("Do you really want to suspend the account « {emailAccount} » ?", { + emailAccount: cachedReportedUser.value.email, + }) + + " " + + userSuspendedProfilesMessages(cachedReportedUser.value) + + "" + + t("There will be no way to restore the user's data!") + + ``, + confirmText: t("Suspend the account"), + cancelText: t("Cancel"), + variant: "danger", + onConfirm: async () => { + doSuspendUser({ + userId: user.id, + }); + return router.push({ name: RouteName.USERS }); + }, + }); + } catch (e) { + console.error(e); + } +}; + +onSuspendUserDone(async () => { + await router.push({ name: RouteName.REPORTS }); + notifier?.success(t("User suspended and report resolved")); +}); + +onSuspendProfileDone(async () => { + await router.push({ name: RouteName.REPORTS }); + notifier?.success(t("Profile suspended and report resolved")); +});