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
|
id
|
||||||
reported {
|
reported {
|
||||||
...ActorFragment
|
...ActorFragment
|
||||||
|
... on Person {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reporter {
|
reporter {
|
||||||
...ActorFragment
|
...ActorFragment
|
||||||
|
|
|
@ -1592,5 +1592,14 @@
|
||||||
"Event deleted and report resolved": "Event deleted and report resolved",
|
"Event deleted and report resolved": "Event deleted and report resolved",
|
||||||
"Event deleted": "Event deleted",
|
"Event deleted": "Event deleted",
|
||||||
"Comment deleted and report resolved": "Comment deleted and report resolved",
|
"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 and report resolved": "Événement supprimé et signalement résolu",
|
||||||
"Event deleted": "Événement supprimé",
|
"Event deleted": "Événement supprimé",
|
||||||
"Comment deleted and report resolved": "Commentaire supprimé et signalement résolu",
|
"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>
|
<td>
|
||||||
{{ t("Reported identity") }}
|
{{ t("Reported identity") }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="flex items-center justify-between pr-6">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.ADMIN_PROFILE,
|
name: RouteName.ADMIN_PROFILE,
|
||||||
|
@ -103,6 +103,20 @@
|
||||||
/>
|
/>
|
||||||
{{ displayNameAndUsername(report.reported) }}
|
{{ displayNameAndUsername(report.reported) }}
|
||||||
</router-link>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -333,7 +347,7 @@ import { ActorType, AntiSpamFeedback, ReportStatusEnum } from "@/types/enums";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import { GraphQLError } from "graphql";
|
import { GraphQLError } from "graphql";
|
||||||
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
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 { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||||
import { useHead } from "@vueuse/head";
|
import { useHead } from "@vueuse/head";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
@ -348,6 +362,10 @@ import { useFeatures } from "@/composition/apollo/config";
|
||||||
import { IEvent } from "@/types/event.model";
|
import { IEvent } from "@/types/event.model";
|
||||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||||
import EventComment from "@/components/Comment/EventComment.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();
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
tbody td img.image,
|
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 buildCurrentUserResolver from "@/apollo/user";
|
||||||
import { cache } from "./apollo/memory";
|
import { cache } from "./apollo/memory";
|
||||||
import { fullLink } from "./apollo/link";
|
import { fullLink } from "./apollo/link";
|
||||||
|
import { UseQueryReturn } from "@vue/apollo-composable";
|
||||||
|
|
||||||
export const apolloClient = new ApolloClient<NormalizedCacheObject>({
|
export const apolloClient = new ApolloClient<NormalizedCacheObject>({
|
||||||
cache,
|
cache,
|
||||||
|
@ -9,3 +15,24 @@ export const apolloClient = new ApolloClient<NormalizedCacheObject>({
|
||||||
connectToDevTools: true,
|
connectToDevTools: true,
|
||||||
resolvers: buildCurrentUserResolver(cache),
|
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