From b28235b5d149ba12c28164febb6edc3238ed7bfa Mon Sep 17 00:00:00 2001 From: Thibaud Collyn Date: Sun, 19 May 2024 16:55:25 +0200 Subject: [PATCH 01/20] Added multiple skeletons for loading components throughout the pages + added loading and redirects to 403 page to a few pages + bugfixed the cancel button redirectin the add project page. --- .../[locale]/components/AddProjectButton.tsx | 47 ++++++----- .../app/[locale]/components/CourseBanner.tsx | 2 +- .../[locale]/components/CourseControls.tsx | 83 +++++++++++++------ .../project_components/finishbuttons.tsx | 10 ++- .../course/[course_id]/add_project/page.tsx | 40 ++++++++- .../[locale]/course/[course_id]/edit/page.tsx | 68 +++++++++++---- .../app/[locale]/course/[course_id]/page.tsx | 8 +- frontend/app/[locale]/course/add/page.tsx | 54 +++++++++--- .../[project_id]/edit/projectEditForm.tsx | 1 + 9 files changed, 228 insertions(+), 85 deletions(-) diff --git a/frontend/app/[locale]/components/AddProjectButton.tsx b/frontend/app/[locale]/components/AddProjectButton.tsx index fe0fc1d7..0b17a986 100644 --- a/frontend/app/[locale]/components/AddProjectButton.tsx +++ b/frontend/app/[locale]/components/AddProjectButton.tsx @@ -1,7 +1,7 @@ "use client"; import {useTranslation} from "react-i18next"; -import {Button, Typography} from "@mui/material"; -import {addProject, getUserData, UserData} from "@lib/api"; +import {Button, Typography, Skeleton} from "@mui/material"; +import {getUserData, UserData} from "@lib/api"; import {useState, useEffect} from "react"; interface EditCourseButtonProps{ @@ -11,6 +11,7 @@ interface EditCourseButtonProps{ const AddProjectButton = ({course_id}: EditCourseButtonProps) => { const {t} = useTranslation(); const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); useEffect(() => { @@ -22,35 +23,43 @@ const AddProjectButton = ({course_id}: EditCourseButtonProps) => { } } + setLoading(false); fetchUser(); }, []) return ( + loading ? + : <> {user?.role !== 3 && ( - + )} ) diff --git a/frontend/app/[locale]/components/CourseBanner.tsx b/frontend/app/[locale]/components/CourseBanner.tsx index 89541dda..192f9b5a 100644 --- a/frontend/app/[locale]/components/CourseBanner.tsx +++ b/frontend/app/[locale]/components/CourseBanner.tsx @@ -34,7 +34,7 @@ const CourseBanner = ({course_id}: CourseBannerProps) => { loading ? ( { @@ -16,6 +15,7 @@ const CourseControls = ({selectedYear, onYearChange}) => { const academicYear = `${currentYear - 1}-${currentYear.toString().slice(-2)}`; const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const {t} = useTranslation() @@ -28,12 +28,10 @@ const CourseControls = ({selectedYear, onYearChange}) => { if (error instanceof APIError) setError(error); console.error(error); } - - }; - fetchUser(); + setLoading(false); }, []); @@ -46,6 +44,28 @@ const CourseControls = ({selectedYear, onYearChange}) => { ]; return ( + loading ? + + + + {t("courses")} + + + {[1, 2, 3, 4, 5, 6, 7].map((i) => ( + + ))} + + + + : @@ -56,28 +76,39 @@ const CourseControls = ({selectedYear, onYearChange}) => { {t("filter_courses")} {user?.role !== 3 ? ( - - - - ) : null - } - - - - - - - - - + ) : null} + + + - + ); diff --git a/frontend/app/[locale]/components/SubmitDetailsPage.tsx b/frontend/app/[locale]/components/SubmitDetailsPage.tsx index fc3fdb1b..17a0937e 100644 --- a/frontend/app/[locale]/components/SubmitDetailsPage.tsx +++ b/frontend/app/[locale]/components/SubmitDetailsPage.tsx @@ -15,9 +15,9 @@ import { Typography, } from '@mui/material'; import { - getProject, + getProject, getUserData, Project, - uploadSubmissionFile, + uploadSubmissionFile, UserData, } from '@lib/api'; import baseTheme from '@styles/theme'; import ProjectReturnButton from '@app/[locale]/components/ProjectReturnButton'; @@ -27,6 +27,7 @@ import PublishIcon from '@mui/icons-material/Publish'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import ErrorIcon from '@mui/icons-material/Error'; import Tree from '@app/[locale]/components/Tree'; +import initTranslations from "@app/i18n"; interface SubmitDetailsPageProps { locale: any; @@ -46,6 +47,8 @@ const SubmitDetailsPage: React.FC = ({ const [submitted, setSubmitted] = useState('no'); const [loadingProject, setLoadingProject] = useState(true); const [isExpanded, setIsExpanded] = useState(false); + const [user, setUser] = useState(null); + const [userLoading, setUserLoading] = useState(true); const previewLength = 300; const toggleDescription = () => { @@ -68,6 +71,29 @@ const SubmitDetailsPage: React.FC = ({ fetchProject().then(() => setLoadingProject(false)); }, [project_id]); + useEffect(() => { + const fetchUser = async () => { + try { + setUser(await getUserData()); + } catch (error) { + console.error("There was an error fetching the user data:", error); + } + } + + fetchUser(); + setUserLoading(false); + }, [locale]) + + useEffect(() => { + if (!userLoading && !loadingProject && user) { + if (!user.course.includes(Number(projectData?.course_id))) { + window.location.href = `/403/`; + } else { + console.log("User is in course"); + } + } + }, [userLoading, user, loadingProject, projectData]); + function folderAdded(event: any) { let newpaths: string[] = []; let result: string[] = []; diff --git a/frontend/app/[locale]/course/[course_id]/edit/page.tsx b/frontend/app/[locale]/course/[course_id]/edit/page.tsx index 3aaee082..63b850d2 100644 --- a/frontend/app/[locale]/course/[course_id]/edit/page.tsx +++ b/frontend/app/[locale]/course/[course_id]/edit/page.tsx @@ -27,14 +27,16 @@ function CourseEditPage({params: {locale, course_id}}: { params: { locale: any, } } - fetchUser().then(() => setUserLoading(false)); - if (user?.course.includes(Number(course_id))) { - console.log("User is in course"); - } else { - window.location.href = `/403/`; - } }, [course_id, locale, user?.course]) + useEffect(() => { + if (!userLoading && user) { + if (!user.course.includes(Number(course_id))) { + window.location.href = `/403/`; + } + } + }, [user, course_id, userLoading]); + return ( (); + const [translations, setTranslations] = useState({ t: (s) => '', resources: {} }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchTranslations = async () => { + setLoading(true); + try { + const { t, resources } = await initTranslations(locale, i18nNamespaces); + setTranslations({ t, resources }); + } catch (error) { + console.error('Failed to initialize translations:', error); + } finally { + setLoading(false); + } + }; + + fetchTranslations(); + }, [locale, i18nNamespaces]); + + useEffect(() => { + const fetchUser = async () => { + try { + const user = await getUserData(); + setUser(user); + + } catch (error) { + console.error("There was an error fetching the user data:", error); + } + } + + fetchUser().then(() => { + setLoading(false) + }); + + }, [course_id, user?.course]); + + useEffect(() => { + if (!loading && user) { + if (!user.course.includes(Number(course_id))) { + window.location.href = `/403/`; + } else { + console.log("User is in course"); + } + } + }, [loading, user, course_id]); const headers = [ - {" " + t('email')}]; + {" " + translations.t('email')}]; const headers_backend = ['email']; return ( @@ -31,7 +77,7 @@ export default async function StudentsPage({ params }: { params: { locale: any, startIcon={} href={`/course/${course_id}`} > - {t('back_to') + ' ' + t('course') + ' ' + t('page')} + {translations.t('back_to') + ' ' + translations.t('course') + ' ' + translations.t('page')} diff --git a/frontend/app/[locale]/course/[course_id]/teachers/page.tsx b/frontend/app/[locale]/course/[course_id]/teachers/page.tsx index 7bf12e3f..e59ac589 100644 --- a/frontend/app/[locale]/course/[course_id]/teachers/page.tsx +++ b/frontend/app/[locale]/course/[course_id]/teachers/page.tsx @@ -1,24 +1,70 @@ +"use client" import initTranslations from "@app/i18n"; import TranslationsProvider from "@app/[locale]/components/TranslationsProvider"; import NavBar from "@app/[locale]/components/NavBar"; import ListView from '@app/[locale]/components/ListView'; import {Box, Button} from "@mui/material"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import React from "react"; +import React, {useEffect, useState} from "react"; import EmailIcon from '@mui/icons-material/Email'; +import {getUserData} from "@lib/api"; const i18nNamespaces = ['common'] -export default async function TeachersPage({params}: { params: { locale: any, course_id: number } }) { - const {locale, course_id} = params; - const {t, resources} = await initTranslations(locale, i18nNamespaces); +export default function TeachersPage({params: {locale, course_id}}: { params: { locale: any, course_id: number } }) { + const [user, setUser] = useState(); + const [translations, setTranslations] = useState({ t: (s) => '', resources: {} }); + const [loading, setLoading] = useState(true); - const headers = [{" " + t('email')}]; + useEffect(() => { + const fetchTranslations = async () => { + setLoading(true); + try { + const { t, resources } = await initTranslations(locale, i18nNamespaces); + setTranslations({ t, resources }); + } catch (error) { + console.error('Failed to initialize translations:', error); + } finally { + setLoading(false); + } + }; + + fetchTranslations(); + }, [locale, i18nNamespaces]); + + useEffect(() => { + const fetchUser = async () => { + try { + const user = await getUserData(); + setUser(user); + + } catch (error) { + console.error("There was an error fetching the user data:", error); + } + } + + fetchUser().then(() => { + setLoading(false) + }); + + }, [course_id, user?.course]); + + useEffect(() => { + if (!loading && user) { + if (!user.course.includes(Number(course_id))) { + window.location.href = `/403/`; + } else { + console.log("User is in course"); + } + } + }, [loading, user, course_id]); + + const headers = [{" " + translations.t('email')}]; const headers_backend = ['email']; return ( @@ -30,7 +76,7 @@ export default async function TeachersPage({params}: { params: { locale: any, co startIcon={} href={`/course/${course_id}`} > - {t('back_to') + ' ' + t('course') + ' ' + t('page')} + {translations.t('back_to') + ' ' + translations.t('course') + ' ' + translations.t('page')} diff --git a/frontend/app/[locale]/course/archived/page.tsx b/frontend/app/[locale]/course/archived/page.tsx index 64db6af1..15b02882 100644 --- a/frontend/app/[locale]/course/archived/page.tsx +++ b/frontend/app/[locale]/course/archived/page.tsx @@ -22,7 +22,12 @@ const ArchivePage = async ({params: {locale}}) => { namespaces={i18nNamespaces} > -
+
- - - + ) From bf8e4b742014a1bbe216f910d33d6bc2567ed8a4 Mon Sep 17 00:00:00 2001 From: Thibaud Collyn Date: Mon, 20 May 2024 17:03:38 +0200 Subject: [PATCH 07/20] Admin button hotfix --- frontend/app/[locale]/components/CourseControls.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/[locale]/components/CourseControls.tsx b/frontend/app/[locale]/components/CourseControls.tsx index 76ca4328..56db4a03 100644 --- a/frontend/app/[locale]/components/CourseControls.tsx +++ b/frontend/app/[locale]/components/CourseControls.tsx @@ -130,6 +130,7 @@ const CourseControls = ({selectedYear, onYearChange}) => { > {t("view_archive")} + {user?.role !== 3 ? ( + ) : null}