Add frontend for accepting group invitations

For #887
This commit is contained in:
Massedil
2025-11-05 17:33:15 +01:00
committed by setop
parent bbeb8d5fae
commit cea2235321
6 changed files with 215 additions and 4 deletions

View File

@@ -35,3 +35,16 @@ export const GROUP_INVITATIONS_DELETE = gql`
}
}
`;
export const GROUP_INVITATIONS_ACCEPT = gql`
mutation acceptInvitationToken(
$groupId: ID!
$token: String!
$actorId: ID!
) {
acceptInvitationToken(groupId: $groupId, token: $token, actorId: $actorId) {
id
role
}
}
`;

View File

@@ -377,7 +377,8 @@
"Do you really want to ban the account \u00ab\u00a0{emailAccount}\u00a0\u00bb\u00a0?": "Do you really want to ban the account \u00ab\u00a0{emailAccount}\u00a0\u00bb\u00a0?",
"Do you really want to ban this account? All of the user's profiles will be deleted.": "Do you really want to ban this account? All of the user's profiles will be deleted.",
"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.",
"Do you really want to unban this account? The user will be able to log-in again.": "Do you really want to unban this account? The user will be able to log-in again.",
"Do you really want to unban this account? The user will be able to log-in again.":"Do you really want to unban this account? The user will be able to log-in again.",
"Do you want to join the group {groupName} with the profile {preferredUsername} ?": "Do you want to join the group {groupName} with the profile {preferredUsername} ?",
"Do you wish to {create_event} or {explore_events}?": "Do you wish to {create_event} or {explore_events}?",
"Do you wish to {create_group} or {explore_groups}?": "Do you wish to {create_group} or {explore_groups}?",
"Does the event needs to be confirmed later or is it cancelled?": "Does the event needs to be confirmed later or is it cancelled?",
@@ -523,6 +524,7 @@
"Glossary": "Glossary",
"Go to booking": "Go to booking",
"Go to the event page": "Go to the event page",
"Go to the group page": "Go to the group page",
"Go!": "Go!",
"Go": "Go",
"Google Meet": "Google Meet",
@@ -624,12 +626,14 @@
"Invitation links": "Invitation links",
"Invitations": "Invitations",
"Invite a new member": "Invite a new member",
"Invite link": "Invite link",
"Invite member": "Invite member",
"Invited": "Invited",
"It is possible that the content is not accessible on this instance, because this instance has blocked the profiles or groups behind this content.": "It is possible that the content is not accessible on this instance, because this instance has blocked the profiles or groups behind this content.",
"Italic": "Italic",
"Jitsi Meet": "Jitsi Meet",
"Join <b>{instance}</b>, a Mobilizon instance": "Join <b>{instance}</b>, a Mobilizon instance",
"Join a group": "Join a group",
"Join group {group}": "Join group {group}",
"Join group": "Join group",
"Join {instance}, a Mobilizon instance": "Join {instance}, a Mobilizon instance",
@@ -1477,6 +1481,7 @@
"You are participating in this event anonymously": "You are participating in this event anonymously",
"You can add resources by using the button above.": "You can add resources by using the button above.",
"You can add tags by hitting the Enter key or by adding a comma": "You can add tags by hitting the Enter key or by adding a comma",
"You can create a new profile or change the actual profile with the top menu.": "You can create a new profile or change the actual profile with the top menu.",
"You can drag and drop the marker below to the desired location": "You can drag and drop the marker below to the desired location",
"You can pick your timezone into your preferences.": "You can pick your timezone into your preferences.",
"You can put any arbitrary content in this element. URLs will be clickable.": "You can put any arbitrary content in this element. URLs will be clickable.",
@@ -1538,6 +1543,7 @@
"You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.",
"You replied to the discussion {discussion}.": "You replied to the discussion {discussion}.",
"You requested to join the group.": "You requested to join the group.",
"You successfully joined the group {groupName} with your profile {preferredUsername} as {role}.": "You successfully joined the group {groupName} with your profile {preferredUsername} as {role}.",
"You updated the event {event}.": "You updated the event {event}.",
"You updated the group {group}.": "You updated the group {group}.",
"You updated the member {member}.": "You updated the member {member}.",

View File

@@ -173,7 +173,7 @@
"Back to group list": "Retour \u00e0 la liste des groupes",
"Back to homepage": "Retour \u00e0 la page d'accueil",
"Back to previous page": "Retour \u00e0 la page pr\u00e9c\u00e9dente",
"Back to profile list": "Retour \u00e0 la liste des profiles",
"Back to profile list": "Retour \u00e0 la liste des profils",
"Back to top": "Retour en haut",
"Back to user list": "Retour \u00e0 la liste des utilisateur\u00b7ices",
"Ban the account": "Bannir le compte",
@@ -378,6 +378,7 @@
"Do you really want to ban this account? All of the user's profiles will be deleted.": "Voulez-vous vraiment bannir ce compte\u00a0? Tous les profils de cet\u00b7te utilisateur\u00b7ice seront supprim\u00e9s.",
"Do you really want to suspend this profile? All of the profiles content will be deleted.": "Voulez-vous vraiment suspendre ce profil\u00a0? Tout le contenu du profil sera supprim\u00e9.",
"Do you really want to unban this account? The user will be able to log-in again.": "Voulez-vous vraiment d\u00e9bannir ce compte\u00a0? L'utilisateur pourra \u00e0 nouveau se connecter.",
"Do you want to join the group {groupName} with the profile {preferredUsername} ?": "Voulez-vous rejoindre le groupe {groupName} avec le profil {preferredUsername} ?",
"Do you wish to {create_event} or {explore_events}?": "Voulez-vous {create_event} ou {explore_events} ?",
"Do you wish to {create_group} or {explore_groups}?": "Voulez-vous {create_group} ou {explore_groups} ?",
"Does the event needs to be confirmed later or is it cancelled?": "Est-ce que l'\u00e9v\u00e9nement doit \u00eatre confirm\u00e9 plus tard ou bien est-il annul\u00e9\u00a0?",
@@ -523,6 +524,7 @@
"Glossary": "Glossaire",
"Go to booking": "Aller \u00e0 la r\u00e9servation",
"Go to the event page": "Aller \u00e0 la page de l'\u00e9v\u00e9nement",
"Go to the group page": "Se rendre à la page du groupe",
"Go!": "Go\u00a0!",
"Go": "Allons-y",
"Google Meet": "Google Meet",
@@ -624,12 +626,14 @@
"Invitation links": "Liens d'invitation",
"Invitations" : "Invitations",
"Invite a new member": "Inviter un nouveau membre",
"Invite link": "Lien d'invitation",
"Invite member": "Inviter un\u00b7e membre",
"Invited": "Invit\u00e9\u00b7e",
"It is possible that the content is not accessible on this instance, because this instance has blocked the profiles or groups behind this content.": "Il est possible que le contenu ne soit pas accessible depuis cette instance, car cette instance a bloqu\u00e9 le profil ou le groupe derri\u00e8re ce contenu.",
"Italic": "Italique",
"Jitsi Meet": "Jitsi Meet",
"Join <b>{instance}</b>, a Mobilizon instance": "Rejoignez <b>{instance}</b>, une instance Mobilizon",
"Join a group": "Rejoindre un groupe",
"Join group {group}": "Rejoindre le groupe {group}",
"Join group": "Rejoindre le groupe",
"Join {instance}, a Mobilizon instance": "Rejoignez {instance}, une instance Mobilizon",
@@ -1474,6 +1478,7 @@
"You are participating in this event anonymously": "Vous participez \u00e0 cet \u00e9v\u00e9nement anonymement",
"You can add resources by using the button above.": "Vous pouvez ajouter des ressources en utilisant le bouton au dessus.",
"You can add tags by hitting the Enter key or by adding a comma": "Vous pouvez ajouter des tags en appuyant sur la touche Entr\u00e9e ou bien en ajoutant une virgule",
"You can create a new profile or change the actual profile with the top menu.": "Vous pouvez créer un nouveau profil ou changer l'actuel à l'aide du menu supérieur.",
"You can drag and drop the marker below to the desired location": "Vous pouvez faire glisser et d\u00e9poser le marqueur ci-dessous \u00e0 l'endroit souhait\u00e9",
"You can pick your timezone into your preferences.": "Vous pouvez choisir votre fuseau horaire dans vos pr\u00e9f\u00e9rences.",
"You can put any arbitrary content in this element. URLs will be clickable.": "Vous pouvez placer n'importe quel contenu arbitraire dans cet \u00e9l\u00e9ment. Les URL seront cliquables.",
@@ -1535,6 +1540,7 @@
"You replied to a comment on the event {event}.": "Vous avez r\u00e9pondu \u00e0 un commentaire sur l'\u00e9v\u00e9nement {event}.",
"You replied to the discussion {discussion}.": "Vous avez r\u00e9pondu \u00e0 la discussion {discussion}.",
"You requested to join the group.": "Vous avez demand\u00e9 \u00e0 rejoindre le groupe.",
"You successfully joined the group {groupName} with your profile {preferredUsername} as {role}.": "Vous avez rejoint avec succès le groupe {groupName} avec votre profil {preferredUsername} en tant que {role}.",
"You updated the event {event}.": "Vous avez mis \u00e0 jour l'\u00e9v\u00e9nement {event}.",
"You updated the group {group}.": "Vous avez mis \u00e0 jour le groupe {group}.",
"You updated the member {member}.": "Vous avez mis \u00e0 jour le ou la membre {member}.",

View File

@@ -9,6 +9,7 @@ export enum GroupsRouteName {
GROUP_MEMBERS_SETTINGS = "GROUP_MEMBERS_SETTINGS",
GROUP_FOLLOWERS_SETTINGS = "GROUP_FOLLOWERS_SETTINGS",
GROUP_INVITATIONS_SETTINGS = "GROUP_INVITATIONS_SETTINGS",
GROUP_INVITATION_ACCEPT = "GROUP_INVITATION_ACCEPT",
RESOURCES = "RESOURCES",
RESOURCE_FOLDER_ROOT = "RESOURCE_FOLDER_ROOT",
RESOURCE_FOLDER = "RESOURCE_FOLDER",
@@ -114,6 +115,14 @@ export const groupsRoutes: RouteRecordRaw[] = [
name: GroupsRouteName.POST_CREATE,
meta: { requiredAuth: true, announcer: { skip: true } },
},
{
path: "/@:preferredUsername/invitation/:token",
component: (): Promise<any> =>
import("@/views/Group/GroupInvitationJoin.vue"),
props: true,
name: GroupsRouteName.GROUP_INVITATION_ACCEPT,
meta: { requiredAuth: true, announcer: { skip: true } },
},
{
path: "/p/:slug/edit",
component: (): Promise<any> => import("@/views/Posts/EditView.vue"),

View File

@@ -0,0 +1,159 @@
<template>
<div class="container mx-auto px-1 mb-6">
<div v-if="groupLoading || currentActorLoading" class="relative min-h-24">
<o-loading :fullPage="false" :active="true" />
</div>
<div v-else-if="groupError || currentActorError">
<o-notification type="danger" variant="danger"
>{{ groupError?.message }}
{{ currentActorError?.message }}</o-notification
>
</div>
<div v-else-if="group && currentActor">
<h1>{{ t("Join a group") }}</h1>
<div v-if="!acceptGroupInvitationSucceeded">
<p class="my-1">
<i18n-t
keypath="Do you want to join the group {groupName} with the profile {preferredUsername} ?"
>
<template #groupName>
<span
>"<b>{{ groupName }}</b
>"</span
>
</template>
<template #preferredUsername>
<span
>"<b>{{ currentActor.preferredUsername }}</b
>"</span
>
</template>
</i18n-t>
</p>
<p class="my-1">
{{
t(
"You can create a new profile or change the actual profile with the top menu."
)
}}
</p>
<o-button
variant="primary"
:loading="acceptGroupInvitationLoading"
:disabled="acceptGroupInvitationSucceeded"
@click="
acceptGroupInvitation({
groupId: group.id,
token: token,
actorId: currentActor.id,
})
"
>{{ t("Join group") }}</o-button
>
</div>
<div v-else>
<o-notification type="success" variant="success"
><i18n-t
keypath="You successfully joined the group {groupName} with your profile {preferredUsername} as {role}."
>
<template #groupName>
<span
>"<b>{{ groupName }}</b
>"</span
>
</template>
<template #preferredUsername>
<span
>"<b>{{ currentActor.preferredUsername }}</b
>"</span
>
</template>
<template #role>
<span
>"<b>{{ memberRole }}</b
>"</span
>
</template>
</i18n-t></o-notification
>
<o-button
tag="router-link"
variant="primary"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: groupName,
},
}"
>{{ t("Go to the group page") }}</o-button
>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useCurrentActorClient } from "@/composition/apollo/actor";
import { useGroup } from "@/composition/apollo/group";
import { GROUP_INVITATIONS_ACCEPT } from "@/graphql/invitations";
import { IMember } from "@/types/actor/member.model";
import { useMutation } from "@vue/apollo-composable";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import RouteName from "../../router/name";
const { t } = useI18n({ useScope: "global" });
const props = defineProps<{ preferredUsername: string; token: string }>();
const groupName = computed(() => props.preferredUsername);
const token = computed(() => props.token);
// -------------------------------------------------------------
// Group informations
// -------------------------------------------------------------
const { group, loading: groupLoading, error: groupError } = useGroup(groupName);
// -------------------------------------------------------------
// Actor informations
// -------------------------------------------------------------
const {
currentActor,
loading: currentActorLoading,
error: currentActorError,
} = useCurrentActorClient();
// -------------------------------------------------------------
// Accept invitation
// -------------------------------------------------------------
const acceptGroupInvitationSucceeded = ref<boolean>(false);
const memberRole = ref<string>();
const {
mutate: acceptGroupInvitation,
onDone: onAcceptGroupInvitationDone,
onError: onAcceptGroupInvitationError,
loading: acceptGroupInvitationLoading,
} = useMutation<{ acceptInvitationToken: IMember }>(GROUP_INVITATIONS_ACCEPT);
onAcceptGroupInvitationDone(({ data }) => {
acceptGroupInvitationSucceeded.value = true;
memberRole.value = data?.acceptInvitationToken?.role;
});
onAcceptGroupInvitationError((error) => {
alert(error.message);
});
watch(currentActor, () => {
acceptGroupInvitationSucceeded.value = false;
});
</script>

View File

@@ -71,8 +71,26 @@
/>
<span v-else>{{ props.row.label }}</span>
</o-table-column>
<o-table-column field="token" :label="t('Token')" sortable />
<o-table-column field="url" :label="t('URL')" sortable />
<o-table-column
field="token"
:label="t('URL')"
sortable
v-slot="props"
>
<o-button
v-if="props.row.token"
tag="router-link"
variant="primary"
:to="{
name: RouteName.GROUP_INVITATION_ACCEPT,
params: {
preferredUsername: group.preferredUsername,
token: props.row.token,
},
}"
>{{ t("Invite link") }}</o-button
>
</o-table-column>
<o-table-column
field="creationDate"
:label="t('Creation date')"