@@ -196,7 +200,7 @@ export default function AdministratorNavigationPanel() {
}
- onClickRef="#"
+ onClickRef="manage-feedbacks"
/>
diff --git a/client/src/icons.tsx b/client/src/icons.tsx
index 08aa11c..3397ad6 100644
--- a/client/src/icons.tsx
+++ b/client/src/icons.tsx
@@ -542,17 +542,19 @@ export const ArrowTopRightOnSquare = () => {
export const TrashDeleteIcon = () => {
return (
-
@@ -582,13 +584,13 @@ export const InfoIcon = () => {
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
- stroke-width="1.5"
+ strokeWidth="1.5"
stroke="currentColor"
className="size-6"
>
@@ -601,13 +603,13 @@ export const TrophyIcon = () => {
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
- stroke-width="1.5"
+ strokeWidth="1.5"
stroke="currentColor"
className="size-6"
>
@@ -651,3 +653,41 @@ export const ChevronDoubleDownIcon = () => {
);
};
+
+export const CheckmarkIcon = () => {
+ return (
+
+
+
+ );
+};
+
+export const BookOpenIcon = () => {
+ return (
+
+
+
+ );
+};
diff --git a/client/src/pages/FeedbackPage.tsx b/client/src/pages/FeedbackPage.tsx
index 7f15ddb..472adc5 100644
--- a/client/src/pages/FeedbackPage.tsx
+++ b/client/src/pages/FeedbackPage.tsx
@@ -30,7 +30,7 @@ export default function FeedbackPage() {
comment: Yup.string()
.trim()
.min(1)
- .max(1024)
+ .max(2048)
.required("Enter your comments."),
allowContact: Yup.boolean().oneOf([true, false], "please decide"),
});
diff --git a/client/src/pages/ManageFeedbacksPage.tsx b/client/src/pages/ManageFeedbacksPage.tsx
new file mode 100644
index 0000000..71109b6
--- /dev/null
+++ b/client/src/pages/ManageFeedbacksPage.tsx
@@ -0,0 +1,246 @@
+import { useEffect, useState } from "react";
+import instance from "../security/http";
+import config from "../config";
+import { popErrorToast } from "../utilities";
+import {
+ Button,
+ Chip,
+ getKeyValue,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ Table,
+ TableBody,
+ TableCell,
+ TableColumn,
+ TableHeader,
+ TableRow,
+ Textarea,
+} from "@nextui-org/react";
+import { BookOpenIcon, CheckmarkIcon, EmailIcon } from "../icons";
+import { retrieveUserInformationById } from "../security/usersbyid";
+
+export default function ManageFeedbacksPage() {
+ const [feedbacksList, setFeedbacksList] = useState
([]);
+ const [viewFeedbackModalOpened, setViewFeedbackModalOpened] = useState(false);
+ const [viewingFeedback, setViewingFeedback] = useState();
+ const columns = [
+ {
+ key: "feedbackCategory",
+ label: "CATEGORY",
+ },
+ {
+ key: "subject",
+ label: "SUBJECT",
+ },
+ {
+ key: "comment",
+ label: "COMMENT",
+ },
+ {
+ key: "createdAt",
+ label: "SUBMITTED",
+ },
+ {
+ key: "actions",
+ label: "ACTIONS",
+ },
+ ];
+
+ const populateFeedbacksList = () => {
+ instance
+ .get(`${config.serverAddress}/feedback/all`)
+ .then((response) => {
+ setFeedbacksList(response.data);
+ console.log(feedbacksList);
+ })
+ .catch((error) => {
+ popErrorToast(error);
+ });
+ };
+
+ useEffect(() => {
+ populateFeedbacksList();
+ }, []);
+
+ const viewFeedback = (feedbackId: string) => {
+ instance
+ .get(`${config.serverAddress}/feedback/${feedbackId}`)
+ .then((feedbackObject) => {
+ setViewingFeedback(feedbackObject.data);
+ setViewFeedbackModalOpened(true);
+ });
+ };
+
+ return (
+
+
Manage Feedbacks
+
+
+ {(column) => (
+ {column.label}
+ )}
+
+
+ {(feedbackEntry: any) => (
+
+ {(columnKey) => (
+
+ {(() => {
+ switch (columnKey) {
+ case "feedbackCategory":
+ let result = "";
+ switch (getKeyValue(feedbackEntry, columnKey)) {
+ case 0:
+ result = "Feature request";
+ break;
+ case 1:
+ result = "Bug report";
+ break;
+ case 2:
+ result = "Get in touch";
+ break;
+ default:
+ result = "Unknown";
+ break;
+ }
+ return {result};
+ case "subject":
+ return (
+
+ {getKeyValue(feedbackEntry, columnKey)}
+
+ );
+ case "comment":
+ return (
+
+
+ {getKeyValue(feedbackEntry, columnKey)}
+
+
+ );
+ case "createdAt":
+ let creationDate = Date.parse(
+ getKeyValue(feedbackEntry, columnKey)
+ );
+ let timing = Math.floor(
+ (Date.now() - creationDate) / (1000 * 60 * 60 * 24)
+ );
+ return (
+
+ {timing <= 0
+ ? "Today"
+ : `${timing} day${timing == 1 ? "" : "s"} ago`}
+
+ );
+ case "actions":
+ return (
+
+
+ }
+ onPress={() => {
+ viewFeedback(getKeyValue(feedbackEntry, "id"));
+ }}
+ >
+ View
+
+
+
+ );
+ default:
+ return {getKeyValue(feedbackEntry, columnKey)}
;
+ }
+ })()}
+
+ )}
+
+ )}
+
+
+
+
+ {(onClose) => {
+ return (
+ viewingFeedback && (
+ <>
+
+
+
"{viewingFeedback.subject}"
+
+ Submitted on{" "}
+ {new Date(viewingFeedback.createdAt).toLocaleString()}
+
+
+
+
+
+
+
+
+
+
+ {viewingFeedback.allowContact == 1 && (
+ }
+ onPress={() => {
+ retrieveUserInformationById(
+ viewingFeedback.userId
+ ).then((value) => {
+ window.location.href = `mailto:${value.email}`;
+ });
+ }}
+ >
+ Get in touch
+
+ )}
+
+
+
+ >
+ )
+ );
+ }}
+
+
+
+ );
+}
diff --git a/server/models/Feedback.js b/server/models/Feedback.js
index 56f71af..07a97b6 100644
--- a/server/models/Feedback.js
+++ b/server/models/Feedback.js
@@ -27,7 +27,7 @@ module.exports = (sequelize) => {
allowNull: false,
},
comment: {
- type: DataTypes.STRING(1024),
+ type: DataTypes.STRING(2048),
allowNull: false,
},
},
diff --git a/server/routes/feedback.js b/server/routes/feedback.js
index feb74c4..c03f3b2 100644
--- a/server/routes/feedback.js
+++ b/server/routes/feedback.js
@@ -2,14 +2,14 @@ const express = require("express");
const router = express.Router();
const yup = require("yup");
const { validateToken } = require("../middlewares/auth");
-const {Feedback} = require("../models");
+const { Feedback } = require("../models");
let validationSchema = yup.object({
userId: yup.string().trim().min(36).max(36).required(),
feedbackCategory: yup.number().min(0).max(2).required(),
allowContact: yup.boolean().required(),
subject: yup.string().trim().min(1).max(100).required(),
- comment: yup.string().trim().min(1).max(100).required(),
+ comment: yup.string().trim().min(1).max(2048).required(),
});
router.get("/all", validateToken, async (req, res) => {
@@ -56,4 +56,21 @@ router.post("/", validateToken, async (req, res) => {
}
});
+router.delete("/:id", validateToken, async (req, res) => {
+ let id = req.params.id;
+
+ try {
+ let result = await Feedback.destroy({ where: { id } });
+
+ if (result === 0) {
+ res.sendStatus(404);
+ } else {
+ res.sendStatus(204);
+ }
+ } catch (err) {
+ console.log("Error caught! Info: " + err);
+ res.status(500).json({ error: "An error occurred while deleting the feedback." });
+ }
+});
+
module.exports = router;
\ No newline at end of file