From 0a9426b128afcd50c944bf3af298d428c88c1535 Mon Sep 17 00:00:00 2001 From: wrachel Date: Mon, 20 May 2024 00:38:24 -0700 Subject: [PATCH 01/13] past events done --- .../src/app/(web app)/past-events/page.tsx | 34 ++++- .../src/app/admin/pageeditor/about/page.tsx | 6 +- .../src/app/admin/pageeditor/events/page.tsx | 4 +- .../app/admin/pageeditor/involved/page.tsx | 4 +- .../pageeditor/pastevents/page.module.css | 20 +++ .../app/admin/pageeditor/pastevents/page.tsx | 139 ++++++++++++++++++ 6 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 frontend/src/app/admin/pageeditor/pastevents/page.module.css create mode 100644 frontend/src/app/admin/pageeditor/pastevents/page.tsx diff --git a/frontend/src/app/(web app)/past-events/page.tsx b/frontend/src/app/(web app)/past-events/page.tsx index 5ee4a65e..5e3e6f42 100644 --- a/frontend/src/app/(web app)/past-events/page.tsx +++ b/frontend/src/app/(web app)/past-events/page.tsx @@ -1,8 +1,10 @@ "use client"; import React, { useEffect, useState } from "react"; +import { getPageText } from "../../../api/pageeditor"; import EventsList from "../../../components/EventsList"; + import styles from "./page.module.css"; import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; @@ -10,6 +12,9 @@ import BackgroundHeader from "@/components/BackgroundHeader"; export default function PastEvents() { const [images, setImages] = useState([]); + const [phSubtitle, setPhSubtitle] = useState(""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); useEffect(() => { getBackgroundImages(BackgroundImagePages.TEAM) @@ -23,22 +28,37 @@ export default function PastEvents() { }); }, []); + let pageText; + useEffect(() => { + getPageText("Past Events") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); + + return (
image.imageURI)} header="GET INVOLVED" title="Past Events" - description="Lorem ipsum dolor sit amet consectetur. Et vestibulum enim nunc ultrices. Donec blandit sollicitudin vitae integer mauris sed. Mattis duis id viverra suscipit morbi." + description={phSubtitle} />
-

Explore our Past Events

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque necessitatibus - asperiores, optio quasi sit tempora in amet aut natus, similique enim explicabo id - expedita minima doloribus repellendus est? Quos, officia? -

+

{s1Subtitle}

+

{s1Text}

diff --git a/frontend/src/app/admin/pageeditor/about/page.tsx b/frontend/src/app/admin/pageeditor/about/page.tsx index d8afddc3..6a657fe9 100644 --- a/frontend/src/app/admin/pageeditor/about/page.tsx +++ b/frontend/src/app/admin/pageeditor/about/page.tsx @@ -11,9 +11,7 @@ import CancelButton from "@/components/CancelButton"; import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; -// import PageEditorCard from "@/components/PageEditorCard"; - -export default function Dashboard() { +export default function AboutEditor() { const [isEdited, setIsEdited] = useState(false); const [phSubtitle, setPhSubtitle] = useState(""); const [s1Subtitle, setS1Subtitle] = useState(""); @@ -185,4 +183,4 @@ export default function Dashboard() {
); -} +} \ No newline at end of file diff --git a/frontend/src/app/admin/pageeditor/events/page.tsx b/frontend/src/app/admin/pageeditor/events/page.tsx index 5c2505ab..f13e5bbb 100644 --- a/frontend/src/app/admin/pageeditor/events/page.tsx +++ b/frontend/src/app/admin/pageeditor/events/page.tsx @@ -141,8 +141,8 @@ export default function EventsPageEditor() { diff --git a/frontend/src/app/admin/pageeditor/involved/page.tsx b/frontend/src/app/admin/pageeditor/involved/page.tsx index ca159865..50ab279a 100644 --- a/frontend/src/app/admin/pageeditor/involved/page.tsx +++ b/frontend/src/app/admin/pageeditor/involved/page.tsx @@ -143,8 +143,8 @@ export default function Dashboard() { diff --git a/frontend/src/app/admin/pageeditor/pastevents/page.module.css b/frontend/src/app/admin/pageeditor/pastevents/page.module.css new file mode 100644 index 00000000..ecf92d9b --- /dev/null +++ b/frontend/src/app/admin/pageeditor/pastevents/page.module.css @@ -0,0 +1,20 @@ +.page { + padding: 90px 80px 170px 300px; +} + +.sectionContainer { + background-color: white; + width: 100%; + display: flex; + flex-direction: column; + gap: 32px; + padding: 32px; +} + +.buttonContainer { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 24px; +} + \ No newline at end of file diff --git a/frontend/src/app/admin/pageeditor/pastevents/page.tsx b/frontend/src/app/admin/pageeditor/pastevents/page.tsx new file mode 100644 index 00000000..5052d7a8 --- /dev/null +++ b/frontend/src/app/admin/pageeditor/pastevents/page.tsx @@ -0,0 +1,139 @@ +"use client"; +import React, { useEffect, useState } from "react"; + +import styles from "./page.module.css"; + +import { getPageText, updatePage } from "../../../../api/pageeditor"; +import Button from "@/components/Button"; +import CancelButton from "@/components/CancelButton"; +import Collapsable from "@/components/Collapsable"; +import PageToggle from "@/components/PageToggle"; + +// import PageEditorCard from "@/components/PageEditorCard"; + +export default function Dashboard() { + const [isEdited, setIsEdited] = useState(false); + const [phSubtitle, setPhSubtitle] = useState(""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); + + /* Get page data from MongoDB */ + let pageText; + useEffect(() => { + getPageText("Past Events") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); + + /* Handle Fields upon edit */ + const handleEdit = (event: React.ChangeEvent) => { + setIsEdited(true); + if (event.target.id === "Page Header: Subtitle") { + setPhSubtitle(event.target.value); + } else if (event.target.id === "Section 1: Section Title") { + setS1Subtitle(event.target.value); + } else if (event.target.id === "Section 1: Section Subtitle") { + setS1Text(event.target.value); + } + }; + + + const handleSave = () => { + // Implement save logic + if (isEdited) { + console.log("Save changes"); + updatePage({ + //Pass edited text to MongoDB + page: "Past Events", + pageSections: [ + { + subtitle: phSubtitle, + }, + { + sectionTitle: s1Subtitle, + sectionSubtitle: s1Text, + }, + ], + }) + .then((response) => { + if (response.success) { + alert("Success!"); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + } + }; + + const handleCancel = () => { + // Implement cancel logic + if (isEdited) { + console.log("Cancel changes"); + getPageText("Past Events") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + } + }; + + return ( +
+ +
+ + + +
+ +
+
+
+ ); +} From 6a8e314a8fb0b6997e3efd9f92ac0da96c25a006 Mon Sep 17 00:00:00 2001 From: wrachel Date: Mon, 20 May 2024 01:17:52 -0700 Subject: [PATCH 02/13] start our team page --- .../src/app/(web app)/past-events/page.tsx | 2 +- frontend/src/app/(web app)/team/page.tsx | 37 +++++-- .../pastevents/page.module.css | 0 .../pastevents/page.tsx | 0 .../src/app/admin/page-editor/team/page.tsx | 99 ++++++++++++++++--- 5 files changed, 116 insertions(+), 22 deletions(-) rename frontend/src/app/admin/{pageeditor => page-editor}/pastevents/page.module.css (100%) rename frontend/src/app/admin/{pageeditor => page-editor}/pastevents/page.tsx (100%) diff --git a/frontend/src/app/(web app)/past-events/page.tsx b/frontend/src/app/(web app)/past-events/page.tsx index 5e3e6f42..e16c2262 100644 --- a/frontend/src/app/(web app)/past-events/page.tsx +++ b/frontend/src/app/(web app)/past-events/page.tsx @@ -64,4 +64,4 @@ export default function PastEvents() { ); -} +} \ No newline at end of file diff --git a/frontend/src/app/(web app)/team/page.tsx b/frontend/src/app/(web app)/team/page.tsx index 6d6db08e..d317f779 100644 --- a/frontend/src/app/(web app)/team/page.tsx +++ b/frontend/src/app/(web app)/team/page.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useState } from "react"; +import { getPageText } from "../../../api/pageeditor"; + import styles from "./page.module.css"; import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; @@ -9,9 +11,13 @@ import { Member, getAllMembers } from "@/api/member"; import BackgroundHeader from "@/components/BackgroundHeader"; import MemberInfo from "@/components/MemberInfo"; + export default function Team() { const [members, setMembers] = useState([]); const [images, setImages] = useState([]); + const [phSubtitle, setPhSubtitle] = useState(""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); useEffect(() => { getBackgroundImages(BackgroundImagePages.TEAM) @@ -39,25 +45,36 @@ export default function Team() { alert(error); }); }, []); + + let pageText; + useEffect(() => { + getPageText("Our Team") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); return (
image.imageURI)} header="OUR TEAM" title="Meet Our Team" - description="Lorem ipsum dolor sit amet consectetur. Et vestibulum enim nunc ultrices. Donec blandit - sollicitudin vitae integer mauris sed. Mattis duis id viverra suscipit morbi." + description={phSubtitle} />
-
Our Team
+
{s1Subtitle}
{/*
Hello.
*/} -

- Our dedicated team @ 4 Future Leaders of Tomorrow is a non-profit charitable organization - committed in preventing and ending homelessness, hunger and disparity in underprivileged - communities. Everyone deserves a chance for a better future!. We are reaching out by - providing resources in needed communities - whether it be a delicious meal, warm clothing, - educational supplies, referrals, toys or even bus passes -

+

{s1Text}

{members.map((member) => ( diff --git a/frontend/src/app/admin/pageeditor/pastevents/page.module.css b/frontend/src/app/admin/page-editor/pastevents/page.module.css similarity index 100% rename from frontend/src/app/admin/pageeditor/pastevents/page.module.css rename to frontend/src/app/admin/page-editor/pastevents/page.module.css diff --git a/frontend/src/app/admin/pageeditor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx similarity index 100% rename from frontend/src/app/admin/pageeditor/pastevents/page.tsx rename to frontend/src/app/admin/page-editor/pastevents/page.tsx diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index b22b9485..5c47b4fb 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -1,8 +1,10 @@ "use client"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; +import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "./page.module.css"; + import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; import Collapsable from "@/components/Collapsable"; @@ -12,20 +14,92 @@ import PageToggle from "@/components/PageToggle"; export default function TeamEditor() { const [isEdited, setIsEdited] = useState(false); - const handleEdit = () => { + const [phSubtitle, setPhSubtitle] = useState(""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); + + /* Get page data from MongoDB */ + let pageText; + useEffect(() => { + getPageText("Our Team") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); + + /* Handle Fields upon edit */ + const handleEdit = (event: React.ChangeEvent) => { setIsEdited(true); + if (event.target.id === "Page Header: Subtitle") { + setPhSubtitle(event.target.value); + } else if (event.target.id === "Section 1: Section Title") { + setS1Subtitle(event.target.value); + } else if (event.target.id === "Section 1: Body Text") { + setS1Text(event.target.value); + } }; const handleSave = () => { // Implement save logic - console.log("Save changes"); - setIsEdited(false); + if (isEdited) { + console.log("Our Team"); + updatePage({ + //Pass edited text to MongoDB + page: "Our Team", + pageSections: [ + { + subtitle: phSubtitle, + }, + { + sectionTitle: s1Subtitle, + sectionSubtitle: s1Text, + }, + ], + }) + .then((response) => { + if (response.success) { + alert("Success!"); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + } }; - const handleCancel = () => { // Implement cancel logic - console.log("Cancel changes"); - setIsEdited(false); + if (isEdited) { + console.log("Cancel changes"); + getPageText("Our Team") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + } }; return ( @@ -37,13 +111,16 @@ export default function TeamEditor() { refreshPage={true} />
+ Date: Mon, 20 May 2024 17:42:02 -0700 Subject: [PATCH 03/13] Alertbanner and Warnings for all pages --- .../src/app/admin/page-editor/about/page.tsx | 73 ++++--- .../admin/page-editor/home/page.module.css | 16 ++ .../src/app/admin/page-editor/home/page.tsx | 79 +++++--- .../src/app/admin/page-editor/impact/page.tsx | 77 +++++--- .../admin/page-editor/mission/page.module.css | 16 ++ .../app/admin/page-editor/mission/page.tsx | 89 ++++++--- .../app/admin/page-editor/newsletter/page.tsx | 75 ++++++-- .../page-editor/pastevents/page.module.css | 50 +++-- .../app/admin/page-editor/pastevents/page.tsx | 126 +++++++----- .../src/app/admin/page-editor/team/page.tsx | 88 ++++++--- .../page-editor/testimonials/page.module.css | 26 +++ .../admin/page-editor/testimonials/page.tsx | 179 ++++++++++++++++++ 12 files changed, 693 insertions(+), 201 deletions(-) create mode 100644 frontend/src/app/admin/page-editor/testimonials/page.module.css create mode 100644 frontend/src/app/admin/page-editor/testimonials/page.tsx diff --git a/frontend/src/app/admin/page-editor/about/page.tsx b/frontend/src/app/admin/page-editor/about/page.tsx index 6a657fe9..12bcfd96 100644 --- a/frontend/src/app/admin/page-editor/about/page.tsx +++ b/frontend/src/app/admin/page-editor/about/page.tsx @@ -10,6 +10,7 @@ import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; +import { WarningModule } from "@/components/WarningModule"; export default function AboutEditor() { const [isEdited, setIsEdited] = useState(false); @@ -22,6 +23,7 @@ export default function AboutEditor() { const [s3Text, setS3Text] = useState(""); const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); /* Get page data from MongoDB */ let pageText; @@ -94,7 +96,7 @@ export default function AboutEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -104,34 +106,41 @@ export default function AboutEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("About Us") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); - setS2Text(pageText.pageSections[2].sectionSubtitle ?? ""); - setS3Subtitle(pageText.pageSections[3].sectionTitle ?? ""); - setS3Text(pageText.pageSections[3].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("About Us") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); + setS2Text(pageText.pageSections[2].sectionSubtitle ?? ""); + setS3Subtitle(pageText.pageSections[3].sectionTitle ?? ""); + setS3Text(pageText.pageSections[3].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + const handleCloseAlert = () => { setShowAlert(false); }; @@ -141,6 +150,24 @@ export default function AboutEditor() {
{showAlert && }
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
); -} \ No newline at end of file +} diff --git a/frontend/src/app/admin/page-editor/home/page.module.css b/frontend/src/app/admin/page-editor/home/page.module.css index e6d5066d..c3ac0e89 100644 --- a/frontend/src/app/admin/page-editor/home/page.module.css +++ b/frontend/src/app/admin/page-editor/home/page.module.css @@ -24,3 +24,19 @@ top: 58px; left: 872px; } + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} diff --git a/frontend/src/app/admin/page-editor/home/page.tsx b/frontend/src/app/admin/page-editor/home/page.tsx index 06fce7c2..33cbb1d7 100644 --- a/frontend/src/app/admin/page-editor/home/page.tsx +++ b/frontend/src/app/admin/page-editor/home/page.tsx @@ -5,12 +5,12 @@ import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "./page.module.css"; +import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; - -// import PageEditorCard from "@/components/PageEditorCard"; +import { WarningModule } from "@/components/WarningModule"; export default function HomeEditor() { const [isEdited, setIsEdited] = useState(false); @@ -20,6 +20,9 @@ export default function HomeEditor() { const [s2Subtitle, setS2Subtitle] = useState(""); const [s2Text, setS2Text] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + /* Get page data from MongoDB */ let pageText; useEffect(() => { @@ -81,7 +84,7 @@ export default function HomeEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -91,34 +94,66 @@ export default function HomeEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Home") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); - setS2Text(pageText.pageSections[2].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Home") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); + setS2Text(pageText.pageSections[2].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
(""); const [s1Subtitle, setS1Subtitle] = useState(""); const [s2Subtitle, setS2Subtitle] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + /* Get page data from MongoDB */ let pageText; useEffect(() => { @@ -73,7 +74,7 @@ export default function ImpactEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -83,32 +84,64 @@ export default function ImpactEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Our Impact") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Our Impact") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionTitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
(""); const [s1Subtitle, setS1Subtitle] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + /* Get page data from MongoDB */ let pageText; useEffect(() => { @@ -112,7 +115,7 @@ export default function MissionEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -122,39 +125,71 @@ export default function MissionEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Our Mission") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setvalueSubtitle(pageText.pageSections[1].subtitle ?? ""); - setValue1(pageText.pageSections[2].sectionTitle ?? ""); - setValue1_Description(pageText.pageSections[2].sectionSubtitle ?? ""); - setValue2(pageText.pageSections[3].sectionTitle ?? ""); - setValue2_Description(pageText.pageSections[3].sectionSubtitle ?? ""); - setValue3(pageText.pageSections[4].sectionTitle ?? ""); - setValue3_Description(pageText.pageSections[4].sectionSubtitle ?? ""); - setS1Subtitle(pageText.pageSections[5].sectionTitle ?? ""); - setS1Text(pageText.pageSections[5].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Our Mission") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setvalueSubtitle(pageText.pageSections[1].subtitle ?? ""); + setValue1(pageText.pageSections[2].sectionTitle ?? ""); + setValue1_Description(pageText.pageSections[2].sectionSubtitle ?? ""); + setValue2(pageText.pageSections[3].sectionTitle ?? ""); + setValue2_Description(pageText.pageSections[3].sectionSubtitle ?? ""); + setValue3(pageText.pageSections[4].sectionTitle ?? ""); + setValue3_Description(pageText.pageSections[4].sectionSubtitle ?? ""); + setS1Subtitle(pageText.pageSections[5].sectionTitle ?? ""); + setS1Text(pageText.pageSections[5].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
(""); const [s1Text, setS1Text] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + /* Get page data from MongoDB */ let pageText; useEffect(() => { @@ -68,7 +71,7 @@ export default function NewsletterEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -78,32 +81,64 @@ export default function NewsletterEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Newsletter") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Newsletter") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
(""); const [s1Text, setS1Text] = useState(""); + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + /* Get page data from MongoDB */ - let pageText; - useEffect(() => { - getPageText("Past Events") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - }, []); + let pageText; + useEffect(() => { + getPageText("Past Events") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); /* Handle Fields upon edit */ const handleEdit = (event: React.ChangeEvent) => { @@ -45,10 +49,9 @@ export default function Dashboard() { setS1Subtitle(event.target.value); } else if (event.target.id === "Section 1: Section Subtitle") { setS1Text(event.target.value); - } + } }; - const handleSave = () => { // Implement save logic if (isEdited) { @@ -68,7 +71,7 @@ export default function Dashboard() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -78,33 +81,64 @@ export default function Dashboard() { }); setIsEdited(false); } + setWarningOpen(false); }; const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Past Events") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Past Events") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
- +
(""); const [s1Text, setS1Text] = useState(""); - /* Get page data from MongoDB */ + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + + /* Get page data from MongoDB */ let pageText; useEffect(() => { getPageText("Our Team") @@ -37,8 +39,8 @@ export default function TeamEditor() { }); }, []); - /* Handle Fields upon edit */ - const handleEdit = (event: React.ChangeEvent) => { + /* Handle Fields upon edit */ + const handleEdit = (event: React.ChangeEvent) => { setIsEdited(true); if (event.target.id === "Page Header: Subtitle") { setPhSubtitle(event.target.value); @@ -46,7 +48,7 @@ export default function TeamEditor() { setS1Subtitle(event.target.value); } else if (event.target.id === "Section 1: Body Text") { setS1Text(event.target.value); - } + } }; const handleSave = () => { @@ -68,7 +70,7 @@ export default function TeamEditor() { }) .then((response) => { if (response.success) { - alert("Success!"); + setShowAlert(true); } else { alert(response.error); } @@ -78,32 +80,64 @@ export default function TeamEditor() { }); setIsEdited(false); } + setWarningOpen(false); }; + const handleCancel = () => { - // Implement cancel logic + // Show cancel warning if (isEdited) { - console.log("Cancel changes"); - getPageText("Our Team") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); + setWarningOpen(true); } }; + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Our Team") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + return (
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
- (""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); + + const [showAlert, setShowAlert] = useState(false); + const [warningOpen, setWarningOpen] = useState(false); + + /* Get page data from MongoDB */ + let pageText; + useEffect(() => { + getPageText("Testimonials") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); + + /* Handle Fields upon edit */ + const handleEdit = (event: React.ChangeEvent) => { + setIsEdited(true); + if (event.target.id === "Page Header: Subtitle") { + setPhSubtitle(event.target.value); + } else if (event.target.id === "Section 1: Section Title") { + setS1Subtitle(event.target.value); + } else if (event.target.id === "Section 1: Body Text") { + setS1Text(event.target.value); + } + }; + + const handleSave = () => { + // Implement save logic + if (isEdited) { + console.log("Testimonials"); + updatePage({ + //Pass edited text to MongoDB + page: "Testimonials", + pageSections: [ + { + subtitle: phSubtitle, + }, + { + sectionTitle: s1Subtitle, + sectionSubtitle: s1Text, + }, + ], + }) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + } + setWarningOpen(false); + }; + + const handleCancel = () => { + // Show cancel warning + if (isEdited) { + setWarningOpen(true); + } + }; + + const confirmCancel = () => { + // Implement cancel logic + setWarningOpen(false); + console.log("Cancel changes"); + getPageText("Testimonials") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + setIsEdited(false); + }; + + const handleCloseAlert = () => { + setShowAlert(false); + }; + + return ( +
+
+ {showAlert && } +
+
+ {warningOpen &&
} +
+ {warningOpen && ( + { + setWarningOpen(false); + }} + /> + )} +
+
+ +
+ + + + +
+ +
+
+
+ ); +} From 9670ce30bc2788e6e07f403f668b0aea23932db4 Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Wed, 22 May 2024 04:45:39 -0700 Subject: [PATCH 04/13] Testimonials Page Editor --- backend/src/controllers/testimonial.ts | 39 +++++ backend/src/routes/testimonial.ts | 7 + frontend/src/api/testimonial.ts | 44 +++++- .../src/app/(web app)/testimonials/page.tsx | 47 ++++-- .../admin/page-editor/impact/page.module.css | 24 +++ .../page-editor/involved/page.module.css | 1 + .../admin/page-editor/mission/page.module.css | 1 + .../page-editor/newsletter/page.module.css | 24 +++ .../page-editor/pastevents/page.module.css | 1 + .../app/admin/page-editor/pastevents/page.tsx | 2 +- .../admin/page-editor/team/page.module.css | 17 ++ .../src/app/admin/page-editor/team/page.tsx | 1 + .../page-editor/testimonials/page.module.css | 35 +++++ .../admin/page-editor/testimonials/page.tsx | 145 ++++++++++++++++-- .../src/components/Collapsable.module.css | 7 +- frontend/src/components/Collapsable.tsx | 40 ++++- 16 files changed, 405 insertions(+), 30 deletions(-) diff --git a/backend/src/controllers/testimonial.ts b/backend/src/controllers/testimonial.ts index 62acb20e..21237ee5 100644 --- a/backend/src/controllers/testimonial.ts +++ b/backend/src/controllers/testimonial.ts @@ -1,5 +1,7 @@ import { RequestHandler } from "express"; import TestimonialModel from "src/models/testimonial"; +import { validationResult } from "express-validator"; +import validationErrorParser from "src/util/validationErrorParser"; export const createTestimonial: RequestHandler = async (req, res, next) => { const { title, description, image, type } = req.body; @@ -26,3 +28,40 @@ export const getAllTestimonials: RequestHandler = async (req, res, next) => { next(error); } }; + +export const getAllQuotes: RequestHandler = async (req, res, next) => { + try { + const testimonials = await TestimonialModel.find({ type: "quote" }); + res.status(200).json(testimonials); + } catch (error) { + next(error); + } +}; + +export const updateTestimonial: RequestHandler = async (req, res, next) => { + const errors = validationResult(req); + const { id } = req.params; + + if (id !== req.body._id) { + // If the _id in the URL does not match the _id in the body, bad request + res.status(400); + } + + try { + validationErrorParser(errors); + + const testimonial = await TestimonialModel.findByIdAndUpdate(id, req.body); + if (testimonial === null) { + // No newsletter found + res.status(404); + } + const updatedTestimonial = await TestimonialModel.findById(id); + if (updatedTestimonial === null) { + // No testimonial found, something went wrong + res.status(404); + } + res.status(200).json(updatedTestimonial); + } catch (error) { + next(error); + } +}; diff --git a/backend/src/routes/testimonial.ts b/backend/src/routes/testimonial.ts index 9bdb5c85..20c356ca 100644 --- a/backend/src/routes/testimonial.ts +++ b/backend/src/routes/testimonial.ts @@ -1,9 +1,16 @@ import express from "express"; import * as TestimonialController from "src/controllers/testimonial"; +import * as TestimonialValidator from "src/validators/testimonial"; const router = express.Router(); router.get("/get", TestimonialController.getAllTestimonials); +router.get("/get/quote", TestimonialController.getAllQuotes); router.post("/post", TestimonialController.createTestimonial); +router.put( + "/:id", // getNewsletter validator works to just check ID + TestimonialValidator.updateTestimonial, + TestimonialController.updateTestimonial, +); export default router; diff --git a/frontend/src/api/testimonial.ts b/frontend/src/api/testimonial.ts index fef271b2..46bfdcb2 100644 --- a/frontend/src/api/testimonial.ts +++ b/frontend/src/api/testimonial.ts @@ -1,4 +1,4 @@ -import { get, handleAPIError } from "./requests"; +import { get, handleAPIError, post, put } from "./requests"; import type { APIResult } from "./requests"; export type Testimonial = { @@ -18,3 +18,45 @@ export async function getAllTestimonials(): Promise> { return handleAPIError(error); } } + +export async function getAllQuotes(): Promise> { + try { + const response = await get(`/api/testimonial/get/quote`); + const json = (await response.json()) as Testimonial[]; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} + +export type CreateTestimonialRequest = { + title: string; + description: string; + image: string; + type: string; +}; + +export async function createTestimonial( + testimonial: CreateTestimonialRequest, +): Promise> { + try { + const response = await post(`/api/testimonial/post`, testimonial); + const data = (await response.json()) as Testimonial; + return { success: true, data }; + } catch (error) { + return handleAPIError(error); + } +} + +export async function updateTestimonial(testimonial: Testimonial): Promise> { + try { + const id = testimonial._id; + const response = await put(`/api/testimonial/${id}`, testimonial, { + "Content-Type": "application/json", + }); + const json = (await response.json()) as Testimonial; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/app/(web app)/testimonials/page.tsx b/frontend/src/app/(web app)/testimonials/page.tsx index 7b204493..b8f060f9 100644 --- a/frontend/src/app/(web app)/testimonials/page.tsx +++ b/frontend/src/app/(web app)/testimonials/page.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; +import { getPageText } from "../../../api/pageeditor"; import { Testimonial, getAllTestimonials } from "../../../api/testimonial"; import TestimonialCard from "../../../components/TestimonialCard"; @@ -15,6 +16,12 @@ export default function Impact() { const [events, setEvents] = useState([]); const [images, setImages] = useState([]); + const [phSubtitle, setPhSubtitle] = useState(""); + const [s1Subtitle, setS1Subtitle] = useState(""); + const [s1Text, setS1Text] = useState(""); + const [s2Title, setS2Title] = useState(""); + const [s2Subtitle, setS2Subtitle] = useState(""); + useEffect(() => { getBackgroundImages(BackgroundImagePages.HOME) .then((result) => { @@ -44,6 +51,26 @@ export default function Impact() { }); }, []); + let pageText; + useEffect(() => { + getPageText("Testimonials") + .then((response) => { + if (response.success) { + pageText = response.data; + setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); + setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); + setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + setS2Title(pageText.pageSections[2].sectionTitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + }, []); + return (
image.imageURI)} header="OUR IMPACT" title="Testimonials" - description="4FLOT is committed in preventing and ending homelessness, hunger and disparity in underprivileged communities. " + description={phSubtitle} />
-
Read Our Stories
-
- A nonprofit is as strong as the community that holds it up. Together, we can do more - than we can do alone. Let's bring our abilities and passions together to make real - change. Your donations will help feed and clothes our underprivileged and underserved - communities. -
+
{s1Subtitle}
+
{s1Text}
{testimonials.map((testimonial) => ( @@ -80,13 +102,8 @@ export default function Impact() { ))}
-
Where We've Been
-
- A nonprofit is as strong as the community that holds it up. Together, we can do more - than we can do alone. Let's bring our abilities and passions together to make real - change. Your donations will help feed and clothes our underprivileged and underserved - communities. -
+
{s2Title}
+
{s2Subtitle}
diff --git a/frontend/src/app/admin/page-editor/impact/page.module.css b/frontend/src/app/admin/page-editor/impact/page.module.css index 98f6d327..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/impact/page.module.css +++ b/frontend/src/app/admin/page-editor/impact/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { @@ -17,3 +18,26 @@ justify-content: flex-end; gap: 24px; } + +.alert { + position: fixed; + z-index: 4; + top: 58px; + left: 872px; +} + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} diff --git a/frontend/src/app/admin/page-editor/involved/page.module.css b/frontend/src/app/admin/page-editor/involved/page.module.css index c3ac0e89..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/involved/page.module.css +++ b/frontend/src/app/admin/page-editor/involved/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { diff --git a/frontend/src/app/admin/page-editor/mission/page.module.css b/frontend/src/app/admin/page-editor/mission/page.module.css index c3ac0e89..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/mission/page.module.css +++ b/frontend/src/app/admin/page-editor/mission/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { diff --git a/frontend/src/app/admin/page-editor/newsletter/page.module.css b/frontend/src/app/admin/page-editor/newsletter/page.module.css index 98f6d327..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/newsletter/page.module.css +++ b/frontend/src/app/admin/page-editor/newsletter/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { @@ -17,3 +18,26 @@ justify-content: flex-end; gap: 24px; } + +.alert { + position: fixed; + z-index: 4; + top: 58px; + left: 872px; +} + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} diff --git a/frontend/src/app/admin/page-editor/pastevents/page.module.css b/frontend/src/app/admin/page-editor/pastevents/page.module.css index c3ac0e89..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.module.css +++ b/frontend/src/app/admin/page-editor/pastevents/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { diff --git a/frontend/src/app/admin/page-editor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx index 6ee4172e..82869ae6 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.tsx +++ b/frontend/src/app/admin/page-editor/pastevents/page.tsx @@ -12,7 +12,7 @@ import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; import { WarningModule } from "@/components/WarningModule"; -export default function Dashboard() { +export default function PastEventsEditor() { const [isEdited, setIsEdited] = useState(false); const [phSubtitle, setPhSubtitle] = useState(""); const [s1Subtitle, setS1Subtitle] = useState(""); diff --git a/frontend/src/app/admin/page-editor/team/page.module.css b/frontend/src/app/admin/page-editor/team/page.module.css index e6d5066d..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/team/page.module.css +++ b/frontend/src/app/admin/page-editor/team/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { @@ -24,3 +25,19 @@ top: 58px; left: 872px; } + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index 5e0dddf6..e7b819a8 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; + import styles from "./page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/testimonials/page.module.css b/frontend/src/app/admin/page-editor/testimonials/page.module.css index e6d5066d..5ba2505f 100644 --- a/frontend/src/app/admin/page-editor/testimonials/page.module.css +++ b/frontend/src/app/admin/page-editor/testimonials/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { @@ -24,3 +25,37 @@ top: 58px; left: 872px; } + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} + +.addButton { + position: relative; + left: 30%; + + background: #f3edf9; + border: 1px dashed var(--color-primary-purple); + border-radius: 4px; + width: 42%; + + font: var(--font-body-reg); + color: var(--color-primary-purple); + padding: 9px; +} + +.addButton:hover { + border: 1px solid var(--color-primary-purple); +} diff --git a/frontend/src/app/admin/page-editor/testimonials/page.tsx b/frontend/src/app/admin/page-editor/testimonials/page.tsx index e5f1cf15..af6350c0 100644 --- a/frontend/src/app/admin/page-editor/testimonials/page.tsx +++ b/frontend/src/app/admin/page-editor/testimonials/page.tsx @@ -2,6 +2,12 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; +import { + Testimonial, + createTestimonial, + getAllQuotes, + updateTestimonial, +} from "../../../../api/testimonial"; import styles from "./page.module.css"; @@ -12,11 +18,17 @@ import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; import { WarningModule } from "@/components/WarningModule"; -export default function TeamEditor() { +export default function TestimonialsEditor() { const [isEdited, setIsEdited] = useState(false); const [phSubtitle, setPhSubtitle] = useState(""); const [s1Subtitle, setS1Subtitle] = useState(""); const [s1Text, setS1Text] = useState(""); + const [s2Title, setS2Title] = useState(""); + const [s2Subtitle, setS2Subtitle] = useState(""); + + const [testimonialData, setTestimonialData] = useState([]); //Holds all testimonials as Testimonials + const [testimonialArray, setTestimonialArray] = useState([]); //Holds all testimonials as strings + const [editedTestimonials] = useState>(new Set()); //Indices of edited testimonials const [showAlert, setShowAlert] = useState(false); const [warningOpen, setWarningOpen] = useState(false); @@ -24,6 +36,22 @@ export default function TeamEditor() { /* Get page data from MongoDB */ let pageText; useEffect(() => { + getAllQuotes() + .then((response2) => { + if (response2.success) { + setTestimonialData(response2.data); + const newArray: string[][] = []; + for (const elem of response2.data) { + newArray.push([elem.title, elem.description]); // and one new item at the end + } + setTestimonialArray(newArray); + } else { + alert(response2.error); + } + }) + .catch((error) => { + alert(error); + }); getPageText("Testimonials") .then((response) => { if (response.success) { @@ -31,6 +59,8 @@ export default function TeamEditor() { setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + setS2Title(pageText.pageSections[2].sectionTitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); } else { alert(response.error); } @@ -47,15 +77,43 @@ export default function TeamEditor() { setPhSubtitle(event.target.value); } else if (event.target.id === "Section 1: Section Title") { setS1Subtitle(event.target.value); - } else if (event.target.id === "Section 1: Body Text") { + } else if (event.target.id === "Section 1: Subtitle") { setS1Text(event.target.value); + } else if (event.target.id === "Section 2: Section Title") { + setS2Title(event.target.value); + } else if (event.target.id === "Section 2: Subtitle") { + setS2Subtitle(event.target.value); + } else if (event.target.id.startsWith("Testimonial Header")) { + const testimonialIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); + //Update textarea by changing testimonialArray element + const updateArray = testimonialArray.map((elem, index) => { + if (index === testimonialIndex) { + return [event.target.value, elem[1]]; + } else { + return elem; + } + }); + setTestimonialArray(updateArray); + editedTestimonials.add(testimonialIndex); //Add index to list of edited indices + } else if (event.target.id.startsWith("Testimonial Description")) { + const testimonialIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); + //Update textarea by changing testimonialArray element + const updateArray = testimonialArray.map((elem, index) => { + if (index === testimonialIndex) { + return [elem[0], event.target.value]; + } else { + return elem; + } + }); + setTestimonialArray(updateArray); + editedTestimonials.add(testimonialIndex); //Add index to list of edited indices } }; const handleSave = () => { // Implement save logic if (isEdited) { - console.log("Testimonials"); + console.log("Save Testimonials Page"); updatePage({ //Pass edited text to MongoDB page: "Testimonials", @@ -67,6 +125,10 @@ export default function TeamEditor() { sectionTitle: s1Subtitle, sectionSubtitle: s1Text, }, + { + sectionTitle: s2Title, + sectionSubtitle: s2Subtitle, + }, ], }) .then((response) => { @@ -79,6 +141,47 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + + if (editedTestimonials.size > 0) { + //Pass edited testimonials to MongoDB + for (const index of Array.from(editedTestimonials)) { + //If creating new testimonial + if (index >= testimonialData.length) { + createTestimonial({ + title: testimonialArray[index][0], + description: testimonialArray[index][1], + image: "/impact1.png", + type: "quote", + }) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + } else { + //If editing testimonial + //Update testimonial with edited values stored in testimonialArray + testimonialData[index].title = testimonialArray[index][0]; + testimonialData[index].description = testimonialArray[index][1]; + updateTestimonial(testimonialData[index]) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + } + } + } setIsEdited(false); } setWarningOpen(false); @@ -102,6 +205,8 @@ export default function TeamEditor() { setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); setS1Subtitle(pageText.pageSections[1].sectionTitle ?? ""); setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); + setS2Title(pageText.pageSections[2].sectionTitle ?? ""); + setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); } else { alert(response.error); } @@ -109,9 +214,26 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + if (editedTestimonials.size > 0) { + const updateArray: string[][] = []; + for (const elem of testimonialData) { + updateArray.push([elem.title, elem.description]); + } + setTestimonialArray(updateArray); + } setIsEdited(false); }; + const handleAdd = () => { + console.log("Add Testimonial"); + setTestimonialArray([ + ...testimonialArray, + ["", ""], // and one new item at the end + ]); + editedTestimonials.add(testimonialArray.length); //Add index to list of edited indices + alert(testimonialArray.length); + }; + const handleCloseAlert = () => { setShowAlert(false); }; @@ -140,9 +262,9 @@ export default function TeamEditor() {
@@ -154,14 +276,19 @@ export default function TeamEditor() { /> + diff --git a/frontend/src/components/Collapsable.module.css b/frontend/src/components/Collapsable.module.css index 63862479..c4e5817d 100644 --- a/frontend/src/components/Collapsable.module.css +++ b/frontend/src/components/Collapsable.module.css @@ -47,6 +47,7 @@ font-weight: 400; line-height: 24px; margin-bottom: 2px; + white-space: pre; } .basicInput { @@ -57,11 +58,11 @@ padding: 6px 12px; } -.imageInput { +.tabInput { font-size: 14px; - width: auto; - height: auto; + width: 100%; border: 1px solid #d8d8d8; border-radius: 4px; padding: 6px 12px; + margin-left: 33px; } diff --git a/frontend/src/components/Collapsable.tsx b/frontend/src/components/Collapsable.tsx index 9fd68ea1..ff5ce8fe 100644 --- a/frontend/src/components/Collapsable.tsx +++ b/frontend/src/components/Collapsable.tsx @@ -9,9 +9,20 @@ type CollapsableProps = { subsection: string[]; textbox: string[]; onChange: (event: React.ChangeEvent) => void; + // The following are only for numbered lists (Our Team, Testimonials) + listTitles?: string[]; + listText?: string[][]; }; -const Collapsable = ({ title, subsection, textbox, onChange }: CollapsableProps) => { +const Collapsable = ({ + title, + subsection, + textbox, + onChange, + listTitles, + listText, +}: CollapsableProps) => { + const hasNumberedList = listTitles !== null && listText !== null; const [open, setOpen] = useState(true); const toggleSection = () => { @@ -57,6 +68,33 @@ const Collapsable = ({ title, subsection, textbox, onChange }: CollapsableProps)
); })} + {hasNumberedList && ( +
+
    + {listText?.map((textArray, index) => { + let subtitle = ""; + return listTitles?.map((listTitle, innerIndex) => { + if (innerIndex === 0) { + subtitle = index + 1 + ". \t" + listTitle; + } else { + subtitle = "\t\t" + listTitle; + } + return ( +
  1. +

    {subtitle}

    + +
  2. + ); + }); + })} +
+
+ )}
)}
From 0a92247ff4644caaad8c234ccef791cc2d393567 Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Wed, 22 May 2024 05:12:51 -0700 Subject: [PATCH 05/13] file structure tweaks --- .../admin/page-editor/about/page.module.css | 17 ++++ .../admin/page-editor/contact/page.module.css | 26 ------ .../app/admin/page-editor/contact/page.tsx | 2 +- .../admin/page-editor/events/page.module.css | 44 ---------- .../src/app/admin/page-editor/events/page.tsx | 2 +- .../admin/page-editor/home/page.module.css | 42 ---------- .../src/app/admin/page-editor/home/page.tsx | 2 +- .../admin/page-editor/impact/page.module.css | 43 ---------- .../src/app/admin/page-editor/impact/page.tsx | 2 +- .../page-editor/involved/page.module.css | 43 ---------- .../app/admin/page-editor/involved/page.tsx | 2 +- .../admin/page-editor/mission/page.module.css | 43 ---------- .../app/admin/page-editor/mission/page.tsx | 2 +- .../page-editor/newsletter/page.module.css | 43 ---------- .../app/admin/page-editor/newsletter/page.tsx | 2 +- .../page-editor/pastevents/page.module.css | 43 ---------- .../app/admin/page-editor/pastevents/page.tsx | 2 +- .../admin/page-editor/team/page.module.css | 43 ---------- .../src/app/admin/page-editor/team/page.tsx | 14 +++- .../admin/page-editor/testimonials/page.tsx | 2 +- frontend/src/components/Collapsable.tsx | 84 ++++++++++--------- 21 files changed, 80 insertions(+), 423 deletions(-) delete mode 100644 frontend/src/app/admin/page-editor/contact/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/events/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/home/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/impact/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/involved/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/mission/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/newsletter/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/pastevents/page.module.css delete mode 100644 frontend/src/app/admin/page-editor/team/page.module.css diff --git a/frontend/src/app/admin/page-editor/about/page.module.css b/frontend/src/app/admin/page-editor/about/page.module.css index e6d5066d..9c8a0aa7 100644 --- a/frontend/src/app/admin/page-editor/about/page.module.css +++ b/frontend/src/app/admin/page-editor/about/page.module.css @@ -9,6 +9,7 @@ flex-direction: column; gap: 32px; padding: 32px; + margin-top: 63px; } .buttonContainer { @@ -24,3 +25,19 @@ top: 58px; left: 872px; } + +.grayOut { + opacity: 0.3; + background: #484848; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 130%; +} + +.warningPopup { + position: relative; + left: 250px; + z-index: 5; +} diff --git a/frontend/src/app/admin/page-editor/contact/page.module.css b/frontend/src/app/admin/page-editor/contact/page.module.css deleted file mode 100644 index e6d5066d..00000000 --- a/frontend/src/app/admin/page-editor/contact/page.module.css +++ /dev/null @@ -1,26 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} diff --git a/frontend/src/app/admin/page-editor/contact/page.tsx b/frontend/src/app/admin/page-editor/contact/page.tsx index 840a3b29..ff45249c 100644 --- a/frontend/src/app/admin/page-editor/contact/page.tsx +++ b/frontend/src/app/admin/page-editor/contact/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState } from "react"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; diff --git a/frontend/src/app/admin/page-editor/events/page.module.css b/frontend/src/app/admin/page-editor/events/page.module.css deleted file mode 100644 index 4eda76be..00000000 --- a/frontend/src/app/admin/page-editor/events/page.module.css +++ /dev/null @@ -1,44 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto+Slab:wght@100..900&display=swap"); - -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/events/page.tsx b/frontend/src/app/admin/page-editor/events/page.tsx index 5fbd599a..39d4fd99 100644 --- a/frontend/src/app/admin/page-editor/events/page.tsx +++ b/frontend/src/app/admin/page-editor/events/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/home/page.module.css b/frontend/src/app/admin/page-editor/home/page.module.css deleted file mode 100644 index c3ac0e89..00000000 --- a/frontend/src/app/admin/page-editor/home/page.module.css +++ /dev/null @@ -1,42 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/home/page.tsx b/frontend/src/app/admin/page-editor/home/page.tsx index 33cbb1d7..68291961 100644 --- a/frontend/src/app/admin/page-editor/home/page.tsx +++ b/frontend/src/app/admin/page-editor/home/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/impact/page.module.css b/frontend/src/app/admin/page-editor/impact/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/impact/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/impact/page.tsx b/frontend/src/app/admin/page-editor/impact/page.tsx index 912f1624..ffd47ee6 100644 --- a/frontend/src/app/admin/page-editor/impact/page.tsx +++ b/frontend/src/app/admin/page-editor/impact/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/involved/page.module.css b/frontend/src/app/admin/page-editor/involved/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/involved/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/involved/page.tsx b/frontend/src/app/admin/page-editor/involved/page.tsx index 17e80225..077ff92c 100644 --- a/frontend/src/app/admin/page-editor/involved/page.tsx +++ b/frontend/src/app/admin/page-editor/involved/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/mission/page.module.css b/frontend/src/app/admin/page-editor/mission/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/mission/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/mission/page.tsx b/frontend/src/app/admin/page-editor/mission/page.tsx index 6c5e31fe..6c8fc0c3 100644 --- a/frontend/src/app/admin/page-editor/mission/page.tsx +++ b/frontend/src/app/admin/page-editor/mission/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/newsletter/page.module.css b/frontend/src/app/admin/page-editor/newsletter/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/newsletter/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/newsletter/page.tsx b/frontend/src/app/admin/page-editor/newsletter/page.tsx index d0926e20..b20e7b60 100644 --- a/frontend/src/app/admin/page-editor/newsletter/page.tsx +++ b/frontend/src/app/admin/page-editor/newsletter/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/pastevents/page.module.css b/frontend/src/app/admin/page-editor/pastevents/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/pastevents/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx index 82869ae6..594a8980 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.tsx +++ b/frontend/src/app/admin/page-editor/pastevents/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; diff --git a/frontend/src/app/admin/page-editor/team/page.module.css b/frontend/src/app/admin/page-editor/team/page.module.css deleted file mode 100644 index 9c8a0aa7..00000000 --- a/frontend/src/app/admin/page-editor/team/page.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.page { - padding: 90px 80px 170px 300px; -} - -.sectionContainer { - background-color: white; - width: 100%; - display: flex; - flex-direction: column; - gap: 32px; - padding: 32px; - margin-top: 63px; -} - -.buttonContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - gap: 24px; -} - -.alert { - position: fixed; - z-index: 4; - top: 58px; - left: 872px; -} - -.grayOut { - opacity: 0.3; - background: #484848; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 130%; -} - -.warningPopup { - position: relative; - left: 250px; - z-index: 5; -} diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index e7b819a8..d39722a0 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "./page.module.css"; +import styles from "../testimonials/page.module.css"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; @@ -112,6 +112,11 @@ export default function TeamEditor() { setIsEdited(false); }; + const handleAdd = () => { + console.log("Add Volunteer"); + setIsEdited(true); + }; + const handleCloseAlert = () => { setShowAlert(false); }; @@ -160,10 +165,13 @@ export default function TeamEditor() { /> +
{ diff --git a/frontend/src/components/Collapsable.tsx b/frontend/src/components/Collapsable.tsx index ff5ce8fe..ebf58f48 100644 --- a/frontend/src/components/Collapsable.tsx +++ b/frontend/src/components/Collapsable.tsx @@ -6,8 +6,8 @@ import styles from "./Collapsable.module.css"; type CollapsableProps = { title: string; - subsection: string[]; - textbox: string[]; + subsection?: string[]; + textbox?: string[]; onChange: (event: React.ChangeEvent) => void; // The following are only for numbered lists (Our Team, Testimonials) listTitles?: string[]; @@ -22,7 +22,6 @@ const Collapsable = ({ listTitles, listText, }: CollapsableProps) => { - const hasNumberedList = listTitles !== null && listText !== null; const [open, setOpen] = useState(true); const toggleSection = () => { @@ -54,47 +53,50 @@ const Collapsable = ({ {open && (
- {subsection.map((subtitle, index) => { - const text = textbox[index]; - return ( -
-

{subtitle}

- -
- ); - })} - {hasNumberedList && ( + {typeof subsection !== "undefined" && typeof textbox !== "undefined" && (
-
    - {listText?.map((textArray, index) => { - let subtitle = ""; - return listTitles?.map((listTitle, innerIndex) => { - if (innerIndex === 0) { - subtitle = index + 1 + ". \t" + listTitle; - } else { - subtitle = "\t\t" + listTitle; - } - return ( -
  1. -

    {subtitle}

    - -
  2. - ); - }); - })} -
+ {subsection.map((subtitle, index) => { + const text = textbox[index]; + return ( +
+

{subtitle}

+ +
+ ); + })}
)} + + {typeof listTitles !== "undefined" && typeof listText !== "undefined" && ( +
    + {listText.map((textArray, index) => { + let subtitle = ""; + return listTitles.map((listTitle, innerIndex) => { + if (innerIndex === 0) { + subtitle = index + 1 + ". \t" + listTitle; + } else { + subtitle = "\t\t" + listTitle; + } + return ( +
  1. +

    {subtitle}

    + +
  2. + ); + }); + })} +
+ )}
)}
From ed31e4581930efa7d30b6d6afba21a1f589ed175 Mon Sep 17 00:00:00 2001 From: wrachel Date: Wed, 22 May 2024 12:25:30 -0700 Subject: [PATCH 06/13] get member working --- backend/src/controllers/member.ts | 29 +++++ backend/src/routes/members.ts | 7 +- frontend/src/api/member.ts | 41 ++++++- .../src/app/admin/page-editor/team/page.tsx | 110 +++++++++++++++++- frontend/src/components/MemberList.module.css | 15 +++ frontend/src/components/MemberList.tsx | 44 +++++++ 6 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/MemberList.module.css create mode 100644 frontend/src/components/MemberList.tsx diff --git a/backend/src/controllers/member.ts b/backend/src/controllers/member.ts index cd71fbbe..2538422d 100644 --- a/backend/src/controllers/member.ts +++ b/backend/src/controllers/member.ts @@ -1,6 +1,8 @@ import { RequestHandler } from "express"; import MemberModel from "src/models/member"; import { Types } from "mongoose"; +import validationErrorParser from "src/util/validationErrorParser"; +import { validationResult } from "express-validator"; export const createMember: RequestHandler = async (req, res, next) => { const { name, role, profilePictureURL } = req.body; @@ -34,3 +36,30 @@ export const getAllMembers: RequestHandler = async (req, res, next) => { next(error); } }; + +export const updateMember: RequestHandler = async (req, res, next) => { + const errors = validationResult(req); + const { id } = req.params; + // if (id !== req.body._id) { + // // If the _id in the URL does not match the _id in the body, bad request + // res.status(400); + // } + + try { + validationErrorParser(errors); + + const member = await MemberModel.findByIdAndUpdate(id, req.body); + if (member === null) { + // No event found + res.status(404); + } + const updatedMember = await MemberModel.findById(id); + if (updatedMember === null) { + // No event found, something went wrong + res.status(404); + } + res.status(200).json(updatedMember); + } catch (error) { + next(error); + } +}; diff --git a/backend/src/routes/members.ts b/backend/src/routes/members.ts index dfedc903..c6d5f7d8 100644 --- a/backend/src/routes/members.ts +++ b/backend/src/routes/members.ts @@ -1,9 +1,14 @@ import express from "express"; import * as MembersController from "src/controllers/member"; +import * as MembersValidator from "src/validators/member" const router = express.Router(); router.get("/get", MembersController.getAllMembers); -router.post("/post", MembersController.createMember); +router.get("/:id", MembersController.getMember); +router.post("/post", MembersValidator.createMember, MembersController.createMember); +router.put( + "/:id", MembersController.updateMember, + ); export default router; diff --git a/frontend/src/api/member.ts b/frontend/src/api/member.ts index 4342fccc..fc6b5fe5 100644 --- a/frontend/src/api/member.ts +++ b/frontend/src/api/member.ts @@ -1,4 +1,4 @@ -import { get, handleAPIError } from "./requests"; +import { get, handleAPIError, post, put } from "./requests"; import type { APIResult } from "./requests"; export type Member = { @@ -18,3 +18,42 @@ export async function getAllMembers(): Promise> { return handleAPIError(error); } } + +type CreateMemberRequest = { + name: string; + role: string; + profilePictureURL?: string; +}; + +export async function createMember( + member: CreateMemberRequest, +): Promise> { + try { + const response = await post("/api/member/post", member); + const json = (await response.json()) as Member; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} + +type updateMemberRequest = { + _id: string; + name: string; + role: string; + profilePictureURL?: string; +}; + +export async function updateMember(member: updateMemberRequest, +): Promise> { + try { + const id = member._id; + const response = await put(`/api/member/${id}`, member, { + "Content-Type": "application/json", + }); + const json = (await response.json()) as Member; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index d39722a0..6af5baca 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -1,8 +1,8 @@ "use client"; import React, { useEffect, useState } from "react"; +import {Member, createMember, getAllMembers, updateMember} from "../../../../api/member" import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../testimonials/page.module.css"; import AlertBanner from "@/components/AlertBanner"; @@ -12,7 +12,13 @@ import Collapsable from "@/components/Collapsable"; import PageToggle from "@/components/PageToggle"; import { WarningModule } from "@/components/WarningModule"; +// import PageEditorCard from "@/components/PageEditorCard"; + export default function TeamEditor() { + const [members, setMembers] = useState([]); + const [membersArray, setMembersArray] = useState([]); + const [editedMembers] = useState>(new Set()); //Indices of edited testimonials + const [isEdited, setIsEdited] = useState(false); const [phSubtitle, setPhSubtitle] = useState(""); const [s1Subtitle, setS1Subtitle] = useState(""); @@ -20,7 +26,6 @@ export default function TeamEditor() { const [showAlert, setShowAlert] = useState(false); const [warningOpen, setWarningOpen] = useState(false); - /* Get page data from MongoDB */ let pageText; useEffect(() => { @@ -38,8 +43,27 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + + getAllMembers() + .then((response2) => { + if(response2.success) { + setMembers(response2.data); + const newArray: string[][] = []; + for (const elem of response2.data) { + newArray.push([elem.name, elem.role]); + } + setMembersArray(newArray); + + } else { + alert(response2.error); + } + }) + .catch((error) => { + alert(error); + }) }, []); + /* Handle Fields upon edit */ const handleEdit = (event: React.ChangeEvent) => { setIsEdited(true); @@ -49,13 +73,37 @@ export default function TeamEditor() { setS1Subtitle(event.target.value); } else if (event.target.id === "Section 1: Body Text") { setS1Text(event.target.value); + } else if (event.target.id.includes("Staff Name")) { + const memberIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); + const updateArray = membersArray.map((elem, index) => { + if (index === memberIndex) { + return [event.target.value, elem[1]]; + } else { + return elem; + } + }); + setMembersArray(updateArray); + editedMembers.add(memberIndex); + }else if (event.target.id.includes("Staff Position")) { + const memberIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); + //Update textarea by changing testimonialArray element + const updateArray = membersArray.map((elem, index) => { + if (index === memberIndex) { + return [elem[0], event.target.value]; + } else { + return elem; + } + }); + setMembersArray(updateArray); + editedMembers.add(memberIndex); //Add index to list of edited indices } }; + const handleSave = () => { // Implement save logic if (isEdited) { - console.log("Our Team"); + console.log("save in team"); updatePage({ //Pass edited text to MongoDB page: "Our Team", @@ -79,6 +127,43 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + + if (editedMembers.size > 0 ) { + for (const index of Array.from(editedMembers)) { + if (index >= members.length) { + createMember({ + name: membersArray[index][0], + role: membersArray[index][1], + // profilePictureURL: "/impact1.png" + }) + .then((response2) => { + if(response2.success) { + setShowAlert(true); + } else { + alert(response2.error); + } + }) + .catch((error) => { + alert(error); + }); + } else { + members[index].name = membersArray[index][0]; + members[index].role = membersArray[index][1]; + updateMember(members[index]) + .then((response) => { + if(response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + } + } + } + setIsEdited(false); } setWarningOpen(false); @@ -109,11 +194,23 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + if(editedMembers.size > 0) { + const updateArray: string[][] = []; + for (const elem of members) { + updateArray.push([elem.name, elem.role]); + } + setMembersArray(updateArray); + } setIsEdited(false); }; const handleAdd = () => { console.log("Add Volunteer"); + setMembersArray([ + ...membersArray, + ["", ""], + ]); + editedMembers.add(members.length); setIsEdited(true); }; @@ -166,12 +263,13 @@ export default function TeamEditor() { - +
) => void; +}; + +const MembersList = ({ idx, name, role, onChange }: MemberInfoProps) => { + const handleChange = (event: React.ChangeEvent) => { + // Auto increase height when typing + event.target.style.height = "auto"; + event.target.style.height = 2 + event.target.scrollHeight + "px"; + // Call onChange function + onChange(event); + console.log(idx) + }; + + + return ( +
+

{idx}

+

name:

+ +

role:

+ +
+ ); +}; + +export default MembersList; From 157ef28556c347c0fb287b0ea2752140813b230c Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Wed, 22 May 2024 17:02:54 -0700 Subject: [PATCH 07/13] Our Team Staff frontend --- .../src/app/admin/page-editor/team/page.tsx | 93 +++++++++---------- .../src/components/Collapsable.module.css | 13 +++ frontend/src/components/Collapsable.tsx | 6 +- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index 6af5baca..58e446f8 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useEffect, useState } from "react"; -import {Member, createMember, getAllMembers, updateMember} from "../../../../api/member" +import { Member, createMember, getAllMembers, updateMember } from "../../../../api/member"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../testimonials/page.module.css"; @@ -45,25 +45,23 @@ export default function TeamEditor() { }); getAllMembers() - .then((response2) => { - if(response2.success) { - setMembers(response2.data); - const newArray: string[][] = []; - for (const elem of response2.data) { - newArray.push([elem.name, elem.role]); + .then((response2) => { + if (response2.success) { + setMembers(response2.data); + const newArray: string[][] = []; + for (const elem of response2.data) { + newArray.push([elem.name, elem.role]); + } + setMembersArray(newArray); + } else { + alert(response2.error); } - setMembersArray(newArray); - - } else { - alert(response2.error); - } - }) - .catch((error) => { - alert(error); - }) + }) + .catch((error) => { + alert(error); + }); }, []); - /* Handle Fields upon edit */ const handleEdit = (event: React.ChangeEvent) => { setIsEdited(true); @@ -84,7 +82,7 @@ export default function TeamEditor() { }); setMembersArray(updateArray); editedMembers.add(memberIndex); - }else if (event.target.id.includes("Staff Position")) { + } else if (event.target.id.includes("Staff Position")) { const memberIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); //Update textarea by changing testimonialArray element const updateArray = membersArray.map((elem, index) => { @@ -99,7 +97,6 @@ export default function TeamEditor() { } }; - const handleSave = () => { // Implement save logic if (isEdited) { @@ -127,8 +124,8 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); - - if (editedMembers.size > 0 ) { + + if (editedMembers.size > 0) { for (const index of Array.from(editedMembers)) { if (index >= members.length) { createMember({ @@ -136,34 +133,34 @@ export default function TeamEditor() { role: membersArray[index][1], // profilePictureURL: "/impact1.png" }) - .then((response2) => { - if(response2.success) { - setShowAlert(true); - } else { - alert(response2.error); - } - }) - .catch((error) => { - alert(error); - }); + .then((response2) => { + if (response2.success) { + setShowAlert(true); + } else { + alert(response2.error); + } + }) + .catch((error) => { + alert(error); + }); } else { members[index].name = membersArray[index][0]; members[index].role = membersArray[index][1]; updateMember(members[index]) - .then((response) => { - if(response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); } } } - + setIsEdited(false); } setWarningOpen(false); @@ -194,7 +191,7 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); - if(editedMembers.size > 0) { + if (editedMembers.size > 0) { const updateArray: string[][] = []; for (const elem of members) { updateArray.push([elem.name, elem.role]); @@ -206,10 +203,7 @@ export default function TeamEditor() { const handleAdd = () => { console.log("Add Volunteer"); - setMembersArray([ - ...membersArray, - ["", ""], - ]); + setMembersArray([...membersArray, ["", ""]]); editedMembers.add(members.length); setIsEdited(true); }; @@ -265,11 +259,12 @@ export default function TeamEditor() { listTitles={["Staff Name", "Staff Position"]} listText={membersArray} onChange={handleEdit} + isAdjacent={true} /> - +
{ const [open, setOpen] = useState(true); @@ -73,7 +75,7 @@ const Collapsable = ({ )} {typeof listTitles !== "undefined" && typeof listText !== "undefined" && ( -
    +
      {listText.map((textArray, index) => { let subtitle = ""; return listTitles.map((listTitle, innerIndex) => { @@ -83,7 +85,7 @@ const Collapsable = ({ subtitle = "\t\t" + listTitle; } return ( -
    1. +
    2. {subtitle}

      -

      role:

      - -
- ); -}; - -export default MembersList; From 7524b5a713f599eb79a10e8dc9ca0f94d2f35368 Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Tue, 28 May 2024 16:18:56 -0700 Subject: [PATCH 09/13] Testimonial and Team delete if empty --- backend/src/controllers/testimonial.ts | 17 +++ backend/src/routes/testimonial.ts | 9 +- backend/src/validators/testimonial.ts | 17 +-- frontend/src/api/testimonial.ts | 12 ++- .../admin/page-editor/about/page.module.css | 12 ++- .../src/app/admin/page-editor/team/page.tsx | 100 ++++++++++-------- .../page-editor/testimonials/page.module.css | 19 +++- .../admin/page-editor/testimonials/page.tsx | 88 ++++++++++----- frontend/src/components/Collapsable.tsx | 3 +- 9 files changed, 175 insertions(+), 102 deletions(-) diff --git a/backend/src/controllers/testimonial.ts b/backend/src/controllers/testimonial.ts index 21237ee5..d9025b9e 100644 --- a/backend/src/controllers/testimonial.ts +++ b/backend/src/controllers/testimonial.ts @@ -1,6 +1,7 @@ import { RequestHandler } from "express"; import TestimonialModel from "src/models/testimonial"; import { validationResult } from "express-validator"; +import createHttpError from "http-errors"; import validationErrorParser from "src/util/validationErrorParser"; export const createTestimonial: RequestHandler = async (req, res, next) => { @@ -65,3 +66,19 @@ export const updateTestimonial: RequestHandler = async (req, res, next) => { next(error); } }; + +export const deleteTestimonial: RequestHandler = async (req, res, next) => { + const { id } = req.params; + + try { + const testimonial = await TestimonialModel.findByIdAndDelete(id); + + if (!testimonial) { + throw createHttpError(404, "Testimonial not found."); + } + + res.status(200).json(testimonial); + } catch (error) { + next(error); + } +}; diff --git a/backend/src/routes/testimonial.ts b/backend/src/routes/testimonial.ts index 20c356ca..a0a92948 100644 --- a/backend/src/routes/testimonial.ts +++ b/backend/src/routes/testimonial.ts @@ -7,10 +7,11 @@ const router = express.Router(); router.get("/get", TestimonialController.getAllTestimonials); router.get("/get/quote", TestimonialController.getAllQuotes); router.post("/post", TestimonialController.createTestimonial); -router.put( - "/:id", // getNewsletter validator works to just check ID - TestimonialValidator.updateTestimonial, - TestimonialController.updateTestimonial, +router.put("/:id", TestimonialValidator.updateTestimonial, TestimonialController.updateTestimonial); +router.delete( + "/:id", + TestimonialValidator.deleteTestimonial, + TestimonialController.deleteTestimonial, ); export default router; diff --git a/backend/src/validators/testimonial.ts b/backend/src/validators/testimonial.ts index af11823e..64854c94 100644 --- a/backend/src/validators/testimonial.ts +++ b/backend/src/validators/testimonial.ts @@ -5,7 +5,7 @@ const makeIDValidator = () => .exists() .withMessage("_id is required") .bail() - .isMongoId() + .isString() .withMessage("_id must be a MongoDB object ID"); const makeTitleValidator = () => @@ -49,15 +49,8 @@ const makeImageValidator = () => .notEmpty() .withMessage("image cannot be empty"); -export const createTestimonial = [ - makeTitleValidator(), - makeDescriptionValidator(), - makeImageValidator(), -]; +export const createTestimonial = [makeTitleValidator(), makeImageValidator()]; + +export const updateTestimonial = [makeIDValidator(), makeImageValidator()]; -export const updateTestimonial = [ - makeIDValidator(), - makeTitleValidator(), - makeDescriptionValidator(), - makeImageValidator(), -]; +export const deleteTestimonial = [makeIDValidator()]; diff --git a/frontend/src/api/testimonial.ts b/frontend/src/api/testimonial.ts index 46bfdcb2..e62e49ee 100644 --- a/frontend/src/api/testimonial.ts +++ b/frontend/src/api/testimonial.ts @@ -1,4 +1,4 @@ -import { get, handleAPIError, post, put } from "./requests"; +import { del, get, handleAPIError, post, put } from "./requests"; import type { APIResult } from "./requests"; export type Testimonial = { @@ -60,3 +60,13 @@ export async function updateTestimonial(testimonial: Testimonial): Promise> { + try { + const response = await del(`/api/testimonial/${id}`); + const json = (await response.json()) as Testimonial; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/app/admin/page-editor/about/page.module.css b/frontend/src/app/admin/page-editor/about/page.module.css index 9c8a0aa7..6e6a4d0b 100644 --- a/frontend/src/app/admin/page-editor/about/page.module.css +++ b/frontend/src/app/admin/page-editor/about/page.module.css @@ -29,15 +29,17 @@ .grayOut { opacity: 0.3; background: #484848; - position: absolute; + position: fixed; top: 0; left: 0; width: 100%; - height: 130%; + height: 100%; + z-index: 2; } .warningPopup { - position: relative; - left: 250px; - z-index: 5; + position: fixed; + left: 35%; + z-index: 3; + width: 100%; } diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index 40ffa7d8..b4bc6fe9 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -1,7 +1,13 @@ "use client"; import React, { useEffect, useState } from "react"; -import { Member, createMember, getAllMembers, updateMember, deleteMember } from "../../../../api/member"; +import { + Member, + createMember, + deleteMember, + getAllMembers, + updateMember, +} from "../../../../api/member"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../testimonials/page.module.css"; @@ -17,7 +23,7 @@ import { WarningModule } from "@/components/WarningModule"; export default function TeamEditor() { const [members, setMembers] = useState([]); const [membersArray, setMembersArray] = useState([]); - const [editedMembers] = useState>(new Set()); //Indices of edited testimonials + const [editedMembers, setEditedMembers] = useState>(new Set()); //Indices of edited testimonials const [isEdited, setIsEdited] = useState(false); const [phSubtitle, setPhSubtitle] = useState(""); @@ -128,56 +134,60 @@ export default function TeamEditor() { if (editedMembers.size > 0) { for (const index of Array.from(editedMembers)) { if (index >= members.length) { - createMember({ - name: membersArray[index][0], - role: membersArray[index][1], - // profilePictureURL: "/impact1.png" - }) - .then((response2) => { - if (response2.success) { - setShowAlert(true); - } else { - alert(response2.error); - } + //Check title & description aren't empty + if (membersArray[index][0] !== "" || membersArray[index][1] !== "") { + createMember({ + name: membersArray[index][0], + role: membersArray[index][1], + // profilePictureURL: "/impact1.png" }) - .catch((error) => { - alert(error); - }); + .then((response2) => { + if (response2.success) { + setShowAlert(true); + } else { + // If adding and missing title/desc + alert(response2.error); + setMembersArray(membersArray.filter((elem, elemIndex) => elemIndex !== index)); + } + }) + .catch((error) => { + alert(error); + }); + } } else { members[index].name = membersArray[index][0]; members[index].role = membersArray[index][1]; - updateMember(members[index]) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - } - } - } - - for(let index = 0; index < membersArray.length; index++) { - const name = membersArray[index][0]; - const role = membersArray[index][1]; - if (name === "" && role === "") { - deleteMember(members[index]) - .then((response) => { - if (response.success) { - setShowAlert(true); + //If deleting member + if (members[index].name === "" && members[index].role === "") { + deleteMember(members[index]) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); } else { - alert(response.error); + //If updating member + updateMember(members[index]) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); } - }) - .catch((error) => { - alert(error); - }); + } } - + setMembersArray(membersArray.filter((elem) => elem[0] !== "" || elem[1] !== "")); + setEditedMembers(new Set()); } setIsEdited(false); diff --git a/frontend/src/app/admin/page-editor/testimonials/page.module.css b/frontend/src/app/admin/page-editor/testimonials/page.module.css index 5ba2505f..e93995a0 100644 --- a/frontend/src/app/admin/page-editor/testimonials/page.module.css +++ b/frontend/src/app/admin/page-editor/testimonials/page.module.css @@ -29,17 +29,26 @@ .grayOut { opacity: 0.3; background: #484848; - position: absolute; + position: fixed; top: 0; left: 0; width: 100%; - height: 130%; + height: 100%; + z-index: 2; } .warningPopup { - position: relative; - left: 250px; - z-index: 5; + position: fixed; + left: 35%; + z-index: 3; + width: 100%; +} + +.fixedPosition { + /* position: fixed; */ + /* top: 0; + bottom: 0; + right: 0; */ } .addButton { diff --git a/frontend/src/app/admin/page-editor/testimonials/page.tsx b/frontend/src/app/admin/page-editor/testimonials/page.tsx index 7b8e72ca..b910cd5b 100644 --- a/frontend/src/app/admin/page-editor/testimonials/page.tsx +++ b/frontend/src/app/admin/page-editor/testimonials/page.tsx @@ -5,6 +5,7 @@ import { getPageText, updatePage } from "../../../../api/pageeditor"; import { Testimonial, createTestimonial, + deleteTestimonial, getAllQuotes, updateTestimonial, } from "../../../../api/testimonial"; @@ -28,7 +29,7 @@ export default function TestimonialsEditor() { const [testimonialData, setTestimonialData] = useState([]); //Holds all testimonials as Testimonials const [testimonialArray, setTestimonialArray] = useState([]); //Holds all testimonials as strings - const [editedTestimonials] = useState>(new Set()); //Indices of edited testimonials + const [editedTestimonials, setEdited] = useState>(new Set()); //Indices of edited testimonials const [showAlert, setShowAlert] = useState(false); const [warningOpen, setWarningOpen] = useState(false); @@ -141,46 +142,75 @@ export default function TestimonialsEditor() { .catch((error) => { alert(error); }); - if (editedTestimonials.size > 0) { //Pass edited testimonials to MongoDB for (const index of Array.from(editedTestimonials)) { //If creating new testimonial if (index >= testimonialData.length) { - createTestimonial({ - title: testimonialArray[index][0], - description: testimonialArray[index][1], - image: "/impact1.png", - type: "quote", - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } + //Check title & description aren't empty + if (testimonialArray[index][0] !== "" || testimonialArray[index][1] !== "") { + createTestimonial({ + title: testimonialArray[index][0], + description: testimonialArray[index][1], + image: "/impact1.png", + type: "quote", }) - .catch((error) => { - alert(error); - }); + .then((response) => { + if (response.success) { + setTestimonialData([ + ...testimonialData, + response.data, // add one new Testimonial to the array + ]); + setShowAlert(true); + } else { + // If adding and missing title/desc + alert(response.error); + setTestimonialArray( + testimonialArray.filter((elem, elemIndex) => elemIndex !== index), + ); + } + }) + .catch((error) => { + alert(error); + }); + } } else { //If editing testimonial //Update testimonial with edited values stored in testimonialArray testimonialData[index].title = testimonialArray[index][0]; testimonialData[index].description = testimonialArray[index][1]; - updateTestimonial(testimonialData[index]) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); + + //If deleting testimonial + if (testimonialData[index].title === "" && testimonialData[index].description === "") { + deleteTestimonial(testimonialData[index]._id) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + } else { + //If updating testimonial + updateTestimonial(testimonialData[index]) + .then((response) => { + if (response.success) { + setShowAlert(true); + } else { + alert(response.error); + } + }) + .catch((error) => { + alert(error); + }); + } } } + setTestimonialArray(testimonialArray.filter((elem) => elem[0] !== "" || elem[1] !== "")); + setEdited(new Set()); } setIsEdited(false); } @@ -228,7 +258,7 @@ export default function TestimonialsEditor() { console.log("Add Testimonial"); setTestimonialArray([ ...testimonialArray, - ["", ""], // and one new item at the end + ["", ""], // add one new item at the end ]); editedTestimonials.add(testimonialArray.length); //Add index to list of edited indices setIsEdited(true); diff --git a/frontend/src/components/Collapsable.tsx b/frontend/src/components/Collapsable.tsx index eb99a9e0..498061cb 100644 --- a/frontend/src/components/Collapsable.tsx +++ b/frontend/src/components/Collapsable.tsx @@ -12,7 +12,7 @@ type CollapsableProps = { // The following are only for numbered lists (Our Team, Testimonials) listTitles?: string[]; listText?: string[][]; - isAdjacent?: boolean; + isAdjacent?: boolean; // Whether to position textboxes on one line }; const Collapsable = ({ @@ -75,6 +75,7 @@ const Collapsable = ({ )} {typeof listTitles !== "undefined" && typeof listText !== "undefined" && ( + // For the numbered list, if it exists
    {listText.map((textArray, index) => { let subtitle = ""; From b8eaecd50bf02687dcaa1774a28b4a6831c749bc Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Tue, 28 May 2024 16:43:05 -0700 Subject: [PATCH 10/13] npm lintfix --- backend/src/validators/testimonial.ts | 6 +- frontend/src/api/member.ts | 7 +- .../src/app/(web app)/past-events/page.tsx | 4 +- frontend/src/app/(web app)/team/page.tsx | 1 - .../app/admin/page-editor/contact/page.tsx | 82 ------------------- .../src/app/admin/page-editor/events/page.tsx | 1 - .../src/app/admin/page-editor/home/page.tsx | 1 - .../src/app/admin/page-editor/impact/page.tsx | 1 - .../app/admin/page-editor/involved/page.tsx | 1 - .../app/admin/page-editor/mission/page.tsx | 1 - .../app/admin/page-editor/newsletter/page.tsx | 1 - .../app/admin/page-editor/pastevents/page.tsx | 1 - 12 files changed, 8 insertions(+), 99 deletions(-) delete mode 100644 frontend/src/app/admin/page-editor/contact/page.tsx diff --git a/backend/src/validators/testimonial.ts b/backend/src/validators/testimonial.ts index 64854c94..6e77aa07 100644 --- a/backend/src/validators/testimonial.ts +++ b/backend/src/validators/testimonial.ts @@ -49,7 +49,11 @@ const makeImageValidator = () => .notEmpty() .withMessage("image cannot be empty"); -export const createTestimonial = [makeTitleValidator(), makeImageValidator()]; +export const createTestimonial = [ + makeTitleValidator(), + makeImageValidator(), + makeDescriptionValidator, +]; export const updateTestimonial = [makeIDValidator(), makeImageValidator()]; diff --git a/frontend/src/api/member.ts b/frontend/src/api/member.ts index 5ac76ad1..2fcd0008 100644 --- a/frontend/src/api/member.ts +++ b/frontend/src/api/member.ts @@ -25,9 +25,7 @@ type CreateMemberRequest = { profilePictureURL?: string; }; -export async function createMember( - member: CreateMemberRequest, -): Promise> { +export async function createMember(member: CreateMemberRequest): Promise> { try { const response = await post("/api/member/post", member); const json = (await response.json()) as Member; @@ -44,8 +42,7 @@ type updateMemberRequest = { profilePictureURL?: string; }; -export async function updateMember(member: updateMemberRequest, -): Promise> { +export async function updateMember(member: updateMemberRequest): Promise> { try { const id = member._id; const response = await put(`/api/member/${id}`, member, { diff --git a/frontend/src/app/(web app)/past-events/page.tsx b/frontend/src/app/(web app)/past-events/page.tsx index e16c2262..8e75222a 100644 --- a/frontend/src/app/(web app)/past-events/page.tsx +++ b/frontend/src/app/(web app)/past-events/page.tsx @@ -4,7 +4,6 @@ import React, { useEffect, useState } from "react"; import { getPageText } from "../../../api/pageeditor"; import EventsList from "../../../components/EventsList"; - import styles from "./page.module.css"; import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; @@ -46,7 +45,6 @@ export default function PastEvents() { }); }, []); - return (
    ); -} \ No newline at end of file +} diff --git a/frontend/src/app/(web app)/team/page.tsx b/frontend/src/app/(web app)/team/page.tsx index d317f779..f95a88ef 100644 --- a/frontend/src/app/(web app)/team/page.tsx +++ b/frontend/src/app/(web app)/team/page.tsx @@ -11,7 +11,6 @@ import { Member, getAllMembers } from "@/api/member"; import BackgroundHeader from "@/components/BackgroundHeader"; import MemberInfo from "@/components/MemberInfo"; - export default function Team() { const [members, setMembers] = useState([]); const [images, setImages] = useState([]); diff --git a/frontend/src/app/admin/page-editor/contact/page.tsx b/frontend/src/app/admin/page-editor/contact/page.tsx deleted file mode 100644 index ff45249c..00000000 --- a/frontend/src/app/admin/page-editor/contact/page.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; -import React, { useState } from "react"; - -import styles from "../about/page.module.css"; - -import Button from "@/components/Button"; -import CancelButton from "@/components/CancelButton"; -import Collapsable from "@/components/Collapsable"; -import PageToggle from "@/components/PageToggle"; - -export default function ContactEditor() { - const [isEdited, setIsEdited] = useState(false); - - const handleEdit = () => { - setIsEdited(true); - }; - - const handleSave = () => { - // Implement save logic - console.log("Save changes"); - setIsEdited(false); - }; - - const handleCancel = () => { - // Implement cancel logic - console.log("Cancel changes"); - setIsEdited(false); - }; - - return ( -
    - -
    - - -
    - -
    -
    -
    - ); -} diff --git a/frontend/src/app/admin/page-editor/events/page.tsx b/frontend/src/app/admin/page-editor/events/page.tsx index 39d4fd99..1796ceb4 100644 --- a/frontend/src/app/admin/page-editor/events/page.tsx +++ b/frontend/src/app/admin/page-editor/events/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/home/page.tsx b/frontend/src/app/admin/page-editor/home/page.tsx index 68291961..07590230 100644 --- a/frontend/src/app/admin/page-editor/home/page.tsx +++ b/frontend/src/app/admin/page-editor/home/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/impact/page.tsx b/frontend/src/app/admin/page-editor/impact/page.tsx index ffd47ee6..e9bce9fd 100644 --- a/frontend/src/app/admin/page-editor/impact/page.tsx +++ b/frontend/src/app/admin/page-editor/impact/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/involved/page.tsx b/frontend/src/app/admin/page-editor/involved/page.tsx index 077ff92c..360c0796 100644 --- a/frontend/src/app/admin/page-editor/involved/page.tsx +++ b/frontend/src/app/admin/page-editor/involved/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/mission/page.tsx b/frontend/src/app/admin/page-editor/mission/page.tsx index 6c8fc0c3..034879bc 100644 --- a/frontend/src/app/admin/page-editor/mission/page.tsx +++ b/frontend/src/app/admin/page-editor/mission/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/newsletter/page.tsx b/frontend/src/app/admin/page-editor/newsletter/page.tsx index b20e7b60..6221cc21 100644 --- a/frontend/src/app/admin/page-editor/newsletter/page.tsx +++ b/frontend/src/app/admin/page-editor/newsletter/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; diff --git a/frontend/src/app/admin/page-editor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx index 594a8980..ebf30d18 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.tsx +++ b/frontend/src/app/admin/page-editor/pastevents/page.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; - import styles from "../about/page.module.css"; import AlertBanner from "@/components/AlertBanner"; From 768f712ffe1039f6e49d192663b2212df456366d Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Tue, 28 May 2024 16:47:19 -0700 Subject: [PATCH 11/13] Navigation Bar with Impact --- frontend/src/components/NavigationBar.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/NavigationBar.tsx b/frontend/src/components/NavigationBar.tsx index 9e84cfd2..4b5d2136 100644 --- a/frontend/src/components/NavigationBar.tsx +++ b/frontend/src/components/NavigationBar.tsx @@ -37,6 +37,12 @@ const NavigationBar = () => { setActivePageEditor("Get Involved"); } else if (url.endsWith("events")) { setActivePageEditor("Upcoming Events"); + } else if (url.endsWith("impact")) { + setActivePageEditor("Our Impact"); + } else if (url.endsWith("testimonials")) { + setActivePageEditor("Testimonials"); + } else if (url.endsWith("newsletter")) { + setActivePageEditor("Newsletter"); } return "Page Editor"; } else if (url === "/admin/newsletter-creator") { From 32f2f3770716503bcf038c1dbdd6013c2b557781 Mon Sep 17 00:00:00 2001 From: katelynpdn <94756838+katelynpdn@users.noreply.github.com> Date: Wed, 29 May 2024 11:46:52 -0700 Subject: [PATCH 12/13] npm lintfix --- backend/src/routes/members.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/members.ts b/backend/src/routes/members.ts index 23c5271a..c449ce8f 100644 --- a/backend/src/routes/members.ts +++ b/backend/src/routes/members.ts @@ -1,15 +1,13 @@ import express from "express"; import * as MembersController from "src/controllers/member"; -import * as MembersValidator from "src/validators/member" +import * as MembersValidator from "src/validators/member"; const router = express.Router(); router.get("/get", MembersController.getAllMembers); router.get("/:id", MembersController.getMember); router.post("/post", MembersValidator.createMember, MembersController.createMember); -router.put( - "/:id", MembersController.updateMember, - ); +router.put("/:id", MembersController.updateMember); router.delete("/:id", MembersController.deleteMember); export default router; From dc3dcf44a73c75b8bbc4f11393798ee536626164 Mon Sep 17 00:00:00 2001 From: jennymar Date: Mon, 3 Jun 2024 07:23:36 -0700 Subject: [PATCH 13/13] Bug fixes and added last updated feature --- backend/dist/app.js | 2 + backend/dist/controllers/member.js | 45 ++++++++++++++- backend/dist/controllers/records.js | 48 ++++++++++++++++ backend/dist/controllers/testimonial.js | 57 ++++++++++++++++++- backend/dist/models/records.js | 8 +++ backend/dist/routes/members.js | 6 +- backend/dist/routes/records.js | 35 ++++++++++++ backend/dist/routes/testimonial.js | 4 ++ backend/dist/validators/records.js | 29 ++++++++++ backend/dist/validators/testimonial.js | 12 ++-- backend/src/app.ts | 2 + backend/src/controllers/records.ts | 36 ++++++++++++ backend/src/controllers/testimonial.ts | 2 + backend/src/models/records.ts | 10 ++++ backend/src/routes/records.ts | 10 ++++ backend/src/validators/records.ts | 31 ++++++++++ backend/src/validators/testimonial.ts | 2 +- frontend/src/api/records.ts | 31 ++++++++++ frontend/src/api/testimonial.ts | 1 + .../src/app/admin/page-editor/about/page.tsx | 8 ++- .../src/app/admin/page-editor/events/page.tsx | 6 ++ .../src/app/admin/page-editor/home/page.tsx | 6 ++ .../src/app/admin/page-editor/impact/page.tsx | 7 +++ .../app/admin/page-editor/involved/page.tsx | 7 +++ .../app/admin/page-editor/mission/page.tsx | 8 ++- .../app/admin/page-editor/newsletter/page.tsx | 7 +++ frontend/src/app/admin/page-editor/page.tsx | 46 +++++++++++---- .../app/admin/page-editor/pastevents/page.tsx | 7 +++ .../src/app/admin/page-editor/team/page.tsx | 49 ++++++++++++---- .../admin/page-editor/testimonials/page.tsx | 40 +++++++++++-- frontend/src/app/admin/util/validateEnv.ts | 2 +- 31 files changed, 523 insertions(+), 41 deletions(-) create mode 100644 backend/dist/controllers/records.js create mode 100644 backend/dist/models/records.js create mode 100644 backend/dist/routes/records.js create mode 100644 backend/dist/validators/records.js create mode 100644 backend/src/controllers/records.ts create mode 100644 backend/src/models/records.ts create mode 100644 backend/src/routes/records.ts create mode 100644 backend/src/validators/records.ts create mode 100644 frontend/src/api/records.ts diff --git a/backend/dist/app.js b/backend/dist/app.js index efab74ac..4d5da502 100644 --- a/backend/dist/app.js +++ b/backend/dist/app.js @@ -20,6 +20,7 @@ const newsletter_1 = __importDefault(require("./routes/newsletter")); // Import const emails_1 = __importDefault(require("./routes/emails")); const pageeditor_1 = __importDefault(require("./routes/pageeditor")); const paypal_1 = __importDefault(require("./routes/paypal")); +const records_1 = __importDefault(require("./routes/records")); const app = (0, express_1.default)(); // initializes Express to accept JSON in the request/response body app.use(express_1.default.json()); @@ -42,6 +43,7 @@ app.use("/api/newsletter", newsletter_1.default); app.use("/api/emails", emails_1.default); app.use("/api/pageeditor", pageeditor_1.default); app.use("/api/orders", paypal_1.default); // Donation Order routes +app.use("/api/records", records_1.default); /** * Error handler; all errors thrown by server are handled here. * Explicit typings required here because TypeScript cannot infer the argument types. diff --git a/backend/dist/controllers/member.js b/backend/dist/controllers/member.js index a386839b..89404b7c 100644 --- a/backend/dist/controllers/member.js +++ b/backend/dist/controllers/member.js @@ -12,9 +12,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAllMembers = exports.getMember = exports.createMember = void 0; +exports.deleteMember = exports.updateMember = exports.getAllMembers = exports.getMember = exports.createMember = void 0; const member_1 = __importDefault(require("../models/member")); const mongoose_1 = require("mongoose"); +const validationErrorParser_1 = __importDefault(require("../util/validationErrorParser")); +const express_validator_1 = require("express-validator"); +const http_errors_1 = __importDefault(require("http-errors")); const createMember = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { const { name, role, profilePictureURL } = req.body; try { @@ -51,3 +54,43 @@ const getAllMembers = (req, res, next) => __awaiter(void 0, void 0, void 0, func } }); exports.getAllMembers = getAllMembers; +const updateMember = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const errors = (0, express_validator_1.validationResult)(req); + const { id } = req.params; + // if (id !== req.body._id) { + // // If the _id in the URL does not match the _id in the body, bad request + // res.status(400); + // } + try { + (0, validationErrorParser_1.default)(errors); + const member = yield member_1.default.findByIdAndUpdate(id, req.body); + if (member === null) { + // No event found + res.status(404); + } + const updatedMember = yield member_1.default.findById(id); + if (updatedMember === null) { + // No event found, something went wrong + res.status(404); + } + res.status(200).json(updatedMember); + } + catch (error) { + next(error); + } +}); +exports.updateMember = updateMember; +const deleteMember = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { id } = req.params; + try { + const member = yield member_1.default.findByIdAndDelete(id); + if (!member) { + throw (0, http_errors_1.default)(404, "Member not found."); + } + res.status(200).json(member); + } + catch (error) { + next(error); + } +}); +exports.deleteMember = deleteMember; diff --git a/backend/dist/controllers/records.js b/backend/dist/controllers/records.js new file mode 100644 index 00000000..7de79219 --- /dev/null +++ b/backend/dist/controllers/records.js @@ -0,0 +1,48 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.updateRecord = exports.getRecord = void 0; +const records_1 = __importDefault(require("../models/records")); +const getRecord = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { card } = req.params; + try { + const updatedRecord = yield records_1.default.findOne({ card }); + if (updatedRecord === null) { + res.status(404); + } + res.status(200).json(updatedRecord); + } + catch (error) { + next(error); + } +}); +exports.getRecord = getRecord; +const updateRecord = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { card } = req.params; + try { + const record = yield records_1.default.findOneAndUpdate({ card: card }, req.body); + if (record === null) { + res.status(404); + } + const updatedRecord = yield records_1.default.findOne({ card }); + if (updatedRecord === null) { + res.status(404); + } + res.status(200).json(updatedRecord); + } + catch (error) { + next(error); + } +}); +exports.updateRecord = updateRecord; diff --git a/backend/dist/controllers/testimonial.js b/backend/dist/controllers/testimonial.js index c2b756c2..9a8ce79b 100644 --- a/backend/dist/controllers/testimonial.js +++ b/backend/dist/controllers/testimonial.js @@ -12,8 +12,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAllTestimonials = exports.createTestimonial = void 0; +exports.deleteTestimonial = exports.updateTestimonial = exports.getAllQuotes = exports.getAllTestimonials = exports.createTestimonial = void 0; const testimonial_1 = __importDefault(require("../models/testimonial")); +const express_validator_1 = require("express-validator"); +const http_errors_1 = __importDefault(require("http-errors")); +const validationErrorParser_1 = __importDefault(require("../util/validationErrorParser")); const createTestimonial = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { const { title, description, image, type } = req.body; try { @@ -23,9 +26,11 @@ const createTestimonial = (req, res, next) => __awaiter(void 0, void 0, void 0, image: image, type: type, }); + console.log("testimonial: ", testimonial); res.status(201).json(testimonial); } catch (error) { + console.log("erroring here"); next(error); } }); @@ -40,3 +45,53 @@ const getAllTestimonials = (req, res, next) => __awaiter(void 0, void 0, void 0, } }); exports.getAllTestimonials = getAllTestimonials; +const getAllQuotes = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + try { + const testimonials = yield testimonial_1.default.find({ type: "quote" }); + res.status(200).json(testimonials); + } + catch (error) { + next(error); + } +}); +exports.getAllQuotes = getAllQuotes; +const updateTestimonial = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const errors = (0, express_validator_1.validationResult)(req); + const { id } = req.params; + if (id !== req.body._id) { + // If the _id in the URL does not match the _id in the body, bad request + res.status(400); + } + try { + (0, validationErrorParser_1.default)(errors); + const testimonial = yield testimonial_1.default.findByIdAndUpdate(id, req.body); + if (testimonial === null) { + // No newsletter found + res.status(404); + } + const updatedTestimonial = yield testimonial_1.default.findById(id); + if (updatedTestimonial === null) { + // No testimonial found, something went wrong + res.status(404); + } + res.status(200).json(updatedTestimonial); + } + catch (error) { + next(error); + } +}); +exports.updateTestimonial = updateTestimonial; +const deleteTestimonial = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { + const { id } = req.params; + try { + const testimonial = yield testimonial_1.default.findByIdAndDelete(id); + if (!testimonial) { + throw (0, http_errors_1.default)(404, "Testimonial not found."); + } + res.status(200).json(testimonial); + } + catch (error) { + next(error); + } +}); +exports.deleteTestimonial = deleteTestimonial; diff --git a/backend/dist/models/records.js b/backend/dist/models/records.js new file mode 100644 index 00000000..92dfbdff --- /dev/null +++ b/backend/dist/models/records.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const mongoose_1 = require("mongoose"); +const recordsSchema = new mongoose_1.Schema({ + card: { type: String, required: true }, + date: { type: String, required: true }, +}); +exports.default = (0, mongoose_1.model)("Editrecords", recordsSchema); diff --git a/backend/dist/routes/members.js b/backend/dist/routes/members.js index a9da4537..b5cd4590 100644 --- a/backend/dist/routes/members.js +++ b/backend/dist/routes/members.js @@ -28,7 +28,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const MembersController = __importStar(require("../controllers/member")); +const MembersValidator = __importStar(require("../validators/member")); const router = express_1.default.Router(); router.get("/get", MembersController.getAllMembers); -router.post("/post", MembersController.createMember); +router.get("/:id", MembersController.getMember); +router.post("/post", MembersValidator.createMember, MembersController.createMember); +router.put("/:id", MembersController.updateMember); +router.delete("/:id", MembersController.deleteMember); exports.default = router; diff --git a/backend/dist/routes/records.js b/backend/dist/routes/records.js new file mode 100644 index 00000000..21985ce1 --- /dev/null +++ b/backend/dist/routes/records.js @@ -0,0 +1,35 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const RecordsController = __importStar(require("../controllers/records")); +const RecordsValidator = __importStar(require("../validators/records")); +const router = express_1.default.Router(); +router.put("/:card", RecordsValidator.updateRecord, RecordsController.updateRecord); +router.get("/:card", RecordsController.getRecord); +exports.default = router; diff --git a/backend/dist/routes/testimonial.js b/backend/dist/routes/testimonial.js index b882f539..02e7af27 100644 --- a/backend/dist/routes/testimonial.js +++ b/backend/dist/routes/testimonial.js @@ -28,7 +28,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const TestimonialController = __importStar(require("../controllers/testimonial")); +const TestimonialValidator = __importStar(require("../validators/testimonial")); const router = express_1.default.Router(); router.get("/get", TestimonialController.getAllTestimonials); +router.get("/get/quote", TestimonialController.getAllQuotes); router.post("/post", TestimonialController.createTestimonial); +router.put("/:id", TestimonialValidator.updateTestimonial, TestimonialController.updateTestimonial); +router.delete("/:id", TestimonialValidator.deleteTestimonial, TestimonialController.deleteTestimonial); exports.default = router; diff --git a/backend/dist/validators/records.js b/backend/dist/validators/records.js new file mode 100644 index 00000000..870c041d --- /dev/null +++ b/backend/dist/validators/records.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.updateRecord = void 0; +const express_validator_1 = require("express-validator"); +const makeIDValidator = () => (0, express_validator_1.body)("_id") + .exists() + .withMessage("_id is required") + .bail() + .isMongoId() + .withMessage("_id must be a MongoDB object ID"); +const makeCardValidator = () => (0, express_validator_1.body)("card") + .exists() + .withMessage("card is required") + .bail() + .isString() + .withMessage("card must be a string") + .bail() + .notEmpty() + .withMessage("card cannot be empty"); +const makeDateValidator = () => (0, express_validator_1.body)("date") + .exists() + .withMessage("date is required") + .bail() + .isString() + .withMessage("date must be a string") + .bail() + .notEmpty() + .withMessage("date cannot be empty"); +exports.updateRecord = [makeIDValidator(), makeCardValidator(), makeDateValidator()]; diff --git a/backend/dist/validators/testimonial.js b/backend/dist/validators/testimonial.js index 04414e12..95fc6002 100644 --- a/backend/dist/validators/testimonial.js +++ b/backend/dist/validators/testimonial.js @@ -1,12 +1,12 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.updateTestimonial = exports.createTestimonial = void 0; +exports.deleteTestimonial = exports.updateTestimonial = exports.createTestimonial = void 0; const express_validator_1 = require("express-validator"); const makeIDValidator = () => (0, express_validator_1.body)("_id") .exists() .withMessage("_id is required") .bail() - .isMongoId() + .isString() .withMessage("_id must be a MongoDB object ID"); const makeTitleValidator = () => (0, express_validator_1.body)("title") // title must exist, if not this message will be displayed @@ -46,12 +46,8 @@ const makeImageValidator = () => (0, express_validator_1.body)("image") .withMessage("image cannot be empty"); exports.createTestimonial = [ makeTitleValidator(), - makeDescriptionValidator(), makeImageValidator(), -]; -exports.updateTestimonial = [ - makeIDValidator(), - makeTitleValidator(), makeDescriptionValidator(), - makeImageValidator(), ]; +exports.updateTestimonial = [makeIDValidator(), makeImageValidator()]; +exports.deleteTestimonial = [makeIDValidator()]; diff --git a/backend/src/app.ts b/backend/src/app.ts index c809ecbe..c627d15d 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -16,6 +16,7 @@ import newsletterRoutes from "src/routes/newsletter"; // Import newsletter route import emailRoutes from "src/routes/emails"; import pageeditorRoutes from "src/routes/pageeditor"; import paypalRoutes from "src/routes/paypal"; +import recordRoutes from "src/routes/records"; const app = express(); @@ -44,6 +45,7 @@ app.use("/api/newsletter", newsletterRoutes); app.use("/api/emails", emailRoutes); app.use("/api/pageeditor", pageeditorRoutes); app.use("/api/orders", paypalRoutes); // Donation Order routes +app.use("/api/records", recordRoutes); /** * Error handler; all errors thrown by server are handled here. diff --git a/backend/src/controllers/records.ts b/backend/src/controllers/records.ts new file mode 100644 index 00000000..84b2b837 --- /dev/null +++ b/backend/src/controllers/records.ts @@ -0,0 +1,36 @@ +import { RequestHandler } from "express"; +import RecordModel from "src/models/records"; + +export const getRecord: RequestHandler = async (req, res, next) => { + const { card } = req.params; + try { + const updatedRecord = await RecordModel.findOne({ card }); + if (updatedRecord === null) { + res.status(404); + } + + res.status(200).json(updatedRecord); + } catch (error) { + next(error); + } +}; + +export const updateRecord: RequestHandler = async (req, res, next) => { + const { card } = req.params; + + try { + const record = await RecordModel.findOneAndUpdate({ card: card }, req.body); + + if (record === null) { + res.status(404); + } + const updatedRecord = await RecordModel.findOne({ card }); + if (updatedRecord === null) { + res.status(404); + } + + res.status(200).json(updatedRecord); + } catch (error) { + next(error); + } +}; diff --git a/backend/src/controllers/testimonial.ts b/backend/src/controllers/testimonial.ts index d9025b9e..ad1cfa73 100644 --- a/backend/src/controllers/testimonial.ts +++ b/backend/src/controllers/testimonial.ts @@ -14,9 +14,11 @@ export const createTestimonial: RequestHandler = async (req, res, next) => { image: image, type: type, }); + console.log("testimonial: ", testimonial); res.status(201).json(testimonial); } catch (error) { + console.log("erroring here"); next(error); } }; diff --git a/backend/src/models/records.ts b/backend/src/models/records.ts new file mode 100644 index 00000000..2556fbad --- /dev/null +++ b/backend/src/models/records.ts @@ -0,0 +1,10 @@ +import { InferSchemaType, Schema, model } from "mongoose"; + +const recordsSchema = new Schema({ + card: { type: String, required: true }, + date: { type: String, required: true }, +}); + +type Record = InferSchemaType; + +export default model("Editrecords", recordsSchema); diff --git a/backend/src/routes/records.ts b/backend/src/routes/records.ts new file mode 100644 index 00000000..b389a158 --- /dev/null +++ b/backend/src/routes/records.ts @@ -0,0 +1,10 @@ +import express from "express"; +import * as RecordsController from "src/controllers/records"; +import * as RecordsValidator from "src/validators/records"; + +const router = express.Router(); + +router.put("/:card", RecordsValidator.updateRecord, RecordsController.updateRecord); +router.get("/:card", RecordsController.getRecord); + +export default router; diff --git a/backend/src/validators/records.ts b/backend/src/validators/records.ts new file mode 100644 index 00000000..6fa6001c --- /dev/null +++ b/backend/src/validators/records.ts @@ -0,0 +1,31 @@ +import { body } from "express-validator"; + +const makeIDValidator = () => + body("_id") + .exists() + .withMessage("_id is required") + .bail() + .isMongoId() + .withMessage("_id must be a MongoDB object ID"); +const makeCardValidator = () => + body("card") + .exists() + .withMessage("card is required") + .bail() + .isString() + .withMessage("card must be a string") + .bail() + .notEmpty() + .withMessage("card cannot be empty"); +const makeDateValidator = () => + body("date") + .exists() + .withMessage("date is required") + .bail() + .isString() + .withMessage("date must be a string") + .bail() + .notEmpty() + .withMessage("date cannot be empty"); + +export const updateRecord = [makeIDValidator(), makeCardValidator(), makeDateValidator()]; diff --git a/backend/src/validators/testimonial.ts b/backend/src/validators/testimonial.ts index 6e77aa07..a1e14b28 100644 --- a/backend/src/validators/testimonial.ts +++ b/backend/src/validators/testimonial.ts @@ -52,7 +52,7 @@ const makeImageValidator = () => export const createTestimonial = [ makeTitleValidator(), makeImageValidator(), - makeDescriptionValidator, + makeDescriptionValidator(), ]; export const updateTestimonial = [makeIDValidator(), makeImageValidator()]; diff --git a/frontend/src/api/records.ts b/frontend/src/api/records.ts new file mode 100644 index 00000000..f467c88b --- /dev/null +++ b/frontend/src/api/records.ts @@ -0,0 +1,31 @@ +import { get, handleAPIError, put } from "./requests"; + +import type { APIResult } from "./requests"; +export type Record = { + _id: string; + card: string; + date: string; +}; + +export async function getRecord(card: string): Promise> { + try { + const response = await get(`/api/records/${card}`); + const json = (await response.json()) as Record; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} + +export async function updateRecord(card: string): Promise> { + try { + const updatedRecord = { card, date: new Date().toLocaleString() }; + const response = await put(`/api/records/${card}`, updatedRecord, { + "Content-Type": "application/json", + }); + const json = (await response.json()) as Record; + return { success: true, data: json }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/api/testimonial.ts b/frontend/src/api/testimonial.ts index e62e49ee..46f6b1be 100644 --- a/frontend/src/api/testimonial.ts +++ b/frontend/src/api/testimonial.ts @@ -40,6 +40,7 @@ export async function createTestimonial( testimonial: CreateTestimonialRequest, ): Promise> { try { + console.log("frontend createTestimonial"); const response = await post(`/api/testimonial/post`, testimonial); const data = (await response.json()) as Testimonial; return { success: true, data }; diff --git a/frontend/src/app/admin/page-editor/about/page.tsx b/frontend/src/app/admin/page-editor/about/page.tsx index 12bcfd96..a155c22d 100644 --- a/frontend/src/app/admin/page-editor/about/page.tsx +++ b/frontend/src/app/admin/page-editor/about/page.tsx @@ -5,6 +5,7 @@ import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "./page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -104,6 +105,11 @@ export default function AboutEditor() { .catch((error) => { alert(error); }); + updateRecord("about") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); @@ -169,7 +175,7 @@ export default function AboutEditor() {
{ alert(error); }); + updateRecord("involved") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); diff --git a/frontend/src/app/admin/page-editor/home/page.tsx b/frontend/src/app/admin/page-editor/home/page.tsx index 07590230..2ac495ca 100644 --- a/frontend/src/app/admin/page-editor/home/page.tsx +++ b/frontend/src/app/admin/page-editor/home/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../about/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -93,6 +94,11 @@ export default function HomeEditor() { }); setIsEdited(false); } + updateRecord("home") + .then() + .catch((error) => { + alert(error); + }); setWarningOpen(false); }; diff --git a/frontend/src/app/admin/page-editor/impact/page.tsx b/frontend/src/app/admin/page-editor/impact/page.tsx index e9bce9fd..53197f15 100644 --- a/frontend/src/app/admin/page-editor/impact/page.tsx +++ b/frontend/src/app/admin/page-editor/impact/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../about/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -81,6 +82,12 @@ export default function ImpactEditor() { .catch((error) => { alert(error); }); + + updateRecord("impact") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); diff --git a/frontend/src/app/admin/page-editor/involved/page.tsx b/frontend/src/app/admin/page-editor/involved/page.tsx index 360c0796..15916756 100644 --- a/frontend/src/app/admin/page-editor/involved/page.tsx +++ b/frontend/src/app/admin/page-editor/involved/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../about/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -81,6 +82,12 @@ export default function InvolvedEditor() { .catch((error) => { alert(error); }); + + updateRecord("involved") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); diff --git a/frontend/src/app/admin/page-editor/mission/page.tsx b/frontend/src/app/admin/page-editor/mission/page.tsx index 034879bc..30ff8b0d 100644 --- a/frontend/src/app/admin/page-editor/mission/page.tsx +++ b/frontend/src/app/admin/page-editor/mission/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../about/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -122,6 +123,11 @@ export default function MissionEditor() { .catch((error) => { alert(error); }); + updateRecord("about") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); @@ -190,7 +196,7 @@ export default function MissionEditor() {
{ alert(error); }); + + updateRecord("impact") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); diff --git a/frontend/src/app/admin/page-editor/page.tsx b/frontend/src/app/admin/page-editor/page.tsx index c4bc488e..b8153ba6 100644 --- a/frontend/src/app/admin/page-editor/page.tsx +++ b/frontend/src/app/admin/page-editor/page.tsx @@ -1,9 +1,41 @@ +"use client"; // Admin Page Editor landing page +import { useEffect, useState } from "react"; + import styles from "./page.module.css"; +import { getRecord } from "@/api/records"; import PageEditorCard from "@/components/PageEditorCard"; export default function PageEditorDashboard() { + const [lastUpdated, setLastUpdated] = useState>({}); + + useEffect(() => { + // Function to fetch last updated date for each card + + const cards = ["home", "about", "involved", "impact"]; // Assuming these are the card names + for (const card of cards) { + try { + getRecord(card) + .then((record) => { + if (record.success) { + setLastUpdated((prevLastUpdated) => ({ + ...prevLastUpdated, + [card]: record.data.date, // Assuming the date is stored in the 'date' field + })); + } else { + alert(record.error); + } + }) + .catch((error) => { + alert(error); + }); + } catch (error) { + console.error(`Error fetching last updated date for ${card}:`, error); + } + } + }, []); + return (
@@ -11,31 +43,25 @@ export default function PageEditorDashboard() { imageURI="/impact_bg.png" url="/page-editor/home" title="Home" - last_updated="Month XX, XXXX, XX:XX" + last_updated={lastUpdated.home ?? ""} /> -
diff --git a/frontend/src/app/admin/page-editor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx index ebf30d18..3b776df1 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.tsx +++ b/frontend/src/app/admin/page-editor/pastevents/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../about/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -78,6 +79,12 @@ export default function PastEventsEditor() { .catch((error) => { alert(error); }); + + updateRecord("involved") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); diff --git a/frontend/src/app/admin/page-editor/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index b4bc6fe9..74726c35 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -11,6 +11,7 @@ import { import { getPageText, updatePage } from "../../../../api/pageeditor"; import styles from "../testimonials/page.module.css"; +import { updateRecord } from "@/api/records"; import AlertBanner from "@/components/AlertBanner"; import Button from "@/components/Button"; import CancelButton from "@/components/CancelButton"; @@ -51,16 +52,16 @@ export default function TeamEditor() { }); getAllMembers() - .then((response2) => { - if (response2.success) { - setMembers(response2.data); + .then((response3) => { + if (response3.success) { + setMembers(response3.data); const newArray: string[][] = []; - for (const elem of response2.data) { + for (const elem of response3.data) { newArray.push([elem.name, elem.role]); } setMembersArray(newArray); } else { - alert(response2.error); + alert(response3.error); } }) .catch((error) => { @@ -70,6 +71,7 @@ export default function TeamEditor() { /* Handle Fields upon edit */ const handleEdit = (event: React.ChangeEvent) => { + console.log("handleEdit"); setIsEdited(true); if (event.target.id === "Page Header: Subtitle") { setPhSubtitle(event.target.value); @@ -88,6 +90,7 @@ export default function TeamEditor() { }); setMembersArray(updateArray); editedMembers.add(memberIndex); + console.log("editedMembers: ", editedMembers); } else if (event.target.id.includes("Staff Position")) { const memberIndex = Number(event.target.id.slice(event.target.id.indexOf(":") + 2)); //Update textarea by changing testimonialArray element @@ -135,15 +138,30 @@ export default function TeamEditor() { for (const index of Array.from(editedMembers)) { if (index >= members.length) { //Check title & description aren't empty - if (membersArray[index][0] !== "" || membersArray[index][1] !== "") { + if (membersArray[index][0] !== "" && membersArray[index][1] !== "") { createMember({ name: membersArray[index][0], role: membersArray[index][1], - // profilePictureURL: "/impact1.png" }) .then((response2) => { if (response2.success) { setShowAlert(true); + getAllMembers() + .then((response3) => { + if (response3.success) { + setMembers(response3.data); + const newArray: string[][] = []; + for (const elem of response3.data) { + newArray.push([elem.name, elem.role]); + } + setMembersArray(newArray); + } else { + alert(response3.error); + } + }) + .catch((error) => { + alert(error); + }); } else { // If adding and missing title/desc alert(response2.error); @@ -158,7 +176,7 @@ export default function TeamEditor() { members[index].name = membersArray[index][0]; members[index].role = membersArray[index][1]; //If deleting member - if (members[index].name === "" && members[index].role === "") { + if (members[index].name === "" || members[index].role === "") { deleteMember(members[index]) .then((response) => { if (response.success) { @@ -170,6 +188,7 @@ export default function TeamEditor() { .catch((error) => { alert(error); }); + members.splice(index, 1); } else { //If updating member updateMember(members[index]) @@ -186,10 +205,15 @@ export default function TeamEditor() { } } } - setMembersArray(membersArray.filter((elem) => elem[0] !== "" || elem[1] !== "")); + setMembersArray(membersArray.filter((elem) => elem[0] !== "" && elem[1] !== "")); setEditedMembers(new Set()); } + updateRecord("about") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); @@ -233,7 +257,10 @@ export default function TeamEditor() { const handleAdd = () => { console.log("Add Volunteer"); setMembersArray([...membersArray, ["", ""]]); - editedMembers.add(members.length); + editedMembers.add(membersArray.length); + console.log("editedMembers: ", editedMembers); + console.log("membersArray: ", membersArray); + console.log("members: ", members); setIsEdited(true); }; @@ -265,7 +292,7 @@ export default function TeamEditor() {
= testimonialData.length) { //Check title & description aren't empty - if (testimonialArray[index][0] !== "" || testimonialArray[index][1] !== "") { + if (testimonialArray[index][0] !== "" && testimonialArray[index][1] !== "") { createTestimonial({ title: testimonialArray[index][0], description: testimonialArray[index][1], @@ -162,6 +163,23 @@ export default function TestimonialsEditor() { response.data, // add one new Testimonial to the array ]); setShowAlert(true); + + getAllQuotes() + .then((response2) => { + if (response2.success) { + setTestimonialData(response2.data); + const newArray: string[][] = []; + for (const elem of response2.data) { + newArray.push([elem.title, elem.description]); // and one new item at the end + } + setTestimonialArray(newArray); + } else { + alert(response2.error); + } + }) + .catch((error) => { + alert(error); + }); } else { // If adding and missing title/desc alert(response.error); @@ -181,7 +199,7 @@ export default function TestimonialsEditor() { testimonialData[index].description = testimonialArray[index][1]; //If deleting testimonial - if (testimonialData[index].title === "" && testimonialData[index].description === "") { + if (testimonialData[index].title === "" || testimonialData[index].description === "") { deleteTestimonial(testimonialData[index]._id) .then((response) => { if (response.success) { @@ -193,6 +211,7 @@ export default function TestimonialsEditor() { .catch((error) => { alert(error); }); + testimonialData.splice(index, 1); } else { //If updating testimonial updateTestimonial(testimonialData[index]) @@ -209,9 +228,15 @@ export default function TestimonialsEditor() { } } } - setTestimonialArray(testimonialArray.filter((elem) => elem[0] !== "" || elem[1] !== "")); + setTestimonialArray(testimonialArray.filter((elem) => elem[0] !== "" && elem[1] !== "")); setEdited(new Set()); } + + updateRecord("impact") + .then() + .catch((error) => { + alert(error); + }); setIsEdited(false); } setWarningOpen(false); @@ -315,12 +340,17 @@ export default function TestimonialsEditor() { - + */}