From aa7383f975db1f90a3d3582566e0a3654db1a105 Mon Sep 17 00:00:00 2001
From: Aron Buzogany <108480125+AronBuzogany@users.noreply.github.com>
Date: Thu, 23 May 2024 20:07:08 +0200
Subject: [PATCH] fixed authorizations in projectview (#418)
* fixed authorizations in projectview
* linting
* added locales
---
frontend/public/locales/en/translation.json | 3 +
frontend/public/locales/nl/translation.json | 3 +
frontend/src/App.tsx | 31 +-
frontend/src/loaders/project-view-loader.ts | 84 +++++
.../pages/project/projectView/ProjectView.tsx | 311 +++++++++---------
5 files changed, 263 insertions(+), 169 deletions(-)
create mode 100644 frontend/src/loaders/project-view-loader.ts
diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json
index e6b5c6e5..317f62c8 100644
--- a/frontend/public/locales/en/translation.json
+++ b/frontend/public/locales/en/translation.json
@@ -65,6 +65,9 @@
"submit": "Submit",
"previousSubmissions": "Previous Submissions",
"noFileSelected": "No file selected",
+ "delete": "Delete",
+ "deleteProjectWarning": "Are you sure you want to delete this project?",
+ "imSure": "Yes, I'm sure",
"submissionGrid": {
"late": "Late",
"fail": "Fail",
diff --git a/frontend/public/locales/nl/translation.json b/frontend/public/locales/nl/translation.json
index 7715aecd..c74cb996 100644
--- a/frontend/public/locales/nl/translation.json
+++ b/frontend/public/locales/nl/translation.json
@@ -102,6 +102,9 @@
"submit": "Indienen",
"previousSubmissions": "Vorige indieningen",
"noFileSelected": "Er is geen bestand geselecteerd",
+ "delete": "Verwijder",
+ "deleteProjectWarning": "Bent u zeker dat u dit project wilt verwijderen?",
+ "imSure": "Ja, ik ben zeker",
"submissionGrid": {
"late": "Te laat",
"fail": "Gefaald",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 53c4d5ec..6d2cad70 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -20,9 +20,10 @@ import HomePages from "./pages/home/HomePages.tsx";
import ProjectOverView from "./pages/project/projectOverview.tsx";
import { synchronizeJoinCode } from "./loaders/join-code.ts";
import { fetchMe } from "./utils/fetches/FetchMe.ts";
-import {fetchProjectForm} from "./components/ProjectForm/project-form.ts";
+import { fetchProjectForm } from "./components/ProjectForm/project-form.ts";
import loadSubmissionOverview from "./loaders/submission-overview-loader.ts";
import CoursesDetail from "./components/Courses/CoursesDetail.tsx";
+import loadProjectViewData from "./loaders/project-view-loader.ts";
const router = createBrowserRouter(
createRoutesFromElements(
@@ -36,9 +37,17 @@ const router = createBrowserRouter(
}>
} loader={fetchProjectPage} />
- } loader={dataLoaderCourses}/>
+ }
+ loader={dataLoaderCourses}
+ />
- } loader={dataLoaderCourseDetail} />
+ }
+ loader={dataLoaderCourseDetail}
+ />
}
/>
- }>
- } loader={fetchProjectForm}/>
+ }
+ loader={loadProjectViewData}
+ >
+ }
+ loader={fetchProjectForm}
+ />
- ,
- ),
+
+ )
);
/**
diff --git a/frontend/src/loaders/project-view-loader.ts b/frontend/src/loaders/project-view-loader.ts
new file mode 100644
index 00000000..0a9cf6f8
--- /dev/null
+++ b/frontend/src/loaders/project-view-loader.ts
@@ -0,0 +1,84 @@
+import { Params } from "react-router-dom";
+import { Deadline } from "../types/deadline";
+import { authenticatedFetch } from "../utils/authenticated-fetch";
+import { fetchMe } from "../utils/fetches/FetchMe";
+import i18next from "i18next";
+
+const API_URL = import.meta.env.VITE_APP_API_HOST;
+
+/**
+ *
+ * @param param0 - params: Params
+ * @returns - projectData: projectData,
+ * courseData: courseData,
+ * me: me,
+ * assignmentText: assignmentText,
+ * isAdmin: isAdmin
+ */
+export default async function loadProjectViewData({
+ params,
+}: {
+ params: Params;
+}) {
+ const me = await fetchMe();
+
+ const projectId = params.projectId;
+
+ const assignmentResponse = await authenticatedFetch(
+ `${API_URL}/projects/${projectId}/assignment?lang=${i18next.resolvedLanguage}`
+ );
+
+ let assignmentText;
+
+ if (assignmentResponse.ok) {
+ assignmentText = await assignmentResponse.text();
+ } else {
+ throw new Response(assignmentResponse.statusText, {
+ status: assignmentResponse.status,
+ });
+ }
+
+ const response = await authenticatedFetch(`${API_URL}/projects/${projectId}`);
+ if (response.ok) {
+ const data = await response.json();
+ const projectData = data["data"];
+
+ const transformedDeadlines = projectData.deadlines.map(
+ (deadlineArray: string[]): Deadline => ({
+ description: deadlineArray[0],
+ deadline: deadlineArray[1],
+ })
+ );
+
+ projectData["deadlines"] = transformedDeadlines;
+
+ const courseResponse = await authenticatedFetch(
+ `${API_URL}/courses/${projectData.course_id}`
+ );
+
+ let courseData;
+ if (courseResponse.ok) {
+ courseData = (await courseResponse.json())["data"];
+ } else {
+ throw new Response(response.statusText, { status: response.status });
+ }
+
+ courseData["admins"] = courseData["admins"].map((admin: string) => {
+ const urlSplit = admin.split("/");
+ return urlSplit[urlSplit.length - 1];
+ });
+
+ const isAdmin =
+ me.uid === courseData["teacher"] || courseData["admins"].includes(me.uid);
+
+ return {
+ projectData: projectData,
+ courseData: courseData,
+ me: me,
+ assignmentText: assignmentText,
+ isAdmin: isAdmin,
+ };
+ } else {
+ throw new Response(response.statusText, { status: response.status });
+ }
+}
diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx
index f5f5df09..8d5a2484 100644
--- a/frontend/src/pages/project/projectView/ProjectView.tsx
+++ b/frontend/src/pages/project/projectView/ProjectView.tsx
@@ -6,27 +6,32 @@ import {
CardHeader,
Container,
Fade,
- Grid, IconButton,
+ Grid,
+ IconButton,
Stack,
TextField,
Typography,
} from "@mui/material";
-import {useCallback, useEffect, useState} from "react";
+import { useState } from "react";
import Markdown from "react-markdown";
-import {useLocation, useNavigate, useParams} from "react-router-dom";
+import {
+ useLoaderData,
+ useLocation,
+ useNavigate,
+ useParams,
+} from "react-router-dom";
import SubmissionCard from "./SubmissionCard";
import { Course } from "../../../types/course";
import { Title } from "../../../components/Header/Title";
import { authenticatedFetch } from "../../../utils/authenticated-fetch";
import i18next from "i18next";
-import {useTranslation} from "react-i18next";
-import {Me} from "../../../types/me.ts";
-import {fetchMe} from "../../../utils/fetches/FetchMe.ts";
+import { useTranslation } from "react-i18next";
+import { Me } from "../../../types/me.ts";
import DeadlineGrid from "../../../components/DeadlineView/DeadlineGrid.tsx";
-import {Deadline} from "../../../types/deadline.ts";
-import EditIcon from '@mui/icons-material/Edit';
-import CheckIcon from '@mui/icons-material/Check';
-import CloseIcon from '@mui/icons-material/Close';
+import { Deadline } from "../../../types/deadline.ts";
+import EditIcon from "@mui/icons-material/Edit";
+import CheckIcon from "@mui/icons-material/Check";
+import CloseIcon from "@mui/icons-material/Close";
const API_URL = import.meta.env.VITE_APP_API_HOST;
@@ -35,6 +40,7 @@ interface Project {
description: string;
regex_expressions: string[];
archived: string;
+ deadlines: Deadline[];
}
/**
@@ -43,129 +49,86 @@ interface Project {
* and submissions of the current user for that project
*/
export default function ProjectView() {
-
const location = useLocation();
- const [me, setMe] = useState(null);
- const { t } = useTranslation('translation', { keyPrefix: 'projectView' });
+ const { projectData, courseData, assignmentText, isAdmin } =
+ useLoaderData() as {
+ me: Me;
+ projectData: Project;
+ courseData: Course;
+ assignmentText: string;
+ isAdmin: boolean;
+ };
+
+ const deadlines = projectData["deadlines"];
+
+ const { t } = useTranslation("translation", { keyPrefix: "projectView" });
const { projectId } = useParams<{ projectId: string }>();
- const [projectData, setProjectData] = useState(null);
- const [courseData, setCourseData] = useState(null);
- const [assignmentRawText, setAssignmentRawText] = useState("");
- const [deadlines, setDeadlines] = useState([]);
- const [alertVisibility, setAlertVisibility] = useState(false)
+ const [alertVisibility, setAlertVisibility] = useState(false);
const [edit, setEdit] = useState(false);
- const [title, setTitle] = useState("");
- const [description, setDescription] = useState("");
+ const [title, setTitle] = useState(projectData["title"]);
+ const [description, setDescription] = useState(projectData["description"]);
- const navigate = useNavigate()
+ const navigate = useNavigate();
const deleteProject = () => {
authenticatedFetch(`${API_URL}/projects/${projectId}`, {
- method: "DELETE"
+ method: "DELETE",
});
- navigate('/projects');
- }
+ navigate("/projects");
+ };
const patchTitleAndDescription = async () => {
setEdit(false);
const formData = new FormData();
- formData.append('title', title);
- formData.append('description', description);
+ formData.append("title", title);
+ formData.append("description", description);
- const response = await authenticatedFetch(`${API_URL}/projects/${projectId}`, {
- method: "PATCH",
- body: formData
- });
+ const response = await authenticatedFetch(
+ `${API_URL}/projects/${projectId}`,
+ {
+ method: "PATCH",
+ body: formData,
+ }
+ );
// Check if the response is ok (status code 2xx)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
-
- updateProject();
- }
+ };
const discardEditTitle = () => {
const title = projectData?.title;
setEdit(false);
- if (title)
- setTitle(title);
-
- if (projectData?.description)
- setDescription(projectData?.description);
- }
-
- const updateProject = useCallback(async () => {
- authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => {
- if (response.ok) {
- response.json().then((data) => {
- const projectData = data["data"];
- setProjectData(projectData);
- setTitle(projectData.title);
- setDescription(projectData.description);
-
- const transformedDeadlines = projectData.deadlines.map((deadlineArray: string[]): Deadline => ({
- description: deadlineArray[0],
- deadline: deadlineArray[1]
- }));
-
- setDeadlines(transformedDeadlines);
+ if (title) setTitle(title);
- authenticatedFetch(
- `${API_URL}/courses/${projectData.course_id}`
- ).then((response) => {
- if (response.ok) {
- response.json().then((data) => {
- setCourseData(data["data"]);
- });
- }
- });
- });
- }
- });
- }, [projectId]);
+ if (projectData?.description) setDescription(projectData?.description);
+ };
const archiveProject = async () => {
const newArchived = !projectData?.archived;
const formData = new FormData();
- formData.append('archived', newArchived.toString());
-
- await authenticatedFetch(`${API_URL}/projects/${projectId}`, {
- method: "PATCH",
- body: formData
- })
+ formData.append("archived", newArchived.toString());
- await updateProject();
- }
-
- useEffect(() => {
- updateProject();
-
- authenticatedFetch(
- `${API_URL}/projects/${projectId}/assignment?lang=${i18next.resolvedLanguage}`
- ).then((response) => {
- if (response.ok) {
- response.text().then((data) => setAssignmentRawText(data));
+ const response = await authenticatedFetch(
+ `${API_URL}/projects/${projectId}`,
+ {
+ method: "PATCH",
+ body: formData,
}
- });
+ );
- fetchMe().then((data) => {
- setMe(data);
- });
-
- }, [projectId, updateProject]);
+ if (response.ok) {
+ navigate(0);
+ }
+ };
if (!projectId) return null;
return (
-
+
{projectData && (
@@ -178,96 +141,114 @@ export default function ProjectView() {
justifyContent="space-between"
alignItems="center"
>
- {
- !edit && <>{projectData.title}>
- }
- {
- edit && <> setTitle(event.target.value)}/>>
- }
+ {!edit && <>{projectData.title}>}
+ {edit && (
+ <>
+ setTitle(event.target.value)}
+ />
+ >
+ )}
{courseData && (
-
)}
- {me && me.role === "TEACHER" && (
-
-
+
-
+
{t("projectOverview")}
-
+
{t("archive")}
@@ -275,16 +256,21 @@ export default function ProjectView() {
display="flex"
flexDirection="row-reverse"
pt={2}
- width="100%">
- setAlertVisibility(true)}>
- Delete
+ width="100%"
+ >
+ setAlertVisibility(true)}
+ >
+ {t("delete")}
-
-
+
+
{
@@ -293,24 +279,26 @@ export default function ProjectView() {
}, 4000);
}}
>
-
- Are you sure you want to delete this project
-
+
+ {t("deleteProjectWarning")}
+
+
- Yes I'm Sure
+ {t("imSure")}
-
)}
@@ -322,5 +310,4 @@ export default function ProjectView() {
);
-
}