diff --git a/backend/src/controllers/pageeditor.ts b/backend/src/controllers/pageeditor.ts index 4d30bdfb..39351a92 100644 --- a/backend/src/controllers/pageeditor.ts +++ b/backend/src/controllers/pageeditor.ts @@ -5,16 +5,16 @@ import PageEditor from "src/models/pageeditor"; import validationErrorParser from "src/util/validationErrorParser"; export const getPage: RequestHandler = async (req, res, next) => { - const { page } = req.params; + const { name } = req.params; try { - const pageText = await PageEditor.findOne({ page: page }); + const page = await PageEditor.findOne({ name: name }); - if (!pageText) { + if (!page) { throw createHttpError(404, "Page not found."); } - res.status(200).json(pageText); + res.status(200).json(page); } catch (error) { next(error); } @@ -22,22 +22,20 @@ export const getPage: RequestHandler = async (req, res, next) => { export const updatePageEditor: RequestHandler = async (req, res, next) => { const errors = validationResult(req); - const { page } = req.params; - - if (page !== req.body.page) { + const { name } = req.params; + if (name !== req.body.name) { // If the page in the URL does not match the page in the body, bad request res.status(400); } try { validationErrorParser(errors); - - const pageText = await PageEditor.findOneAndUpdate({ page }, req.body); - if (pageText === null) { + const page = await PageEditor.findOneAndUpdate({ name: name }, { $set: req.body }); + if (page === null) { // No page found res.status(404); } - const updatedPage = await PageEditor.findOne({ page }); + const updatedPage = await PageEditor.findOne({ name: name }); if (updatedPage === null) { // No page found after updating, something went wrong res.status(404); diff --git a/backend/src/controllers/testimonial.ts b/backend/src/controllers/testimonial.ts index ad1cfa73..cfc5479e 100644 --- a/backend/src/controllers/testimonial.ts +++ b/backend/src/controllers/testimonial.ts @@ -6,7 +6,6 @@ import validationErrorParser from "src/util/validationErrorParser"; export const createTestimonial: RequestHandler = async (req, res, next) => { const { title, description, image, type } = req.body; - try { const testimonial = await TestimonialModel.create({ title: title, @@ -14,11 +13,9 @@ 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/pageeditor.ts b/backend/src/models/pageeditor.ts index 7b3085b4..c1e9c0d4 100644 --- a/backend/src/models/pageeditor.ts +++ b/backend/src/models/pageeditor.ts @@ -1,8 +1,15 @@ import { InferSchemaType, Schema, model } from "mongoose"; const pageEditorSchema = new Schema({ - page: { type: String, required: true }, - pageSections: [{ type: Schema.Types.Mixed, required: true }], + name: { type: String, required: true }, + isEdited: { type: Boolean, required: true }, + fields: [ + { + name: { type: String, required: true }, + type: { type: String, required: true }, + data: { type: Schema.Types.Mixed, required: true }, + }, + ], }); type PageEditor = InferSchemaType; diff --git a/backend/src/models/testimonial.ts b/backend/src/models/testimonial.ts index 898fef82..99d6329b 100644 --- a/backend/src/models/testimonial.ts +++ b/backend/src/models/testimonial.ts @@ -3,10 +3,10 @@ import { InferSchemaType, Schema, model } from "mongoose"; const testimonialSchema = new Schema({ title: { type: String, required: true }, description: { type: String, required: true }, - image: { type: String, required: true }, + image: { type: String, required: false }, type: { type: String, required: true }, }); -type testimonial = InferSchemaType; +type Testimonial = InferSchemaType; -export default model("testimonial", testimonialSchema); +export default model("Testimonial", testimonialSchema); diff --git a/backend/src/routes/pageeditor.ts b/backend/src/routes/pageeditor.ts index 9f2c8293..da09ad36 100644 --- a/backend/src/routes/pageeditor.ts +++ b/backend/src/routes/pageeditor.ts @@ -4,11 +4,7 @@ import * as PageEditorValidator from "src/validators/pageeditor"; const router = express.Router(); -router.get("/:page", PageEditorValidator.getPageEditor, PageEditorController.getPage); -router.put( - "/:page", // getPageEditor validator works to just check page - // PageEditorValidator.getPageEditor, - PageEditorController.updatePageEditor, -); +router.get("/:name", PageEditorValidator.getPageEditor, PageEditorController.getPage); +router.put("/:name", PageEditorValidator.updatePageEditor, PageEditorController.updatePageEditor); export default router; diff --git a/backend/src/validators/pageeditor.ts b/backend/src/validators/pageeditor.ts index 90047678..db970e49 100644 --- a/backend/src/validators/pageeditor.ts +++ b/backend/src/validators/pageeditor.ts @@ -1,47 +1,28 @@ import { body } from "express-validator"; -const makeIDValidator = () => - body("_id") +const makeNameValidator = () => + body("name") .exists() - .withMessage("_id is required") + .withMessage("name is required") .bail() .isString() - .withMessage("_id must be a number"); -const makePageValidator = () => - body("page") - .exists() - .withMessage("image is required") - .bail() - .isString() - .withMessage("image must be a string"); -const makeSubtitleValidator = () => - body("ph_subtitle") - .exists() - .withMessage("subtitle is required") - .bail() - .isString() - .withMessage("subtitle must be a string"); -const makeTitleValidator = () => - body("s1_title") + .withMessage("name must be a string"); + +const makeEditedValidator = () => + body("isEdited") .exists() - .withMessage("date is required") + .withMessage("isEdited is required") .bail() - .isString() - .withMessage("date must be a string"); -const makeTextValidator = () => - body("s1_text") + .isBoolean() + .withMessage("isEdited must be a boolean"); + +const makeFieldsValidator = () => + body("fields") .exists() - .withMessage("content is required") + .withMessage("fields is required") .bail() - .isString() - .withMessage("content must be a string"); - -export const getPageEditor = [makePageValidator()]; + .isArray() + .withMessage("fields must be an array"); -export const createPageEditor = [ - makeIDValidator(), - makePageValidator(), - makeSubtitleValidator(), - makeTitleValidator(), - makeTextValidator(), -]; +export const getPageEditor = [makeNameValidator(), makeEditedValidator(), makeFieldsValidator()]; +export const updatePageEditor = [makeNameValidator(), makeEditedValidator(), makeFieldsValidator()]; diff --git a/backend/src/validators/testimonial.ts b/backend/src/validators/testimonial.ts index a1e14b28..7e60f8fe 100644 --- a/backend/src/validators/testimonial.ts +++ b/backend/src/validators/testimonial.ts @@ -36,18 +36,7 @@ const makeDescriptionValidator = () => .withMessage("description cannot be empty"); const makeImageValidator = () => - body("image") - // title must exist, if not this message will be displayed - .exists() - .withMessage("image is required") - // bail prevents the remainder of the validation chain for this field from being executed if - // there was an error - .bail() - .isString() - .withMessage("image must be a string") - .bail() - .notEmpty() - .withMessage("image cannot be empty"); + body("image").optional().isString().withMessage("image must be a string"); export const createTestimonial = [ makeTitleValidator(), diff --git a/frontend/next.config.js b/frontend/next.config.js index 151cdec6..ba9bb05e 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -8,6 +8,8 @@ const nextConfig = { "i.imgur.com", "images.unsplash.com", "plus.unsplash.com", + "firebasestorage.googleapis.com", + "tse.ucsd.edu", ], }, }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7ee6b774..6e39ba76 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "date-fns": "^3.6.0", "envalid": "^8.0.0", "firebase": "^10.11.0", + "framer-motion": "^11.2.6", "html2canvas": "^1.4.1", "html2pdf.js": "^0.9.3", "jspdf": "^2.5.1", @@ -26,7 +27,9 @@ "react": "^18", "react-datepicker": "^6.9.0", "react-dom": "^18", + "react-dropzone": "^14.2.3", "react-firebase-hooks": "^5.1.1", + "react-icons": "^5.2.1", "react-material-symbols": "^4.3.1" }, "devDependencies": { @@ -2389,6 +2392,14 @@ "node": ">= 4.5.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.17", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", @@ -3970,6 +3981,17 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz", "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==" }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4116,6 +4138,30 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.6.tgz", + "integrity": "sha512-XUrjjBt57e5YoHQtjwc3eNchFBuHvIgN/cS8SC4oIaAn2J/0+bLanUxXizidJKZVeHJam/JrmMnPRjYMglVn5g==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6121,6 +6167,22 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-firebase-hooks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz", @@ -6130,6 +6192,14 @@ "react": ">= 16.8.0" } }, + "node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index f2c0920d..c16a6e30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "date-fns": "^3.6.0", "envalid": "^8.0.0", "firebase": "^10.11.0", + "framer-motion": "^11.2.6", "html2canvas": "^1.4.1", "html2pdf.js": "^0.9.3", "jspdf": "^2.5.1", @@ -32,7 +33,9 @@ "react": "^18", "react-datepicker": "^6.9.0", "react-dom": "^18", + "react-dropzone": "^14.2.3", "react-firebase-hooks": "^5.1.1", + "react-icons": "^5.2.1", "react-material-symbols": "^4.3.1" }, "devDependencies": { diff --git a/frontend/public/pastEvents.svg b/frontend/public/pastEvents.svg new file mode 100644 index 00000000..2ecca393 --- /dev/null +++ b/frontend/public/pastEvents.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/api/member.ts b/frontend/src/api/member.ts index 2fcd0008..daad1840 100644 --- a/frontend/src/api/member.ts +++ b/frontend/src/api/member.ts @@ -55,16 +55,8 @@ export async function updateMember(member: updateMemberRequest): Promise> { +export async function deleteMember(id: string): Promise> { try { - const id = member._id; const response = await del(`/api/member/${id}`); const json = (await response.json()) as Member; return { success: true, data: json }; diff --git a/frontend/src/api/pageeditor.ts b/frontend/src/api/pageeditor.ts index 7ee6312c..cc87be99 100644 --- a/frontend/src/api/pageeditor.ts +++ b/frontend/src/api/pageeditor.ts @@ -2,60 +2,50 @@ import { get, handleAPIError, put } from "./requests"; import type { APIResult } from "./requests"; -export type PageSection = { - subtitle?: string; - sectionTitle?: string; - sectionSubtitle?: string; +export type Page = { + name: string; + isEdited: boolean; + fields: Field[]; }; -export type PageText = { - page: string; - pageSections: PageSection[]; +export type Field = { + name: string; + type: string; + data: TextData | ImageData | GalleryData; }; -export async function getPageText(page: string): Promise> { +export type TextData = { + text: string; +}; + +export type ImageData = { + image: string; + hasImage: boolean; +}; + +export type GalleryData = { + images: string[]; + maxImages: number; +}; + +export async function getPageData(name: string): Promise> { try { - const response = await get(`/api/pageeditor/${page}`); - const json = (await response.json()) as PageText; + const response = await get(`/api/pageeditor/${name}`); + const json = (await response.json()) as Page; return { success: true, data: json }; } catch (error) { return handleAPIError(error); } } -export async function updatePage(pageData: PageText): Promise> { +export async function updatePageData(name: string, newPage: Page): Promise> { try { - const page = pageData.page; - const response = await put(`/api/pageeditor/${page}`, pageData); - const json = (await response.json()) as PageText; + const response = await put(`/api/pageeditor/${name}`, newPage, { + "Content-Type": "application/json", + }); + const json = (await response.json()) as Page; return { success: true, data: json }; } catch (error) { return handleAPIError(error); } } - -// type UpdateEventDetailsRequest = { -// _id: string; -// name: string; -// description: string; -// guidelines: string; -// date: string; -// location: string; -// imageURI: string; -// volunteers: string[]; -// }; - -// export async function updateEventDetails( -// eventDetails: UpdateEventDetailsRequest, -// ): Promise> { -// try { -// const id = eventDetails._id; -// const response = await put(`/api/eventDetails/${id}`, eventDetails, { -// "Content-Type": "application/json", -// }); -// const json = (await response.json()) as EventDetails; -// 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 46f6b1be..e62e49ee 100644 --- a/frontend/src/api/testimonial.ts +++ b/frontend/src/api/testimonial.ts @@ -40,7 +40,6 @@ 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/(web app)/about/page.tsx b/frontend/src/app/(web app)/about/page.tsx index 82995cf0..73e142e7 100644 --- a/frontend/src/app/(web app)/about/page.tsx +++ b/frontend/src/app/(web app)/about/page.tsx @@ -1,94 +1,72 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import AboutCard from "../../../components/AboutCard"; +import BackgroundHeader from "../../../components/BackgroundHeader"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; +import { generatePageMap } from "../../admin/util/pageeditUtil"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import AboutCard from "@/components/AboutCard"; -import BackgroundHeader from "@/components/BackgroundHeader"; - -export default function Impact() { - const [images, setImages] = useState([]); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); - const [s2Subtitle, setS2Subtitle] = useState(""); - const [s2Text, setS2Text] = useState(""); - const [s3Subtitle, setS3Subtitle] = useState(""); - const [s3Text, setS3Text] = useState(""); - - useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); +export default function AboutPage() { + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - let pageText; useEffect(() => { - getPageText("About Us") + setLoading(true); + getPageData("about") .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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="" title="About Us" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
diff --git a/frontend/src/app/(web app)/contact/page.tsx b/frontend/src/app/(web app)/contact/page.tsx index 25cc4de5..d17b0cdc 100644 --- a/frontend/src/app/(web app)/contact/page.tsx +++ b/frontend/src/app/(web app)/contact/page.tsx @@ -1,38 +1,60 @@ +"use client"; + import Image from "next/image"; -import React from "react"; +import React, { useEffect, useState } from "react"; -import styles from "./page.module.css"; +import { getPageData } from "../../../api/pageeditor"; +import ContactForm from "../../../components/ContactForm"; +import ContactInfoCard from "../../../components/ContactInfoCard"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; +import { generatePageMap } from "../../admin/util/pageeditUtil"; -import ContactForm from "@/components/ContactForm"; -import ContactInfoCard from "@/components/ContactInfoCard"; +import styles from "./page.module.css"; export default function Contact() { + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setLoading(true); + getPageData("contact") + .then((response) => { + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); + }) + .catch((error) => { + alert(error); + }); + setLoading(false); + }, []); + + if (loading || !pageMap) { + return ; + } + return (
{/*Reach Out To Us */}
Reach Out To Us
-

- There are many ways to join us and support our mission. Contact us to find out more - about volunteering opportunities, fundraising, and more ! -

+

{pageMap.get("Subtitle") as string}

diff --git a/frontend/src/app/(web app)/impact/page.tsx b/frontend/src/app/(web app)/impact/page.tsx index 5b754237..955309e9 100644 --- a/frontend/src/app/(web app)/impact/page.tsx +++ b/frontend/src/app/(web app)/impact/page.tsx @@ -1,60 +1,44 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; +import WhiteCard from "../../../components/WhiteCard"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; -import WhiteCard from "@/components/WhiteCard"; - export default function Impact() { - const [images, setImages] = useState([]); - - //admin variables - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s2Subtitle, setS2Subtitle] = useState(""); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - let pageText; - useEffect(() => { - getPageText("Our Impact") + setLoading(true); + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="" title="Our Impact" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
@@ -65,14 +49,14 @@ export default function Impact() { buttonUrl="/testimonials" buttonText="Learn More" title="Testimonals" - description={s1Subtitle} + description={pageMap.get("Testimonials Subtitle") as string} />
diff --git a/frontend/src/app/(web app)/involved/page.module.css b/frontend/src/app/(web app)/involved/page.module.css index fe501c54..29dbef8c 100644 --- a/frontend/src/app/(web app)/involved/page.module.css +++ b/frontend/src/app/(web app)/involved/page.module.css @@ -1,12 +1,11 @@ @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wdth,wght@0,75..100,300..800;1,75..100,300..800&family=Roboto+Slab:wght@100..900&display=swap"); .cards { + margin: 0 auto 136px; display: flex; flex-direction: column; + justify-content: center; gap: 48px; - margin-left: 202px; - margin-right: 202px; - margin-bottom: 136px; } .backgroundImageContainer { @@ -15,9 +14,10 @@ } .whiteCardsContainer { - position: absolute; - top: 688px; - z-index: 1; + display: flex; + flex-direction: column; + width: 100%; + justify-content: center; } .cardsBackground { diff --git a/frontend/src/app/(web app)/involved/page.tsx b/frontend/src/app/(web app)/involved/page.tsx index 04b113ce..befffee8 100644 --- a/frontend/src/app/(web app)/involved/page.tsx +++ b/frontend/src/app/(web app)/involved/page.tsx @@ -1,76 +1,71 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; +import WhiteCard from "../../../components/WhiteCard"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; -import WhiteCard from "@/components/WhiteCard"; - export default function Involved() { - const [images, setImages] = useState([]); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s2Subtitle, setS2Subtitle] = useState(""); - - useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - let pageText; useEffect(() => { - getPageText("Get Involved") + setLoading(true); + getPageData("involved") .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionSubtitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); - } else { - alert(response.error); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="" title="Get Involved" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} + pushUpButtons={true} />
-
+
+ +
diff --git a/frontend/src/app/(web app)/mission/page.module.css b/frontend/src/app/(web app)/mission/page.module.css index a5bbcfd4..2908e656 100644 --- a/frontend/src/app/(web app)/mission/page.module.css +++ b/frontend/src/app/(web app)/mission/page.module.css @@ -74,7 +74,9 @@ /*For the images*/ .imageContainer { + margin-top: -64px; display: flex; + gap: 32px; flex-direction: column; } @@ -87,6 +89,7 @@ } .storyContainer { + margin-top: 24px; display: flex; flex-direction: row; justify-content: space-between; diff --git a/frontend/src/app/(web app)/mission/page.tsx b/frontend/src/app/(web app)/mission/page.tsx index f7e37705..e062a2c2 100644 --- a/frontend/src/app/(web app)/mission/page.tsx +++ b/frontend/src/app/(web app)/mission/page.tsx @@ -1,96 +1,66 @@ "use client"; -import Image from "next/image"; import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import BackgroundHeader from "../../../components/BackgroundHeader"; import Button from "../../../components/Button"; import ValueCard from "../../../components/ValueCard"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; +import { generatePageMap } from "../../admin/util/pageeditUtil"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; - export default function Mission() { - const [images, setImages] = useState([]); - - const [valueSubtitle, setvalueSubtitle] = useState(""); - const [phSubtitle, setPhSubtitle] = useState(""); - const [Value1, setValue1] = useState(""); - const [Value1_Description, setValue1_Description] = useState(""); - const [Value2, setValue2] = useState(""); - const [Value2_Description, setValue2_Description] = useState(""); - const [Value3, setValue3] = useState(""); - const [Value3_Description, setValue3_Description] = useState(""); - const [s1Text, setS1Text] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - let pageText; - useEffect(() => { - getPageText("Our Mission") + setLoading(true); + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + console.log((pageMap.get("Image Gallery") as string[])[0]); + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="OUR MISSION" title="Why We Do It" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
{/* We pay it forward*/}
-
{valueSubtitle}
+
{pageMap.get("Values Section Title") as string}
@@ -98,41 +68,40 @@ export default function Mission() { {/* OUR STORY*/}
-
{s1Subtitle}
-

{s1Text}

+
{pageMap.get("Story Section Title") as string}
+

{pageMap.get("Body Text") as string}

-
- Story image 1 -
- -
- Story image 2 -
-
-
- Story image 3 +
+
diff --git a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx index c308a9dd..7fa09f91 100644 --- a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx +++ b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx @@ -1,15 +1,16 @@ "use client"; + +import Image from "next/image"; import React, { useEffect, useState } from "react"; import { Newsletter, getNewsletter } from "../../../../api/newsletter"; +import { GalleryData, getPageData } from "../../../../api/pageeditor"; +import BackgroundHeader from "../../../../components/BackgroundHeader"; +import Button from "../../../../components/Button"; import NewsletterPopup from "../../../../components/NewsletterPopup"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; -import Button from "@/components/Button"; - type Props = { params: { newsletterID: string }; }; @@ -18,18 +19,17 @@ export default function NewsletterDisplay({ params }: Props) { const [popupOpen, setPopup] = useState(false); const [newsletter, setNewsletter] = useState(null); - const [images, setImages] = useState([]); + const [images, setImages] = useState([]); useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } + getPageData("newsletter") + .then((response) => { + if (response.success) { + const carouselImages = (response.data.fields[1].data as GalleryData).images; + setImages(carouselImages); + } else throw new Error(response.error); }) - .catch((error) => { - alert(error); - }); + .catch(console.error); }, []); const handleSubscribeClick = () => { @@ -53,7 +53,7 @@ export default function NewsletterDisplay({ params }: Props) { return (
image.imageURI)} + backgroundImageURIs={images} header="" title="The 4FLOT Quarterly" description="4FLOT is committed in preventing and ending homelessness, hunger and disparity in underprivileged communities. " @@ -69,8 +69,8 @@ export default function NewsletterDisplay({ params }: Props) {
- Description of the image
Here’s Our Story @@ -90,10 +92,12 @@ export default function NewsletterDisplay({ params }: Props) {
Share This Post - facebook Icon - twitter Icon
diff --git a/frontend/src/app/(web app)/newsletter/page.tsx b/frontend/src/app/(web app)/newsletter/page.tsx index e41aef69..0427ef20 100644 --- a/frontend/src/app/(web app)/newsletter/page.tsx +++ b/frontend/src/app/(web app)/newsletter/page.tsx @@ -1,43 +1,41 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { Newsletter, getAllNewsletters } from "../../../api/newsletter"; +import { getPageData } from "../../../api/pageeditor"; +import BackgroundHeader from "../../../components/BackgroundHeader"; +import Button from "../../../components/Button"; import NewsletterArchive from "../../../components/NewsletterArchive"; import NewsletterCard from "../../../components/NewsletterCard"; import NewsletterPopup from "../../../components/NewsletterPopup"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; +import { generatePageMap } from "../../admin/util/pageeditUtil"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import { Newsletter, getAllNewsletters } from "@/api/newsletter"; -import BackgroundHeader from "@/components/BackgroundHeader"; -import Button from "@/components/Button"; - export default function NewsletterPage() { const [popupOpen, setPopup] = useState(false); - const [images, setImages] = useState([]); const [curNewsletters, setCurNewsletters] = useState([]); const [archiveNewsletters, setArchiveNewsletters] = useState>({}); const [sortedArchives, setSortedArchives] = useState([]); - //admin variables - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); + // admin variables + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } + function loadPageMap() { + getPageData("newsletter") + .then((response) => { + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); - }, []); + } - useEffect(() => { + function loadNewspapers() { getAllNewsletters() .then((response) => { if (response.success) { @@ -75,42 +73,35 @@ export default function NewsletterPage() { .catch((error) => { alert(error); }); + } + + useEffect(() => { + setLoading(true); + loadNewspapers(); + loadPageMap(); + setLoading(false); }, []); const handleSubscribeClick = () => { setPopup(true); }; - let pageText; - useEffect(() => { - 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); - }); - }, []); + if (loading || !pageMap) { + return ; + } return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="OUR IMPACT" title="Newsletter" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
-
{s1Subtitle}
+
{pageMap.get("Section Title") as string}
-
{s1Text}
+
{pageMap.get("Section Subtitle") as string}
diff --git a/frontend/src/app/(web app)/page.tsx b/frontend/src/app/(web app)/page.tsx index c4d8e11c..058f5082 100644 --- a/frontend/src/app/(web app)/page.tsx +++ b/frontend/src/app/(web app)/page.tsx @@ -1,75 +1,50 @@ "use client"; + +import Image from "next/image"; import React, { useEffect, useState } from "react"; -import "../globals.css"; -import { getPageText } from "../../api/pageeditor"; +import { getPageData } from "../../api/pageeditor"; import BackgroundHeader from "../../components/BackgroundHeader"; +import Button from "../../components/Button"; +import Description from "../../components/Description"; +import EventsList from "../../components/EventsList"; +import WhiteCard from "../../components/WhiteCard"; +import LoadingSpinner from "../../components/admin/LoadingSpinner"; +import { generatePageMap } from "../admin/util/pageeditUtil"; import styles from "./page.module.css"; - -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import Button from "@/components/Button"; -import Description from "@/components/Description"; -import EventsList from "@/components/EventsList"; -import WhiteCard from "@/components/WhiteCard"; +import "../globals.css"; export default function Home() { - const [images, setImages] = useState([]); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); - const [s2Subtitle, setS2Subtitle] = useState(""); - const [s2Text, setS2Text] = useState(""); - - const see_more_text = "See More"; - const sponsor_us_text = "Sponsor Us"; + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); useEffect(() => { - getBackgroundImages(BackgroundImagePages.HOME) - .then((result) => { - if (result.success) { - console.log(result.data, "images"); - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - /* Get page data from MongoDB */ - let pageText; - useEffect(() => { - getPageText("Home") + setLoading(true); + getPageData("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 ?? ""); - console.log("response.data: ", response.data); - } else { - alert(response.error); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
- {images.length > 0 && ( - image.imageURI)} - header={""} - title={"4 Future Leaders of Tomorrow"} - description={phSubtitle} - button={
+ +
+
+ {(pageMap.get("Sponsor Image Gallery") as string[]).map((url) => ( +
+ sponsor +
+ ))} +
- - Sponsors
-
diff --git a/frontend/src/app/(web app)/past-events/page.tsx b/frontend/src/app/(web app)/past-events/page.tsx index 8e75222a..469324b7 100644 --- a/frontend/src/app/(web app)/past-events/page.tsx +++ b/frontend/src/app/(web app)/past-events/page.tsx @@ -1,62 +1,51 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; import EventsList from "../../../components/EventsList"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -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(""); +export default function UpcomingEvents() { + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - let pageText; - useEffect(() => { - getPageText("Past Events") + setLoading(true); + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="GET INVOLVED" title="Past Events" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} /> +
-

{s1Subtitle}

-

{s1Text}

+

{pageMap.get("Section Title") as string}

+

+ {pageMap.get("Section Subtitle") as string} +

diff --git a/frontend/src/app/(web app)/team/page.tsx b/frontend/src/app/(web app)/team/page.tsx index f95a88ef..29886007 100644 --- a/frontend/src/app/(web app)/team/page.tsx +++ b/frontend/src/app/(web app)/team/page.tsx @@ -2,35 +2,22 @@ import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { Member, getAllMembers } from "../../../api/member"; +import { getPageData } from "../../../api/pageeditor"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; +import MemberInfo from "../../../components/MemberInfo"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -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) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - useEffect(() => { + const loadMembers = () => { + setLoading(true); getAllMembers() .then((result) => { if (result.success) { @@ -43,37 +30,44 @@ export default function Team() { .catch((error) => { alert(error); }); - }, []); + setLoading(false); + }; - let pageText; - useEffect(() => { - getPageText("Our Team") + const loadPage = () => { + setLoading(true); + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); + }; + + useEffect(() => { + loadMembers(); + loadPage(); }, []); + + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="OUR TEAM" title="Meet Our Team" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
-
{s1Subtitle}
+
{pageMap.get("Section Title") as string}
{/*
Hello.
*/} -

{s1Text}

+ {/*
👋
*/} +

{pageMap.get("Body Text") as string}

{members.map((member) => ( diff --git a/frontend/src/app/(web app)/testimonials/page.tsx b/frontend/src/app/(web app)/testimonials/page.tsx index b8f060f9..b996bbfc 100644 --- a/frontend/src/app/(web app)/testimonials/page.tsx +++ b/frontend/src/app/(web app)/testimonials/page.tsx @@ -1,40 +1,36 @@ +// "use client"; + +// import React, { useEffect, useState } from "react"; + +// import { getPageText } from "../../../api/pageeditor"; +// import { Testimonial, getAllTestimonials } from "../../../api/testimonial"; + +// import styles from "./page.module.css"; + +// import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; +// import BackgroundHeader from "@/components/BackgroundHeader"; + "use client"; import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; import { Testimonial, getAllTestimonials } from "../../../api/testimonial"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; import TestimonialCard from "../../../components/TestimonialCard"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; - export default function Impact() { const [testimonials, setTestimonials] = useState([]); 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(""); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - useEffect(() => { - getBackgroundImages(BackgroundImagePages.HOME) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - useEffect(() => { + const loadTestimonials = () => { + console.log("getting from loadTestimonials"); getAllTestimonials() .then((result) => { if (result.success) { @@ -49,28 +45,30 @@ export default function Impact() { .catch((error) => { alert(error); }); - }, []); + }; - let pageText; - useEffect(() => { - getPageText("Testimonials") + const loadPage = () => { + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + }; + + useEffect(() => { + setLoading(true); + loadTestimonials(); + loadPage(); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="OUR IMPACT" title="Testimonials" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
-
{s1Subtitle}
-
{s1Text}
+
{pageMap.get("Stories Section Title") as string}
+
{pageMap.get("Stories Body Text") as string}
{testimonials.map((testimonial) => ( @@ -102,8 +100,8 @@ export default function Impact() { ))}
-
{s2Title}
-
{s2Subtitle}
+
{pageMap.get("Events Section Title") as string}
+
{pageMap.get("Events Body Text") as string}
diff --git a/frontend/src/app/(web app)/upcoming-events/page.tsx b/frontend/src/app/(web app)/upcoming-events/page.tsx index e7388d7b..3f81df9e 100644 --- a/frontend/src/app/(web app)/upcoming-events/page.tsx +++ b/frontend/src/app/(web app)/upcoming-events/page.tsx @@ -1,63 +1,51 @@ "use client"; + import React, { useEffect, useState } from "react"; -import { getPageText } from "../../../api/pageeditor"; +import { getPageData } from "../../../api/pageeditor"; +import { generatePageMap } from "../../../app/admin/util/pageeditUtil"; +import BackgroundHeader from "../../../components/BackgroundHeader"; import EventsList from "../../../components/EventsList"; +import LoadingSpinner from "../../../components/admin/LoadingSpinner"; import styles from "./page.module.css"; -import { BackgroundImage, BackgroundImagePages, getBackgroundImages } from "@/api/images"; -import BackgroundHeader from "@/components/BackgroundHeader"; - export default function UpcomingEvents() { - const [images, setImages] = useState([]); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); - - useEffect(() => { - getBackgroundImages(BackgroundImagePages.TEAM) - .then((result) => { - if (result.success) { - setImages(result.data); - } - }) - .catch((error) => { - alert(error); - }); - }, []); + const [pageMap, setPageMap] = useState>(); + const [loading, setLoading] = useState(false); - let pageText; useEffect(() => { - getPageText("Upcoming Events") + setLoading(true); + getPageData("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); - } + if (response.success) setPageMap(generatePageMap(response.data)); + else throw new Error(response.error); }) .catch((error) => { alert(error); }); + setLoading(false); }, []); + if (loading || !pageMap) { + return ; + } + return (
image.imageURI)} + backgroundImageURIs={pageMap.get("Header Image Carousel") as string[]} header="GET INVOLVED" title="Upcoming Events" - description={phSubtitle} + description={pageMap.get("Subtitle") as string} />
-

{s1Subtitle}

-

{s1Text}

+

{pageMap.get("Section Title") as string}

+

+ {pageMap.get("Section Subtitle") as string} +

diff --git a/frontend/src/app/admin/mailing-list/page.tsx b/frontend/src/app/admin/mailing-list/page.tsx index 2d916b3a..aa5bde0a 100644 --- a/frontend/src/app/admin/mailing-list/page.tsx +++ b/frontend/src/app/admin/mailing-list/page.tsx @@ -25,62 +25,6 @@ import RowCopyBtn from "@/components/RowCopyBtn"; import RowDeleteBtn from "@/components/RowDeleteBtn"; export default function MailingList() { - const columns: GridColDef<(typeof rows)[number]>[] = [ - { - field: "lastName", - headerName: "Last name", - width: 280, - editable: false, - resizable: false, - headerClassName: `${styles.headingBackground} ${styles.cellBorderStyle} ${styles.Headings}`, - cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, - disableColumnMenu: true, - renderHeader: () =>
Last Name
, - }, - { - field: "firstName", - headerName: "First Name", - width: 280, - editable: false, - resizable: false, - headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`, - cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, - disableColumnMenu: true, - renderHeader: () =>
First Name
, - }, - - { - field: "memberSince", - headerName: "Member Since", - width: 280, - editable: false, - resizable: false, - headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`, - cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, - disableColumnMenu: true, - renderHeader: () =>
Member Since
, - }, - - { - field: "email", - headerName: "Email", - width: 280, - sortable: false, - editable: false, - resizable: false, - flex: 1, - headerClassName: `${styles.Headings} ${styles.headingBackground}`, - cellClassName: styles.cellEntry, - disableColumnMenu: true, - renderHeader: () => ( -
-
Email
- -
- ), - }, - ]; - const [rows, setRow] = useState([]); const [rowsCurrent, setRowsCurrent] = React.useState(rows); const [alertType, setAlertType] = useState(""); @@ -286,6 +230,62 @@ export default function MailingList() { } }; + const columns: GridColDef<(typeof rows)[number]>[] = [ + { + field: "lastName", + headerName: "Last name", + width: 280, + editable: false, + resizable: false, + headerClassName: `${styles.headingBackground} ${styles.cellBorderStyle} ${styles.Headings}`, + cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, + disableColumnMenu: true, + renderHeader: () =>
Last Name
, + }, + { + field: "firstName", + headerName: "First Name", + width: 280, + editable: false, + resizable: false, + headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`, + cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, + disableColumnMenu: true, + renderHeader: () =>
First Name
, + }, + + { + field: "memberSince", + headerName: "Member Since", + width: 280, + editable: false, + resizable: false, + headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`, + cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`, + disableColumnMenu: true, + renderHeader: () =>
Member Since
, + }, + + { + field: "email", + headerName: "Email", + width: 280, + sortable: false, + editable: false, + resizable: false, + flex: 1, + headerClassName: `${styles.Headings} ${styles.headingBackground}`, + cellClassName: styles.cellEntry, + disableColumnMenu: true, + renderHeader: () => ( +
+
Email
+ +
+ ), + }, + ]; + return (
@@ -350,9 +350,11 @@ export default function MailingList() { lineHeight: "24px", }} // Make room for the image /> - search icon - Add Icon + Add Icon Add Newsletter diff --git a/frontend/src/app/admin/page-editor/about/AboutEditor.tsx b/frontend/src/app/admin/page-editor/about/AboutEditor.tsx new file mode 100644 index 00000000..6ec7f2c7 --- /dev/null +++ b/frontend/src/app/admin/page-editor/about/AboutEditor.tsx @@ -0,0 +1,26 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function AboutEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/about/page.tsx b/frontend/src/app/admin/page-editor/about/page.tsx index a155c22d..76126954 100644 --- a/frontend/src/app/admin/page-editor/about/page.tsx +++ b/frontend/src/app/admin/page-editor/about/page.tsx @@ -1,219 +1,25 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; +import { Page } from "../../../../api/pageeditor"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/aboutPageDefault.json"; +import AboutEditor from "./AboutEditor"; 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"; -import Collapsable from "@/components/Collapsable"; -import PageToggle from "@/components/PageToggle"; -import { WarningModule } from "@/components/WarningModule"; - -export default function AboutEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); - const [s2Subtitle, setS2Subtitle] = useState(""); - const [s2Text, setS2Text] = useState(""); - const [s3Subtitle, setS3Subtitle] = useState(""); - const [s3Text, setS3Text] = useState(""); - - const [showAlert, setShowAlert] = useState(false); - const [warningOpen, setWarningOpen] = useState(false); - - /* Get page data from MongoDB */ - let pageText; - useEffect(() => { - 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 ?? ""); - console.log("response.data: ", response.data); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - /* Handle Fields upon edit */ - const handleEdit = (event: React.ChangeEvent) => { - setIsEdited(true); - if (event.target.id === "Page Subtitle: Subtitle") { - setPhSubtitle(event.target.value); - } else if (event.target.id === "Section 1 - Our Mission: Section Title") { - setS1Subtitle(event.target.value); - } else if (event.target.id === "Section 1 - Our Mission: Body Text") { - setS1Text(event.target.value); - } else if (event.target.id === "Section 2 - Our Team: Section Title") { - setS2Subtitle(event.target.value); - } else if (event.target.id === "Section 2 - Our Team: Body Text") { - setS2Text(event.target.value); - } else if (event.target.id === "Section 3 - Contact Us: Section Title") { - setS3Subtitle(event.target.value); - } else if (event.target.id === "Section 3 - Contact Us: Body Text") { - setS3Text(event.target.value); - } - }; - - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save changes"); - updatePage({ - //Pass edited text to MongoDB - page: "About Us", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - { - sectionTitle: s2Subtitle, - sectionSubtitle: s2Text, - }, - { - sectionTitle: s3Subtitle, - sectionSubtitle: s3Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - updateRecord("about") - .then() - .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("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); - }; - +export default function AboutEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/contact/ContactEditor.tsx b/frontend/src/app/admin/page-editor/contact/ContactEditor.tsx new file mode 100644 index 00000000..bea49cc4 --- /dev/null +++ b/frontend/src/app/admin/page-editor/contact/ContactEditor.tsx @@ -0,0 +1,18 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function ContactEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/contact/page.module.css b/frontend/src/app/admin/page-editor/contact/page.module.css new file mode 100644 index 00000000..6e6a4d0b --- /dev/null +++ b/frontend/src/app/admin/page-editor/contact/page.module.css @@ -0,0 +1,45 @@ +.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: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.warningPopup { + position: fixed; + left: 35%; + z-index: 3; + width: 100%; +} diff --git a/frontend/src/app/admin/page-editor/contact/page.tsx b/frontend/src/app/admin/page-editor/contact/page.tsx new file mode 100644 index 00000000..911b33c5 --- /dev/null +++ b/frontend/src/app/admin/page-editor/contact/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/contactPageDefault.json"; + +import ContactEditor from "./ContactEditor"; +import styles from "./page.module.css"; + +export default function ContactEditorPage() { + return ( +
+ + + + + +
+ ); +} diff --git a/frontend/src/app/admin/page-editor/events/UpcomingEventsEditor.tsx b/frontend/src/app/admin/page-editor/events/UpcomingEventsEditor.tsx new file mode 100644 index 00000000..9590aa79 --- /dev/null +++ b/frontend/src/app/admin/page-editor/events/UpcomingEventsEditor.tsx @@ -0,0 +1,18 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function UpcomingEventsEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/events/page.module.css b/frontend/src/app/admin/page-editor/events/page.module.css new file mode 100644 index 00000000..46fdfb6e --- /dev/null +++ b/frontend/src/app/admin/page-editor/events/page.module.css @@ -0,0 +1,47 @@ +@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; + 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: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.warningPopup { + position: fixed; + left: 35%; + z-index: 3; + width: 100%; +} diff --git a/frontend/src/app/admin/page-editor/events/page.tsx b/frontend/src/app/admin/page-editor/events/page.tsx index d81fd680..2e4feda6 100644 --- a/frontend/src/app/admin/page-editor/events/page.tsx +++ b/frontend/src/app/admin/page-editor/events/page.tsx @@ -1,178 +1,25 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/eventsPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function EventsEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Title, setS1Title] = useState(""); - const [s1Text, setS1Text] = useState(""); - - const [showAlert, setShowAlert] = useState(false); - const [warningOpen, setWarningOpen] = useState(false); - - /* Get page data from MongoDB */ - let pageText; - useEffect(() => { - getPageText("Upcoming Events") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Title(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - console.log("response.data: ", response.data); - } 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") { - setS1Title(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: "Upcoming Events", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Title, - sectionSubtitle: s1Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - updateRecord("involved") - .then() - .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("Upcoming Events") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Title(pageText.pageSections[1].sectionTitle ?? ""); - setS1Text(pageText.pageSections[1].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); - }; - - const handleCloseAlert = () => { - setShowAlert(false); - }; +import UpcomingEventsEditor from "./UpcomingEventsEditor"; +import styles from "./page.module.css"; +export default function UpcomingEventEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - -
- -
-
+ + + +
); } diff --git a/frontend/src/app/admin/page-editor/home/HomeEditor.tsx b/frontend/src/app/admin/page-editor/home/HomeEditor.tsx new file mode 100644 index 00000000..b9c93682 --- /dev/null +++ b/frontend/src/app/admin/page-editor/home/HomeEditor.tsx @@ -0,0 +1,22 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function HomeEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/home/page.module.css b/frontend/src/app/admin/page-editor/home/page.module.css new file mode 100644 index 00000000..46fdfb6e --- /dev/null +++ b/frontend/src/app/admin/page-editor/home/page.module.css @@ -0,0 +1,47 @@ +@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; + 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: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.warningPopup { + position: fixed; + left: 35%; + z-index: 3; + width: 100%; +} diff --git a/frontend/src/app/admin/page-editor/home/page.tsx b/frontend/src/app/admin/page-editor/home/page.tsx index 2ac495ca..d05ef1d2 100644 --- a/frontend/src/app/admin/page-editor/home/page.tsx +++ b/frontend/src/app/admin/page-editor/home/page.tsx @@ -1,193 +1,19 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/homePageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function HomeEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - const [s1Text, setS1Text] = useState(""); - 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(() => { - 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 ?? ""); - console.log("response.data: ", response.data); - } 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); - } else if (event.target.id === "Section 2: Section Title") { - setS2Subtitle(event.target.value); - } else if (event.target.id === "Section 2: Body Text") { - setS2Text(event.target.value); - } - }; - - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save changes"); - updatePage({ - //Pass edited text to MongoDB - page: "Home", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - { - sectionTitle: s2Subtitle, - sectionSubtitle: s2Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); - } - updateRecord("home") - .then() - .catch((error) => { - alert(error); - }); - setWarningOpen(false); - }; - - const handleCancel = () => { - // Show cancel warning - if (isEdited) { - 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); - }; +import HomeEditor from "./HomeEditor"; +import styles from "./page.module.css"; +export default function HomePage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/impact/ImpactEditor.tsx b/frontend/src/app/admin/page-editor/impact/ImpactEditor.tsx new file mode 100644 index 00000000..6719ae42 --- /dev/null +++ b/frontend/src/app/admin/page-editor/impact/ImpactEditor.tsx @@ -0,0 +1,22 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function ImpactEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/impact/page.module.css b/frontend/src/app/admin/page-editor/impact/page.module.css new file mode 100644 index 00000000..98f6d327 --- /dev/null +++ b/frontend/src/app/admin/page-editor/impact/page.module.css @@ -0,0 +1,19 @@ +.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; +} diff --git a/frontend/src/app/admin/page-editor/impact/page.tsx b/frontend/src/app/admin/page-editor/impact/page.tsx index 53197f15..863ff084 100644 --- a/frontend/src/app/admin/page-editor/impact/page.tsx +++ b/frontend/src/app/admin/page-editor/impact/page.tsx @@ -1,188 +1,25 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import { Page } from "../../../../api/pageeditor"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/impactPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function ImpactEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - 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(() => { - 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 ?? ""); - console.log("response.data: ", response.data); - } 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 - Testimonials: Subtitle") { - setS1Subtitle(event.target.value); - } else if (event.target.id === "Section 2 - Newsletter: Subtitle") { - setS2Subtitle(event.target.value); - } - }; - - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save changes"); - updatePage({ - //Pass edited text to MongoDB - page: "Our Impact", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - }, - { - sectionTitle: s2Subtitle, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - - updateRecord("impact") - .then() - .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("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); - }; +import ImpactEditor from "./ImpactEditor"; +import styles from "./page.module.css"; +export default function ImpactEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/involved/InvolvedEditor.tsx b/frontend/src/app/admin/page-editor/involved/InvolvedEditor.tsx new file mode 100644 index 00000000..3d39e2ae --- /dev/null +++ b/frontend/src/app/admin/page-editor/involved/InvolvedEditor.tsx @@ -0,0 +1,26 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function InvolvedEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/involved/page.module.css b/frontend/src/app/admin/page-editor/involved/page.module.css new file mode 100644 index 00000000..c3ac0e89 --- /dev/null +++ b/frontend/src/app/admin/page-editor/involved/page.module.css @@ -0,0 +1,42 @@ +.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/involved/page.tsx b/frontend/src/app/admin/page-editor/involved/page.tsx index 15916756..ed8bde49 100644 --- a/frontend/src/app/admin/page-editor/involved/page.tsx +++ b/frontend/src/app/admin/page-editor/involved/page.tsx @@ -1,187 +1,25 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/involvedPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function InvolvedEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - 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(() => { - getPageText("Get Involved") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionSubtitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); - console.log("response.data: ", response.data); - } 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 - Upcoming Events: Subtitle") { - setS1Subtitle(event.target.value); - } else if (event.target.id === "Section 2 - Donate: Subtitle") { - setS2Subtitle(event.target.value); - } - }; - - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save changes"); - updatePage({ - //Pass edited text to MongoDB - page: "Get Involved", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionSubtitle: s1Subtitle, - }, - { - sectionSubtitle: s2Subtitle, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - - updateRecord("involved") - .then() - .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("Get Involved") - .then((response) => { - if (response.success) { - pageText = response.data; - setPhSubtitle(pageText.pageSections[0].subtitle ?? ""); - setS1Subtitle(pageText.pageSections[1].sectionSubtitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - setIsEdited(false); - }; - - const handleCloseAlert = () => { - setShowAlert(false); - }; +import InvolvedEditor from "./InvolvedEditor"; +import styles from "./page.module.css"; +export default function InvolvedEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - -
- -
-
+ + + +
); } diff --git a/frontend/src/app/admin/page-editor/mission/MissionEditor.tsx b/frontend/src/app/admin/page-editor/mission/MissionEditor.tsx new file mode 100644 index 00000000..5f700b4a --- /dev/null +++ b/frontend/src/app/admin/page-editor/mission/MissionEditor.tsx @@ -0,0 +1,30 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function MissionEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/mission/page.module.css b/frontend/src/app/admin/page-editor/mission/page.module.css new file mode 100644 index 00000000..e6d5066d --- /dev/null +++ b/frontend/src/app/admin/page-editor/mission/page.module.css @@ -0,0 +1,26 @@ +.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/mission/page.tsx b/frontend/src/app/admin/page-editor/mission/page.tsx index 30ff8b0d..fb02301a 100644 --- a/frontend/src/app/admin/page-editor/mission/page.tsx +++ b/frontend/src/app/admin/page-editor/mission/page.tsx @@ -1,250 +1,24 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/missionPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function MissionEditor() { - const [isEdited, setIsEdited] = useState(false); - const [valueSubtitle, setvalueSubtitle] = useState(""); - const [phSubtitle, setPhSubtitle] = useState(""); - const [Value1, setValue1] = useState(""); - const [Value1_Description, setValue1_Description] = useState(""); - const [Value2, setValue2] = useState(""); - const [Value2_Description, setValue2_Description] = useState(""); - const [Value3, setValue3] = useState(""); - const [Value3_Description, setValue3_Description] = useState(""); - const [s1Text, setS1Text] = useState(""); - const [s1Subtitle, setS1Subtitle] = useState(""); - - const [showAlert, setShowAlert] = useState(false); - const [warningOpen, setWarningOpen] = useState(false); - - /* Get page data from MongoDB */ - let pageText; - useEffect(() => { - 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 ?? ""); - console.log("response.data: ", response.data); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - }, []); - - /* Handle Fields upon edit */ - const handleEdit = (event: React.ChangeEvent) => { - setIsEdited(true); - if (event.target.id === "Page Subtitle: Subtitle") { - setPhSubtitle(event.target.value); - } else if (event.target.id === "Section 1: Section Title") { - setvalueSubtitle(event.target.value); - } else if (event.target.id === "Section 1: Value #1") { - setValue1(event.target.value); - } else if (event.target.id === "Section 1: Value #1 Description") { - setValue1_Description(event.target.value); - } else if (event.target.id === "Section 1: Value #2") { - setValue2(event.target.value); - } else if (event.target.id === "Section 1: Value #2 Description") { - setValue2_Description(event.target.value); - } else if (event.target.id === "Section 1: Value #3") { - setValue3(event.target.value); - } else if (event.target.id === "Section 1: Value #3 Description") { - setValue3_Description(event.target.value); - } else if (event.target.id === "Section 2: Section Title") { - setS1Subtitle(event.target.value); - } else if (event.target.id === "Section 2: Body Text") { - setS1Text(event.target.value); - } - }; - - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save changes"); - updatePage({ - //Pass edited text to MongoDB - page: "Our Mission", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - subtitle: valueSubtitle, - }, - { - sectionTitle: Value1, - sectionSubtitle: Value1_Description, - }, - { - sectionTitle: Value2, - sectionSubtitle: Value2_Description, - }, - { - sectionTitle: Value3, - sectionSubtitle: Value3_Description, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - updateRecord("about") - .then() - .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("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); - }; +import MissionEditor from "./MissionEditor"; +import styles from "./page.module.css"; +export default function MissionEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/newsletter/NewsletterEditor.tsx b/frontend/src/app/admin/page-editor/newsletter/NewsletterEditor.tsx new file mode 100644 index 00000000..31081072 --- /dev/null +++ b/frontend/src/app/admin/page-editor/newsletter/NewsletterEditor.tsx @@ -0,0 +1,18 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function NewsletterEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/newsletter/page.module.css b/frontend/src/app/admin/page-editor/newsletter/page.module.css new file mode 100644 index 00000000..98f6d327 --- /dev/null +++ b/frontend/src/app/admin/page-editor/newsletter/page.module.css @@ -0,0 +1,19 @@ +.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; +} diff --git a/frontend/src/app/admin/page-editor/newsletter/page.tsx b/frontend/src/app/admin/page-editor/newsletter/page.tsx index 69ce0d39..a3886820 100644 --- a/frontend/src/app/admin/page-editor/newsletter/page.tsx +++ b/frontend/src/app/admin/page-editor/newsletter/page.tsx @@ -1,179 +1,24 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../about/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/newsletterPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -export default function NewsletterEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - 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("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); - }); - }, []); - - /* 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: "Newsletter", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - - updateRecord("impact") - .then() - .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("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); - }; +import NewsletterEditor from "./NewsletterEditor"; +import styles from "./page.module.css"; +export default function NewsletterEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/page.module.css b/frontend/src/app/admin/page-editor/page.module.css index c6131f68..f402fb38 100644 --- a/frontend/src/app/admin/page-editor/page.module.css +++ b/frontend/src/app/admin/page-editor/page.module.css @@ -1,15 +1,16 @@ .page { display: flex; + flex-direction: column; justify-content: center; - padding-left: 30px; - padding-top: 50px; + margin-left: 234px; + margin-top: 50px; } .gridContainer { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + display: flex; + flex-direction: row; flex-wrap: wrap; - justify-content: space-between; + justify-content: flex-start; max-width: 100%; margin-top: 50px; margin-left: 140px; diff --git a/frontend/src/app/admin/page-editor/page.tsx b/frontend/src/app/admin/page-editor/page.tsx index b8153ba6..900ae016 100644 --- a/frontend/src/app/admin/page-editor/page.tsx +++ b/frontend/src/app/admin/page-editor/page.tsx @@ -2,10 +2,10 @@ // 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"; -import { getRecord } from "@/api/records"; -import PageEditorCard from "@/components/PageEditorCard"; +import styles from "./page.module.css"; export default function PageEditorDashboard() { const [lastUpdated, setLastUpdated] = useState>({}); diff --git a/frontend/src/app/admin/page-editor/pastevents/PastEventsEditor.tsx b/frontend/src/app/admin/page-editor/pastevents/PastEventsEditor.tsx new file mode 100644 index 00000000..e55ef872 --- /dev/null +++ b/frontend/src/app/admin/page-editor/pastevents/PastEventsEditor.tsx @@ -0,0 +1,18 @@ +import Editor from "../../../../components/admin/pageeditor/Editor"; + +export default function PastEventsEditor() { + return ( + + ); +} diff --git a/frontend/src/app/admin/page-editor/pastevents/page.tsx b/frontend/src/app/admin/page-editor/pastevents/page.tsx index 3b776df1..38f912c0 100644 --- a/frontend/src/app/admin/page-editor/pastevents/page.tsx +++ b/frontend/src/app/admin/page-editor/pastevents/page.tsx @@ -1,179 +1,25 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/pastEventsPageDefault.json"; 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"; -import Collapsable from "@/components/Collapsable"; -import PageToggle from "@/components/PageToggle"; -import { WarningModule } from "@/components/WarningModule"; - -export default function PastEventsEditor() { - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - 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("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) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - - updateRecord("involved") - .then() - .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("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); - }; +import PastEventsEditor from "./PastEventsEditor"; +export default function UpcomingEventEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/team/TeamEditor.tsx b/frontend/src/app/admin/page-editor/team/TeamEditor.tsx new file mode 100644 index 00000000..414534f8 --- /dev/null +++ b/frontend/src/app/admin/page-editor/team/TeamEditor.tsx @@ -0,0 +1,177 @@ +"use client"; + +import React, { useEffect, useState } from "react"; + +import { + Member, + createMember, + deleteMember, + getAllMembers, + updateMember, +} from "../../../../api/member"; +import { Collapsible } from "../../../../components/admin/pageeditor/Collapsible"; +import Editor from "../../../../components/admin/pageeditor/Editor"; +import { usePageDispatch } from "../../../../components/admin/pageeditor/PageProvider"; +import MemberBox from "../../../../components/admin/pageeditor/inputBoxes/MemberBox"; + +export default function TeamEditor() { + const dispatch = usePageDispatch(); + const [members, setMembers] = useState([]); + + const setMembersFromDB = () => { + getAllMembers() + .then((res) => { + if (res.success) { + setMembers([...res.data]); + } else { + alert(res.error); + } + }) + .catch(console.error); + }; + + useEffect(() => { + setMembersFromDB(); + }, []); + + const onSave = (saveMembers: Member[]) => { + // get most recent mongodb data + getAllMembers() + .then((res) => { + if (res.success) { + // create "id" -> "member" map for members state + const memberMap = new Map(); + saveMembers.forEach((m) => { + memberMap.set(m._id, m); + }); + // get all ids stored on mongodb + const ids = res.data.map((m) => m._id); + // do something for every id in mongodb + let id; + while ((id = ids.pop())) { + if (!memberMap.has(id)) { + // if map does not have id, delete id from mongodb + deleteMember(id).catch(console.error); + } else { + // if map contains id, update member with id + const memberAtId = memberMap.get(id); + updateMember({ + _id: id, + name: memberAtId?.name ?? "", + role: memberAtId?.role ?? "", + ...memberAtId, + }).catch(console.error); + memberMap.delete(id); // delete after updating + } + } + // if memberMap still has items, add them + if (memberMap.size > 0) { + // for each value (member) left in memberMap + Array.from(memberMap.values()).forEach((m) => { + createMember({ + name: m.name, + role: m.role, + profilePictureURL: m.profilePictureURL, + }).catch(console.error); + }); + } + } + }) + .catch(console.error); + }; + + const onCancel = () => { + // overwrite local members with mongodb members + setMembersFromDB(); + }; + + const handleEditMember = (m: Member, isTextEdit: boolean) => { + const idx = members.findIndex((_) => _._id === m._id); + if (idx > -1) { + // if matching id found + const _members = [...members]; // shallow copy + _members[idx] = m; // overwrite matching member + setMembers(_members); // set with new member + } else { + // otherwise append + const _members = [...members, m]; + setMembers(_members); + } + // image url edits do not set isEdited + if (isTextEdit) { + // set page isEdited = true + dispatch({ + type: "set_isEdited", + setIsEdited: true, + }); + } + }; + + const handleRemoveMember = (m: Member) => { + // filter out member that has id = m._id + const _members = members.filter((_) => _._id !== m._id); + // remove member from local state + setMembers([..._members]); + // save the page, can't undo a delete + onSave(_members); + }; + + const handleAddMember = () => { + // create dummy member to pass to createMember() + const m: Member = { + _id: `new-member-${crypto.randomUUID()}`, // will get real mongodb ObjectID if saved + name: "", + role: "", + }; + // append to members + setMembers([...members, m]); + // set page isEdited = true + dispatch({ + type: "set_isEdited", + setIsEdited: true, + }); + }; + + const membersArray = members; + + return ( + { + onSave(members); + }} + onCancel={onCancel} + sections={[ + { + title: "Page Header", + fieldNames: ["Subtitle", "Header Image Carousel"], + }, + { + title: "Section 1", + fieldNames: ["Section Title", "Body Text"], + }, + ]} + > + +
+ {membersArray.map((m, idx) => ( + + ))} +
+
+ +
+
+
+ ); +} diff --git a/frontend/src/app/admin/page-editor/team/page.module.css b/frontend/src/app/admin/page-editor/team/page.module.css new file mode 100644 index 00000000..e6d5066d --- /dev/null +++ b/frontend/src/app/admin/page-editor/team/page.module.css @@ -0,0 +1,26 @@ +.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/team/page.tsx b/frontend/src/app/admin/page-editor/team/page.tsx index 74726c35..0a7fde38 100644 --- a/frontend/src/app/admin/page-editor/team/page.tsx +++ b/frontend/src/app/admin/page-editor/team/page.tsx @@ -1,336 +1,24 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { - Member, - createMember, - deleteMember, - getAllMembers, - updateMember, -} from "../../../../api/member"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import styles from "../testimonials/page.module.css"; +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/teamPageDefault.json"; -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; - -// import PageEditorCard from "@/components/PageEditorCard"; - -export default function TeamEditor() { - const [members, setMembers] = useState([]); - const [membersArray, setMembersArray] = useState([]); - const [editedMembers, setEditedMembers] = useState>(new Set()); //Indices of edited testimonials - - const [isEdited, setIsEdited] = useState(false); - const [phSubtitle, setPhSubtitle] = useState(""); - 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("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); - }); - - 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); - }); - }, []); - - /* 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); - } 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); - } 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); - 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 - 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("save in team"); - updatePage({ - //Pass edited text to MongoDB - page: "Our Team", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - - if (editedMembers.size > 0) { - for (const index of Array.from(editedMembers)) { - if (index >= members.length) { - //Check title & description aren't empty - if (membersArray[index][0] !== "" && membersArray[index][1] !== "") { - createMember({ - name: membersArray[index][0], - role: membersArray[index][1], - }) - .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); - setMembersArray(membersArray.filter((elem, elemIndex) => elemIndex !== index)); - } - }) - .catch((error) => { - alert(error); - }); - } - } else { - members[index].name = membersArray[index][0]; - members[index].role = membersArray[index][1]; - //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); - }); - members.splice(index, 1); - } else { - //If updating member - updateMember(members[index]) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .catch((error) => { - alert(error); - }); - } - } - } - setMembersArray(membersArray.filter((elem) => elem[0] !== "" && elem[1] !== "")); - setEditedMembers(new Set()); - } - - updateRecord("about") - .then() - .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("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); - }); - 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(membersArray.length); - console.log("editedMembers: ", editedMembers); - console.log("membersArray: ", membersArray); - console.log("members: ", members); - setIsEdited(true); - }; - - const handleCloseAlert = () => { - setShowAlert(false); - }; +import TeamEditor from "./TeamEditor"; +import styles from "./page.module.css"; +export default function TeamEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - - - - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/page-editor/testimonials/TestimonialsEditor.tsx b/frontend/src/app/admin/page-editor/testimonials/TestimonialsEditor.tsx new file mode 100644 index 00000000..da72cc53 --- /dev/null +++ b/frontend/src/app/admin/page-editor/testimonials/TestimonialsEditor.tsx @@ -0,0 +1,221 @@ +"use client"; + +import React, { useEffect, useState } from "react"; + +import { + Testimonial, + createTestimonial, + deleteTestimonial, + getAllTestimonials, + updateTestimonial, +} from "../../../../api/testimonial"; +import { Collapsible } from "../../../../components/admin/pageeditor/Collapsible"; +import { CollapsibleFields } from "../../../../components/admin/pageeditor/CollapsibleFields"; +import Editor from "../../../../components/admin/pageeditor/Editor"; +import { usePageDispatch } from "../../../../components/admin/pageeditor/PageProvider"; +import TestimonialsBox from "../../../../components/admin/pageeditor/inputBoxes/TestimonialsBox"; + +export default function TestimonialsEditor() { + const dispatch = usePageDispatch(); + const [testimonials, setTestimonials] = useState([]); + + const setTestimonialsFromDB = () => { + getAllTestimonials() + .then((res) => { + if (res.success) { + setTestimonials([...res.data]); + } else { + alert(res.error); + } + }) + .catch(console.error); + }; + + useEffect(() => { + setTestimonialsFromDB(); + }, []); + + const onSave = (saveTestimonials: Testimonial[]) => { + // get most recent mongodb data + getAllTestimonials() + .then((res) => { + if (res.success) { + // update testimonials in the same way as in page-editor/team/TeamEditor.tsx + const testimonialMap = new Map(); + saveTestimonials.forEach((t) => { + testimonialMap.set(t._id, t); + }); + const ids = res.data.map((t) => t._id); + let id; + while ((id = ids.pop())) { + if (!testimonialMap.has(id)) { + deleteTestimonial(id).catch(console.error); + } else { + const testimonialAtId = testimonialMap.get(id); + updateTestimonial({ + _id: id, + title: testimonialAtId?.title ?? "", + description: testimonialAtId?.description ?? "", + image: testimonialAtId?.image ?? "", + type: testimonialAtId?.type ?? "", + }).catch(console.error); + testimonialMap.delete(id); + } + } + if (testimonialMap.size > 0) { + Array.from(testimonialMap.values()).forEach((t) => { + createTestimonial({ + title: t.title, + description: t.description, + image: t.image, + type: t.type, + }).catch(console.error); + }); + } + } + }) + .catch(console.error); + }; + + const onCancel = () => { + // overwrite local testimonials with mongodb testimonials + setTestimonialsFromDB(); + }; + + const handleEdit = (t: Testimonial, isTextEdit: boolean) => { + const idx = testimonials.findIndex((_) => _._id === t._id); + if (idx > -1) { + // if matching id found + const _testimonials = [...testimonials]; // shallow copy + _testimonials[idx] = t; // overwrite matching testimonial + setTestimonials(_testimonials); // set with new testimonial + } else { + // otherwise append + const _testimonials = [...testimonials, t]; + setTestimonials(_testimonials); + } + // image url edits do not set isEdited + if (isTextEdit) { + // set page isEdited = true + dispatch({ + type: "set_isEdited", + setIsEdited: true, + }); + } + }; + + const handleRemove = (t: Testimonial) => { + // filter out testimonial that has id = m._id + const _testimonials = testimonials.filter((_) => _._id !== t._id); + // remove testimonial from local state + setTestimonials([..._testimonials]); + // save the page, can't undo a delete + onSave(_testimonials); + }; + + const handleAddQuote = () => { + // create dummy quote to pass to createMember() + const t: Testimonial = { + _id: `new-quote-${crypto.randomUUID()}`, // will get real mongodb ObjectID if saved + title: "", + description: "", + image: "", + type: "quote", + }; + // append to members + setTestimonials([...testimonials, t]); + // set page isEdited = true + dispatch({ + type: "set_isEdited", + setIsEdited: true, + }); + }; + + const handleAddEvent = () => { + // create dummy quote to pass to createMember() + const t: Testimonial = { + _id: `new-event-${crypto.randomUUID()}`, // will get real mongodb ObjectID if saved + title: "", + description: "", + image: "", + type: "event", + }; + // append to members + setTestimonials([...testimonials, t]); + // set page isEdited = true + dispatch({ + type: "set_isEdited", + setIsEdited: true, + }); + }; + + return ( + { + onSave(testimonials); + }} + onCancel={onCancel} + sections={[ + { + title: "Page Header", + fieldNames: ["Subtitle", "Header Image Carousel"], + }, + { + title: "Section 1", + fieldNames: ["Stories Section Title", "Stories Body Text"], + }, + ]} + > + +
+ {testimonials + .filter((t) => t.type === "quote") + .map((t, idx) => ( + + ))} +
+
+ +
+
+ + +
+ {testimonials + .filter((t) => t.type === "event") + .map((t, idx) => ( + + ))} +
+
+ +
+
+
+ ); +} diff --git a/frontend/src/app/admin/page-editor/testimonials/page.tsx b/frontend/src/app/admin/page-editor/testimonials/page.tsx index 5db40ad4..597677fe 100644 --- a/frontend/src/app/admin/page-editor/testimonials/page.tsx +++ b/frontend/src/app/admin/page-editor/testimonials/page.tsx @@ -1,366 +1,391 @@ -"use client"; -import React, { useEffect, useState } from "react"; +// "use client"; +// import React, { useEffect, useState } from "react"; -import { getPageText, updatePage } from "../../../../api/pageeditor"; -import { - Testimonial, - createTestimonial, - deleteTestimonial, - getAllQuotes, - updateTestimonial, -} from "../../../../api/testimonial"; +// import { getPageText, updatePage } from "../../../../api/pageeditor"; +// import { +// Testimonial, +// createTestimonial, +// deleteTestimonial, +// getAllQuotes, +// updateTestimonial, +// } from "../../../../api/testimonial"; -import styles from "./page.module.css"; +// 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"; +// import Collapsable from "@/components/Collapsable"; +// import PageToggle from "@/components/PageToggle"; +// import { WarningModule } from "@/components/WarningModule"; + +// 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, setEdited] = useState>(new Set()); //Indices of edited testimonials -import { updateRecord } from "@/api/records"; -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 { WarningModule } from "@/components/WarningModule"; +// const [showAlert, setShowAlert] = useState(false); +// const [warningOpen, setWarningOpen] = useState(false); -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(""); +// /* 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) { +// 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); +// }); +// }, []); - const [testimonialData, setTestimonialData] = useState([]); //Holds all testimonials as Testimonials - const [testimonialArray, setTestimonialArray] = useState([]); //Holds all testimonials as strings - const [editedTestimonials, setEdited] = useState>(new Set()); //Indices of edited testimonials +// /* 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: 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 [showAlert, setShowAlert] = useState(false); - const [warningOpen, setWarningOpen] = useState(false); +// const handleSave = () => { +// // Implement save logic +// if (isEdited) { +// console.log("Save Testimonials Page"); +// updatePage({ +// //Pass edited text to MongoDB +// page: "Testimonials", +// pageSections: [ +// { +// subtitle: phSubtitle, +// }, +// { +// sectionTitle: s1Subtitle, +// sectionSubtitle: s1Text, +// }, +// { +// sectionTitle: s2Title, +// sectionSubtitle: s2Subtitle, +// }, +// ], +// }) +// .then((response) => { +// if (response.success) { +// setShowAlert(true); +// } else { +// alert(response.error); +// } +// }) +// .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) { +// //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", +// }) +// .then((response) => { +// if (response.success) { +// setTestimonialData([ +// ...testimonialData, +// response.data, // add one new Testimonial to the array +// ]); +// setShowAlert(true); - /* 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) { - 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); - }); - }, []); +// 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); +// 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]; - /* 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: 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 - } - }; +// //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); +// }); +// testimonialData.splice(index, 1); +// } 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()); +// } - const handleSave = () => { - // Implement save logic - if (isEdited) { - console.log("Save Testimonials Page"); - updatePage({ - //Pass edited text to MongoDB - page: "Testimonials", - pageSections: [ - { - subtitle: phSubtitle, - }, - { - sectionTitle: s1Subtitle, - sectionSubtitle: s1Text, - }, - { - sectionTitle: s2Title, - sectionSubtitle: s2Subtitle, - }, - ], - }) - .then((response) => { - if (response.success) { - setShowAlert(true); - } else { - alert(response.error); - } - }) - .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) { - //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", - }) - .then((response) => { - if (response.success) { - setTestimonialData([ - ...testimonialData, - response.data, // add one new Testimonial to the array - ]); - setShowAlert(true); +// updateRecord("impact") +// .then() +// .catch((error) => { +// alert(error); +// }); +// setIsEdited(false); +// } +// setWarningOpen(false); +// }; - 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); - 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]; +// const handleCancel = () => { +// // Show cancel warning +// if (isEdited) { +// setWarningOpen(true); +// } +// }; - //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); - }); - testimonialData.splice(index, 1); - } 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()); - } +// 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 ?? ""); +// setS2Title(pageText.pageSections[2].sectionTitle ?? ""); +// setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); +// } else { +// alert(response.error); +// } +// }) +// .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); +// }; - updateRecord("impact") - .then() - .catch((error) => { - alert(error); - }); - setIsEdited(false); - } - setWarningOpen(false); - }; +// const handleAdd = () => { +// console.log("Add Testimonial"); +// setTestimonialArray([ +// ...testimonialArray, +// ["", ""], // add one new item at the end +// ]); +// editedTestimonials.add(testimonialArray.length); //Add index to list of edited indices +// setIsEdited(true); +// }; - const handleCancel = () => { - // Show cancel warning - if (isEdited) { - setWarningOpen(true); - } - }; +// const handleCloseAlert = () => { +// setShowAlert(false); +// }; - 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 ?? ""); - setS2Title(pageText.pageSections[2].sectionTitle ?? ""); - setS2Subtitle(pageText.pageSections[2].sectionSubtitle ?? ""); - } else { - alert(response.error); - } - }) - .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); - }; +// return ( +//
+//
+// {showAlert && } +//
+//
+// {warningOpen &&
} +//
+// {warningOpen && ( +// { +// setWarningOpen(false); +// }} +// /> +// )} +//
+//
+// +//
+// +// +// +// {/* +// */} - const handleAdd = () => { - console.log("Add Testimonial"); - setTestimonialArray([ - ...testimonialArray, - ["", ""], // add one new item at the end - ]); - editedTestimonials.add(testimonialArray.length); //Add index to list of edited indices - setIsEdited(true); - }; +//
+// +//
+//
+//
+// ); +// } - const handleCloseAlert = () => { - setShowAlert(false); - }; +"use client"; + +import PageToggle from "../../../../components/PageToggle"; +import { PageProvider } from "../../../../components/admin/pageeditor/PageProvider"; +import defaultPage from "../../../../components/admin/pageeditor/defaultPages/testimonialsPageDefault.json"; +import TestimonialsEditor from "./TestimonialsEditor"; +import styles from "./page.module.css"; + +export default function TestimonialsEditorPage() { return (
-
- {showAlert && } -
-
- {warningOpen &&
} -
- {warningOpen && ( - { - setWarningOpen(false); - }} - /> - )} -
-
-
- - - - {/* - */} - -
- -
-
+ + +
); } diff --git a/frontend/src/app/admin/util/pageeditUtil.ts b/frontend/src/app/admin/util/pageeditUtil.ts new file mode 100644 index 00000000..1a163360 --- /dev/null +++ b/frontend/src/app/admin/util/pageeditUtil.ts @@ -0,0 +1,149 @@ +import { deleteObject, getDownloadURL, getStorage, listAll, ref } from "firebase/storage"; + +import { Field, GalleryData, ImageData, Page, TextData } from "../../../api/pageeditor"; + +/** + * generateFieldMap + * Generates a mapping from field names to their index + * in the page.fields array. For example, if we wanted + * the field with the name "Image Gallery" : + * + * const imageGalleryField = page.fields[map.get("Image Gallery")] + * + * @param page + * @returns Map object + */ +export function generateFieldMap(page: Page) { + const map = new Map(); + page.fields.forEach((field, index) => { + map.set(field.name, index); + }); + return map; +} + +/** + * generatePageMap + * Generates a mapping from field names to the data that they + * hold. For example, if `const map = generatePageMap(page)`, + * then `map.get("Body Text")` would return the associated text. + * + * @param page Page object to generate map for + * @returns Map object + */ +export function generatePageMap(page: Page) { + const map = new Map(); + + page.fields.forEach((f: Field) => { + const name = f.name; + if (f.type === "text") { + const text = (f.data as TextData).text; + map.set(name, text); + } else if (f.type === "image") { + const image = (f.data as ImageData).image; + map.set(name, image); + } else if (f.type === "gallery") { + const images = (f.data as GalleryData).images; + map.set(name, images); + } + }); + + return map; +} + +/** + * sanitizeFilename + * Takes a string and parses it to be friendly + * as a filename for Firebase storage + * @param s + * @returns + */ +export function sanitizeFilename(s: string) { + const illegalRe = /[/?<>\\:*|"]/g; // illegal OS characters + const controlRe = /\p{Cc}/gu; // remove unicode control codes + const reservedRe = /^\.+$/; // reserved unix filenames + const windowsRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; // reserved windows filenames + const trailDotRe = /[. ]+$/g; // remove trailing periods or spaces + + const sanitized = s + .trim() + .replace(/\s/g, "_") // spaces -> _ + .replace(illegalRe, "") + .replace(controlRe, "") + .replace(reservedRe, "") + .replace(windowsRe, "") + .replace(trailDotRe, ""); + + if (sanitized.length >= 256) return sanitized.substring(0, 255); + else return sanitized; +} + +/** + * deleteFile + * Deletes a file in Firebase storage + * by its download url + * @param url + */ +export async function deleteFile(url: string) { + const storage = getStorage(); + const imRef = ref(storage, url); + + await deleteObject(imRef); +} + +/** + * deleteFiles + * Deletes a string of files from Firebase storage + * + * @param urls + */ +export async function deleteFiles(urls: string[]) { + await Promise.all(urls.map((url) => deleteFile(url))); +} + +/** + * createUniqueFilename + * Sanitizes fname string and checks against other + * files in the target folder for duplicate names. + * + * @param folder name of folder + * @param fname file name of uploaded file + * @returns unique and sanitized filename safe for upload + */ +export async function createUniqueFilename(folder: string, fname: string) { + const storage = getStorage(); + const extRe = /(?=\.[^/.]+$)/g; // regex to split filename and extension + const [origName, ext] = sanitizeFilename(fname).split(extRe); + const listRef = ref(storage, folder); + + let count = 0; + let fileName = origName; + await listAll(listRef) + .then((result) => { + while (result.items.some((itemRef) => itemRef.name === `${fileName}${ext}`)) { + fileName = `${origName}_${++count}`; + } + }) + .catch(console.error); + + return `${fileName}${ext}`; +} + +/** + * listAllUrls + * List all image URLs in a given folder. + * + * @param folder folder name + * @returns string[] of urls + */ +export async function listAllUrls(folder: string) { + const storage = getStorage(); + const listRef = ref(storage, folder); + + const urls = await Promise.all( + (await listAll(listRef)).items.map((item) => { + return getDownloadURL(item); + }), + ); + + return urls; +} diff --git a/frontend/src/components/BackgroundHeader.tsx b/frontend/src/components/BackgroundHeader.tsx index a6214dfa..878aae5c 100644 --- a/frontend/src/components/BackgroundHeader.tsx +++ b/frontend/src/components/BackgroundHeader.tsx @@ -11,6 +11,7 @@ type BackgroundHeaderProps = { description: string; interval?: number; button?: React.ReactNode; + pushUpButtons?: boolean; }; const BackgroundHeader = ({ @@ -20,14 +21,8 @@ const BackgroundHeader = ({ description, interval = 5000, button = null, + pushUpButtons = false, }: BackgroundHeaderProps) => { - backgroundImageURIs = [ - "back1.png", - "back2.jpeg", - "back3.png", - // Add more URIs as needed - ]; - const [activeIndex, setActiveIndex] = useState(0); const nextSlide = () => { @@ -54,7 +49,7 @@ const BackgroundHeader = ({ ))}
-
-
-
- -
+ +
+ +
+
+
); @@ -273,15 +217,21 @@ const EventSidebar = ({ if (isEditing) { return (
-
{ - handleCloseSidebar(); + { + void handleSave(); }} > - test -

Close Window

-
+
+ test +

Close Window

+
+

Event Details

@@ -289,7 +239,7 @@ const EventSidebar = ({
-

Event Description (short)

{ setDescription_short(event.target.value); }} + error={errors.description_short} maxCount={200} /> -

Event Description (long)

{ setDescription(event.target.value); }} + error={errors.description} maxCount={275} />
@@ -354,6 +306,7 @@ const EventSidebar = ({ onChange={(event: React.ChangeEvent) => { setStartTime(event.target.value); }} + error={errors.startTime} />

) => { setEndTime(event.target.value); }} + error={errors.endTime} />

@@ -389,8 +343,8 @@ const EventSidebar = ({ }} error={errors.location} /> -

Guidelines

-