Skip to content
Snippets Groups Projects
Commit 78490690 authored by Petar Hristov's avatar Petar Hristov :speech_balloon: Committed by Hanna Führ
Browse files

Fix: Added missing contact change token listener (coscine/issues#2130)

parent a34a92a9
No related branches found
No related tags found
2 merge requests!67Chore: 1.8.2,!63Fix: Contact Change handling
......@@ -2,23 +2,23 @@
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module "vue" {
declare module 'vue' {
export interface GlobalComponents {
BreadCrumbs: typeof import("./components/elements/BreadCrumbs.vue")["default"];
CoscineCard: typeof import("./components/coscine/CoscineCard.vue")["default"];
CoscineFormGroup: typeof import("./components/coscine/CoscineFormGroup.vue")["default"];
CoscineHeadline: typeof import("./components/coscine/CoscineHeadline.vue")["default"];
CoscineModal: typeof import("./components/coscine/CoscineModal.vue")["default"];
ExpiryToast: typeof import("./components/toasts/ExpiryToast.vue")["default"];
LoadingIndicator: typeof import("./components/elements/LoadingIndicator.vue")["default"];
LoadingSpinner: typeof import("./components/coscine/LoadingSpinner.vue")["default"];
Maintenance: typeof import("./components/banner/Maintenance.vue")["default"];
MultiSelect: typeof import("./components/coscine/MultiSelect.vue")["default"];
Navbar: typeof import("./components/elements/Navbar.vue")["default"];
NotificationToast: typeof import("./components/toasts/NotificationToast.vue")["default"];
Pilot: typeof import("./components/banner/Pilot.vue")["default"];
SidebarMenu: typeof import("./components/elements/SidebarMenu.vue")["default"];
BreadCrumbs: typeof import('./components/elements/BreadCrumbs.vue')['default']
CoscineCard: typeof import('./components/coscine/CoscineCard.vue')['default']
CoscineFormGroup: typeof import('./components/coscine/CoscineFormGroup.vue')['default']
CoscineHeadline: typeof import('./components/coscine/CoscineHeadline.vue')['default']
CoscineModal: typeof import('./components/coscine/CoscineModal.vue')['default']
ExpiryToast: typeof import('./components/toasts/ExpiryToast.vue')['default']
LoadingIndicator: typeof import('./components/elements/LoadingIndicator.vue')['default']
LoadingSpinner: typeof import('./components/coscine/LoadingSpinner.vue')['default']
Maintenance: typeof import('./components/banner/Maintenance.vue')['default']
MultiSelect: typeof import('./components/coscine/MultiSelect.vue')['default']
Navbar: typeof import('./components/elements/Navbar.vue')['default']
NotificationToast: typeof import('./components/toasts/NotificationToast.vue')['default']
Pilot: typeof import('./components/banner/Pilot.vue')['default']
SidebarMenu: typeof import('./components/elements/SidebarMenu.vue')['default']
}
}
export {};
export { }
......@@ -78,6 +78,17 @@ export default {
},
toast: {
contactChange: {
success: {
title: "Änderung der Kontaktdaten",
message: "E-Mail Adresse wurde erfolgreich bestätigt.",
},
failure: {
title: "@:(toast.contactChange.success.title)",
message:
"Es ist ein Fehler bei der Bestätigung der E-Mail Adresse aufgetreten. Das angegebene Token ist ungültig oder wurde bereits verwendet.",
},
},
session: {
title: "Ihre Sitzung ist abgelaufen",
message: "Um weiterzuarbeiten, loggen Sie sich erneut ein:",
......
......@@ -76,6 +76,17 @@ export default {
},
toast: {
contactChange: {
success: {
title: "Change contact information",
message: "Email address has been successfully confirmed.",
},
failure: {
title: "@:(toast.contactChange.success.title)",
message:
"Email address confirmation failed. The supplied token is invalid or was already used.",
},
},
session: {
title: "Your session has expired",
message: "To continue working, log in again:",
......
......@@ -177,7 +177,7 @@ export default defineComponent({
this.formValidations.naming.$reset();
this.formValidations.metadata.$reset();
// Refresh the project information in the store
await this.projectStore.setProjectInformation(this.project);
await this.projectStore.refreshProjectInformation(this.project);
// Navigate inside the newly created Project
navigateToProject(createdProject);
} else {
......
......@@ -48,7 +48,7 @@ export default defineComponent({
created() {
// handle possible invitation token
this.projectStore.retrieveInvitation();
this.projectStore.resolveProjectInvitation();
},
methods: {
......
......@@ -199,7 +199,7 @@ export default defineComponent({
? this.projectStore.currentParentProjects[0]
: null;
// Refresh the project information in the store
await this.projectStore.setProjectInformation(parentProject);
await this.projectStore.refreshProjectInformation(parentProject);
// Replace the current location with parent project or project list
this.notificationStore.postNotification({
title: this.$t("toast.onDelete.success.title").toString(),
......@@ -239,7 +239,7 @@ export default defineComponent({
this.formValidations.naming.$reset();
this.formValidations.metadata.$reset();
// Refresh the project information in the store
await this.projectStore.setProjectInformation(this.project);
await this.projectStore.refreshProjectInformation(this.project);
this.notificationStore.postNotification({
title: this.$t("toast.onSave.success.title").toString(),
body: this.$t("toast.onSave.success.message").toString(),
......
......@@ -466,14 +466,14 @@ export const useProjectStore = defineStore({
}
},
async retrieveInvitation() {
async resolveProjectInvitation() {
const notificationStore = useNotificationStore();
try {
// .../?invitationToken=<token>
const invitationToken = this.router.currentRoute.query.invitationToken;
if (invitationToken) {
await ProjectApi.projectResolveInvitation(invitationToken.toString());
this.setProjectInformation();
this.refreshProjectInformation();
removeQueryParameterFromUrl(
this.router.currentRoute,
"invitationToken"
......@@ -554,7 +554,9 @@ export const useProjectStore = defineStore({
}
},
async setProjectInformation(parentProject: ProjectObject | null = null) {
async refreshProjectInformation(
parentProject: ProjectObject | null = null
) {
await Promise.all([
this.retrieveAllProjects(),
this.retrieveTopLevelProjects(),
......@@ -695,7 +697,7 @@ export const useProjectStore = defineStore({
if (project && project.slug && project.id) {
await ProjectRoleApi.projectRoleDelete2(project.id);
this.visitedProjects[project.slug].roles = null;
this.setProjectInformation(this.currentProject);
this.refreshProjectInformation(this.currentProject);
this.router.push({ name: "list-projects" });
} else {
console.error("Selected project is null or its ID is undefined.");
......
......@@ -187,7 +187,7 @@ export default defineComponent({
if (createdResource) {
// On Success
// Refresh the project information in the store
await this.projectStore.setProjectInformation(this.project);
await this.projectStore.refreshProjectInformation(this.project);
// Navigate inside the parent project
navigateToProject(this.project);
} else {
......
......@@ -248,7 +248,7 @@ export default defineComponent({
if (success) {
// On Success
// Refresh the project information in the store
await this.projectStore.setProjectInformation(this.project);
await this.projectStore.refreshProjectInformation(this.project);
if (this.resource && this.resource.id) {
await this.resourceStore.retrieveResource(this.resource.id);
}
......@@ -277,7 +277,7 @@ export default defineComponent({
if (success) {
// On Success
// Refresh the project information in the store
await this.projectStore.setProjectInformation(this.project);
await this.projectStore.refreshProjectInformation(this.project);
if (this.resource && this.resource.id) {
await this.resourceStore.retrieveResource(this.resource.id);
}
......@@ -311,7 +311,7 @@ export default defineComponent({
// On Success
const parentProject = this.project;
// Refresh the project information in the store
await this.projectStore.setProjectInformation(parentProject);
await this.projectStore.refreshProjectInformation(parentProject);
this.notificationStore.postNotification({
title: this.$t("toast.onDelete.success.title").toString(),
body: this.$t("toast.onDelete.success.message").toString(),
......
......@@ -16,6 +16,9 @@ import type { UserState } from "./types";
import useMainStore from "@/store/index";
import useNotificationStore from "@/store/notification";
import type { AxiosError } from "axios";
import { Route, VueRouter } from "vue-router/types/router";
import i18n from "@/plugins/vue-i18n";
import { removeQueryParameterFromUrl } from "@/router";
/*
Store variable name is "this.<id>Store"
......@@ -88,6 +91,36 @@ export const useUserStore = defineStore({
}
},
async resolveContactChangeToken(router: VueRouter, route: Route) {
// .../?emailtoken=<token>
const notificationStore = useNotificationStore();
const contactChangeTokenName = "emailtoken";
try {
const contactChangeToken = route.query[contactChangeTokenName];
if (contactChangeToken) {
await ContactChangeApi.contactChangeConfirmContactEmail(
contactChangeToken.toString()
);
notificationStore.postNotification({
title: i18n.t("toast.contactChange.success.title").toString(),
body: i18n.t("toast.contactChange.success.message").toString(),
});
// Update user information inside the store
this.retrieveUser();
// Remove token from the URL
removeQueryParameterFromUrl(route, contactChangeTokenName);
}
} catch (error) {
// Handle other Status Codes
notificationStore.postNotification({
title: i18n.t("toast.contactChange.failure.title").toString(),
body: i18n.t("toast.contactChange.failure.message").toString(),
variant: "warning",
});
notificationStore.postApiErrorNotification(error as AxiosError);
}
},
async retrieveDisciplines() {
const notificationStore = useNotificationStore();
try {
......
......@@ -31,7 +31,7 @@ const router = new VueRouter({
export default router;
// additional method to silence "NavigationDuplicated" errors when source and target routes are equal
// Additional method to silence "NavigationDuplicated" errors when source and target routes are equal
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = async function (location: RawLocation) {
let route: Route;
......@@ -51,8 +51,9 @@ VueRouter.prototype.push = async function (location: RawLocation) {
return route!;
};
// import the main store
// Import the relevant stores
import useMainStore from "@/store/index";
import useUserStore from "@/modules/user/store";
import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project";
import i18n, { def } from "@/plugins/vue-i18n";
import type VueI18n from "vue-i18n";
......@@ -69,21 +70,26 @@ router.beforeEach((to, _, next) => {
i18n.mergeLocaleMessage(locale, localeMessages[locale]); // append the locale messages for the component
});
}
// Define the relevant stores
const mainStore = useMainStore();
const userStore = useUserStore();
// Handle access token from URL
mainStore.setAccessTokenFromRoute(router, to);
// Handle contact change token from URL
userStore.resolveContactChangeToken(router, to);
// Collect ongoing Maintenance information
mainStore.getMaintenance();
if (to.meta?.requiresAuth && !mainStore.loggedIn) {
// Route requires auth, check if logged in
// if not, redirect to login page.
next({
path: "/login",
// save the location we were at to come back later
// Save the location we were at to come back later
query: { redirect: to.fullPath },
});
} else {
// continue navigation
// Continue navigation
next();
}
});
......@@ -92,7 +98,7 @@ export const removeQueryParameterFromUrl = function (
route: Route,
param: string
) {
// remove /?<param>=... from the URL
// Remove /?<param>=... from the URL
const newQuery = route.query;
if (newQuery[param] !== null && newQuery[param] !== undefined) {
delete newQuery[param];
......
......@@ -84,21 +84,18 @@ export const useMainStore = defineStore({
actions: {
setAccessTokenFromRoute(router: VueRouter, route: Route) {
// .../?accesstoken=<token>
if (
route.query.accesstoken !== null &&
route.query.accesstoken !== undefined
) {
const acccessTokenName = "accesstoken";
if (route.query[acccessTokenName]) {
this.$patch((state) => {
state.coscine.authorization.bearer =
route.query.accesstoken.toString();
// add further fields if necessary
route.query[acccessTokenName].toString();
});
if (router.currentRoute.name === "login") {
router.push({ name: "list-projects" });
}
// remove accesstoken from the URL
removeQueryParameterFromUrl(route, "accesstoken");
// Remove accesstoken from the URL
removeQueryParameterFromUrl(route, acccessTokenName);
}
},
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment