From 5c3f83220ad5d5570ee15463462957ee0f3ce1d4 Mon Sep 17 00:00:00 2001 From: Rose Wang <51464298+rosewang01@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:39:51 -0500 Subject: [PATCH] done --- .../src/AdminDashboard/AdminDashboardPage.tsx | 87 +++++-- .../AdminDashboard/Buttons/DeleteButton.tsx | 40 ++++ .../{ => Buttons}/DeleteUserButton.tsx | 6 +- .../src/AdminDashboard/Buttons/EditButton.tsx | 39 ++++ .../{ => Buttons}/PromoteUserButton.tsx | 6 +- .../AdminDashboard/DeleteQuestionButton.tsx | 56 ----- .../src/AdminDashboard/EditDefinitionPage.tsx | 90 +++++++ .../src/AdminDashboard/EditQuestionButton.tsx | 48 ---- ...{EditQuestion.tsx => EditQuestionPage.tsx} | 54 +++-- client/src/AdminDashboard/EditResource.tsx | 85 ------- .../src/AdminDashboard/EditResourcePage.tsx | 97 ++++++++ .../EditResourceTitleContent.tsx | 63 ----- client/src/AdminDashboard/QuestionTable.tsx | 112 --------- .../AdminDashboard/Tables/DefinitionTable.tsx | 103 ++++++++ .../AdminDashboard/Tables/QuestionTable.tsx | 75 ++++++ .../AdminDashboard/Tables/ResourceTable.tsx | 104 +++++++++ client/src/AdminDashboard/api.tsx | 35 ++- client/src/App.tsx | 20 +- client/src/Home/AboutThisProjectPage.tsx | 219 ++++++++++-------- client/src/Home/AllResourcesPage.tsx | 6 +- .../Question/Components/ResourceDropdown.tsx | 54 ----- .../Question/Components/StartOverButton.tsx | 5 +- client/src/Question/QuestionPage.tsx | 18 +- client/src/Question/ResourceComponent.tsx | 4 +- client/src/components/AlertDialog.tsx | 6 +- client/src/components/ConfirmationModal.tsx | 6 +- client/src/components/EditorGUI.tsx | 103 ++++---- client/src/components/HTMLMapper.tsx | 10 + client/src/components/PaginationTable.tsx | 2 +- client/src/components/ResourceDropdown.tsx | 9 +- client/src/components/ScreenGrid.tsx | 1 - .../components/sidebar/SidebarComponent.tsx | 3 +- .../src/components/sidebar/SidebarContent.tsx | 4 +- .../components/sidebar/SidebarContentItem.tsx | 6 +- dataloader/csv_to_json.py | 4 +- dataloader/json_files/definitions.json | 2 +- server/src/controllers/admin.controller.ts | 132 +++++++++-- .../src/controllers/definition.controller.ts | 2 +- server/src/models/answer.model.ts | 6 +- server/src/routes/admin.route.ts | 30 ++- server/src/services/answer.service.ts | 82 +++++++ server/src/services/definition.service.ts | 58 ++++- server/src/services/question.service.ts | 13 +- server/src/util/importData.ts | 6 +- 44 files changed, 1199 insertions(+), 712 deletions(-) create mode 100644 client/src/AdminDashboard/Buttons/DeleteButton.tsx rename client/src/AdminDashboard/{ => Buttons}/DeleteUserButton.tsx (88%) create mode 100644 client/src/AdminDashboard/Buttons/EditButton.tsx rename client/src/AdminDashboard/{ => Buttons}/PromoteUserButton.tsx (88%) delete mode 100644 client/src/AdminDashboard/DeleteQuestionButton.tsx create mode 100644 client/src/AdminDashboard/EditDefinitionPage.tsx delete mode 100644 client/src/AdminDashboard/EditQuestionButton.tsx rename client/src/AdminDashboard/{EditQuestion.tsx => EditQuestionPage.tsx} (63%) delete mode 100644 client/src/AdminDashboard/EditResource.tsx create mode 100644 client/src/AdminDashboard/EditResourcePage.tsx delete mode 100644 client/src/AdminDashboard/EditResourceTitleContent.tsx delete mode 100644 client/src/AdminDashboard/QuestionTable.tsx create mode 100644 client/src/AdminDashboard/Tables/DefinitionTable.tsx create mode 100644 client/src/AdminDashboard/Tables/QuestionTable.tsx create mode 100644 client/src/AdminDashboard/Tables/ResourceTable.tsx delete mode 100644 client/src/Question/Components/ResourceDropdown.tsx create mode 100644 server/src/services/answer.service.ts diff --git a/client/src/AdminDashboard/AdminDashboardPage.tsx b/client/src/AdminDashboard/AdminDashboardPage.tsx index 99befe25..f20a7027 100644 --- a/client/src/AdminDashboard/AdminDashboardPage.tsx +++ b/client/src/AdminDashboard/AdminDashboardPage.tsx @@ -1,7 +1,9 @@ import React from 'react'; -import { Typography, Grid } from '@mui/material'; +import { Typography, AppBar, Box } from '@mui/material'; import ScreenGrid from '../components/ScreenGrid'; -import QuestionTable from './QuestionTable'; +import QuestionTable from './Tables/QuestionTable'; +import ResourceTable from './Tables/ResourceTable'; +import DefinitionTable from './Tables/DefinitionTable'; import NavBar from '../components/NavBar'; /** @@ -11,26 +13,73 @@ import NavBar from '../components/NavBar'; function AdminDashboardPage() { return ( - - - - - + + + +
Welcome to the Admin Dashboard - -
- {/* */} - +
+ + Questions + +
+ +
+
+
+ + Resources + +
+ +
+
+
+ + Definitions + +
+ +
- - +
+ ); } diff --git a/client/src/AdminDashboard/Buttons/DeleteButton.tsx b/client/src/AdminDashboard/Buttons/DeleteButton.tsx new file mode 100644 index 00000000..8bb382a3 --- /dev/null +++ b/client/src/AdminDashboard/Buttons/DeleteButton.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import LoadingButton from '../../components/buttons/LoadingButton'; +import ConfirmationModal from '../../components/ConfirmationModal'; +import { IDefinition } from '../../util/types/definition'; +import { IAnswer } from '../../util/types/answer'; + +interface DeleteButtonProps { + id: string; + object: IDefinition | IAnswer; + removeRow: (object: IDefinition | IAnswer) => void; +} + +function DeleteButton({ id, object, removeRow }: DeleteButtonProps) { + const [isLoading, setLoading] = useState(false); + + const isDefinition = (object as IDefinition).word !== undefined; + + const bodyString = `Are you sure you want to delete this ${ + isDefinition ? 'definition' : 'resource' + }?`; + + async function handleDelete() { + setLoading(true); + removeRow(object); + setLoading(false); + } + if (isLoading) { + return ; + } + return ( + handleDelete()} + /> + ); +} + +export default DeleteButton; diff --git a/client/src/AdminDashboard/DeleteUserButton.tsx b/client/src/AdminDashboard/Buttons/DeleteUserButton.tsx similarity index 88% rename from client/src/AdminDashboard/DeleteUserButton.tsx rename to client/src/AdminDashboard/Buttons/DeleteUserButton.tsx index ccf7f935..61356018 100644 --- a/client/src/AdminDashboard/DeleteUserButton.tsx +++ b/client/src/AdminDashboard/Buttons/DeleteUserButton.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; import Button from '@mui/material/Button'; -import { deleteUser } from './api'; -import LoadingButton from '../components/buttons/LoadingButton'; -import ConfirmationModal from '../components/ConfirmationModal'; +import { deleteUser } from '../api'; +import LoadingButton from '../../components/buttons/LoadingButton'; +import ConfirmationModal from '../../components/ConfirmationModal'; interface DeleteUserButtonProps { admin: boolean; diff --git a/client/src/AdminDashboard/Buttons/EditButton.tsx b/client/src/AdminDashboard/Buttons/EditButton.tsx new file mode 100644 index 00000000..f06aa018 --- /dev/null +++ b/client/src/AdminDashboard/Buttons/EditButton.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import Button from '@mui/material/Button'; +import { Link } from 'react-router-dom'; +import { IQuestion } from '../../util/types/question'; +import { IAnswer } from '../../util/types/answer'; +import { IDefinition } from '../../util/types/definition'; + +/** + * Returns the edit button for the object + * @param object - the object to edit + * @returns the edit button for the object + */ + +interface EditButtonProps { + object: IQuestion | IAnswer | IDefinition; +} + +function EditButton({ object }: EditButtonProps) { + let toPath = ''; + if ((object as IQuestion).isQuestion !== undefined) { + toPath = '/edit-question'; + } else if ((object as IAnswer).resourceContent !== undefined) { + toPath = '/edit-resource'; + } else if ((object as IDefinition).word !== undefined) { + toPath = '/edit-definition'; + } else { + toPath = '/home'; + } + + return ( +
+ + + +
+ ); +} + +export default EditButton; diff --git a/client/src/AdminDashboard/PromoteUserButton.tsx b/client/src/AdminDashboard/Buttons/PromoteUserButton.tsx similarity index 88% rename from client/src/AdminDashboard/PromoteUserButton.tsx rename to client/src/AdminDashboard/Buttons/PromoteUserButton.tsx index 388fa108..41da1d84 100644 --- a/client/src/AdminDashboard/PromoteUserButton.tsx +++ b/client/src/AdminDashboard/Buttons/PromoteUserButton.tsx @@ -1,8 +1,8 @@ import React, { useState } from 'react'; import Button from '@mui/material/Button'; -import { upgradePrivilege } from './api'; -import LoadingButton from '../components/buttons/LoadingButton'; -import ConfirmationModal from '../components/ConfirmationModal'; +import { upgradePrivilege } from '../api'; +import LoadingButton from '../../components/buttons/LoadingButton'; +import ConfirmationModal from '../../components/ConfirmationModal'; interface PromoteUserButtonProps { admin: boolean; diff --git a/client/src/AdminDashboard/DeleteQuestionButton.tsx b/client/src/AdminDashboard/DeleteQuestionButton.tsx deleted file mode 100644 index bcd3fb25..00000000 --- a/client/src/AdminDashboard/DeleteQuestionButton.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState } from 'react'; -import Button from '@mui/material/Button'; -import { deleteResource } from './api'; // change to deleteQuestion -import LoadingButton from '../components/buttons/LoadingButton'; -import ConfirmationModal from '../components/ConfirmationModal'; -import { IQuestion } from '../util/types/question'; - -interface DeleteQuestionButtonProps { - id: string; - question: IQuestion; - removeRow: (question: IQuestion) => void; -} - -/** - * The button component which, when clicked, will delete the question from the database. - * If the user is not a valid question, button will be unclickable //this is kinda unnecessary lowkey - * @param id - id of the question to delete - * @param question - the question to delete - * @param removeRow - a function which removes a row from the question table. This - * function is called upon successfully deletion of user from the database. - */ -function DeleteQuestionButton({ - id, - question, - removeRow, -}: DeleteQuestionButtonProps) { - const [isLoading, setLoading] = useState(false); - - async function handleDeleteResource() { - setLoading(true); - await deleteResource(id); - removeRow(question); - setLoading(false); - } - if (isLoading) { - return ; - } - if (question.isQuestion) { - return ( - - ); - } - // resource - return ( - handleDeleteResource()} - /> - ); -} - -export default DeleteQuestionButton; diff --git a/client/src/AdminDashboard/EditDefinitionPage.tsx b/client/src/AdminDashboard/EditDefinitionPage.tsx new file mode 100644 index 00000000..1bb6e986 --- /dev/null +++ b/client/src/AdminDashboard/EditDefinitionPage.tsx @@ -0,0 +1,90 @@ +/* eslint-disable no-underscore-dangle */ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { + Typography, + Container, + Box, + Alert, + AlertTitle, + AppBar, +} from '@mui/material'; +import { editDefinition } from './api'; +import { IDefinition } from '../util/types/definition'; +import EditorGUI from '../components/EditorGUI'; +import NavBar from '../components/NavBar'; +import ScreenGrid from '../components/ScreenGrid'; + +export default function EditDefinitionPage() { + const defaultDefinition: IDefinition = useLocation().state.object; + const didMountRef = useRef(false); + + const [values, setValueState] = useState(defaultDefinition); + const setValue = (field: string, value: any) => { + setValueState((prevState) => ({ + ...prevState, + ...{ [field]: value }, + })); + }; + + useEffect(() => { + if (!didMountRef.current) { + didMountRef.current = true; + } else { + // console.log(values); + editDefinition(values); + } + }, [values]); + + return ( + + + + + + + + + + Please make sure that any link you add starts with + "http://" or{' '} + "https://"! + + + + + + Word: + + + + + + Definition: + + + + + + Link: + + + + + + + ); +} diff --git a/client/src/AdminDashboard/EditQuestionButton.tsx b/client/src/AdminDashboard/EditQuestionButton.tsx deleted file mode 100644 index 2969948c..00000000 --- a/client/src/AdminDashboard/EditQuestionButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState } from 'react'; -import Button from '@mui/material/Button'; -import { Navigate, useNavigate, Link } from 'react-router-dom'; -import LoadingButton from '../components/buttons/LoadingButton'; -import { IQuestion } from '../util/types/question'; - -interface EditQuestionButtonProps { - question: IQuestion; -} - -/** - * The button component which, when clicked, will edit the question from the database. - * If the user is not a valid question, button will be unclickable //this is kinda unnecessary lowkey - * @param question - the question to edit - * data in the database if put data works right lol - */ -function EditQuestionButton({ question }: EditQuestionButtonProps) { - const navigate = useNavigate(); - - if (question.isQuestion) { - // valid question - return ( -
- - - -
- ); - } - - return ( -
- - - -
- ); -} - -export default EditQuestionButton; diff --git a/client/src/AdminDashboard/EditQuestion.tsx b/client/src/AdminDashboard/EditQuestionPage.tsx similarity index 63% rename from client/src/AdminDashboard/EditQuestion.tsx rename to client/src/AdminDashboard/EditQuestionPage.tsx index 43f9da16..b276c9de 100644 --- a/client/src/AdminDashboard/EditQuestion.tsx +++ b/client/src/AdminDashboard/EditQuestionPage.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; -import { Typography, Container, Box, Grid } from '@mui/material'; +import { Typography, Container, Box, AppBar, Grid } from '@mui/material'; import { editQuestion } from './api'; import { IAnswer } from '../util/types/answer'; import { IQuestion } from '../util/types/question'; @@ -8,8 +8,8 @@ import EditorGUI from '../components/EditorGUI'; import NavBar from '../components/NavBar'; import ScreenGrid from '../components/ScreenGrid'; -export default function EditQuestion() { - const defaultResource: IQuestion = useLocation().state.question; +export default function EditQuestionPage() { + const defaultResource: IQuestion = useLocation().state.object; const didMountRef = useRef(false); const [values, setValueState] = useState(defaultResource); @@ -29,50 +29,58 @@ export default function EditQuestion() { }, [values]); return ( - - + + - - + + Question: - + - - {values.resultantAnswers.length > 0 ? ( - + + {!values.resultantAnswers || values.resultantAnswers.length > 0 ? ( + Answers: ) : null} - {values.resultantAnswers.map((ans: IAnswer, idx: any) => { + {values.resultantAnswers.map((ans: IAnswer, idx: number) => { return ( {' '} + /> ); })} - - + + ); } diff --git a/client/src/AdminDashboard/EditResource.tsx b/client/src/AdminDashboard/EditResource.tsx deleted file mode 100644 index a7e90887..00000000 --- a/client/src/AdminDashboard/EditResource.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import React, { useEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { Typography, Container, Box, Alert, AlertTitle } from '@mui/material'; -import { editQuestion } from './api'; -import { IAnswer } from '../util/types/answer'; -import { IQuestion } from '../util/types/question'; -import EditorGUI from '../components/EditorGUI'; -import EditResourceTitleContent from './EditResourceTitleContent'; - -export default function EditResource() { - const defaultResource: IQuestion = useLocation().state.question; - const didMountRef = useRef(false); - - const [values, setValueState] = useState(defaultResource); - const setValue = (field: string, value: any) => { - setValueState((prevState) => ({ - ...prevState, - ...{ [field]: value }, - })); - }; - - useEffect(() => { - if (!didMountRef.current) { - didMountRef.current = true; - } else { - // console.log(values); - editQuestion(values); - } - }, [values]); - - return ( - - - - - Please make sure that any link you add starts with - "http://" or{' '} - "https://"! - - - - Resource Name: - - - -
- {values.resultantAnswers.map((ans: IAnswer, idx: any) => { - return ( - - // - //
- // - // Resource #{idx + 1}: - // - // - //
- // Title: - // {' '} - //
- // Content: - // {' '} - //
- ); - })} -
- ); -} diff --git a/client/src/AdminDashboard/EditResourcePage.tsx b/client/src/AdminDashboard/EditResourcePage.tsx new file mode 100644 index 00000000..14757d75 --- /dev/null +++ b/client/src/AdminDashboard/EditResourcePage.tsx @@ -0,0 +1,97 @@ +/* eslint-disable no-underscore-dangle */ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { + Typography, + Container, + Box, + Alert, + AlertTitle, + AppBar, +} from '@mui/material'; +import { editResource } from './api'; +import { IAnswer } from '../util/types/answer'; +import EditorGUI from '../components/EditorGUI'; +import NavBar from '../components/NavBar'; +import ScreenGrid from '../components/ScreenGrid'; + +export default function EditResourcePage() { + const defaultResource: IAnswer = useLocation().state.object; + const didMountRef = useRef(false); + + const [values, setValueState] = useState(defaultResource); + const setValue = (field: string, value: any) => { + setValueState((prevState) => ({ + ...prevState, + ...{ [field]: value }, + })); + }; + + useEffect(() => { + if (!didMountRef.current) { + didMountRef.current = true; + } else { + editResource(values); + } + }, [values]); + + return ( + + + + + + + + + + Please make sure that any link you add starts with + "http://" or{' '} + "https://"! + + + + + + Resource Name: + + + + + + Resource Content: + + + + + + Resource Link: + + + + + + + ); +} diff --git a/client/src/AdminDashboard/EditResourceTitleContent.tsx b/client/src/AdminDashboard/EditResourceTitleContent.tsx deleted file mode 100644 index 00787eb1..00000000 --- a/client/src/AdminDashboard/EditResourceTitleContent.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import React, { useState } from 'react'; -import { - Typography, - Container, - Box, - Alert, - AlertTitle, - Button, -} from '@mui/material'; -import { sizing } from '@mui/system'; -import { IAnswer } from '../util/types/answer'; -import { IQuestion } from '../util/types/question'; -import EditorGUI from '../components/EditorGUI'; -import { deleteResource } from './api'; - -export default function EditResourceTitleContent({ - values, - setValue, - ans, - idx, -}: any) { - const [deleted, setDeleted] = useState(false); - - const handleDelete = () => { - deleteResource(values); - setDeleted(true); - }; - - return ( - -
- - Resource #{idx + 1}: - - -
- Title: - {' '} -
- Content: - {' '} -
- ); -} diff --git a/client/src/AdminDashboard/QuestionTable.tsx b/client/src/AdminDashboard/QuestionTable.tsx deleted file mode 100644 index 9e27aadf..00000000 --- a/client/src/AdminDashboard/QuestionTable.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/** - * A file that contains all the components and logic for the table of users - * in the AdminDashboardPage. - */ -import React, { useEffect, useState } from 'react'; -import CircularProgress from '@mui/material/CircularProgress'; -import { PaginationTable, TColumn } from '../components/PaginationTable'; -import DeleteQuestionButton from './DeleteQuestionButton'; -import { useData } from '../util/api'; -import { IQuestion } from '../util/types/question'; -import EditQuestionButton from './EditQuestionButton'; -import { deleteResource } from './api'; - -interface AdminDashboardRow { - key: string; - question: string; - deleteButton: React.ReactElement; - edit: React.ReactElement; -} - -/** - * The standalone table component for holding information about the users in - * the database and allowing admins to remove users and promote users to admins. - */ -function QuestionTable() { - // define columns for the table - const columns: TColumn[] = [ - { - id: 'question', - label: 'Question/Resource', - }, - // { id: 'promote', label: 'Promote to Admin' }, - { id: 'edit', label: 'Edit' }, - ]; - - // Used to create the data type to create a row in the table - function createAdminDashboardRow( - question: IQuestion, // IUser, //fix this to question type - deleteButton: React.ReactElement, - edit: React.ReactElement, - ): AdminDashboardRow { - const { _id, text } = question; - return { - key: _id, - question: text, - deleteButton, - edit, - }; - } - - const [questionList, setQuestionList] = useState([]); - const questions = useData('admin/allQuestions'); // this is a route for GETTING ALL question data; TODO: update later - - // Upon getting the list of users for the database, set the state of the userList to contain all users except for logged in user - useEffect(() => { - setQuestionList(questions?.data); - }, [questions]); - - // update state of userlist to remove a user from the frontend representation of the data - const removeResource = (question: IQuestion) => { - setQuestionList( - questionList.filter( - (entry: IQuestion) => - entry && - entry.text && - entry.text !== question.text && - // eslint-disable-next-line no-underscore-dangle - entry._id !== question._id, - ), - ); - // eslint-disable-next-line no-underscore-dangle - deleteResource(question._id); - }; - - const handleEditChange = (oldQ: IQuestion, newQ: IQuestion) => { - setQuestionList( - questionList.map((q: IQuestion) => - // eslint-disable-next-line no-underscore-dangle - q.text === oldQ.text && q._id === oldQ._id ? newQ : q, - ), - ); - }; - - // if the questionlist is not yet populated, display a loading spinner - if (!questionList) { - return ( -
- -
- ); - } - - return ( - - createAdminDashboardRow( - question, - removeResource(question)} - />, - , - ), - )} - columns={columns} - /> - ); -} - -export default QuestionTable; diff --git a/client/src/AdminDashboard/Tables/DefinitionTable.tsx b/client/src/AdminDashboard/Tables/DefinitionTable.tsx new file mode 100644 index 00000000..084d7717 --- /dev/null +++ b/client/src/AdminDashboard/Tables/DefinitionTable.tsx @@ -0,0 +1,103 @@ +/** + * A file that contains all the components and logic for the table of definitions + * in the AdminDashboardPage. + */ +import React, { useEffect, useState } from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import { PaginationTable, TColumn } from '../../components/PaginationTable'; +import DeleteButton from '../Buttons/DeleteButton'; +import { useData } from '../../util/api'; +import { IDefinition } from '../../util/types/definition'; +import EditButton from '../Buttons/EditButton'; +import { deleteDefinition } from '../api'; + +interface DefinitionRow { + key: string; + definition: string; + delete: React.ReactElement; + edit: React.ReactElement; +} + +/** + * The standalone table component for holding information about the users in + * the database and allowing admins to remove users and promote users to admins. + */ +function DefinitionTable() { + // define columns for the table + const columns: TColumn[] = [ + { + id: 'definition', + label: 'Definition', + }, + { id: 'edit', label: 'Edit' }, + { id: 'delete', label: 'Delete' }, + ]; + + // Used to create the data type to create a row in the table + function createDefinitionRow( + definition: IDefinition, + deleteButton: React.ReactElement, + edit: React.ReactElement, + ): DefinitionRow { + const { _id, word } = definition; + return { + key: _id, + definition: word, + edit, + delete: deleteButton, + }; + } + + const [definitionList, setDefinitionList] = useState([]); + const definitions = useData('admin/allDefinitions'); + + useEffect(() => { + const definitionsData = definitions?.data; + setDefinitionList(definitionsData); + }, [definitions]); + + // update state of userlist to remove a user from the frontend representation of the data + const removeDefinition = (definition: IDefinition) => { + setDefinitionList( + definitionList.filter( + (entry: IDefinition) => + entry && + entry.word && + entry.word !== definition.word && + // eslint-disable-next-line no-underscore-dangle + entry._id !== definition._id, + ), + ); + // eslint-disable-next-line no-underscore-dangle + deleteDefinition(definition._id); + }; + + // if the definitionlist is not yet populated, display a loading spinner + if (!definitionList) { + return ( +
+ +
+ ); + } + + return ( + + createDefinitionRow( + definition, + removeDefinition(definition)} + />, + , + ), + )} + columns={columns} + /> + ); +} + +export default DefinitionTable; diff --git a/client/src/AdminDashboard/Tables/QuestionTable.tsx b/client/src/AdminDashboard/Tables/QuestionTable.tsx new file mode 100644 index 00000000..c8d859db --- /dev/null +++ b/client/src/AdminDashboard/Tables/QuestionTable.tsx @@ -0,0 +1,75 @@ +/** + * A file that contains all the components and logic for the table of users + * in the AdminDashboardPage. + */ +import React, { useEffect, useState } from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import { PaginationTable, TColumn } from '../../components/PaginationTable'; +import { useData } from '../../util/api'; +import { IQuestion } from '../../util/types/question'; +import EditButton from '../Buttons/EditButton'; + +interface AdminDashboardRow { + key: string; + question: string; + edit: React.ReactElement; +} + +/** + * The standalone table component for holding information about the users in + * the database and allowing admins to remove users and promote users to admins. + */ +function QuestionTable() { + // define columns for the table + const columns: TColumn[] = [ + { + id: 'question', + label: 'Question', + }, + { id: 'edit', label: 'Edit' }, + ]; + + // Used to create the data type to create a row in the table + function createAdminDashboardRow( + question: IQuestion, + edit: React.ReactElement, + ): AdminDashboardRow { + const { _id, text } = question; + return { + key: _id, + question: text, + edit, + }; + } + + const [questionList, setQuestionList] = useState([]); + const questions = useData('admin/allQuestions'); // this is a route for GETTING ALL question data; TODO: update later + + // Upon getting the list of users for the database, set the state of the userList to contain all users except for logged in user + useEffect(() => { + const questionsData = questions?.data.filter( + (question: IQuestion) => question.isQuestion, + ); + setQuestionList(questionsData); + }, [questions]); + + // if the questionlist is not yet populated, display a loading spinner + if (!questionList) { + return ( +
+ +
+ ); + } + + return ( + + createAdminDashboardRow(question, ), + )} + columns={columns} + /> + ); +} + +export default QuestionTable; diff --git a/client/src/AdminDashboard/Tables/ResourceTable.tsx b/client/src/AdminDashboard/Tables/ResourceTable.tsx new file mode 100644 index 00000000..8dbe871c --- /dev/null +++ b/client/src/AdminDashboard/Tables/ResourceTable.tsx @@ -0,0 +1,104 @@ +/** + * A file that contains all the components and logic for the table of resources + * in the AdminDashboardPage. + */ +import React, { useEffect, useState } from 'react'; +import CircularProgress from '@mui/material/CircularProgress'; +import { PaginationTable, TColumn } from '../../components/PaginationTable'; +import DeleteButton from '../Buttons/DeleteButton'; +import { useData } from '../../util/api'; +import { IAnswer } from '../../util/types/answer'; +import EditButton from '../Buttons/EditButton'; +import { deleteResource } from '../api'; + +interface ResourceRow { + key: string; + resource: string; + edit: React.ReactElement; + delete: React.ReactElement; +} + +/** + * The standalone table component for holding information about the users in + * the database and allowing admins to remove users and promote users to admins. + */ +function ResourceTable() { + // define columns for the table + const columns: TColumn[] = [ + { + id: 'resource', + label: 'Resource', + }, + { id: 'edit', label: 'Edit' }, + { id: 'delete', label: 'Delete' }, + ]; + + // Used to create the data type to create a row in the table + function createResourceRow( + resource: IAnswer, + deleteButton: React.ReactElement, + edit: React.ReactElement, + ): ResourceRow { + const { _id, text } = resource; + return { + key: _id, + resource: text, + edit, + delete: deleteButton, + }; + } + + const [resourceList, setResourceList] = useState([]); + const resources = useData('admin/allResources'); // this is a route for GETTING ALL resource data; TODO: update later + + // Upon getting the list of users for the database, set the state of the userList to contain all users except for logged in user + useEffect(() => { + const resourcesData = resources?.data; + setResourceList(resourcesData); + }, [resources]); + + // update state of userlist to remove a user from the frontend representation of the data + const removeResource = (resource: IAnswer) => { + setResourceList( + resourceList.filter( + (entry: IAnswer) => + entry && + entry.text && + entry.text !== resource.text && + // eslint-disable-next-line no-underscore-dangle + entry._id !== resource._id, + ), + ); + // eslint-disable-next-line no-underscore-dangle + deleteResource(resource._id); + }; + + // if the resourcelist is not yet populated, display a loading spinner + if (!resourceList) { + return ( +
+ +
+ ); + } + + return ( + + createResourceRow( + resource, + removeResource(resource)} + />, + , + ), + )} + columns={columns} + /> + ); +} + +export default ResourceTable; diff --git a/client/src/AdminDashboard/api.tsx b/client/src/AdminDashboard/api.tsx index a1c94f62..205f96ad 100644 --- a/client/src/AdminDashboard/api.tsx +++ b/client/src/AdminDashboard/api.tsx @@ -3,6 +3,8 @@ */ import { deleteData, putData } from '../util/api'; import { IQuestion } from '../util/types/question'; +import { IAnswer } from '../util/types/answer'; +import { IDefinition } from '../util/types/definition'; /** * Sends a request to the server to delete a user @@ -21,7 +23,14 @@ async function deleteResource(id: string) { return true; } -// routes! hopefully +async function editResource(resource: IAnswer) { + const res = await putData(`admin/editResource`, { + resource, + }); + if (res.error) return false; + return true; +} + async function editQuestion(question: IQuestion) { const res = await putData(`admin/editQuestion`, { question, @@ -30,6 +39,20 @@ async function editQuestion(question: IQuestion) { return true; } +async function deleteDefinition(id: string) { + const res = await deleteData(`admin/definition/${id}`); + if (res.error) return false; + return true; +} + +async function editDefinition(definition: IDefinition) { + const res = await putData(`admin/editDefinition`, { + definition, + }); + if (res.error) return false; + return true; +} + /** * Sends a request to the server to promote a user to admin * @param email - the email of the user to promote @@ -41,4 +64,12 @@ async function upgradePrivilege(email: string) { return true; } -export { deleteUser, editQuestion, deleteResource, upgradePrivilege }; +export { + deleteUser, + editQuestion, + editResource, + deleteResource, + editDefinition, + deleteDefinition, + upgradePrivilege, +}; diff --git a/client/src/App.tsx b/client/src/App.tsx index 387f3f09..7a315cc6 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -10,13 +10,13 @@ import NotFoundPage from './NotFound/NotFoundPage'; import HomePage from './Home/HomePage'; import AboutThisProjectPage from './Home/AboutThisProjectPage'; import AdminDashboardPage from './AdminDashboard/AdminDashboardPage'; -import EditResource from './AdminDashboard/EditResource'; -import EditQuestion from './AdminDashboard/EditQuestion'; +import EditResourcePage from './AdminDashboard/EditResourcePage'; +import EditQuestionPage from './AdminDashboard/EditQuestionPage'; +import EditDefinitionPage from './AdminDashboard/EditDefinitionPage'; import { UnauthenticatedRoutesWrapper, ProtectedRoutesWrapper, DynamicRedirect, - AdminRoutesWrapper, } from './util/routes'; import VerifyAccountPage from './Authentication/VerifyAccountPage'; import RegisterPage from './Authentication/RegisterPage'; @@ -75,8 +75,18 @@ function App() { element={} /> } /> - } /> - } /> + } + /> + } + /> + } + /> {/* Route which redirects to a different page depending on if the user is an authenticated or not by utilizing the DynamicRedirect component */} diff --git a/client/src/Home/AboutThisProjectPage.tsx b/client/src/Home/AboutThisProjectPage.tsx index 7632c7b5..a4ea8bb4 100644 --- a/client/src/Home/AboutThisProjectPage.tsx +++ b/client/src/Home/AboutThisProjectPage.tsx @@ -1,12 +1,11 @@ import React, { useState } from 'react'; -import Button from '@mui/material/Button'; import { Typography, Grid, ListItem, - ListItemText, - ListSubheader, List, + AppBar, + Toolbar, } from '@mui/material'; import Box from '@mui/system/Box'; import ScreenGrid from '../components/ScreenGrid'; @@ -16,132 +15,152 @@ import NavBar from '../components/NavBar'; function AboutThisProjectPage() { return ( - + + + - - - - - - - Guide to Interpersonal Resources at Penn - - - + + + Guide to Interpersonal Resources at Penn + + + - - How does this work? - + + + How does this work? + + + + Anyone who visits this website can answer questions that may + may relate to a situation of interpersonal violence that they + or a friend have experienced. We will provide definitions + along the way, and try to help users navigate resources on + campus and in in Philadelphia. + + + + + + Why are we doing this? + - - Anyone who visits this website can answer questions that may may - relate to a situation of interpersonal violence that they or a - friend have experienced. We will provide definitions along the - way, and try to help users navigate resources on campus and in - in Philadelphia. - + + This is an app by students and for students. We understand + that navigating resources can sometimes be overwhelming, + scary, and disheartening. We want to help students explore + their options, and hopefully connect them with a resource that + can give them the support they deserve. + + - - Why are we doing this? - + + + Who created this guide? + - - This is an app by students and for students. We understand that - navigating resources can sometimes be overwhelming, scary, and - disheartening. We want to help students explore their options, - and hopefully connect them with a resource that can give them - the support they deserve. - + + This app was founded by Abuse and Sexual Assault Prevention at + Penn (ASAP) in collaboration with Hack4Impact. Both groups are + undergraduate clubs at the University of Pennsylvania. + Hack4Impact created the webpage you are seeing while ASAP + curated the content with the help from the following offices + at Penn: + + + + + + Penn Violence Prevention + + + Penn Womens Center + + + Special Services + + + + + - - - - Who created this guide? - - - - This app was founded by Abuse and Sexual Assault Prevention at - Penn (ASAP) in collaboration with Hack4Impact. Both groups are - undergraduate clubs at the University of Pennsylvania. Hack4Impact - created the webpage you are seeing while ASAP curated the content - with the help from the following offices at Penn: - - - - - - Penn Violence Prevention - - - Penn Womens Center - - - Special Services - - - - - - -