Merge pull request #152 from SELab-2/fix/typeerror-bij-het-reloaden-van-een-pagina-met-menubalk-#150
fix: `TypeError` bij het reloaden van een pagina met menubalk (#150) + kleine fixes
This commit is contained in:
		
						commit
						c6aea229f0
					
				
					 10 changed files with 177 additions and 261 deletions
				
			
		|  | @ -60,7 +60,7 @@ export async function createStudentHandler(req: Request, res: Response) { | |||
| 
 | ||||
|     if (!newUser) { | ||||
|         res.status(500).json({ | ||||
|             error: 'Something went wrong while creating student' | ||||
|             error: 'Something went wrong while creating student', | ||||
|         }); | ||||
|         return; | ||||
|     } | ||||
|  |  | |||
|  | @ -92,4 +92,3 @@ export async function getLearningObjectsFromPath(hruid: string, language: string | |||
| export async function getLearningObjectIdsFromPath(hruid: string, language: string): Promise<string[]> { | ||||
|     return (await fetchLearningObjects(hruid, false, language)) as string[]; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,26 @@ | |||
| <script setup lang="ts"> | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
|     import MenuBar from "@/components/MenuBar.vue"; | ||||
|     import { useRoute } from "vue-router"; | ||||
|     import { computed } from "vue"; | ||||
| 
 | ||||
|     const route = useRoute(); | ||||
|     auth.loadUser(); | ||||
| 
 | ||||
|     interface RouteMeta { | ||||
|         requiresAuth?: boolean; | ||||
|     } | ||||
| 
 | ||||
|     const showMenuBar = computed(() => (route.meta as RouteMeta).requiresAuth && auth.authState.user); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <router-view /> | ||||
|     <v-app> | ||||
|         <menu-bar v-if="showMenuBar"></menu-bar> | ||||
|         <v-main> | ||||
|             <router-view /> | ||||
|         </v-main> | ||||
|     </v-app> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
|  |  | |||
|  | @ -31,79 +31,95 @@ | |||
|         localStorage.setItem("user-lang", langCode); | ||||
|     }; | ||||
| 
 | ||||
|     // contains functionality to let the collapsed menu appear and disappear | ||||
|     // when the screen size varies | ||||
|     // Contains functionality to let the collapsed menu appear and disappear | ||||
|     // When the screen size varies | ||||
|     const drawer = ref(false); | ||||
| 
 | ||||
|     // when the user wants to logout, a popup is shown to verify this | ||||
|     // if verified, the user should be logged out | ||||
|     // When the user wants to logout, a popup is shown to verify this | ||||
|     // If verified, the user should be logged out | ||||
|     const performLogout = () => { | ||||
|         auth.logout(); | ||||
|     }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <main> | ||||
|         <v-app class="menu_collapsed"> | ||||
|             <v-app-bar | ||||
|                 app | ||||
|                 style="background-color: #f6faf2" | ||||
|     <v-app-bar | ||||
|         class="app-bar" | ||||
|         app | ||||
|     > | ||||
|         <v-app-bar-nav-icon | ||||
|             class="menu_collapsed" | ||||
|             @click="drawer = !drawer" | ||||
|         /> | ||||
|         <router-link | ||||
|             to="/user" | ||||
|             class="dwengo_home" | ||||
|         > | ||||
|             <div> | ||||
|                 <img | ||||
|                     class="dwengo_logo" | ||||
|                     alt="Dwengo logo" | ||||
|                     :src="dwengoLogo" | ||||
|                 /> | ||||
|                 <p class="caption"> | ||||
|                     {{ t(`${role}`) }} | ||||
|                 </p> | ||||
|             </div> | ||||
|         </router-link> | ||||
|         <v-toolbar-items class="menu"> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/assignment" | ||||
|             > | ||||
|                 <template v-slot:prepend> | ||||
|                     <v-app-bar-nav-icon @click="drawer = !drawer" /> | ||||
|                 </template> | ||||
| 
 | ||||
|                 <v-app-bar-title> | ||||
|                     <router-link | ||||
|                         to="/user" | ||||
|                         class="dwengo_home" | ||||
|                 {{ t("assignments") }} | ||||
|             </v-btn> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/class" | ||||
|             > | ||||
|                 {{ t("classes") }} | ||||
|             </v-btn> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/discussion" | ||||
|             > | ||||
|                 {{ t("discussions") }} | ||||
|             </v-btn> | ||||
|             <v-menu open-on-hover> | ||||
|                 <template v-slot:activator="{ props }"> | ||||
|                     <v-btn | ||||
|                         v-bind="props" | ||||
|                         icon | ||||
|                         variant="text" | ||||
|                     > | ||||
|                         <div> | ||||
|                             <img | ||||
|                                 class="dwengo_logo" | ||||
|                                 :src="dwengoLogo" | ||||
|                                 style="width: 100px" | ||||
|                             /> | ||||
|                             <p | ||||
|                                 class="caption" | ||||
|                                 style="font-size: smaller" | ||||
|                             > | ||||
|                                 {{ t(`${role}`) }} | ||||
|                             </p> | ||||
|                         </div> | ||||
|                     </router-link> | ||||
|                 </v-app-bar-title> | ||||
| 
 | ||||
|                 <v-spacer></v-spacer> | ||||
| 
 | ||||
|                 <v-menu open-on-hover> | ||||
|                     <template v-slot:activator="{ props }"> | ||||
|                         <v-btn | ||||
|                             v-bind="props" | ||||
|                             icon | ||||
|                             variant="text" | ||||
|                         > | ||||
|                             <v-icon | ||||
|                                 icon="mdi-translate" | ||||
|                                 size="small" | ||||
|                                 color="#0e6942" | ||||
|                             ></v-icon> | ||||
|                         </v-btn> | ||||
|                     </template> | ||||
|                     <v-list> | ||||
|                         <v-list-item | ||||
|                             v-for="(language, index) in languages" | ||||
|                             :key="index" | ||||
|                             @click="changeLanguage(language.code)" | ||||
|                         > | ||||
|                             <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                         </v-list-item> | ||||
|                     </v-list> | ||||
|                 </v-menu> | ||||
| 
 | ||||
|                         <v-icon | ||||
|                             icon="mdi-translate" | ||||
|                             size="small" | ||||
|                             color="#0e6942" | ||||
|                         ></v-icon> | ||||
|                     </v-btn> | ||||
|                 </template> | ||||
|                 <v-list> | ||||
|                     <v-list-item | ||||
|                         v-for="(language, index) in languages" | ||||
|                         :key="index" | ||||
|                         @click="changeLanguage(language.code)" | ||||
|                     > | ||||
|                         <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                     </v-list-item> | ||||
|                 </v-list> | ||||
|             </v-menu> | ||||
|         </v-toolbar-items> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-dialog max-width="500"> | ||||
|             <template v-slot:activator="{ props: activatorProps }"> | ||||
|                 <v-btn | ||||
|                     @click="performLogout" | ||||
|                     text | ||||
|                     v-bind="activatorProps" | ||||
|                     :rounded="true" | ||||
|                     variant="text" | ||||
|                 > | ||||
|                     <v-tooltip | ||||
|                         :text="t('logout')" | ||||
|  | @ -115,201 +131,81 @@ | |||
|                                 icon="mdi-logout" | ||||
|                                 size="x-large" | ||||
|                                 color="#0e6942" | ||||
|                             /> | ||||
|                             > | ||||
|                             </v-icon> | ||||
|                         </template> | ||||
|                     </v-tooltip> | ||||
|                 </v-btn> | ||||
|             </v-app-bar> | ||||
|             </template> | ||||
| 
 | ||||
|             <v-navigation-drawer | ||||
|                 v-model="drawer" | ||||
|                 app | ||||
|             > | ||||
|                 <v-list> | ||||
|                     <v-list-item | ||||
|                         to="/user/assignment" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("assignments") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
|             <template v-slot:default="{ isActive }"> | ||||
|                 <v-card :title="t('logoutVerification')"> | ||||
|                     <v-card-actions> | ||||
|                         <v-spacer></v-spacer> | ||||
| 
 | ||||
|                     <v-list-item | ||||
|                         to="/user/class" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("classes") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
| 
 | ||||
|                     <v-list-item | ||||
|                         to="/user/discussion" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("discussions") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
|                 </v-list> | ||||
|             </v-navigation-drawer> | ||||
|         </v-app> | ||||
| 
 | ||||
|         <nav class="menu"> | ||||
|             <div class="left"> | ||||
|                 <ul> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user" | ||||
|                             class="dwengo_home" | ||||
|                         > | ||||
|                             <img | ||||
|                                 class="dwengo_logo" | ||||
|                                 :src="dwengoLogo" | ||||
|                             /> | ||||
|                             <p class="caption"> | ||||
|                                 {{ t(`${role}`) }} | ||||
|                             </p> | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             :to="`/user/assignment`" | ||||
|                             class="menu_item" | ||||
|                         > | ||||
|                             {{ t("assignments") }} | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user/class" | ||||
|                             class="menu_item" | ||||
|                             >{{ t("classes") }}</router-link | ||||
|                         > | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user/discussion" | ||||
|                             class="menu_item" | ||||
|                             >{{ t("discussions") }} | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <v-menu open-on-hover> | ||||
|                             <template v-slot:activator="{ props }"> | ||||
|                                 <v-btn | ||||
|                                     v-bind="props" | ||||
|                                     icon | ||||
|                                     variant="text" | ||||
|                                 > | ||||
|                                     <v-icon | ||||
|                                         icon="mdi-translate" | ||||
|                                         size="small" | ||||
|                                         color="#0e6942" | ||||
|                                     ></v-icon> | ||||
|                                 </v-btn> | ||||
|                             </template> | ||||
|                             <v-list> | ||||
|                                 <v-list-item | ||||
|                                     v-for="(language, index) in languages" | ||||
|                                     :key="index" | ||||
|                                     @click="changeLanguage(language.code)" | ||||
|                                 > | ||||
|                                     <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                                 </v-list-item> | ||||
|                             </v-list> | ||||
|                         </v-menu> | ||||
|                     </li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|             <div class="right"> | ||||
|                 <li> | ||||
|                     <!-- <v-btn | ||||
|                         @click="performLogout" | ||||
|                         to="/login" | ||||
|                         style="background-color: transparent; box-shadow: none !important" | ||||
|                     > | ||||
|                         <v-tooltip | ||||
|                         <v-btn | ||||
|                             :text="t('cancel')" | ||||
|                             @click="isActive.value = false" | ||||
|                         ></v-btn> | ||||
|                         <v-btn | ||||
|                             :text="t('logout')" | ||||
|                             location="bottom" | ||||
|                         > | ||||
|                             <template v-slot:activator="{ props }"> | ||||
|                                 <v-icon | ||||
|                                     v-bind="props" | ||||
|                                     icon="mdi-logout" | ||||
|                                     size="x-large" | ||||
|                                     color="#0e6942" | ||||
|                                 ></v-icon> | ||||
|                             </template> | ||||
|                         </v-tooltip> | ||||
|                     </v-btn> --> | ||||
|                     <v-dialog max-width="500"> | ||||
|                         <template v-slot:activator="{ props: activatorProps }"> | ||||
|                             <v-btn | ||||
|                                 v-bind="activatorProps" | ||||
|                                 style="background-color: transparent; box-shadow: none !important" | ||||
|                             > | ||||
|                                 <v-tooltip | ||||
|                                     :text="t('logout')" | ||||
|                                     location="bottom" | ||||
|                                 > | ||||
|                                     <template v-slot:activator="{ props }"> | ||||
|                                         <v-icon | ||||
|                                             v-bind="props" | ||||
|                                             icon="mdi-logout" | ||||
|                                             size="x-large" | ||||
|                                             color="#0e6942" | ||||
|                                         > | ||||
|                                         </v-icon> | ||||
|                                     </template> | ||||
|                                 </v-tooltip> | ||||
|                             </v-btn> | ||||
|                         </template> | ||||
|                             @click="performLogout" | ||||
|                             to="/login" | ||||
|                         ></v-btn> | ||||
|                     </v-card-actions> | ||||
|                 </v-card> | ||||
|             </template> | ||||
|         </v-dialog> | ||||
|         <v-avatar | ||||
|             size="large" | ||||
|             color="#0e6942" | ||||
|             class="user-button" | ||||
|             >{{ initials }}</v-avatar | ||||
|         > | ||||
|     </v-app-bar> | ||||
|     <v-navigation-drawer | ||||
|         v-model="drawer" | ||||
|         temporary | ||||
|         app | ||||
|     > | ||||
|         <v-list> | ||||
|             <v-list-item | ||||
|                 to="/user/assignment" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("assignments") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
| 
 | ||||
|                         <template v-slot:default="{ isActive }"> | ||||
|                             <v-card :title="t('logoutVerification')"> | ||||
|                                 <v-card-actions> | ||||
|                                     <v-spacer></v-spacer> | ||||
|             <v-list-item | ||||
|                 to="/user/class" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("classes") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
| 
 | ||||
|                                     <v-btn | ||||
|                                         :text="t('cancel')" | ||||
|                                         @click="isActive.value = false" | ||||
|                                     ></v-btn> | ||||
|                                     <v-btn | ||||
|                                         :text="t('logout')" | ||||
|                                         @click="performLogout" | ||||
|                                         to="/login" | ||||
|                                     ></v-btn> | ||||
|                                 </v-card-actions> | ||||
|                             </v-card> | ||||
|                         </template> | ||||
|                     </v-dialog> | ||||
|                 </li> | ||||
|                 <li> | ||||
|                     <v-avatar | ||||
|                         size="large" | ||||
|                         color="#0e6942" | ||||
|                         style="font-size: large; font-weight: bold" | ||||
|                         >{{ initials }}</v-avatar | ||||
|                     > | ||||
|                 </li> | ||||
|             </div> | ||||
|         </nav> | ||||
|         <router-view /> | ||||
|     </main> | ||||
|             <v-list-item | ||||
|                 to="/user/discussion" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("discussions") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
|         </v-list> | ||||
|     </v-navigation-drawer> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
|     .app-bar { | ||||
|         background-color: #f6faf2; | ||||
|     } | ||||
|     .menu { | ||||
|         background-color: #f6faf2; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|     } | ||||
| 
 | ||||
|     .right { | ||||
|         align-items: center; | ||||
|         padding: 10px; | ||||
|     .user-button { | ||||
|         margin-right: 10px; | ||||
|         font-size: large; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
|     .right li { | ||||
|  | @ -347,16 +243,19 @@ | |||
|         color: #0e6942; | ||||
|         text-decoration: none; | ||||
|         font-size: large; | ||||
|     } | ||||
| 
 | ||||
|     nav a.router-link-active { | ||||
|         font-weight: bold; | ||||
|         text-transform: none; | ||||
|     } | ||||
| 
 | ||||
|     @media (max-width: 700px) { | ||||
|         .menu { | ||||
|             display: none; | ||||
|         } | ||||
|         .caption { | ||||
|             font-size: smaller; | ||||
|         } | ||||
|         .dwengo_logo { | ||||
|             width: 100px; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @media (min-width: 701px) { | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { createRouter, createWebHistory } from "vue-router"; | ||||
| import MenuBar from "@/components/MenuBar.vue"; | ||||
| import SingleAssignment from "@/views/assignments/SingleAssignment.vue"; | ||||
| import SingleClass from "@/views/classes/SingleClass.vue"; | ||||
| import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue"; | ||||
|  | @ -38,7 +37,6 @@ const router = createRouter({ | |||
| 
 | ||||
|         { | ||||
|             path: "/user", | ||||
|             component: MenuBar, | ||||
|             meta: { requiresAuth: true }, | ||||
|             children: [ | ||||
|                 { | ||||
|  |  | |||
|  | @ -1,11 +1,14 @@ | |||
| import apiClient from "@/services/api-client.ts"; | ||||
| import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts"; | ||||
| 
 | ||||
| export const AUTH_CONFIG_ENDPOINT = "auth/config"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetch the authentication configuration from the backend. | ||||
|  */ | ||||
| export async function loadAuthConfig() { | ||||
|     const authConfig = (await apiClient.get<FrontendAuthConfig>("auth/config")).data; | ||||
|     const authConfigResponse = await apiClient.get<FrontendAuthConfig>(AUTH_CONFIG_ENDPOINT); | ||||
|     const authConfig = authConfigResponse.data; | ||||
|     return { | ||||
|         student: { | ||||
|             authority: authConfig.student.authority, | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| import { computed, reactive } from "vue"; | ||||
| import type { AuthState, Role, UserManagersForRoles } from "@/services/auth/auth.d.ts"; | ||||
| import { User, UserManager } from "oidc-client-ts"; | ||||
| import { loadAuthConfig } from "@/services/auth/auth-config-loader.ts"; | ||||
| import { AUTH_CONFIG_ENDPOINT, loadAuthConfig } from "@/services/auth/auth-config-loader.ts"; | ||||
| import authStorage from "./auth-storage.ts"; | ||||
| import { loginRoute } from "@/config.ts"; | ||||
| import apiClient from "@/services/api-client.ts"; | ||||
|  | @ -108,7 +108,7 @@ async function logout(): Promise<void> { | |||
| apiClient.interceptors.request.use( | ||||
|     async (reqConfig) => { | ||||
|         const token = authState?.user?.access_token; | ||||
|         if (token) { | ||||
|         if (token && reqConfig.url !== AUTH_CONFIG_ENDPOINT) { | ||||
|             reqConfig.headers.Authorization = `Bearer ${token}`; | ||||
|         } | ||||
|         return reqConfig; | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|     onMounted(async () => { | ||||
|         try { | ||||
|             await auth.handleLoginCallback(); | ||||
|             await router.replace("/"); // Redirect to home (or dashboard) | ||||
|             await router.replace("/user"); // Redirect to theme page | ||||
|         } catch (error) { | ||||
|             console.error("OIDC callback error:", error); | ||||
|         } | ||||
|  |  | |||
|  | @ -25,9 +25,10 @@ | |||
|             <div class="container_left"> | ||||
|                 <img | ||||
|                     :src="dwengoLogo" | ||||
|                     alt="Dwengo logo" | ||||
|                     style="align-self: center" | ||||
|                 /> | ||||
|                 <h> {{ t("homeTitle") }}</h> | ||||
|                 <h1>{{ t("homeTitle") }}</h1> | ||||
|                 <p class="info"> | ||||
|                     {{ t("homeIntroduction1") }} | ||||
|                 </p> | ||||
|  | @ -55,7 +56,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/innovative.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("innovative") }}</h> | ||||
|                     <h2 class="big">{{ t("innovative") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -63,7 +64,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/research_based.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("researchBased") }}</h> | ||||
|                     <h2 class="big">{{ t("researchBased") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -71,7 +72,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/inclusive.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("sociallyRelevant") }}</h> | ||||
|                     <h2 class="big">{{ t("sociallyRelevant") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -79,7 +80,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/socially_relevant.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("inclusive") }}</h> | ||||
|                     <h2 class="big">{{ t("inclusive") }}</h2> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="container_right"> | ||||
|  | @ -158,7 +159,7 @@ | |||
|         margin-bottom: 10px; | ||||
|     } | ||||
| 
 | ||||
|     h { | ||||
|     h2 { | ||||
|         font-size: large; | ||||
|         font-weight: bold; | ||||
|         align-self: center; | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger