From 947a7977bd101a04f2792716bd8765d25db2011d Mon Sep 17 00:00:00 2001 From: abuzogan Date: Thu, 23 May 2024 19:32:08 +0200 Subject: [PATCH 1/3] fixed authorizations in projectview --- frontend/src/App.tsx | 31 +- frontend/src/loaders/project-view-loader.ts | 77 +++++ .../pages/project/projectView/ProjectView.tsx | 307 +++++++++--------- 3 files changed, 248 insertions(+), 167 deletions(-) create mode 100644 frontend/src/loaders/project-view-loader.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c3a967a0..f14c4914 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,8 +21,9 @@ 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 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..07911b19 --- /dev/null +++ b/frontend/src/loaders/project-view-loader.ts @@ -0,0 +1,77 @@ +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; + +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); + + console.log(courseData); + + 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..7955340a 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 { me, 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 && ( - )} } subheader={ - + - { - !edit - ? <>{projectData.description} - : edit && <> setDescription(event.target.value)}/> - } + {!edit ? ( + <> + {projectData.description} + + ) : ( + edit && ( + <> + + setDescription(event.target.value) + } + /> + + ) + )} } /> - {assignmentRawText} - - { me && me.role === "TEACHER" && ( - edit - ? ( - <> - - - - - - - ) - : ( - setEdit(true)}> - + {assignmentText} + + {isAdmin && + (edit ? ( + <> + + - ) - )} + + + + + ) : ( + setEdit(true)}> + + + ))} )} - {me && me.role === "TEACHER" && ( - - + - + - @@ -275,16 +256,21 @@ export default function ProjectView() { display="flex" flexDirection="row-reverse" pt={2} - width="100%"> - - -
+ +
{ @@ -293,12 +279,15 @@ export default function ProjectView() { }, 4000); }} > - - Are you sure you want to delete this project - + + Are you sure you want to delete this project + + - )} @@ -322,5 +310,4 @@ export default function ProjectView() { ); - } From edffe2176098296da3d0172c833b76355452db22 Mon Sep 17 00:00:00 2001 From: abuzogan Date: Thu, 23 May 2024 19:36:49 +0200 Subject: [PATCH 2/3] linting --- frontend/src/loaders/project-view-loader.ts | 11 +++++++++-- .../src/pages/project/projectView/ProjectView.tsx | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/loaders/project-view-loader.ts b/frontend/src/loaders/project-view-loader.ts index 07911b19..0a9cf6f8 100644 --- a/frontend/src/loaders/project-view-loader.ts +++ b/frontend/src/loaders/project-view-loader.ts @@ -6,6 +6,15 @@ 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, }: { @@ -62,8 +71,6 @@ export default async function loadProjectViewData({ const isAdmin = me.uid === courseData["teacher"] || courseData["admins"].includes(me.uid); - console.log(courseData); - return { projectData: projectData, courseData: courseData, diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index 7955340a..da731ed4 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -51,7 +51,7 @@ interface Project { export default function ProjectView() { const location = useLocation(); - const { me, projectData, courseData, assignmentText, isAdmin } = + const { projectData, courseData, assignmentText, isAdmin } = useLoaderData() as { me: Me; projectData: Project; From a6f6947b566fa78afbdc11722c3012421a00a497 Mon Sep 17 00:00:00 2001 From: abuzogan Date: Thu, 23 May 2024 20:02:33 +0200 Subject: [PATCH 3/3] added locales --- frontend/public/locales/en/translation.json | 3 +++ frontend/public/locales/nl/translation.json | 3 +++ frontend/src/pages/project/projectView/ProjectView.tsx | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) 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/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index da731ed4..8d5a2484 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -263,7 +263,7 @@ export default function ProjectView() { color="error" onClick={() => setAlertVisibility(true)} > - Delete + {t("delete")} @@ -281,7 +281,7 @@ export default function ProjectView() { > - Are you sure you want to delete this project + {t("deleteProjectWarning")}