Feedbacks management go brr
This commit is contained in:
@@ -34,6 +34,7 @@ import ManageVoucherPage from "./pages/ManageVoucherPage";
|
|||||||
import CreateVoucherPage from "./pages/CreateVoucherPage";
|
import CreateVoucherPage from "./pages/CreateVoucherPage";
|
||||||
import EditVoucherPage from "./pages/EditVoucherPage";
|
import EditVoucherPage from "./pages/EditVoucherPage";
|
||||||
import FeedbackPage from "./pages/FeedbackPage";
|
import FeedbackPage from "./pages/FeedbackPage";
|
||||||
|
import ManageFeedbacksPage from "./pages/ManageFeedbacksPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -91,6 +92,7 @@ function App() {
|
|||||||
<Route path="/admin" element={<AdministratorLayout />}>
|
<Route path="/admin" element={<AdministratorLayout />}>
|
||||||
<Route index element={<AdministratorSpringboard />} />
|
<Route index element={<AdministratorSpringboard />} />
|
||||||
<Route path="manage-account" element={<ManageUserAccountPage />} />
|
<Route path="manage-account" element={<ManageUserAccountPage />} />
|
||||||
|
<Route path="manage-feedbacks" element={<ManageFeedbacksPage />} />
|
||||||
<Route path="users-management">
|
<Route path="users-management">
|
||||||
<Route index element={<UsersManagement />} />
|
<Route index element={<UsersManagement />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ export default function AdministratorNavigationPanel() {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<div
|
<div
|
||||||
className={`fixed transition-all ${isScrolled ? "top-2" : "top-10"
|
className={`fixed transition-all ${
|
||||||
|
isScrolled ? "top-2" : "top-10"
|
||||||
} left-2`}
|
} left-2`}
|
||||||
>
|
>
|
||||||
<div className="bg-white rounded-full z-40">
|
<div className="bg-white rounded-full z-40">
|
||||||
@@ -85,14 +86,17 @@ export default function AdministratorNavigationPanel() {
|
|||||||
|
|
||||||
{/* Panel */}
|
{/* Panel */}
|
||||||
<div
|
<div
|
||||||
className={`h-full transition-all z-50 ${panelVisible
|
className={`h-full transition-all z-50 ${
|
||||||
|
panelVisible
|
||||||
? "scale-100 opacity-100 w-[300px] px-2"
|
? "scale-100 opacity-100 w-[300px] px-2"
|
||||||
: "w-0 scale-[98%] opacity-0 px-0"
|
: "w-0 scale-[98%] opacity-0 px-0"
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={`fixed h-full transition-all z-50 ${isScrolled ? "pb-2 -mt-8" : "pb-10"
|
className={`fixed h-full transition-all z-50 ${
|
||||||
} ${panelVisible
|
isScrolled ? "pb-2 -mt-8" : "pb-10"
|
||||||
|
} ${
|
||||||
|
panelVisible
|
||||||
? "scale-100 opacity-100 w-[300px] p-2"
|
? "scale-100 opacity-100 w-[300px] p-2"
|
||||||
: "w-0 scale-[98%] opacity-0 p-0"
|
: "w-0 scale-[98%] opacity-0 p-0"
|
||||||
}`}
|
}`}
|
||||||
@@ -196,7 +200,7 @@ export default function AdministratorNavigationPanel() {
|
|||||||
<AdministratorNavigationPanelNavigationButton
|
<AdministratorNavigationPanelNavigationButton
|
||||||
text="Feedbacks"
|
text="Feedbacks"
|
||||||
icon={<ChatBubbleOvalLeftIcon />}
|
icon={<ChatBubbleOvalLeftIcon />}
|
||||||
onClickRef="#"
|
onClickRef="manage-feedbacks"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -544,15 +544,17 @@ export const TrashDeleteIcon = () => {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none" viewBox="0 0 24 24"
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="size-6">
|
className="size-6"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -563,13 +565,13 @@ export const EmailIcon = () => {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="size-6"
|
className="size-6"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"
|
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -582,13 +584,13 @@ export const InfoIcon = () => {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="size-6"
|
className="size-6"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -601,13 +603,13 @@ export const TrophyIcon = () => {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="size-6"
|
className="size-6"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 0 1-.982-3.172M9.497 14.25a7.454 7.454 0 0 0 .981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 0 0 7.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 0 0 2.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 0 1 2.916.52 6.003 6.003 0 0 1-5.395 4.972m0 0a6.726 6.726 0 0 1-2.749 1.35m0 0a6.772 6.772 0 0 1-3.044 0"
|
d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 0 1-.982-3.172M9.497 14.25a7.454 7.454 0 0 0 .981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 0 0 7.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 0 0 2.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 0 1 2.916.52 6.003 6.003 0 0 1-5.395 4.972m0 0a6.726 6.726 0 0 1-2.749 1.35m0 0a6.772 6.772 0 0 1-3.044 0"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -651,3 +653,41 @@ export const ChevronDoubleDownIcon = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CheckmarkIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="m4.5 12.75 6 6 9-13.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BookOpenIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function FeedbackPage() {
|
|||||||
comment: Yup.string()
|
comment: Yup.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(1024)
|
.max(2048)
|
||||||
.required("Enter your comments."),
|
.required("Enter your comments."),
|
||||||
allowContact: Yup.boolean().oneOf([true, false], "please decide"),
|
allowContact: Yup.boolean().oneOf([true, false], "please decide"),
|
||||||
});
|
});
|
||||||
|
|||||||
246
client/src/pages/ManageFeedbacksPage.tsx
Normal file
246
client/src/pages/ManageFeedbacksPage.tsx
Normal file
@@ -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<any>([]);
|
||||||
|
const [viewFeedbackModalOpened, setViewFeedbackModalOpened] = useState(false);
|
||||||
|
const [viewingFeedback, setViewingFeedback] = useState<any>();
|
||||||
|
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 (
|
||||||
|
<div className="flex flex-col gap-8 p-8">
|
||||||
|
<p className="text-4xl font-bold">Manage Feedbacks</p>
|
||||||
|
<Table aria-label="User Information Table">
|
||||||
|
<TableHeader columns={columns}>
|
||||||
|
{(column) => (
|
||||||
|
<TableColumn key={column.key}>{column.label}</TableColumn>
|
||||||
|
)}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody items={feedbacksList}>
|
||||||
|
{(feedbackEntry: any) => (
|
||||||
|
<TableRow key={feedbackEntry.id}>
|
||||||
|
{(columnKey) => (
|
||||||
|
<TableCell>
|
||||||
|
{(() => {
|
||||||
|
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 <Chip>{result}</Chip>;
|
||||||
|
case "subject":
|
||||||
|
return (
|
||||||
|
<p className="w-max max-w-60 font-bold">
|
||||||
|
{getKeyValue(feedbackEntry, columnKey)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case "comment":
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
<p className="flex-grow line-clamp-1 max-h-4 overflow-hidden overflow-ellipsis">
|
||||||
|
{getKeyValue(feedbackEntry, columnKey)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "createdAt":
|
||||||
|
let creationDate = Date.parse(
|
||||||
|
getKeyValue(feedbackEntry, columnKey)
|
||||||
|
);
|
||||||
|
let timing = Math.floor(
|
||||||
|
(Date.now() - creationDate) / (1000 * 60 * 60 * 24)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
{timing <= 0
|
||||||
|
? "Today"
|
||||||
|
: `${timing} day${timing == 1 ? "" : "s"} ago`}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case "actions":
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
startContent={
|
||||||
|
<div className="scale-80">
|
||||||
|
<BookOpenIcon />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
viewFeedback(getKeyValue(feedbackEntry, "id"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="success"
|
||||||
|
variant="faded"
|
||||||
|
startContent={
|
||||||
|
<div className="scale-80">
|
||||||
|
<CheckmarkIcon />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
instance.delete(
|
||||||
|
`${
|
||||||
|
config.serverAddress
|
||||||
|
}/feedback/${getKeyValue(
|
||||||
|
feedbackEntry,
|
||||||
|
"id"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Resolve
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <p>{getKeyValue(feedbackEntry, columnKey)}</p>;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<Modal
|
||||||
|
isOpen={viewFeedbackModalOpened}
|
||||||
|
onOpenChange={setViewFeedbackModalOpened}
|
||||||
|
size="3xl"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => {
|
||||||
|
return (
|
||||||
|
viewingFeedback && (
|
||||||
|
<>
|
||||||
|
<ModalHeader>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<p>"{viewingFeedback.subject}"</p>
|
||||||
|
<p className="font-normal text-sm opacity-50">
|
||||||
|
Submitted on{" "}
|
||||||
|
{new Date(viewingFeedback.createdAt).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<div>
|
||||||
|
<Textarea
|
||||||
|
label="Comment"
|
||||||
|
size="lg"
|
||||||
|
disabled
|
||||||
|
value={viewingFeedback.comment}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
{viewingFeedback.allowContact == 1 && (
|
||||||
|
<Button
|
||||||
|
startContent={<EmailIcon />}
|
||||||
|
onPress={() => {
|
||||||
|
retrieveUserInformationById(
|
||||||
|
viewingFeedback.userId
|
||||||
|
).then((value) => {
|
||||||
|
window.location.href = `mailto:${value.email}`;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get in touch
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button onPress={onClose}>Close</Button>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ module.exports = (sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
comment: {
|
comment: {
|
||||||
type: DataTypes.STRING(1024),
|
type: DataTypes.STRING(2048),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ const express = require("express");
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const yup = require("yup");
|
const yup = require("yup");
|
||||||
const { validateToken } = require("../middlewares/auth");
|
const { validateToken } = require("../middlewares/auth");
|
||||||
const {Feedback} = require("../models");
|
const { Feedback } = require("../models");
|
||||||
|
|
||||||
let validationSchema = yup.object({
|
let validationSchema = yup.object({
|
||||||
userId: yup.string().trim().min(36).max(36).required(),
|
userId: yup.string().trim().min(36).max(36).required(),
|
||||||
feedbackCategory: yup.number().min(0).max(2).required(),
|
feedbackCategory: yup.number().min(0).max(2).required(),
|
||||||
allowContact: yup.boolean().required(),
|
allowContact: yup.boolean().required(),
|
||||||
subject: yup.string().trim().min(1).max(100).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) => {
|
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;
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user