forked from potsda.mn/mobilizon
feat(reports): allow to suspend a profile or a user account directly from the report view
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
b105c508c0
commit
69588dbf4c
|
@ -42,6 +42,11 @@ const REPORT_FRAGMENT = gql`
|
|||
id
|
||||
reported {
|
||||
...ActorFragment
|
||||
... on Person {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
reporter {
|
||||
...ActorFragment
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<td>
|
||||
{{ t("Reported identity") }}
|
||||
</td>
|
||||
<td>
|
||||
<td class="flex items-center justify-between pr-6">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_PROFILE,
|
||||
|
@ -103,6 +103,20 @@
|
|||
/>
|
||||
{{ displayNameAndUsername(report.reported) }}
|
||||
</router-link>
|
||||
<o-button
|
||||
v-if="report.reported.domain"
|
||||
variant="danger"
|
||||
@click="suspendProfile(report.reported.id as string)"
|
||||
icon-left="delete"
|
||||
>{{ t("Suspend the profile") }}</o-button
|
||||
>
|
||||
<o-button
|
||||
v-else-if="(report.reported as IPerson).user"
|
||||
variant="danger"
|
||||
@click="suspendUser((report.reported as IPerson).user as IUser)"
|
||||
icon-left="delete"
|
||||
>{{ t("Suspend the account") }}</o-button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -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<void> => {
|
||||
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."
|
||||
) +
|
||||
`<p><b>` +
|
||||
t("There will be no way to restore the profile's data!") +
|
||||
`</b></p>`,
|
||||
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:") +
|
||||
`<ul class="list-disc pl-3">` +
|
||||
user.actors
|
||||
.map((person) => `<li>${displayNameAndUsername(person)}</li>`)
|
||||
.join("") +
|
||||
`</ul><b>`
|
||||
);
|
||||
};
|
||||
|
||||
const cachedReportedUser = ref<IUser | undefined>();
|
||||
|
||||
const suspendUser = async (user: IUser): Promise<void> => {
|
||||
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) +
|
||||
"<b>" +
|
||||
t("There will be no way to restore the user's data!") +
|
||||
`</b>`,
|
||||
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"));
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
tbody td img.image,
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { ApolloClient, NormalizedCacheObject } from "@apollo/client/core";
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloQueryResult,
|
||||
NormalizedCacheObject,
|
||||
OperationVariables,
|
||||
} from "@apollo/client/core";
|
||||
import buildCurrentUserResolver from "@/apollo/user";
|
||||
import { cache } from "./apollo/memory";
|
||||
import { fullLink } from "./apollo/link";
|
||||
import { UseQueryReturn } from "@vue/apollo-composable";
|
||||
|
||||
export const apolloClient = new ApolloClient<NormalizedCacheObject>({
|
||||
cache,
|
||||
|
@ -9,3 +15,24 @@ export const apolloClient = new ApolloClient<NormalizedCacheObject>({
|
|||
connectToDevTools: true,
|
||||
resolvers: buildCurrentUserResolver(cache),
|
||||
});
|
||||
|
||||
export function waitApolloQuery<
|
||||
TResult = any,
|
||||
TVariables extends OperationVariables = OperationVariables,
|
||||
>({
|
||||
onResult,
|
||||
onError,
|
||||
}: UseQueryReturn<TResult, TVariables>): Promise<ApolloQueryResult<TResult>> {
|
||||
return new Promise((res, rej) => {
|
||||
const { off: offResult } = onResult((result) => {
|
||||
if (result.loading === false) {
|
||||
offResult();
|
||||
res(result);
|
||||
}
|
||||
});
|
||||
const { off: offError } = onError((error) => {
|
||||
offError();
|
||||
rej(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue