From 78c766c86af00726accbe5edd0fcf2ad0e4091f9 Mon Sep 17 00:00:00 2001
From: kevindo0720 <80845738+kevindo0720@users.noreply.github.com>
Date: Tue, 30 Apr 2024 19:20:32 -0700
Subject: [PATCH 1/6] table ready with backend
---
frontend/public/ic_add.svg | 5 +
.../admin/newslettercreator/page.module.css | 55 ++++
.../src/app/admin/newslettercreator/page.tsx | 276 ++++++++++++++++++
3 files changed, 336 insertions(+)
create mode 100644 frontend/public/ic_add.svg
create mode 100644 frontend/src/app/admin/newslettercreator/page.module.css
create mode 100644 frontend/src/app/admin/newslettercreator/page.tsx
diff --git a/frontend/public/ic_add.svg b/frontend/public/ic_add.svg
new file mode 100644
index 00000000..f96f9e49
--- /dev/null
+++ b/frontend/public/ic_add.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/frontend/src/app/admin/newslettercreator/page.module.css b/frontend/src/app/admin/newslettercreator/page.module.css
new file mode 100644
index 00000000..2ee3848a
--- /dev/null
+++ b/frontend/src/app/admin/newslettercreator/page.module.css
@@ -0,0 +1,55 @@
+@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");
+
+.page {
+ display: flex;
+ justify-content: flex-start;
+ padding-left: 282px;
+ padding-top: 22px;
+ padding-bottom: 50px;
+}
+
+.Headings {
+ text-align: left;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 24px; /* 133.333% */
+ color: white;
+}
+
+.cellentry {
+ text-align: left;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 133.333% */
+ color: black;
+}
+
+.headingBackground {
+ background-color: #694c97; /* Replace with your desired color */
+}
+
+.cellBorderStyle {
+ border-right: 1px solid #c9c9c9; /* Adjust the border style as needed */
+}
+
+.selectedRow {
+ border-radius: 5px;
+ box-shadow: inset 0 0 0 2px #bda7e0;
+ box-shadow: inset 0 0.5px 0 2px #bda7e0;
+}
+.selectedCol {
+ background: rgba(105, 76, 151, 0.05);
+}
+
+.evenRow {
+ background-color: #ffffff; /* White color for even rows */
+}
+
+.oddRow {
+ background-color: #f8f5fb; /* #F8F5FB color for odd rows */
+}
+
diff --git a/frontend/src/app/admin/newslettercreator/page.tsx b/frontend/src/app/admin/newslettercreator/page.tsx
new file mode 100644
index 00000000..e7b51b3d
--- /dev/null
+++ b/frontend/src/app/admin/newslettercreator/page.tsx
@@ -0,0 +1,276 @@
+"use client";
+import Box from "@mui/material/Box";
+import {
+ DataGrid,
+ GridCellParams,
+ GridColDef,
+ GridEventListener,
+ GridRowClassNameParams,
+ GridRowId,
+} from "@mui/x-data-grid";
+import Image from "next/image";
+import React, { useEffect, useState } from "react";
+
+import styles from "./page.module.css";
+
+import {
+ Newsletter,
+ getAllNewsletters
+} from "@/api/newsletter";
+import EmailCopyBtn from "@/components/EmailCopyBtn";
+import RowCopyBtn from "@/components/RowCopyBtn";
+import RowDeleteBtn from "@/components/RowDeleteBtn";
+
+export default function MailingList() {
+ const columns: GridColDef<(typeof rows)[number]>[] = [
+ {
+ field: "title",
+ headerName: "Newsletter Title",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.headingBackground} ${styles.cellBorderStyle} ${styles.Headings}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () =>
Newsletter Title
,
+ },
+ {
+ field: "description",
+ headerName: "Subtitle",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () => Subtitle
,
+ },
+
+ {
+ field: "date",
+ headerName: "Date",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () => Date
,
+ },
+
+ ];
+
+ const [rows, setRow] = useState([]);
+ const [rowsCurrent, setRowsCurrent] = React.useState(rows);
+ const [alertType, setAlertType] = useState("");
+ const [hover, setHover] = useState(false);
+ const [selectedRow, setSelectedRow] = useState(null);
+ const [currentPage, setCurrentPage] = useState(1); // Track current page
+ const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
+ const [showAlert, setShowAlert] = useState(false);
+ const [deletedRow, setDeletedRow] = useState(null);
+
+
+ useEffect(() => {
+ getAllNewsletters()
+ .then((result) => {
+ if (result.success) {
+ console.log("Data:", result.data);
+
+ const formattedRows = result.data.map((item) => ({
+ ...item,
+ id: item._id.toString(),
+ }));
+
+ setRow(formattedRows);
+ setRowsCurrent(formattedRows);
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ }, []);
+
+
+
+ useEffect(() => {
+ // Update total pages when rows change
+ setTotalPages(Math.ceil(rows.length / 14));
+ }, [rows]);
+
+
+ const handleCellClick: GridEventListener<"rowClick"> = (params) => {
+ setSelectedRow(params.id === selectedRow ? null : params.id);
+ };
+
+ const getCellClassName = (params: GridCellParams) => {
+ let colClasses = "";
+ if (params.colDef.field === "email" && hover) {
+ colClasses += ` ${styles.selectedCol}`;
+ if (params.id === 2) {
+ colClasses += ` ${styles.selectedColStart}`;
+ }
+ if (params.id === 14) {
+ colClasses += ` ${styles.selectedColEnd}`;
+ }
+ }
+ return colClasses;
+ };
+
+
+
+
+ const handlePreviousPage = () => {
+ if (currentPage > 1) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < totalPages) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ const getRowClassName = (params: GridRowClassNameParams) => {
+ let rowClasses = "";
+
+ // Add alternating row colors
+ rowClasses += params.indexRelativeToCurrentPage % 2 === 0 ? styles.evenRow : styles.oddRow;
+
+ // Add border to the selected row
+ if (selectedRow === params.id) {
+ rowClasses += ` ${styles.selectedRow}`;
+ }
+ return rowClasses;
+ };
+
+
+ return (
+
+
+
+
+
+ Add Newsletter
+
+
+
+
+
+
+
+
+
Page
+
+ {currentPage}
+
+
of
+
{totalPages}
+
+
+
+
+
+ );
+}
From 2848a4ccf203ea196a72340c74e81ab3d92fddbb Mon Sep 17 00:00:00 2001
From: kevindo0720 <80845738+kevindo0720@users.noreply.github.com>
Date: Thu, 9 May 2024 15:45:46 -0700
Subject: [PATCH 2/6] incorporated sidebar with archive section, implemented
delete functionality
---
backend/src/controllers/newsletter.ts | 20 +-
backend/src/routes/newsletter.ts | 1 +
backend/src/validators/newsletter.ts | 19 +
.../public/{close_icon.svg => ic_close1.svg} | 0
frontend/public/ic_close2.svg | 6 +
frontend/public/ic_doublecaretright.svg | 4 +
frontend/public/ic_edit.svg | 7 +
frontend/src/api/newsletter.ts | 25 +-
.../newslettercreator/archive/page.module.css | 55 +++
.../admin/newslettercreator/archive/page.tsx | 368 ++++++++++++++++++
.../src/app/admin/newslettercreator/page.tsx | 184 ++++++---
frontend/src/components/AlertBanner.tsx | 8 +-
.../NewsletterDeleteWarning.module.css | 67 ++++
.../components/NewsletterDeleteWarning.tsx | 38 ++
.../components/NewsletterSidebar.module.css | 156 ++++++++
frontend/src/components/NewsletterSidebar.tsx | 359 +++++++++++++++++
.../NewsletterSidebarWarning.module.css | 69 ++++
.../components/NewsletterSidebarWarning.tsx | 38 ++
frontend/src/components/PageToggle.module.css | 26 ++
frontend/src/components/PageToggle.tsx | 31 ++
frontend/src/components/TextField.module.css | 32 ++
frontend/src/components/TextField.tsx | 41 ++
22 files changed, 1501 insertions(+), 53 deletions(-)
rename frontend/public/{close_icon.svg => ic_close1.svg} (100%)
create mode 100644 frontend/public/ic_close2.svg
create mode 100644 frontend/public/ic_doublecaretright.svg
create mode 100644 frontend/public/ic_edit.svg
create mode 100644 frontend/src/app/admin/newslettercreator/archive/page.module.css
create mode 100644 frontend/src/app/admin/newslettercreator/archive/page.tsx
create mode 100644 frontend/src/components/NewsletterDeleteWarning.module.css
create mode 100644 frontend/src/components/NewsletterDeleteWarning.tsx
create mode 100644 frontend/src/components/NewsletterSidebar.module.css
create mode 100644 frontend/src/components/NewsletterSidebar.tsx
create mode 100644 frontend/src/components/NewsletterSidebarWarning.module.css
create mode 100644 frontend/src/components/NewsletterSidebarWarning.tsx
create mode 100644 frontend/src/components/PageToggle.module.css
create mode 100644 frontend/src/components/PageToggle.tsx
create mode 100644 frontend/src/components/TextField.module.css
create mode 100644 frontend/src/components/TextField.tsx
diff --git a/backend/src/controllers/newsletter.ts b/backend/src/controllers/newsletter.ts
index 5dce921b..e6e2dbea 100644
--- a/backend/src/controllers/newsletter.ts
+++ b/backend/src/controllers/newsletter.ts
@@ -35,8 +35,9 @@ export const getNewsletter: RequestHandler = async (req, res, next) => {
};
export const createNewsletter: RequestHandler = async (req, res, next) => {
+ console.log(req.body);
const errors = validationResult(req);
- const { _id, image, title, description, date, content } = req.body;
+ const { _id, image, title, description, date, content, archive } = req.body;
try {
validationErrorParser(errors);
@@ -48,6 +49,7 @@ export const createNewsletter: RequestHandler = async (req, res, next) => {
description,
date,
content,
+ archive,
});
res.status(201).json(newsletter);
@@ -83,3 +85,19 @@ export const updateNewsletter: RequestHandler = async (req, res, next) => {
next(error);
}
};
+
+export const deleteNewsletter: RequestHandler = async (req, res, next) => {
+ const { id } = req.params;
+
+ try {
+ const newsletter = await Newsletter.findByIdAndDelete(id);
+
+ if (!newsletter) {
+ throw createHttpError(404, "Newsletter not found.");
+ }
+
+ res.status(200).json(newsletter);
+ } catch (error) {
+ next(error);
+ }
+};
\ No newline at end of file
diff --git a/backend/src/routes/newsletter.ts b/backend/src/routes/newsletter.ts
index aad2637d..eb5c99a7 100644
--- a/backend/src/routes/newsletter.ts
+++ b/backend/src/routes/newsletter.ts
@@ -12,5 +12,6 @@ router.put(
NewsletterController.updateNewsletter,
);
router.post("/", NewsletterValidator.createNewsletter, NewsletterController.createNewsletter);
+router.delete("/:id", NewsletterValidator.deleteNewsletter , NewsletterController.deleteNewsletter);
export default router;
diff --git a/backend/src/validators/newsletter.ts b/backend/src/validators/newsletter.ts
index 88af767c..ceb499af 100644
--- a/backend/src/validators/newsletter.ts
+++ b/backend/src/validators/newsletter.ts
@@ -42,14 +42,33 @@ const makeContentValidator = () =>
.bail()
.isArray()
.withMessage("content must be an array of strings");
+const makeArchiveValidator = () =>
+ body("archive")
+ .exists()
+ .withMessage("archive is required")
+ .bail()
+ .isBoolean()
+ .withMessage("archive must be a boolean");
export const createNewsletter = [
+ makeImageValidator(),
+ makeTitleValidator(),
+ makeDescriptionValidator(),
+ makeDateValidator(),
+ makeContentValidator(),
+ makeArchiveValidator(),
+];
+
+export const updateNewsletter = [
makeIDValidator(),
makeImageValidator(),
makeTitleValidator(),
makeDescriptionValidator(),
makeDateValidator(),
makeContentValidator(),
+ makeArchiveValidator(),
];
export const getNewsletter = [makeIDValidator()];
+
+export const deleteNewsletter = [makeIDValidator()];
diff --git a/frontend/public/close_icon.svg b/frontend/public/ic_close1.svg
similarity index 100%
rename from frontend/public/close_icon.svg
rename to frontend/public/ic_close1.svg
diff --git a/frontend/public/ic_close2.svg b/frontend/public/ic_close2.svg
new file mode 100644
index 00000000..dd6d0ef6
--- /dev/null
+++ b/frontend/public/ic_close2.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/frontend/public/ic_doublecaretright.svg b/frontend/public/ic_doublecaretright.svg
new file mode 100644
index 00000000..1e0ad210
--- /dev/null
+++ b/frontend/public/ic_doublecaretright.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/frontend/public/ic_edit.svg b/frontend/public/ic_edit.svg
new file mode 100644
index 00000000..104e5082
--- /dev/null
+++ b/frontend/public/ic_edit.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/api/newsletter.ts b/frontend/src/api/newsletter.ts
index 772144ca..bec3cb15 100644
--- a/frontend/src/api/newsletter.ts
+++ b/frontend/src/api/newsletter.ts
@@ -1,4 +1,4 @@
-import { get, handleAPIError, post, put } from "./requests";
+import { get, handleAPIError, post, put,del } from "./requests";
import type { APIResult } from "./requests";
@@ -12,6 +12,15 @@ export type Newsletter = {
archive: boolean;
};
+export type CreateNewsletterRequest = {
+ image: string;
+ title: string;
+ description: string;
+ date: string;
+ content: string[];
+ archive: boolean;
+};
+
export async function getNewsletter(id: string): Promise> {
try {
const response = await get(`/api/newsletter/${id}`);
@@ -36,7 +45,9 @@ export async function getAllNewsletters(): Promise> {
}
}
-export async function createNewsletter(newsletter: Newsletter): Promise> {
+export async function createNewsletter(
+ newsletter: CreateNewsletterRequest,
+): Promise> {
try {
const response = await post("/api/newsletter", newsletter);
const json = (await response.json()) as Newsletter;
@@ -58,3 +69,13 @@ export async function updateNewsletter(newsletter: Newsletter): Promise> {
+ try {
+ const response = await del(`/api/newsletter/${id}`);
+ const json = (await response.json()) as Newsletter;
+ return { success: true, data: json };
+ } catch (error) {
+ return handleAPIError(error);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/admin/newslettercreator/archive/page.module.css b/frontend/src/app/admin/newslettercreator/archive/page.module.css
new file mode 100644
index 00000000..2ee3848a
--- /dev/null
+++ b/frontend/src/app/admin/newslettercreator/archive/page.module.css
@@ -0,0 +1,55 @@
+@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");
+
+.page {
+ display: flex;
+ justify-content: flex-start;
+ padding-left: 282px;
+ padding-top: 22px;
+ padding-bottom: 50px;
+}
+
+.Headings {
+ text-align: left;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 24px; /* 133.333% */
+ color: white;
+}
+
+.cellentry {
+ text-align: left;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 133.333% */
+ color: black;
+}
+
+.headingBackground {
+ background-color: #694c97; /* Replace with your desired color */
+}
+
+.cellBorderStyle {
+ border-right: 1px solid #c9c9c9; /* Adjust the border style as needed */
+}
+
+.selectedRow {
+ border-radius: 5px;
+ box-shadow: inset 0 0 0 2px #bda7e0;
+ box-shadow: inset 0 0.5px 0 2px #bda7e0;
+}
+.selectedCol {
+ background: rgba(105, 76, 151, 0.05);
+}
+
+.evenRow {
+ background-color: #ffffff; /* White color for even rows */
+}
+
+.oddRow {
+ background-color: #f8f5fb; /* #F8F5FB color for odd rows */
+}
+
diff --git a/frontend/src/app/admin/newslettercreator/archive/page.tsx b/frontend/src/app/admin/newslettercreator/archive/page.tsx
new file mode 100644
index 00000000..b7efe9d0
--- /dev/null
+++ b/frontend/src/app/admin/newslettercreator/archive/page.tsx
@@ -0,0 +1,368 @@
+"use client";
+import Box from "@mui/material/Box";
+import {
+ DataGrid,
+ GridColDef,
+ GridEventListener,
+ GridRowClassNameParams,
+ GridRowId,
+} from "@mui/x-data-grid";
+import Image from "next/image";
+import React, { useEffect, useState } from "react";
+
+import styles from "./page.module.css";
+
+
+import {
+ CreateNewsletterRequest,
+ Newsletter,
+ createNewsletter,
+ getAllNewsletters,
+ getNewsletter,
+ updateNewsletter,
+ deleteNewsletter,
+} from "@/api/newsletter";
+import NewsletterSidebar from "@/components/NewsletterSidebar";
+import PageToggle from "@/components/PageToggle";
+
+export default function MailingList() {
+ const columns: GridColDef<(typeof rows)[number]>[] = [
+ {
+ field: "title",
+ headerName: "Newsletter Title",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.headingBackground} ${styles.cellBorderStyle} ${styles.Headings}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () => Newsletter Title
,
+ },
+ {
+ field: "description",
+ headerName: "Subtitle",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () => Subtitle
,
+ },
+
+ {
+ field: "date",
+ headerName: "Date",
+ width: 372.29,
+ editable: false,
+ resizable: false,
+ headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
+ cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
+ disableColumnMenu: true,
+ renderHeader: () => Date
,
+ },
+
+ ];
+
+
+ const [rows, setRow] = useState([]);
+ const [rowsCurrent, setRowsCurrent] = React.useState(rows);
+ const [selectedRow, setSelectedRow] = useState(null);
+ const [currentPage, setCurrentPage] = useState(1); // Track current page
+ const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
+ const [selectedNewsletter, setSelectedNewsletter] = useState(null);
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [rerenderKey, setRerenderKey] = useState(0);
+
+ useEffect(() => {
+ getAllNewsletters()
+ .then((result) => {
+ if (result.success) {
+ const formattedRows = result.data
+ .filter(item => item.archive) // filter out items where archive is false
+ .map((item) => ({
+ ...item,
+ id: item._id.toString(),
+ }));
+
+ setRow(formattedRows);
+ setRowsCurrent(formattedRows);
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ }, []);
+
+ useEffect(() => {
+ if (selectedRow) {
+ getNewsletter(selectedRow?.toString())
+ .then((result) => {
+ if (result.success) {
+ setSelectedNewsletter(result.data);
+ setRerenderKey((prevKey) => prevKey + 1);
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ } else {
+ setSelectedNewsletter(null);
+ }
+ }, [selectedRow]);
+
+ useEffect(() => {
+ if (sidebarOpen) {
+ setRerenderKey((prevKey) => prevKey + 1);
+ }
+ }, [sidebarOpen]);
+
+ const openNewsletter = (createNew: boolean) => {
+ if (createNew) {
+ setSelectedRow(null);
+ }
+ setSidebarOpen(true);
+ };
+
+
+ useEffect(() => {
+ // Update total pages when rows change
+ setTotalPages(Math.ceil(rows.length / 14));
+ }, [rows]);
+
+
+ const handleCellClick: GridEventListener<"rowClick"> = (params) => {
+ if (!sidebarOpen) {
+ setSelectedRow(params.id === selectedRow ? null : params.id);
+ openNewsletter(false);
+ }
+ };
+
+
+
+ const handleSetSidebarOpen = (open: boolean) => {
+ setSidebarOpen(open);
+ };
+ const handleUpdateNewsletter = (newsletterData: Newsletter) => {
+ updateNewsletter(newsletterData)
+ .then((result) => {
+ if (result.success) {
+ // TODO: add success message, update table
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ };
+
+ const handleCreateNewsletter = (newsletterData: CreateNewsletterRequest) => {
+ console.log(newsletterData);
+ createNewsletter(newsletterData)
+ .then((result) => {
+ if (result.success) {
+ //TODO: add success message, update table
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ };
+
+
+
+
+ const handlePreviousPage = () => {
+ if (currentPage > 1) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < totalPages) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ const getRowClassName = (params: GridRowClassNameParams) => {
+ let rowClasses = "";
+
+ // Add alternating row colors
+ rowClasses += params.indexRelativeToCurrentPage % 2 === 0 ? styles.evenRow : styles.oddRow;
+
+ // Add border to the selected row
+ if (selectedRow === params.id) {
+ rowClasses += ` ${styles.selectedRow}`;
+ }
+ return rowClasses;
+ };
+
+
+ return (
+
+
+ {sidebarOpen && (
+
+ )}
+
+
+
+
+
+
+
+ {
+ openNewsletter(true);
+ }}
+ style={{
+ position: 'relative',
+ fontFamily: "Open Sans",
+ fontWeight: 700,
+ fontSize: "16px",
+ lineHeight: "24px",
+ letterSpacing: "0.32",
+ width: 196,
+ height: 40,
+ backgroundColor: '#694C97',
+ color: '#FFF',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+
+ }}
+ >
+ Add Newsletter
+
+
+
+
+
+
+
+
+
+
Page
+
+ {currentPage}
+
+
of
+
{totalPages}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/admin/newslettercreator/page.tsx b/frontend/src/app/admin/newslettercreator/page.tsx
index e7b51b3d..ed517660 100644
--- a/frontend/src/app/admin/newslettercreator/page.tsx
+++ b/frontend/src/app/admin/newslettercreator/page.tsx
@@ -2,7 +2,6 @@
import Box from "@mui/material/Box";
import {
DataGrid,
- GridCellParams,
GridColDef,
GridEventListener,
GridRowClassNameParams,
@@ -13,13 +12,18 @@ import React, { useEffect, useState } from "react";
import styles from "./page.module.css";
+
import {
+ CreateNewsletterRequest,
Newsletter,
- getAllNewsletters
+ createNewsletter,
+ getAllNewsletters,
+ getNewsletter,
+ updateNewsletter,
+ deleteNewsletter,
} from "@/api/newsletter";
-import EmailCopyBtn from "@/components/EmailCopyBtn";
-import RowCopyBtn from "@/components/RowCopyBtn";
-import RowDeleteBtn from "@/components/RowDeleteBtn";
+import NewsletterSidebar from "@/components/NewsletterSidebar";
+import PageToggle from "@/components/PageToggle";
export default function MailingList() {
const columns: GridColDef<(typeof rows)[number]>[] = [
@@ -60,28 +64,27 @@ export default function MailingList() {
];
+
const [rows, setRow] = useState([]);
const [rowsCurrent, setRowsCurrent] = React.useState(rows);
- const [alertType, setAlertType] = useState("");
- const [hover, setHover] = useState(false);
const [selectedRow, setSelectedRow] = useState(null);
const [currentPage, setCurrentPage] = useState(1); // Track current page
const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
- const [showAlert, setShowAlert] = useState(false);
- const [deletedRow, setDeletedRow] = useState(null);
-
+ const [selectedNewsletter, setSelectedNewsletter] = useState(null);
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [rerenderKey, setRerenderKey] = useState(0);
useEffect(() => {
getAllNewsletters()
.then((result) => {
- if (result.success) {
- console.log("Data:", result.data);
-
- const formattedRows = result.data.map((item) => ({
- ...item,
- id: item._id.toString(),
- }));
-
+ if (result.success) {
+ const formattedRows = result.data
+ .filter(item => !item.archive) // filter out items where archive is false
+ .map((item) => ({
+ ...item,
+ id: item._id.toString(),
+ }));
+
setRow(formattedRows);
setRowsCurrent(formattedRows);
} else {
@@ -93,6 +96,37 @@ export default function MailingList() {
});
}, []);
+ useEffect(() => {
+ if (selectedRow) {
+ getNewsletter(selectedRow?.toString())
+ .then((result) => {
+ if (result.success) {
+ setSelectedNewsletter(result.data);
+ setRerenderKey((prevKey) => prevKey + 1);
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ } else {
+ setSelectedNewsletter(null);
+ }
+ }, [selectedRow]);
+
+ useEffect(() => {
+ if (sidebarOpen) {
+ setRerenderKey((prevKey) => prevKey + 1);
+ }
+ }, [sidebarOpen]);
+
+ const openNewsletter = (createNew: boolean) => {
+ if (createNew) {
+ setSelectedRow(null);
+ }
+ setSidebarOpen(true);
+ };
useEffect(() => {
@@ -102,21 +136,44 @@ export default function MailingList() {
const handleCellClick: GridEventListener<"rowClick"> = (params) => {
- setSelectedRow(params.id === selectedRow ? null : params.id);
+ if (!sidebarOpen) {
+ setSelectedRow(params.id === selectedRow ? null : params.id);
+ openNewsletter(false);
+ }
};
- const getCellClassName = (params: GridCellParams) => {
- let colClasses = "";
- if (params.colDef.field === "email" && hover) {
- colClasses += ` ${styles.selectedCol}`;
- if (params.id === 2) {
- colClasses += ` ${styles.selectedColStart}`;
- }
- if (params.id === 14) {
- colClasses += ` ${styles.selectedColEnd}`;
- }
- }
- return colClasses;
+
+
+ const handleSetSidebarOpen = (open: boolean) => {
+ setSidebarOpen(open);
+ };
+ const handleUpdateNewsletter = (newsletterData: Newsletter) => {
+ updateNewsletter(newsletterData)
+ .then((result) => {
+ if (result.success) {
+ // TODO: add success message, update table
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ };
+
+ const handleCreateNewsletter = (newsletterData: CreateNewsletterRequest) => {
+ console.log(newsletterData);
+ createNewsletter(newsletterData)
+ .then((result) => {
+ if (result.success) {
+ //TODO: add success message, update table
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
};
@@ -150,18 +207,54 @@ export default function MailingList() {
return (
-
-
+
+ {sidebarOpen && (
+
+ )}
+
+
+
+
+
+
{
+ openNewsletter(true);
+ }}
style={{
position: 'relative',
fontFamily: "Open Sans",
@@ -177,14 +270,14 @@ export default function MailingList() {
justifyContent: 'center',
alignItems: 'center',
- }}
-
+ }}
>
Add Newsletter
+
+
+
-
-
void;
onClose: () => void;
};
@@ -13,7 +13,7 @@ const AlertBanner = ({ text, img, undo, onClose }: ButtonProps) => {
return (
@@ -23,10 +23,10 @@ const AlertBanner = ({ text, img, undo, onClose }: ButtonProps) => {
)}
-
+
);
};
-export default AlertBanner;
+export default AlertBanner;
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterDeleteWarning.module.css b/frontend/src/components/NewsletterDeleteWarning.module.css
new file mode 100644
index 00000000..2f90d7d6
--- /dev/null
+++ b/frontend/src/components/NewsletterDeleteWarning.module.css
@@ -0,0 +1,67 @@
+.wrapper {
+ position: absolute;
+ top: 200px;
+ padding: 24px;
+ background-color: #fff;
+ border-radius: 5px;
+ margin: 52px;
+ }
+
+ .wrapper button img {
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .wrapper button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+ }
+
+ .closeButton {
+ position: relative;
+ float: right;
+ }
+
+ .saveButton {
+ background: #694c97;
+ color: #fff;
+ }
+
+ .deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+ }
+
+ .wrapper h1 {
+ color: #000;
+ font: var(--font-small-subtitle);
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 36px */
+ letter-spacing: 0.48px;
+ }
+
+ .wrapper p {
+ color: #000;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+ }
+
+ .buttonWrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: end;
+ gap: 18px;
+ margin-top: 18px;
+ }
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterDeleteWarning.tsx b/frontend/src/components/NewsletterDeleteWarning.tsx
new file mode 100644
index 00000000..6bb4150a
--- /dev/null
+++ b/frontend/src/components/NewsletterDeleteWarning.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import Image from "next/image";
+import React from "react";
+
+import styles from "./NewsletterDeleteWarning.module.css";
+
+type NewsletterDeleteWarningProps = {
+ save: () => void;
+ discard: () => void;
+ onClose: () => void;
+};
+
+export const NewsletterDeleteWarning = ({
+ save,
+ discard,
+ onClose,
+}: NewsletterDeleteWarningProps) => {
+ return (
+
+
+
+
+
Are you sure you want to delete this newsletter?
+
This action is permanent and cannot be undone.
+
+
+ {" "}
+ No, cancel{" "}
+
+
+ {" "}
+ Delete newsletter{" "}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterSidebar.module.css b/frontend/src/components/NewsletterSidebar.module.css
new file mode 100644
index 00000000..8180b9da
--- /dev/null
+++ b/frontend/src/components/NewsletterSidebar.module.css
@@ -0,0 +1,156 @@
+.sidebar {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ overscroll-behavior: contain;
+ width: 36vw;
+ z-index: 3;
+ border-left: 1px solid #c9c9c9;
+ background: #fff;
+ }
+
+ .grayOut {
+ opacity: 0.3;
+ background: #484848;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ .closeWindow {
+ display: flex;
+ width: 524px;
+ height: 41px;
+ padding: 10px 20px;
+ align-items: center;
+ gap: 8px;
+ flex-shrink: 0;
+ color: var(--Neutral-Gray4, #909090);
+ border-bottom: 1px solid #c9c9c9;
+ font: var(--font-body);
+ font-size: 14px;
+ }
+
+ .closeWindow:hover {
+ cursor: pointer;
+ }
+
+ .sidebarContents {
+ padding: 60px;
+ padding-left: 35px;
+ }
+
+ .sidebarContents h1 {
+ font: var(--font-small-subtitle);
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 27px */
+ letter-spacing: 0.36px;
+ }
+
+ .sidebarContents h2 {
+ padding-top: 24px;
+ font: var(--font-body);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ color: #909090;
+ }
+
+ .sidebarContents {
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+ }
+
+ .header {
+ display: flex;
+ direction: row;
+ align-items: center;
+ justify-content: space-between;
+ height: 46px;
+ }
+
+ .sidebar button {
+ display: flex;
+ padding: 4px 16px;
+ justify-content: center;
+ align-items: center;
+ gap: 6px;
+ border-radius: 4px;
+ }
+
+ .sidebar button img {
+ padding: 4px;
+ }
+
+ .sidebar button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+ }
+
+ .editButton {
+ background: #694c97;
+ color: #fff;
+ }
+
+ .cancelButton {
+ background: #fff;
+ border: 1px solid #694c97;
+ color: #694c97;
+ }
+
+ .saveButton {
+ background: #694c97;
+ color: #fff;
+ }
+
+ .deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+ }
+
+ .bottomButtons {
+ display: flex;
+ direction: row;
+ gap: 24px;
+ justify-content: end;
+ margin-right: 60px;
+ }
+
+ .deleteButtonWrapper {
+ display: flex;
+ direction: row;
+ justify-content: center;
+ margin-top: 50px;
+ }
+
+ .contentPar {
+ margin-bottom: 1rem;
+ }
+
+ .sidebar .alert {
+ position: relative;
+ top: 35px;
+ }
+
+ .sidebar .alert .div {
+ position: absolute;
+ }
+
+ .sidebar .alert img {
+ padding: 0;
+ }
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterSidebar.tsx b/frontend/src/components/NewsletterSidebar.tsx
new file mode 100644
index 00000000..60d38e71
--- /dev/null
+++ b/frontend/src/components/NewsletterSidebar.tsx
@@ -0,0 +1,359 @@
+"use client";
+import Image from "next/image";
+import React, { useState } from "react";
+import { useRouter } from 'next/router';
+
+import { CreateNewsletterRequest, Newsletter,deleteNewsletter } from "../api/newsletter";
+
+import styles from "./NewsletterSidebar.module.css";
+
+import AlertBanner from "@/components/AlertBanner";
+import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
+import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
+
+import { TextField } from "@/components/TextField";
+
+type newsletterSidebarProps = {
+ newsletter: null | Newsletter;
+ setSidebarOpen: (open: boolean) => void;
+ updateNewsletter: (newsletterData: Newsletter) => void;
+ createNewsletter: (newsletterData: CreateNewsletterRequest) => void;
+};
+
+type formErrors = {
+ title?: boolean;
+ description?: boolean;
+ date?: boolean;
+ content?: boolean;
+};
+
+const NewsletterSidebar = ({
+ newsletter,
+ setSidebarOpen,
+ updateNewsletter,
+ createNewsletter,
+}: newsletterSidebarProps) => {
+ const [title, setTitle] = useState(newsletter ? newsletter.title : "");
+ const [description, setDescription] = useState(newsletter ? newsletter.description : "");
+ const [date, setDate] = useState(newsletter ? newsletter.date : "");
+ const [content, setContent] = useState(newsletter ? newsletter.content : []);
+ const [isEditing, setIsEditing] = useState(!newsletter);
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [errors, setErrors] = useState({});
+ const [warningOpen, setWarningOpen] = useState(false);
+ const [warningDelete, setWarningDelete] = useState(false);
+ const [showAlert, setShowAlert] = useState(false);
+
+ const confirmCancel = () => {
+ setTitle(newsletter ? newsletter.title : "");
+ setDescription(newsletter ? newsletter.description : "");
+ setDate(newsletter ? newsletter.date : "");
+ setContent(newsletter ? newsletter.content : []);
+ setIsEditing(false);
+ setIsDeleting(false);
+ setErrors({});
+ setWarningOpen(false);
+ };
+
+ const handleCancel = () => {
+ if (
+ title !== (newsletter ? newsletter.title : "") ||
+ description !== (newsletter ? newsletter.description : "") ||
+ date !== (newsletter ? newsletter.date : "") ||
+ content !== (newsletter ? newsletter.content : [])
+ ) {
+ setWarningOpen(true);
+ } else {
+ confirmCancel();
+ }
+ };
+
+ const handleCloseSidebar = () => {
+ if (
+ title !== (newsletter ? newsletter.title : "") ||
+ description !== (newsletter ? newsletter.description : "") ||
+ date !== (newsletter ? newsletter.date : "") ||
+ content !== (newsletter ? newsletter.content : [])
+ ) {
+ setWarningOpen(true);
+ } else {
+ confirmCancel();
+ setSidebarOpen(false);
+ }
+ };
+
+ const handleSave = () => {
+ setWarningOpen(false);
+ if (title === "" || description === "" || date === "" || content.length === 0) {
+ setErrors({
+ title: title === "",
+ description: description === "",
+ date: date === "",
+ content: content.length === 0,
+ });
+ } else {
+ setIsEditing(false);
+ if (newsletter) {
+ updateNewsletter({
+ _id: newsletter._id,
+ image: newsletter.image,
+ title,
+ description,
+ date,
+ content,
+ archive: newsletter.archive,
+ });
+
+ } else {
+ createNewsletter({
+ image: "/newsletter2.png",
+ title,
+ description,
+ date,
+ content,
+ archive: false,
+ });
+ }
+ setIsEditing(false);
+ setErrors({});
+ setShowAlert(true);
+ window.location.reload();
+ }
+ };
+
+
+
+ const handleDelete = () => {
+
+ setIsDeleting(true);
+
+ };
+
+ const confirmDelete = () => {
+
+ deleteNewsletter(newsletter._id);
+ setSidebarOpen(false);
+ window.location.reload();
+ };
+
+
+ const alertContent = {
+ text: "Newsletter Saved!",
+ };
+
+ const handleCloseAlert = () => {
+ setShowAlert(false);
+ };
+
+
+ if(isDeleting) {
+
+ return (
+
+
+
+
{
+ setSidebarOpen(false);
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+ {/* Edit button */}
+
{
+ setIsEditing(true);
+ console.log("isEditing:", isEditing);
+ }}
+ className={styles.editButton}
+ >
+
+ Edit
+
+
+
Newsletter Title
+
{title}
+
Newsletter Description
+
{description}
+
Date & Time
+
{date}
+
Newsletter Cover
+
Placeholder - to be replaced with image
+
Newsletter Content
+ {content.map((paragraph: string, index: number) => (
+
+ {paragraph}
+
+ ))}
+ {/* Delete button */}
+
+
+
+
+
+
+ );
+ }
+
+ if (isEditing) {
+ return (
+
+ {warningOpen &&
}
+ {warningOpen && (
+
{
+ setWarningOpen(false);
+ }}
+ />
+ )}
+ {
+ handleCloseSidebar();
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+
+
+
+ {/* Cancel button */}
+
+ Cancel
+
+
+ {/* Save button */}
+
+ Save
+
+
+
+ );
+
+ //if is deleting
+ }
+ else {
+ // not in edit mode
+ return (
+
+
+
+
{
+ setSidebarOpen(false);
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+ {/* Edit button */}
+
{
+ setIsEditing(true);
+ console.log("isEditing:", isEditing);
+ }}
+ className={styles.editButton}
+ >
+
+ Edit
+
+
+
Newsletter Title
+
{title}
+
Newsletter Description
+
{description}
+
Date & Time
+
{date}
+
Newsletter Cover
+
Placeholder - to be replaced with image
+
Newsletter Content
+ {content.map((paragraph: string, index: number) => (
+
+ {paragraph}
+
+ ))}
+ {/* Delete button */}
+
+
+
+ );
+ }
+};
+
+export default NewsletterSidebar;
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterSidebarWarning.module.css b/frontend/src/components/NewsletterSidebarWarning.module.css
new file mode 100644
index 00000000..905e297f
--- /dev/null
+++ b/frontend/src/components/NewsletterSidebarWarning.module.css
@@ -0,0 +1,69 @@
+.wrapper {
+ position: absolute;
+ top: 200px;
+ padding: 24px;
+ background-color: #fff;
+ border-radius: 5px;
+ margin: 52px;
+
+
+ }
+
+ .wrapper button img {
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .wrapper button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+ }
+
+ .closeButton {
+ position: relative;
+ float: right;
+ }
+
+ .saveButton {
+ background: #694c97;
+ color: #fff;
+ }
+
+ .deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+ }
+
+ .wrapper h1 {
+ color: #000;
+ font: var(--font-small-subtitle);
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 36px */
+ letter-spacing: 0.48px;
+ }
+
+ .wrapper p {
+ color: #000;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+ }
+
+ .buttonWrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: end;
+ gap: 18px;
+ margin-top: 18px;
+ }
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterSidebarWarning.tsx b/frontend/src/components/NewsletterSidebarWarning.tsx
new file mode 100644
index 00000000..9ec439dd
--- /dev/null
+++ b/frontend/src/components/NewsletterSidebarWarning.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import Image from "next/image";
+import React from "react";
+
+import styles from "./NewsletterSidebarWarning.module.css";
+
+type NewsletterSidebarWarningProps = {
+ save: () => void;
+ discard: () => void;
+ onClose: () => void;
+};
+
+export const NewsletterSidebarWarning = ({
+ save,
+ discard,
+ onClose,
+}: NewsletterSidebarWarningProps) => {
+ return (
+
+
+
+
+
You have unsaved changes!
+
Do you want to save the changes you made to this event?
+
+
+ {" "}
+ Discard changes{" "}
+
+
+ {" "}
+ Save changes{" "}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/components/PageToggle.module.css b/frontend/src/components/PageToggle.module.css
new file mode 100644
index 00000000..95caff44
--- /dev/null
+++ b/frontend/src/components/PageToggle.module.css
@@ -0,0 +1,26 @@
+.container {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ float: left;
+ margin-top: 12px;
+ gap: 24px;
+ margin-bottom: 0px;
+ padding: 0 12px;
+ border-bottom: solid 1px #6c6c6c;
+}
+
+ .menu {
+ font: var(--font-body-reg);
+ font-size: 14px;
+ color: #6c6c6c;
+ padding-bottom: 12px;
+ }
+
+ .menuActive {
+ font: var(--font-body-bold);
+ font-size: 14px;
+ color: var(--color-primary-purple);
+ border-bottom: solid 3px var(--color-primary-purple);
+ padding-bottom: 12px;
+ }
\ No newline at end of file
diff --git a/frontend/src/components/PageToggle.tsx b/frontend/src/components/PageToggle.tsx
new file mode 100644
index 00000000..1027fe9d
--- /dev/null
+++ b/frontend/src/components/PageToggle.tsx
@@ -0,0 +1,31 @@
+import Link from "next/link";
+import React from "react";
+
+import styles from "./PageToggle.module.css";
+
+type PageToggleProps = {
+ pages: string[];
+ links: string[];
+ currPage: number;
+};
+
+const PageToggle = ({ pages, links, currPage }: PageToggleProps) => {
+ return (
+
+ {pages.map((page, index) => {
+ const link = links[index];
+ return (
+
+ {page}
+
+ );
+ })}
+
+ );
+};
+
+export default PageToggle;
\ No newline at end of file
diff --git a/frontend/src/components/TextField.module.css b/frontend/src/components/TextField.module.css
new file mode 100644
index 00000000..3f7e3077
--- /dev/null
+++ b/frontend/src/components/TextField.module.css
@@ -0,0 +1,32 @@
+.wrapper {
+ padding-top: 24px;
+ width: 20rem;
+ height: auto;
+ }
+
+ .label {
+ font: var(--font-body);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ color: #909090;
+ }
+
+ .input {
+ width: 100%;
+ line-height: 20px;
+ padding: 6px 12px;
+ border: 1px solid #d8d8d8;
+ border-radius: 4px;
+ font: var(--font-body);
+ color: var(--Neutral-Black, #000);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ }
+
+ .input.error {
+ border-color: #b93b3b;
+ }
\ No newline at end of file
diff --git a/frontend/src/components/TextField.tsx b/frontend/src/components/TextField.tsx
new file mode 100644
index 00000000..017bb3a8
--- /dev/null
+++ b/frontend/src/components/TextField.tsx
@@ -0,0 +1,41 @@
+import React from "react";
+
+import styles from "./TextField.module.css";
+
+/**
+ * See `src/components/Button.tsx` for basic info about prop interfaces. Here we also use an `Omit`
+ * type, which is a built-in TypeScript utility type. `Omit` gives us the type X, excluding
+ * any fields Y. In this case, we are extending `React.ComponentProps<"input">` (the props that an
+ * ` ` component can receive), but excluding the specific prop `type`. We exclude `type`
+ * because we will set `type="text"` on the underlying ` ` component, so there's no point in
+ * allowing the developer to pass that prop in themselves.
+ */
+export type TextFieldProps = {
+ label: string;
+ error?: boolean;
+} & Omit, "type">;
+
+/**
+ * See `src/components/Button.tsx` for an explanation of `React.forwardRef`.
+ */
+export const TextField = React.forwardRef(function TextField(
+ { label, error = false, className, ...props },
+ ref,
+) {
+ let wrapperClass = styles.wrapper;
+ if (className) {
+ wrapperClass += ` ${className}`;
+ }
+ let inputClass = styles.input;
+ if (error) {
+ inputClass += ` ${styles.error}`;
+ }
+ return (
+
+ );
+});
\ No newline at end of file
From 70435cfd5b2c36b6c4a62017901510388c46e44d Mon Sep 17 00:00:00 2001
From: kevindo0720 <80845738+kevindo0720@users.noreply.github.com>
Date: Thu, 9 May 2024 18:17:09 -0700
Subject: [PATCH 3/6] create empty popups on archive
---
.../admin/newslettercreator/archive/page.tsx | 3 +-
frontend/src/components/NewsletterSidebar.tsx | 4 +
.../components/NewsletterSidebarArchive.tsx | 363 ++++++++++++++++++
3 files changed, 368 insertions(+), 2 deletions(-)
create mode 100644 frontend/src/components/NewsletterSidebarArchive.tsx
diff --git a/frontend/src/app/admin/newslettercreator/archive/page.tsx b/frontend/src/app/admin/newslettercreator/archive/page.tsx
index b7efe9d0..23a677b7 100644
--- a/frontend/src/app/admin/newslettercreator/archive/page.tsx
+++ b/frontend/src/app/admin/newslettercreator/archive/page.tsx
@@ -22,7 +22,7 @@ import {
updateNewsletter,
deleteNewsletter,
} from "@/api/newsletter";
-import NewsletterSidebar from "@/components/NewsletterSidebar";
+import NewsletterSidebar from "@/components/NewsletterSidebarArchive";
import PageToggle from "@/components/PageToggle";
export default function MailingList() {
@@ -204,7 +204,6 @@ export default function MailingList() {
return rowClasses;
};
-
return (
diff --git a/frontend/src/components/NewsletterSidebar.tsx b/frontend/src/components/NewsletterSidebar.tsx
index 60d38e71..ec5af7fd 100644
--- a/frontend/src/components/NewsletterSidebar.tsx
+++ b/frontend/src/components/NewsletterSidebar.tsx
@@ -81,6 +81,7 @@ const NewsletterSidebar = ({
setSidebarOpen(false);
}
};
+
const handleSave = () => {
setWarningOpen(false);
@@ -105,6 +106,8 @@ const NewsletterSidebar = ({
});
} else {
+
+
createNewsletter({
image: "/newsletter2.png",
title,
@@ -121,6 +124,7 @@ const NewsletterSidebar = ({
}
};
+
const handleDelete = () => {
diff --git a/frontend/src/components/NewsletterSidebarArchive.tsx b/frontend/src/components/NewsletterSidebarArchive.tsx
new file mode 100644
index 00000000..7119b4d2
--- /dev/null
+++ b/frontend/src/components/NewsletterSidebarArchive.tsx
@@ -0,0 +1,363 @@
+"use client";
+import Image from "next/image";
+import React, { useState } from "react";
+import { useRouter } from 'next/router';
+
+import { CreateNewsletterRequest, Newsletter,deleteNewsletter } from "../api/newsletter";
+
+import styles from "./NewsletterSidebar.module.css";
+
+import AlertBanner from "@/components/AlertBanner";
+import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
+import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
+
+import { TextField } from "@/components/TextField";
+
+type newsletterSidebarProps = {
+ newsletter: null | Newsletter;
+ setSidebarOpen: (open: boolean) => void;
+ updateNewsletter: (newsletterData: Newsletter) => void;
+ createNewsletter: (newsletterData: CreateNewsletterRequest) => void;
+};
+
+type formErrors = {
+ title?: boolean;
+ description?: boolean;
+ date?: boolean;
+ content?: boolean;
+};
+
+const NewsletterSidebar = ({
+ newsletter,
+ setSidebarOpen,
+ updateNewsletter,
+ createNewsletter,
+}: newsletterSidebarProps) => {
+ const [title, setTitle] = useState(newsletter ? newsletter.title : "");
+ const [description, setDescription] = useState(newsletter ? newsletter.description : "");
+ const [date, setDate] = useState(newsletter ? newsletter.date : "");
+ const [content, setContent] = useState(newsletter ? newsletter.content : []);
+ const [isEditing, setIsEditing] = useState(!newsletter);
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [errors, setErrors] = useState({});
+ const [warningOpen, setWarningOpen] = useState(false);
+ const [warningDelete, setWarningDelete] = useState(false);
+ const [showAlert, setShowAlert] = useState(false);
+
+ const confirmCancel = () => {
+ setTitle(newsletter ? newsletter.title : "");
+ setDescription(newsletter ? newsletter.description : "");
+ setDate(newsletter ? newsletter.date : "");
+ setContent(newsletter ? newsletter.content : []);
+ setIsEditing(false);
+ setIsDeleting(false);
+ setErrors({});
+ setWarningOpen(false);
+ };
+
+ const handleCancel = () => {
+ if (
+ title !== (newsletter ? newsletter.title : "") ||
+ description !== (newsletter ? newsletter.description : "") ||
+ date !== (newsletter ? newsletter.date : "") ||
+ content !== (newsletter ? newsletter.content : [])
+ ) {
+ setWarningOpen(true);
+ } else {
+ confirmCancel();
+ }
+ };
+
+ const handleCloseSidebar = () => {
+ if (
+ title !== (newsletter ? newsletter.title : "") ||
+ description !== (newsletter ? newsletter.description : "") ||
+ date !== (newsletter ? newsletter.date : "") ||
+ content !== (newsletter ? newsletter.content : [])
+ ) {
+ setWarningOpen(true);
+ } else {
+ confirmCancel();
+ setSidebarOpen(false);
+ }
+ };
+
+
+ const handleSave = () => {
+ setWarningOpen(false);
+ if (title === "" || description === "" || date === "" || content.length === 0) {
+ setErrors({
+ title: title === "",
+ description: description === "",
+ date: date === "",
+ content: content.length === 0,
+ });
+ } else {
+ setIsEditing(false);
+ if (newsletter) {
+ updateNewsletter({
+ _id: newsletter._id,
+ image: newsletter.image,
+ title,
+ description,
+ date,
+ content,
+ archive: newsletter.archive,
+ });
+
+ } else {
+
+
+ createNewsletter({
+ image: "/newsletter2.png",
+ title,
+ description,
+ date,
+ content,
+ archive: true,
+ });
+ }
+ setIsEditing(false);
+ setErrors({});
+ setShowAlert(true);
+ window.location.reload();
+ }
+ };
+
+
+
+
+ const handleDelete = () => {
+
+ setIsDeleting(true);
+
+ };
+
+ const confirmDelete = () => {
+
+ deleteNewsletter(newsletter._id);
+ setSidebarOpen(false);
+ window.location.reload();
+ };
+
+
+ const alertContent = {
+ text: "Newsletter Saved!",
+ };
+
+ const handleCloseAlert = () => {
+ setShowAlert(false);
+ };
+
+
+ if(isDeleting) {
+
+ return (
+
+
+
+
{
+ setSidebarOpen(false);
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+ {/* Edit button */}
+
{
+ setIsEditing(true);
+ console.log("isEditing:", isEditing);
+ }}
+ className={styles.editButton}
+ >
+
+ Edit
+
+
+
Newsletter Title
+
{title}
+
Newsletter Description
+
{description}
+
Date & Time
+
{date}
+
Newsletter Cover
+
Placeholder - to be replaced with image
+
Newsletter Content
+ {content.map((paragraph: string, index: number) => (
+
+ {paragraph}
+
+ ))}
+ {/* Delete button */}
+
+
+
+
+
+
+ );
+ }
+
+ if (isEditing) {
+ return (
+
+ {warningOpen &&
}
+ {warningOpen && (
+
{
+ setWarningOpen(false);
+ }}
+ />
+ )}
+ {
+ handleCloseSidebar();
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+
+
+
+ {/* Cancel button */}
+
+ Cancel
+
+
+ {/* Save button */}
+
+ Save
+
+
+
+ );
+
+ //if is deleting
+ }
+ else {
+ // not in edit mode
+ return (
+
+
+
+
{
+ setSidebarOpen(false);
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+ {/* Edit button */}
+
{
+ setIsEditing(true);
+ console.log("isEditing:", isEditing);
+ }}
+ className={styles.editButton}
+ >
+
+ Edit
+
+
+
Newsletter Title
+
{title}
+
Newsletter Description
+
{description}
+
Date & Time
+
{date}
+
Newsletter Cover
+
Placeholder - to be replaced with image
+
Newsletter Content
+ {content.map((paragraph: string, index: number) => (
+
+ {paragraph}
+
+ ))}
+ {/* Delete button */}
+
+
+
+ );
+ }
+};
+
+export default NewsletterSidebar;
\ No newline at end of file
From f45cabbefe931faad32012c5ef724edfefd93ba9 Mon Sep 17 00:00:00 2001
From: kevindo0720 <80845738+kevindo0720@users.noreply.github.com>
Date: Fri, 10 May 2024 15:57:01 -0700
Subject: [PATCH 4/6] make content input box expandable
---
.../src/app/admin/newslettercreator/page.tsx | 2 -
frontend/src/components/NewsletterSidebar.tsx | 3 +-
.../components/NewsletterSidebarArchive.tsx | 3 +-
frontend/src/components/TextField.module.css | 21 +++++++++-
frontend/src/components/TextFieldContent.tsx | 41 +++++++++++++++++++
5 files changed, 64 insertions(+), 6 deletions(-)
create mode 100644 frontend/src/components/TextFieldContent.tsx
diff --git a/frontend/src/app/admin/newslettercreator/page.tsx b/frontend/src/app/admin/newslettercreator/page.tsx
index ed517660..462041ba 100644
--- a/frontend/src/app/admin/newslettercreator/page.tsx
+++ b/frontend/src/app/admin/newslettercreator/page.tsx
@@ -64,7 +64,6 @@ export default function MailingList() {
];
-
const [rows, setRow] = useState([]);
const [rowsCurrent, setRowsCurrent] = React.useState(rows);
const [selectedRow, setSelectedRow] = useState(null);
@@ -143,7 +142,6 @@ export default function MailingList() {
};
-
const handleSetSidebarOpen = (open: boolean) => {
setSidebarOpen(open);
};
diff --git a/frontend/src/components/NewsletterSidebar.tsx b/frontend/src/components/NewsletterSidebar.tsx
index ec5af7fd..0d62aca7 100644
--- a/frontend/src/components/NewsletterSidebar.tsx
+++ b/frontend/src/components/NewsletterSidebar.tsx
@@ -12,6 +12,7 @@ import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning"
import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
import { TextField } from "@/components/TextField";
+import { TextFieldContent } from "@/components/TextFieldContent";
type newsletterSidebarProps = {
newsletter: null | Newsletter;
@@ -272,7 +273,7 @@ const NewsletterSidebar = ({
/>
Newsletter Cover
Placeholder - to be replaced with image
-
Newsletter Cover
Placeholder - to be replaced with image
- ` gives us the type X, excluding
+ * any fields Y. In this case, we are extending `React.ComponentProps<"input">` (the props that an
+ * ` ` component can receive), but excluding the specific prop `type`. We exclude `type`
+ * because we will set `type="text"` on the underlying ` ` component, so there's no point in
+ * allowing the developer to pass that prop in themselves.
+ */
+export type TextFieldContentProps = {
+ label: string;
+ error?: boolean;
+} & Omit, "type">;
+
+/**
+ * See `src/components/Button.tsx` for an explanation of `React.forwardRef`.
+ */
+export const TextFieldContent = React.forwardRef(function TextField(
+ { label, error = false, className, ...props },
+ ref,
+) {
+ let wrapperClass = styles.wrapper;
+ if (className) {
+ wrapperClass += ` ${className}`;
+ }
+ let inputClass = styles.inputcontent;
+ if (error) {
+ inputClass += ` ${styles.error}`;
+ }
+ return (
+
+ );
+});
\ No newline at end of file
From c217facf2a3be79d27729db6e134bb13d2377930 Mon Sep 17 00:00:00 2001
From: jennymar
Date: Sun, 12 May 2024 18:39:39 -0700
Subject: [PATCH 5/6] Changed newsletter schema to archive based on year,
content is string not array
---
backend/src/controllers/newsletter.ts | 9 +-
backend/src/models/newsletter.ts | 3 +-
backend/src/routes/newsletter.ts | 2 +-
backend/src/validators/newsletter.ts | 13 +-
frontend/src/api/newsletter.ts | 10 +-
.../newsletter/[newsletterID]/page.module.css | 15 +-
.../newsletter/[newsletterID]/page.tsx | 31 +-
.../src/app/(web app)/newsletter/page.tsx | 14 +-
.../page.module.css | 13 +
.../page.tsx | 187 +++++----
.../admin/newslettercreator/archive/page.tsx | 367 ------------------
.../admin/newslettercreator/page.module.css | 55 ---
frontend/src/components/AlertBanner.tsx | 2 +-
.../NewsletterDeleteWarning.module.css | 132 +++----
.../components/NewsletterDeleteWarning.tsx | 8 +-
.../components/NewsletterSidebar.module.css | 321 +++++++--------
frontend/src/components/NewsletterSidebar.tsx | 211 +++++-----
.../components/NewsletterSidebarArchive.tsx | 364 -----------------
.../NewsletterSidebarWarning.module.css | 132 ++++---
.../components/NewsletterSidebarWarning.tsx | 2 +-
frontend/src/components/PageToggle.module.css | 48 +--
frontend/src/components/PageToggle.tsx | 53 ++-
frontend/src/components/TextField.module.css | 88 ++---
frontend/src/components/TextField.tsx | 2 +-
frontend/src/components/TextFieldContent.tsx | 41 --
25 files changed, 683 insertions(+), 1440 deletions(-)
rename frontend/src/app/admin/{newslettercreator/archive => newsletter-creator}/page.module.css (82%)
rename frontend/src/app/admin/{newslettercreator => newsletter-creator}/page.tsx (70%)
delete mode 100644 frontend/src/app/admin/newslettercreator/archive/page.tsx
delete mode 100644 frontend/src/app/admin/newslettercreator/page.module.css
delete mode 100644 frontend/src/components/NewsletterSidebarArchive.tsx
delete mode 100644 frontend/src/components/TextFieldContent.tsx
diff --git a/backend/src/controllers/newsletter.ts b/backend/src/controllers/newsletter.ts
index e6e2dbea..e3b57c66 100644
--- a/backend/src/controllers/newsletter.ts
+++ b/backend/src/controllers/newsletter.ts
@@ -35,25 +35,24 @@ export const getNewsletter: RequestHandler = async (req, res, next) => {
};
export const createNewsletter: RequestHandler = async (req, res, next) => {
- console.log(req.body);
const errors = validationResult(req);
- const { _id, image, title, description, date, content, archive } = req.body;
+ const { image, title, description, date, content } = req.body;
try {
validationErrorParser(errors);
const newsletter = await Newsletter.create({
- _id,
image,
title,
description,
date,
content,
- archive,
});
+ console.log("newsletter: ", newsletter);
res.status(201).json(newsletter);
} catch (error) {
+ console.error("Error creating newsletter:", error);
next(error);
}
};
@@ -100,4 +99,4 @@ export const deleteNewsletter: RequestHandler = async (req, res, next) => {
} catch (error) {
next(error);
}
-};
\ No newline at end of file
+};
diff --git a/backend/src/models/newsletter.ts b/backend/src/models/newsletter.ts
index 3c55b978..a5487924 100644
--- a/backend/src/models/newsletter.ts
+++ b/backend/src/models/newsletter.ts
@@ -5,8 +5,7 @@ const newsletterSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
date: { type: String, required: true },
- content: { type: [String], required: true },
- archive: { type: Boolean, required: true },
+ content: { type: String, required: true },
});
type Newsletters = InferSchemaType;
diff --git a/backend/src/routes/newsletter.ts b/backend/src/routes/newsletter.ts
index eb5c99a7..716d8e5d 100644
--- a/backend/src/routes/newsletter.ts
+++ b/backend/src/routes/newsletter.ts
@@ -12,6 +12,6 @@ router.put(
NewsletterController.updateNewsletter,
);
router.post("/", NewsletterValidator.createNewsletter, NewsletterController.createNewsletter);
-router.delete("/:id", NewsletterValidator.deleteNewsletter , NewsletterController.deleteNewsletter);
+router.delete("/:id", NewsletterValidator.deleteNewsletter, NewsletterController.deleteNewsletter);
export default router;
diff --git a/backend/src/validators/newsletter.ts b/backend/src/validators/newsletter.ts
index ceb499af..27cb6d42 100644
--- a/backend/src/validators/newsletter.ts
+++ b/backend/src/validators/newsletter.ts
@@ -40,15 +40,8 @@ const makeContentValidator = () =>
.exists()
.withMessage("content is required")
.bail()
- .isArray()
- .withMessage("content must be an array of strings");
-const makeArchiveValidator = () =>
- body("archive")
- .exists()
- .withMessage("archive is required")
- .bail()
- .isBoolean()
- .withMessage("archive must be a boolean");
+ .isString()
+ .withMessage("content must be a string");
export const createNewsletter = [
makeImageValidator(),
@@ -56,7 +49,6 @@ export const createNewsletter = [
makeDescriptionValidator(),
makeDateValidator(),
makeContentValidator(),
- makeArchiveValidator(),
];
export const updateNewsletter = [
@@ -66,7 +58,6 @@ export const updateNewsletter = [
makeDescriptionValidator(),
makeDateValidator(),
makeContentValidator(),
- makeArchiveValidator(),
];
export const getNewsletter = [makeIDValidator()];
diff --git a/frontend/src/api/newsletter.ts b/frontend/src/api/newsletter.ts
index bec3cb15..cf1b6f7d 100644
--- a/frontend/src/api/newsletter.ts
+++ b/frontend/src/api/newsletter.ts
@@ -1,4 +1,4 @@
-import { get, handleAPIError, post, put,del } from "./requests";
+import { del, get, handleAPIError, post, put } from "./requests";
import type { APIResult } from "./requests";
@@ -8,8 +8,7 @@ export type Newsletter = {
title: string;
description: string;
date: string;
- content: string[];
- archive: boolean;
+ content: string;
};
export type CreateNewsletterRequest = {
@@ -17,8 +16,7 @@ export type CreateNewsletterRequest = {
title: string;
description: string;
date: string;
- content: string[];
- archive: boolean;
+ content: string;
};
export async function getNewsletter(id: string): Promise> {
@@ -78,4 +76,4 @@ export async function deleteNewsletter(id: string): Promise {
- window.scrollTo(0, 0);
- // html2canvas(document.body, { scale: 0.32 }).then((canvas) => {
- // const imgData = canvas.toDataURL("image/png");
- // const pdf = new jsPDF();
- // pdf.addImage(imgData, "PNG", 0, 0);
- // pdf.save("download.pdf");
- // });
- };
-
useEffect(() => {
getNewsletter(params.newsletterID)
.then((response) => {
@@ -93,24 +81,13 @@ export default function NewsletterDisplay({ params }: Props) {
flexShrink: 0,
}}
/>
-
+
Here’s Our Story
-
- {newsletter?.content.map((paragraph, index) => (
-
- {paragraph}
-
- ))}
+
+ {newsletter?.content.split("\n").map((line, index) =>
{line}
)}
+
Share This Post
diff --git a/frontend/src/app/(web app)/newsletter/page.tsx b/frontend/src/app/(web app)/newsletter/page.tsx
index a741924c..f4fe3ca5 100644
--- a/frontend/src/app/(web app)/newsletter/page.tsx
+++ b/frontend/src/app/(web app)/newsletter/page.tsx
@@ -12,7 +12,7 @@ import { Newsletter, getAllNewsletters } from "@/api/newsletter";
import BackgroundHeader from "@/components/BackgroundHeader";
import Button from "@/components/Button";
-export default function Newsletter() {
+export default function NewsletterPage() {
const [popupOpen, setPopup] = useState(false);
const [images, setImages] = useState
([]);
const [curNewsletters, setCurNewsletters] = useState([]);
@@ -35,10 +35,18 @@ export default function Newsletter() {
getAllNewsletters()
.then((response) => {
if (response.success) {
- const curLetters = response.data.filter((item) => !item.archive);
+ const currentYear = new Date().getFullYear();
+
+ const curLetters = response.data.filter((item) => {
+ const itemDate = new Date(item.date);
+ return itemDate.getFullYear() === currentYear;
+ });
setCurNewsletters(curLetters);
- const archiveLetters = response.data.filter((item) => item.archive);
+ const archiveLetters = response.data.filter((item) => {
+ const itemDate = new Date(item.date);
+ return itemDate.getFullYear() < currentYear;
+ });
const newslettersByYear: Record = {};
archiveLetters.forEach((newsletter) => {
diff --git a/frontend/src/app/admin/newslettercreator/archive/page.module.css b/frontend/src/app/admin/newsletter-creator/page.module.css
similarity index 82%
rename from frontend/src/app/admin/newslettercreator/archive/page.module.css
rename to frontend/src/app/admin/newsletter-creator/page.module.css
index 2ee3848a..a76ae236 100644
--- a/frontend/src/app/admin/newslettercreator/archive/page.module.css
+++ b/frontend/src/app/admin/newsletter-creator/page.module.css
@@ -53,3 +53,16 @@
background-color: #f8f5fb; /* #F8F5FB color for odd rows */
}
+.sidebar-container {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 400px; /* Adjust width as needed */
+ transition: transform 0.3s ease-out;
+ transform: translateX(100%);
+}
+
+.sidebar-container.open {
+ transform: translateX(0%);
+}
diff --git a/frontend/src/app/admin/newslettercreator/page.tsx b/frontend/src/app/admin/newsletter-creator/page.tsx
similarity index 70%
rename from frontend/src/app/admin/newslettercreator/page.tsx
rename to frontend/src/app/admin/newsletter-creator/page.tsx
index 462041ba..b1edad65 100644
--- a/frontend/src/app/admin/newslettercreator/page.tsx
+++ b/frontend/src/app/admin/newsletter-creator/page.tsx
@@ -12,7 +12,6 @@ import React, { useEffect, useState } from "react";
import styles from "./page.module.css";
-
import {
CreateNewsletterRequest,
Newsletter,
@@ -20,12 +19,11 @@ import {
getAllNewsletters,
getNewsletter,
updateNewsletter,
- deleteNewsletter,
} from "@/api/newsletter";
import NewsletterSidebar from "@/components/NewsletterSidebar";
import PageToggle from "@/components/PageToggle";
-export default function MailingList() {
+export default function NewsletterCreator() {
const columns: GridColDef<(typeof rows)[number]>[] = [
{
field: "title",
@@ -61,11 +59,12 @@ export default function MailingList() {
disableColumnMenu: true,
renderHeader: () => Date
,
},
-
];
const [rows, setRow] = useState([]);
const [rowsCurrent, setRowsCurrent] = React.useState(rows);
+ const [currentNewsletters, setCurrentNewsletters] = useState([]);
+ const [archiveNewsletters, setArchiveNewsletters] = useState([]);
const [selectedRow, setSelectedRow] = useState(null);
const [currentPage, setCurrentPage] = useState(1); // Track current page
const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
@@ -76,16 +75,35 @@ export default function MailingList() {
useEffect(() => {
getAllNewsletters()
.then((result) => {
- if (result.success) {
- const formattedRows = result.data
- .filter(item => !item.archive) // filter out items where archive is false
- .map((item) => ({
- ...item,
- id: item._id.toString(),
- }));
-
- setRow(formattedRows);
- setRowsCurrent(formattedRows);
+ if (result.success) {
+ const currentYear = new Date().getFullYear();
+
+ const filteredCurrent = result.data.filter((item) => {
+ const itemDate = new Date(item.date);
+ return itemDate.getFullYear() === currentYear;
+ });
+
+ const formattedCurrentRows = filteredCurrent.map((item) => ({
+ ...item,
+ id: item._id.toString(),
+ }));
+
+ setCurrentNewsletters(formattedCurrentRows);
+
+ const filteredArchive = result.data.filter((item) => {
+ const itemDate = new Date(item.date);
+ return itemDate.getFullYear() < currentYear;
+ });
+
+ const formattedArchiveRows = filteredArchive.map((item) => ({
+ ...item,
+ id: item._id.toString(),
+ }));
+
+ setArchiveNewsletters(formattedArchiveRows);
+
+ setRow(formattedCurrentRows);
+ setRowsCurrent(formattedCurrentRows);
} else {
console.error("ERROR:", result.error);
}
@@ -120,6 +138,14 @@ export default function MailingList() {
}
}, [sidebarOpen]);
+ const handleTogglePage = (index: number) => {
+ if (index === 0) {
+ setRowsCurrent(currentNewsletters);
+ } else if (index === 1) {
+ setRowsCurrent(archiveNewsletters);
+ }
+ };
+
const openNewsletter = (createNew: boolean) => {
if (createNew) {
setSelectedRow(null);
@@ -127,13 +153,11 @@ export default function MailingList() {
setSidebarOpen(true);
};
-
useEffect(() => {
// Update total pages when rows change
setTotalPages(Math.ceil(rows.length / 14));
}, [rows]);
-
const handleCellClick: GridEventListener<"rowClick"> = (params) => {
if (!sidebarOpen) {
setSelectedRow(params.id === selectedRow ? null : params.id);
@@ -141,7 +165,6 @@ export default function MailingList() {
}
};
-
const handleSetSidebarOpen = (open: boolean) => {
setSidebarOpen(open);
};
@@ -174,9 +197,6 @@ export default function MailingList() {
});
};
-
-
-
const handlePreviousPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
@@ -202,59 +222,60 @@ export default function MailingList() {
return rowClasses;
};
-
return (
-
- {sidebarOpen && (
-
+
+ {sidebarOpen && (
+
+
+
)}
-
-
-
-
-
-
- {
- openNewsletter(true);
+
+
+
+
+
+
+ {
+ openNewsletter(true);
+ }}
+ style={{
+ position: "relative",
fontFamily: "Open Sans",
fontWeight: 700,
fontSize: "16px",
@@ -262,19 +283,23 @@ export default function MailingList() {
letterSpacing: "0.32",
width: 196,
height: 40,
- backgroundColor: '#694C97',
- color: '#FFF',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
-
- }}
- >
- Add Newsletter
-
-
-
-
+ backgroundColor: "#694C97",
+ color: "#FFF",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ borderRadius: "4px",
+ }}
+ >
+
+ Add Newsletter
+
+
+
[] = [
- {
- field: "title",
- headerName: "Newsletter Title",
- width: 372.29,
- editable: false,
- resizable: false,
- headerClassName: `${styles.headingBackground} ${styles.cellBorderStyle} ${styles.Headings}`,
- cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
- disableColumnMenu: true,
- renderHeader: () => Newsletter Title
,
- },
- {
- field: "description",
- headerName: "Subtitle",
- width: 372.29,
- editable: false,
- resizable: false,
- headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
- cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
- disableColumnMenu: true,
- renderHeader: () => Subtitle
,
- },
-
- {
- field: "date",
- headerName: "Date",
- width: 372.29,
- editable: false,
- resizable: false,
- headerClassName: `${styles.Headings} ${styles.headingBackground} ${styles.cellBorderStyle}`,
- cellClassName: `${styles.cellEntry} ${styles.cellBorderStyle}`,
- disableColumnMenu: true,
- renderHeader: () => Date
,
- },
-
- ];
-
-
- const [rows, setRow] = useState([]);
- const [rowsCurrent, setRowsCurrent] = React.useState(rows);
- const [selectedRow, setSelectedRow] = useState(null);
- const [currentPage, setCurrentPage] = useState(1); // Track current page
- const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
- const [selectedNewsletter, setSelectedNewsletter] = useState(null);
- const [sidebarOpen, setSidebarOpen] = useState(false);
- const [rerenderKey, setRerenderKey] = useState(0);
-
- useEffect(() => {
- getAllNewsletters()
- .then((result) => {
- if (result.success) {
- const formattedRows = result.data
- .filter(item => item.archive) // filter out items where archive is false
- .map((item) => ({
- ...item,
- id: item._id.toString(),
- }));
-
- setRow(formattedRows);
- setRowsCurrent(formattedRows);
- } else {
- console.error("ERROR:", result.error);
- }
- })
- .catch((error) => {
- alert(error);
- });
- }, []);
-
- useEffect(() => {
- if (selectedRow) {
- getNewsletter(selectedRow?.toString())
- .then((result) => {
- if (result.success) {
- setSelectedNewsletter(result.data);
- setRerenderKey((prevKey) => prevKey + 1);
- } else {
- console.error("ERROR:", result.error);
- }
- })
- .catch((error) => {
- alert(error);
- });
- } else {
- setSelectedNewsletter(null);
- }
- }, [selectedRow]);
-
- useEffect(() => {
- if (sidebarOpen) {
- setRerenderKey((prevKey) => prevKey + 1);
- }
- }, [sidebarOpen]);
-
- const openNewsletter = (createNew: boolean) => {
- if (createNew) {
- setSelectedRow(null);
- }
- setSidebarOpen(true);
- };
-
-
- useEffect(() => {
- // Update total pages when rows change
- setTotalPages(Math.ceil(rows.length / 14));
- }, [rows]);
-
-
- const handleCellClick: GridEventListener<"rowClick"> = (params) => {
- if (!sidebarOpen) {
- setSelectedRow(params.id === selectedRow ? null : params.id);
- openNewsletter(false);
- }
- };
-
-
-
- const handleSetSidebarOpen = (open: boolean) => {
- setSidebarOpen(open);
- };
- const handleUpdateNewsletter = (newsletterData: Newsletter) => {
- updateNewsletter(newsletterData)
- .then((result) => {
- if (result.success) {
- // TODO: add success message, update table
- } else {
- console.error("ERROR:", result.error);
- }
- })
- .catch((error) => {
- alert(error);
- });
- };
-
- const handleCreateNewsletter = (newsletterData: CreateNewsletterRequest) => {
- console.log(newsletterData);
- createNewsletter(newsletterData)
- .then((result) => {
- if (result.success) {
- //TODO: add success message, update table
- } else {
- console.error("ERROR:", result.error);
- }
- })
- .catch((error) => {
- alert(error);
- });
- };
-
-
-
-
- const handlePreviousPage = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
-
- const handleNextPage = () => {
- if (currentPage < totalPages) {
- setCurrentPage(currentPage + 1);
- }
- };
-
- const getRowClassName = (params: GridRowClassNameParams) => {
- let rowClasses = "";
-
- // Add alternating row colors
- rowClasses += params.indexRelativeToCurrentPage % 2 === 0 ? styles.evenRow : styles.oddRow;
-
- // Add border to the selected row
- if (selectedRow === params.id) {
- rowClasses += ` ${styles.selectedRow}`;
- }
- return rowClasses;
- };
-
- return (
-
-
- {sidebarOpen && (
-
- )}
-
-
-
-
-
-
-
- {
- openNewsletter(true);
- }}
- style={{
- position: 'relative',
- fontFamily: "Open Sans",
- fontWeight: 700,
- fontSize: "16px",
- lineHeight: "24px",
- letterSpacing: "0.32",
- width: 196,
- height: 40,
- backgroundColor: '#694C97',
- color: '#FFF',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
-
- }}
- >
- Add Newsletter
-
-
-
-
-
-
-
-
-
-
Page
-
- {currentPage}
-
-
of
-
{totalPages}
-
-
-
-
-
- );
-}
diff --git a/frontend/src/app/admin/newslettercreator/page.module.css b/frontend/src/app/admin/newslettercreator/page.module.css
deleted file mode 100644
index 2ee3848a..00000000
--- a/frontend/src/app/admin/newslettercreator/page.module.css
+++ /dev/null
@@ -1,55 +0,0 @@
-@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");
-
-.page {
- display: flex;
- justify-content: flex-start;
- padding-left: 282px;
- padding-top: 22px;
- padding-bottom: 50px;
-}
-
-.Headings {
- text-align: left;
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 700;
- line-height: 24px; /* 133.333% */
- color: white;
-}
-
-.cellentry {
- text-align: left;
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px; /* 133.333% */
- color: black;
-}
-
-.headingBackground {
- background-color: #694c97; /* Replace with your desired color */
-}
-
-.cellBorderStyle {
- border-right: 1px solid #c9c9c9; /* Adjust the border style as needed */
-}
-
-.selectedRow {
- border-radius: 5px;
- box-shadow: inset 0 0 0 2px #bda7e0;
- box-shadow: inset 0 0.5px 0 2px #bda7e0;
-}
-.selectedCol {
- background: rgba(105, 76, 151, 0.05);
-}
-
-.evenRow {
- background-color: #ffffff; /* White color for even rows */
-}
-
-.oddRow {
- background-color: #f8f5fb; /* #F8F5FB color for odd rows */
-}
-
diff --git a/frontend/src/components/AlertBanner.tsx b/frontend/src/components/AlertBanner.tsx
index 32fa0363..a71d6546 100644
--- a/frontend/src/components/AlertBanner.tsx
+++ b/frontend/src/components/AlertBanner.tsx
@@ -29,4 +29,4 @@ const AlertBanner = ({ text, img, undo, onClose }: ButtonProps) => {
);
};
-export default AlertBanner;
\ No newline at end of file
+export default AlertBanner;
diff --git a/frontend/src/components/NewsletterDeleteWarning.module.css b/frontend/src/components/NewsletterDeleteWarning.module.css
index 2f90d7d6..2cf152c3 100644
--- a/frontend/src/components/NewsletterDeleteWarning.module.css
+++ b/frontend/src/components/NewsletterDeleteWarning.module.css
@@ -1,67 +1,67 @@
.wrapper {
- position: absolute;
- top: 200px;
- padding: 24px;
- background-color: #fff;
- border-radius: 5px;
- margin: 52px;
- }
-
- .wrapper button img {
- padding: 4px;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
- }
-
- .wrapper button p {
- font: var(--text-body);
- font-size: 20px;
- font-weight: 700;
- line-height: 150%; /* 30px */
- letter-spacing: 0.7px;
- }
-
- .closeButton {
- position: relative;
- float: right;
- }
-
- .saveButton {
- background: #694c97;
- color: #fff;
- }
-
- .deleteButton {
- background: #fff;
- border: 1px solid #b93b3b;
- color: #b93b3b;
- }
-
- .wrapper h1 {
- color: #000;
- font: var(--font-small-subtitle);
- font-size: 24px;
- font-style: normal;
- font-weight: 700;
- line-height: 150%; /* 36px */
- letter-spacing: 0.48px;
- }
-
- .wrapper p {
- color: #000;
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px; /* 150% */
- }
-
- .buttonWrapper {
- display: flex;
- flex-direction: row;
- justify-content: end;
- gap: 18px;
- margin-top: 18px;
- }
\ No newline at end of file
+ position: absolute;
+ top: 200px;
+ padding: 24px;
+ background-color: #fff;
+ border-radius: 5px;
+ margin: 52px;
+}
+
+.wrapper button img {
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+
+.wrapper button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+}
+
+.closeButton {
+ position: relative;
+ float: right;
+}
+
+.saveButton {
+ background: #694c97;
+ color: #fff;
+}
+
+.deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+}
+
+.wrapper h1 {
+ color: #000;
+ font: var(--font-small-subtitle);
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 36px */
+ letter-spacing: 0.48px;
+}
+
+.wrapper p {
+ color: #000;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+}
+
+.buttonWrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ gap: 18px;
+ margin-top: 18px;
+}
diff --git a/frontend/src/components/NewsletterDeleteWarning.tsx b/frontend/src/components/NewsletterDeleteWarning.tsx
index 6bb4150a..14b4ba03 100644
--- a/frontend/src/components/NewsletterDeleteWarning.tsx
+++ b/frontend/src/components/NewsletterDeleteWarning.tsx
@@ -11,11 +11,7 @@ type NewsletterDeleteWarningProps = {
onClose: () => void;
};
-export const NewsletterDeleteWarning = ({
- save,
- discard,
- onClose,
-}: NewsletterDeleteWarningProps) => {
+export const NewsletterDeleteWarning = ({ save, discard }: NewsletterDeleteWarningProps) => {
return (
@@ -35,4 +31,4 @@ export const NewsletterDeleteWarning = ({
);
-};
\ No newline at end of file
+};
diff --git a/frontend/src/components/NewsletterSidebar.module.css b/frontend/src/components/NewsletterSidebar.module.css
index 8180b9da..f3bac86b 100644
--- a/frontend/src/components/NewsletterSidebar.module.css
+++ b/frontend/src/components/NewsletterSidebar.module.css
@@ -1,156 +1,167 @@
.sidebar {
- position: fixed;
- top: 0;
- bottom: 0;
- right: 0;
- overflow-y: scroll;
- overflow-x: hidden;
- overscroll-behavior: contain;
- width: 36vw;
- z-index: 3;
- border-left: 1px solid #c9c9c9;
- background: #fff;
- }
-
- .grayOut {
- opacity: 0.3;
- background: #484848;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
-
- .closeWindow {
- display: flex;
- width: 524px;
- height: 41px;
- padding: 10px 20px;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
- color: var(--Neutral-Gray4, #909090);
- border-bottom: 1px solid #c9c9c9;
- font: var(--font-body);
- font-size: 14px;
- }
-
- .closeWindow:hover {
- cursor: pointer;
- }
-
- .sidebarContents {
- padding: 60px;
- padding-left: 35px;
- }
-
- .sidebarContents h1 {
- font: var(--font-small-subtitle);
- font-size: 18px;
- font-style: normal;
- font-weight: 700;
- line-height: 150%; /* 27px */
- letter-spacing: 0.36px;
- }
-
- .sidebarContents h2 {
- padding-top: 24px;
- font: var(--font-body);
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px; /* 142.857% */
- color: #909090;
- }
-
- .sidebarContents {
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px; /* 150% */
- }
-
- .header {
- display: flex;
- direction: row;
- align-items: center;
- justify-content: space-between;
- height: 46px;
- }
-
- .sidebar button {
- display: flex;
- padding: 4px 16px;
- justify-content: center;
- align-items: center;
- gap: 6px;
- border-radius: 4px;
- }
-
- .sidebar button img {
- padding: 4px;
- }
-
- .sidebar button p {
- font: var(--text-body);
- font-size: 20px;
- font-weight: 700;
- line-height: 150%; /* 30px */
- letter-spacing: 0.7px;
- }
-
- .editButton {
- background: #694c97;
- color: #fff;
- }
-
- .cancelButton {
- background: #fff;
- border: 1px solid #694c97;
- color: #694c97;
- }
-
- .saveButton {
- background: #694c97;
- color: #fff;
- }
-
- .deleteButton {
- background: #fff;
- border: 1px solid #b93b3b;
- color: #b93b3b;
- }
-
- .bottomButtons {
- display: flex;
- direction: row;
- gap: 24px;
- justify-content: end;
- margin-right: 60px;
- }
-
- .deleteButtonWrapper {
- display: flex;
- direction: row;
- justify-content: center;
- margin-top: 50px;
- }
-
- .contentPar {
- margin-bottom: 1rem;
- }
-
- .sidebar .alert {
- position: relative;
- top: 35px;
- }
-
- .sidebar .alert .div {
- position: absolute;
- }
-
- .sidebar .alert img {
- padding: 0;
- }
\ No newline at end of file
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ overscroll-behavior: contain;
+ width: 38vw;
+ z-index: 3;
+ border-left: 1px solid #c9c9c9;
+ background: #fff;
+}
+
+.grayOut {
+ opacity: 0.3;
+ background: #484848;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 30%;
+}
+
+.closeWindow {
+ display: flex;
+ width: 524px;
+ height: 41px;
+ padding: 10px 20px;
+ align-items: center;
+ gap: 8px;
+ flex-shrink: 0;
+ color: var(--Neutral-Gray4, #909090);
+ border-bottom: 1px solid #c9c9c9;
+ font: var(--font-body);
+ font-size: 14px;
+}
+
+.closeWindow:hover {
+ cursor: pointer;
+}
+
+.sidebarContents {
+ padding: 60px;
+ padding-left: 35px;
+}
+
+.sidebarContents h1 {
+ font: var(--font-small-subtitle);
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 27px */
+ letter-spacing: 0.36px;
+}
+
+.sidebarContents h2 {
+ padding-top: 24px;
+ font: var(--font-body);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ color: #909090;
+}
+
+.sidebarContents {
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+}
+
+.header {
+ display: flex;
+ direction: row;
+ align-items: center;
+ justify-content: space-between;
+ height: 46px;
+}
+
+.sidebar button {
+ display: flex;
+ padding: 4px 16px;
+ justify-content: center;
+ align-items: center;
+ gap: 6px;
+ border-radius: 4px;
+}
+
+.sidebar button img {
+ padding: 4px;
+}
+
+.sidebar button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+}
+
+.editButton {
+ background: #694c97;
+ color: #fff;
+}
+
+.cancelButton {
+ background: #fff;
+ border: 1px solid #694c97;
+ color: #694c97;
+}
+
+.saveButton {
+ background: #694c97;
+ color: #fff;
+}
+
+.deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+}
+
+.bottomButtons {
+ display: flex;
+ direction: row;
+ gap: 24px;
+ justify-content: flex-end;
+ margin-right: 60px;
+ margin-bottom: 77px;
+}
+
+.deleteButtonWrapper {
+ display: flex;
+ direction: row;
+ justify-content: center;
+ margin-top: 50px;
+}
+
+.contentPar {
+ margin-bottom: 1rem;
+}
+
+.sidebar .alert {
+ position: relative;
+ top: 35px;
+}
+
+.sidebar .alert .div {
+ position: absolute;
+}
+
+.sidebar .alert img {
+ padding: 0;
+}
+
+.textField {
+ width: 454px;
+}
+
+.textArea {
+ width: 454px;
+ height: 288px;
+ padding: 12px;
+ border: 1px solid grey;
+}
diff --git a/frontend/src/components/NewsletterSidebar.tsx b/frontend/src/components/NewsletterSidebar.tsx
index 0d62aca7..a2ad0be7 100644
--- a/frontend/src/components/NewsletterSidebar.tsx
+++ b/frontend/src/components/NewsletterSidebar.tsx
@@ -1,18 +1,15 @@
"use client";
import Image from "next/image";
import React, { useState } from "react";
-import { useRouter } from 'next/router';
-import { CreateNewsletterRequest, Newsletter,deleteNewsletter } from "../api/newsletter";
+import { CreateNewsletterRequest, Newsletter, deleteNewsletter } from "../api/newsletter";
import styles from "./NewsletterSidebar.module.css";
import AlertBanner from "@/components/AlertBanner";
-import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
-
+import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
import { TextField } from "@/components/TextField";
-import { TextFieldContent } from "@/components/TextFieldContent";
type newsletterSidebarProps = {
newsletter: null | Newsletter;
@@ -37,23 +34,23 @@ const NewsletterSidebar = ({
const [title, setTitle] = useState(newsletter ? newsletter.title : "");
const [description, setDescription] = useState(newsletter ? newsletter.description : "");
const [date, setDate] = useState(newsletter ? newsletter.date : "");
- const [content, setContent] = useState(newsletter ? newsletter.content : []);
+ const [content, setContent] = useState(newsletter ? newsletter.content : "");
const [isEditing, setIsEditing] = useState
(!newsletter);
const [isDeleting, setIsDeleting] = useState(false);
const [errors, setErrors] = useState({});
const [warningOpen, setWarningOpen] = useState(false);
- const [warningDelete, setWarningDelete] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const confirmCancel = () => {
setTitle(newsletter ? newsletter.title : "");
setDescription(newsletter ? newsletter.description : "");
setDate(newsletter ? newsletter.date : "");
- setContent(newsletter ? newsletter.content : []);
+ setContent(newsletter ? newsletter.content : "");
setIsEditing(false);
setIsDeleting(false);
setErrors({});
setWarningOpen(false);
+ setSidebarOpen(false);
};
const handleCancel = () => {
@@ -82,7 +79,6 @@ const NewsletterSidebar = ({
setSidebarOpen(false);
}
};
-
const handleSave = () => {
setWarningOpen(false);
@@ -103,19 +99,14 @@ const NewsletterSidebar = ({
description,
date,
content,
- archive: newsletter.archive,
});
-
} else {
-
-
createNewsletter({
image: "/newsletter2.png",
title,
description,
date,
content,
- archive: false,
});
}
setIsEditing(false);
@@ -125,22 +116,27 @@ const NewsletterSidebar = ({
}
};
-
-
-
const handleDelete = () => {
-
setIsDeleting(true);
-
};
const confirmDelete = () => {
-
- deleteNewsletter(newsletter._id);
- setSidebarOpen(false);
- window.location.reload();
+ if (newsletter) {
+ deleteNewsletter(newsletter._id)
+ .then((result) => {
+ if (result.success) {
+ console.log("successful deletion");
+ } else {
+ console.error("ERROR:", result.error);
+ }
+ })
+ .catch((error) => {
+ alert(error);
+ });
+ setSidebarOpen(false);
+ window.location.reload();
+ }
};
-
const alertContent = {
text: "Newsletter Saved!",
@@ -150,71 +146,68 @@ const NewsletterSidebar = ({
setShowAlert(false);
};
-
- if(isDeleting) {
-
+ if (isDeleting) {
return (
-
-
-
-
{
- setSidebarOpen(false);
- }}
- >
-
-
Close Window
+
+
+
{
+ setSidebarOpen(false);
+ }}
+ >
+
+
Close Window
+
+
+
+
Newsletter Details
+
+ {/* Edit button */}
+
{
+ setIsEditing(true);
+ console.log("isEditing:", isEditing);
+ }}
+ className={styles.editButton}
+ >
+
+ Edit
+
-
-
-
Newsletter Details
-
- {/* Edit button */}
-
{
- setIsEditing(true);
- console.log("isEditing:", isEditing);
- }}
- className={styles.editButton}
- >
-
- Edit
-
-
-
Newsletter Title
-
{title}
-
Newsletter Description
-
{description}
-
Date & Time
-
{date}
-
Newsletter Cover
-
Placeholder - to be replaced with image
-
Newsletter Content
- {content.map((paragraph: string, index: number) => (
-
- {paragraph}
-
- ))}
- {/* Delete button */}
+
Newsletter Title
+
{title}
+
Newsletter Description
+
{description}
+
Date & Time
+
{date}
+
Newsletter Cover
+
Placeholder - to be replaced with image
+
Newsletter Content
+ {content.split(".").map((paragraph: string, index: number) => (
+
+ {paragraph}
+
+ ))}
+ {/* Delete button */}
-
-
-
+
+ Delete
+
+
+
+
-
+ />
- );
- }
+
+ );
+ }
if (isEditing) {
return (
@@ -273,16 +266,55 @@ const NewsletterSidebar = ({
/>
Newsletter Cover
Placeholder - to be replaced with image
-
Newsletter Content
+
@@ -290,7 +322,6 @@ const NewsletterSidebar = ({
{/* Cancel button */}
Cancel
-
{/* Save button */}
@@ -301,12 +332,10 @@ const NewsletterSidebar = ({
);
//if is deleting
- }
- else {
+ } else {
// not in edit mode
return (
-
@@ -344,7 +373,7 @@ const NewsletterSidebar = ({
Newsletter Cover
Placeholder - to be replaced with image
Newsletter Content
- {content.map((paragraph: string, index: number) => (
+ {content.split("\n").map((paragraph: string, index: number) => (
{paragraph}
@@ -361,4 +390,4 @@ const NewsletterSidebar = ({
}
};
-export default NewsletterSidebar;
\ No newline at end of file
+export default NewsletterSidebar;
diff --git a/frontend/src/components/NewsletterSidebarArchive.tsx b/frontend/src/components/NewsletterSidebarArchive.tsx
deleted file mode 100644
index d0209613..00000000
--- a/frontend/src/components/NewsletterSidebarArchive.tsx
+++ /dev/null
@@ -1,364 +0,0 @@
-"use client";
-import Image from "next/image";
-import React, { useState } from "react";
-import { useRouter } from 'next/router';
-
-import { CreateNewsletterRequest, Newsletter,deleteNewsletter } from "../api/newsletter";
-
-import styles from "./NewsletterSidebar.module.css";
-
-import AlertBanner from "@/components/AlertBanner";
-import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
-import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
-
-import { TextField } from "@/components/TextField";
-import { TextFieldContent } from "@/components/TextFieldContent";
-
-type newsletterSidebarProps = {
- newsletter: null | Newsletter;
- setSidebarOpen: (open: boolean) => void;
- updateNewsletter: (newsletterData: Newsletter) => void;
- createNewsletter: (newsletterData: CreateNewsletterRequest) => void;
-};
-
-type formErrors = {
- title?: boolean;
- description?: boolean;
- date?: boolean;
- content?: boolean;
-};
-
-const NewsletterSidebar = ({
- newsletter,
- setSidebarOpen,
- updateNewsletter,
- createNewsletter,
-}: newsletterSidebarProps) => {
- const [title, setTitle] = useState(newsletter ? newsletter.title : "");
- const [description, setDescription] = useState(newsletter ? newsletter.description : "");
- const [date, setDate] = useState(newsletter ? newsletter.date : "");
- const [content, setContent] = useState(newsletter ? newsletter.content : []);
- const [isEditing, setIsEditing] = useState
(!newsletter);
- const [isDeleting, setIsDeleting] = useState(false);
- const [errors, setErrors] = useState({});
- const [warningOpen, setWarningOpen] = useState(false);
- const [warningDelete, setWarningDelete] = useState(false);
- const [showAlert, setShowAlert] = useState(false);
-
- const confirmCancel = () => {
- setTitle(newsletter ? newsletter.title : "");
- setDescription(newsletter ? newsletter.description : "");
- setDate(newsletter ? newsletter.date : "");
- setContent(newsletter ? newsletter.content : []);
- setIsEditing(false);
- setIsDeleting(false);
- setErrors({});
- setWarningOpen(false);
- };
-
- const handleCancel = () => {
- if (
- title !== (newsletter ? newsletter.title : "") ||
- description !== (newsletter ? newsletter.description : "") ||
- date !== (newsletter ? newsletter.date : "") ||
- content !== (newsletter ? newsletter.content : [])
- ) {
- setWarningOpen(true);
- } else {
- confirmCancel();
- }
- };
-
- const handleCloseSidebar = () => {
- if (
- title !== (newsletter ? newsletter.title : "") ||
- description !== (newsletter ? newsletter.description : "") ||
- date !== (newsletter ? newsletter.date : "") ||
- content !== (newsletter ? newsletter.content : [])
- ) {
- setWarningOpen(true);
- } else {
- confirmCancel();
- setSidebarOpen(false);
- }
- };
-
-
- const handleSave = () => {
- setWarningOpen(false);
- if (title === "" || description === "" || date === "" || content.length === 0) {
- setErrors({
- title: title === "",
- description: description === "",
- date: date === "",
- content: content.length === 0,
- });
- } else {
- setIsEditing(false);
- if (newsletter) {
- updateNewsletter({
- _id: newsletter._id,
- image: newsletter.image,
- title,
- description,
- date,
- content,
- archive: newsletter.archive,
- });
-
- } else {
-
-
- createNewsletter({
- image: "/newsletter2.png",
- title,
- description,
- date,
- content,
- archive: true,
- });
- }
- setIsEditing(false);
- setErrors({});
- setShowAlert(true);
- window.location.reload();
- }
- };
-
-
-
-
- const handleDelete = () => {
-
- setIsDeleting(true);
-
- };
-
- const confirmDelete = () => {
-
- deleteNewsletter(newsletter._id);
- setSidebarOpen(false);
- window.location.reload();
- };
-
-
- const alertContent = {
- text: "Newsletter Saved!",
- };
-
- const handleCloseAlert = () => {
- setShowAlert(false);
- };
-
-
- if(isDeleting) {
-
- return (
-
-
-
-
{
- setSidebarOpen(false);
- }}
- >
-
-
Close Window
-
-
-
-
Newsletter Details
-
- {/* Edit button */}
-
{
- setIsEditing(true);
- console.log("isEditing:", isEditing);
- }}
- className={styles.editButton}
- >
-
- Edit
-
-
-
Newsletter Title
-
{title}
-
Newsletter Description
-
{description}
-
Date & Time
-
{date}
-
Newsletter Cover
-
Placeholder - to be replaced with image
-
Newsletter Content
- {content.map((paragraph: string, index: number) => (
-
- {paragraph}
-
- ))}
- {/* Delete button */}
-
-
-
-
-
-
- );
- }
-
- if (isEditing) {
- return (
-
- {warningOpen &&
}
- {warningOpen && (
-
{
- setWarningOpen(false);
- }}
- />
- )}
- {
- handleCloseSidebar();
- }}
- >
-
-
Close Window
-
-
-
-
Newsletter Details
-
-
-
-
- {/* Cancel button */}
-
- Cancel
-
-
- {/* Save button */}
-
- Save
-
-
-
- );
-
- //if is deleting
- }
- else {
- // not in edit mode
- return (
-
-
-
-
{
- setSidebarOpen(false);
- }}
- >
-
-
Close Window
-
-
-
-
Newsletter Details
-
- {/* Edit button */}
-
{
- setIsEditing(true);
- console.log("isEditing:", isEditing);
- }}
- className={styles.editButton}
- >
-
- Edit
-
-
-
Newsletter Title
-
{title}
-
Newsletter Description
-
{description}
-
Date & Time
-
{date}
-
Newsletter Cover
-
Placeholder - to be replaced with image
-
Newsletter Content
- {content.map((paragraph: string, index: number) => (
-
- {paragraph}
-
- ))}
- {/* Delete button */}
-
-
-
- );
- }
-};
-
-export default NewsletterSidebar;
\ No newline at end of file
diff --git a/frontend/src/components/NewsletterSidebarWarning.module.css b/frontend/src/components/NewsletterSidebarWarning.module.css
index 905e297f..2cf152c3 100644
--- a/frontend/src/components/NewsletterSidebarWarning.module.css
+++ b/frontend/src/components/NewsletterSidebarWarning.module.css
@@ -1,69 +1,67 @@
.wrapper {
- position: absolute;
- top: 200px;
- padding: 24px;
- background-color: #fff;
- border-radius: 5px;
- margin: 52px;
-
+ position: absolute;
+ top: 200px;
+ padding: 24px;
+ background-color: #fff;
+ border-radius: 5px;
+ margin: 52px;
+}
- }
-
- .wrapper button img {
- padding: 4px;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
- }
-
- .wrapper button p {
- font: var(--text-body);
- font-size: 20px;
- font-weight: 700;
- line-height: 150%; /* 30px */
- letter-spacing: 0.7px;
- }
-
- .closeButton {
- position: relative;
- float: right;
- }
-
- .saveButton {
- background: #694c97;
- color: #fff;
- }
-
- .deleteButton {
- background: #fff;
- border: 1px solid #b93b3b;
- color: #b93b3b;
- }
-
- .wrapper h1 {
- color: #000;
- font: var(--font-small-subtitle);
- font-size: 24px;
- font-style: normal;
- font-weight: 700;
- line-height: 150%; /* 36px */
- letter-spacing: 0.48px;
- }
-
- .wrapper p {
- color: #000;
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px; /* 150% */
- }
-
- .buttonWrapper {
- display: flex;
- flex-direction: row;
- justify-content: end;
- gap: 18px;
- margin-top: 18px;
- }
\ No newline at end of file
+.wrapper button img {
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+
+.wrapper button p {
+ font: var(--text-body);
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 150%; /* 30px */
+ letter-spacing: 0.7px;
+}
+
+.closeButton {
+ position: relative;
+ float: right;
+}
+
+.saveButton {
+ background: #694c97;
+ color: #fff;
+}
+
+.deleteButton {
+ background: #fff;
+ border: 1px solid #b93b3b;
+ color: #b93b3b;
+}
+
+.wrapper h1 {
+ color: #000;
+ font: var(--font-small-subtitle);
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 150%; /* 36px */
+ letter-spacing: 0.48px;
+}
+
+.wrapper p {
+ color: #000;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px; /* 150% */
+}
+
+.buttonWrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ gap: 18px;
+ margin-top: 18px;
+}
diff --git a/frontend/src/components/NewsletterSidebarWarning.tsx b/frontend/src/components/NewsletterSidebarWarning.tsx
index 9ec439dd..c52256ee 100644
--- a/frontend/src/components/NewsletterSidebarWarning.tsx
+++ b/frontend/src/components/NewsletterSidebarWarning.tsx
@@ -35,4 +35,4 @@ export const NewsletterSidebarWarning = ({
);
-};
\ No newline at end of file
+};
diff --git a/frontend/src/components/PageToggle.module.css b/frontend/src/components/PageToggle.module.css
index 95caff44..a1db3004 100644
--- a/frontend/src/components/PageToggle.module.css
+++ b/frontend/src/components/PageToggle.module.css
@@ -1,26 +1,26 @@
.container {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- float: left;
- margin-top: 12px;
- gap: 24px;
- margin-bottom: 0px;
- padding: 0 12px;
- border-bottom: solid 1px #6c6c6c;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ float: left;
+ margin-top: 12px;
+ gap: 24px;
+ margin-bottom: 0px;
+ padding: 0 12px;
+ border-bottom: solid 1px #6c6c6c;
+}
+
+.menu {
+ font: var(--font-body-reg);
+ font-size: 14px;
+ color: #6c6c6c;
+ padding-bottom: 12px;
+}
+
+.menuActive {
+ font: var(--font-body-bold);
+ font-size: 14px;
+ color: var(--color-primary-purple);
+ border-bottom: solid 3px var(--color-primary-purple);
+ padding-bottom: 12px;
}
-
- .menu {
- font: var(--font-body-reg);
- font-size: 14px;
- color: #6c6c6c;
- padding-bottom: 12px;
- }
-
- .menuActive {
- font: var(--font-body-bold);
- font-size: 14px;
- color: var(--color-primary-purple);
- border-bottom: solid 3px var(--color-primary-purple);
- padding-bottom: 12px;
- }
\ No newline at end of file
diff --git a/frontend/src/components/PageToggle.tsx b/frontend/src/components/PageToggle.tsx
index 1027fe9d..79917be9 100644
--- a/frontend/src/components/PageToggle.tsx
+++ b/frontend/src/components/PageToggle.tsx
@@ -4,28 +4,47 @@ import React from "react";
import styles from "./PageToggle.module.css";
type PageToggleProps = {
- pages: string[];
- links: string[];
- currPage: number;
+ pages?: string[];
+ links?: string[];
+ onTogglePage?: (index: number) => void;
+ currPage?: number;
};
-const PageToggle = ({ pages, links, currPage }: PageToggleProps) => {
- return (
-
- {pages.map((page, index) => {
- const link = links[index];
- return (
-
{
+ if (pages && links) {
+ return (
+
+ {pages.map((page, index) => {
+ const link = links[index];
+ return (
+
+ {page}
+
+ );
+ })}
+
+ );
+ } else if (pages && onTogglePage) {
+ return (
+
+ {pages.map((page, index) => (
+ {
+ onTogglePage(index);
+ }}
>
{page}
-
- );
- })}
-
- );
+
+ ))}
+
+ );
+ }
};
-export default PageToggle;
\ No newline at end of file
+export default PageToggle;
diff --git a/frontend/src/components/TextField.module.css b/frontend/src/components/TextField.module.css
index a0baa07b..a059f242 100644
--- a/frontend/src/components/TextField.module.css
+++ b/frontend/src/components/TextField.module.css
@@ -1,49 +1,47 @@
.wrapper {
- padding-top: 24px;
- width: 20rem;
- height: auto;
- }
+ padding-top: 24px;
+ width: 20rem;
+ height: auto;
+}
+.label {
+ font: var(--font-body);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ color: #909090;
+}
- .label {
- font: var(--font-body);
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px; /* 142.857% */
- color: #909090;
- }
-
- .input {
- width: 100%;
- line-height: 20px;
- padding: 6px 12px;
- border: 1px solid #d8d8d8;
- border-radius: 4px;
- font: var(--font-body);
- color: var(--Neutral-Black, #000);
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px; /* 142.857% */
- }
+.input {
+ width: 100%;
+ line-height: 20px;
+ padding: 6px 12px;
+ border: 1px solid #d8d8d8;
+ border-radius: 4px;
+ font: var(--font-body);
+ color: var(--Neutral-Black, #000);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+}
- .inputcontent {
- width: 100%;
- height: 300px;
- line-height: 20px;
- padding: 6px 12px;
- border: 1px solid #d8d8d8;
- border-radius: 4px;
- font: var(--font-body);
- color: var(--Neutral-Black, #000);
- font-size: 14px;
- font-style: normal;
- font-weight: 400;
- line-height: 20px; /* 142.857% */
- }
-
- .input.error {
- border-color: #b93b3b;
- }
-
\ No newline at end of file
+.inputcontent {
+ width: 100%;
+ height: 300px;
+ line-height: 20px;
+ padding: 6px 12px;
+ border: 1px solid #d8d8d8;
+ border-radius: 4px;
+ font: var(--font-body);
+ color: var(--Neutral-Black, #000);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+}
+
+.input.error {
+ border-color: #b93b3b;
+}
diff --git a/frontend/src/components/TextField.tsx b/frontend/src/components/TextField.tsx
index 017bb3a8..3bf1315c 100644
--- a/frontend/src/components/TextField.tsx
+++ b/frontend/src/components/TextField.tsx
@@ -38,4 +38,4 @@ export const TextField = React.forwardRef(func
);
-});
\ No newline at end of file
+});
diff --git a/frontend/src/components/TextFieldContent.tsx b/frontend/src/components/TextFieldContent.tsx
deleted file mode 100644
index 6bbf52b0..00000000
--- a/frontend/src/components/TextFieldContent.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from "react";
-
-import styles from "./TextField.module.css";
-
-/**
- * See `src/components/Button.tsx` for basic info about prop interfaces. Here we also use an `Omit`
- * type, which is a built-in TypeScript utility type. `Omit` gives us the type X, excluding
- * any fields Y. In this case, we are extending `React.ComponentProps<"input">` (the props that an
- * ` ` component can receive), but excluding the specific prop `type`. We exclude `type`
- * because we will set `type="text"` on the underlying ` ` component, so there's no point in
- * allowing the developer to pass that prop in themselves.
- */
-export type TextFieldContentProps = {
- label: string;
- error?: boolean;
-} & Omit, "type">;
-
-/**
- * See `src/components/Button.tsx` for an explanation of `React.forwardRef`.
- */
-export const TextFieldContent = React.forwardRef(function TextField(
- { label, error = false, className, ...props },
- ref,
-) {
- let wrapperClass = styles.wrapper;
- if (className) {
- wrapperClass += ` ${className}`;
- }
- let inputClass = styles.inputcontent;
- if (error) {
- inputClass += ` ${styles.error}`;
- }
- return (
-
- );
-});
\ No newline at end of file
From 6d8efeaa21cc2ec35bab56c25c51a53b1feb0eec Mon Sep 17 00:00:00 2001
From: jennymar
Date: Mon, 13 May 2024 07:30:08 -0700
Subject: [PATCH 6/6] Current newsletters and archive on same page, fixed
content string
---
.../newsletter/[newsletterID]/page.module.css | 9 ++-
.../newsletter/[newsletterID]/page.tsx | 4 +-
.../src/app/admin/newsletter-creator/page.tsx | 8 +-
.../components/NewsletterDeleteWarning.tsx | 34 --------
.../components/NewsletterSidebar.module.css | 18 ++++-
frontend/src/components/NewsletterSidebar.tsx | 78 +++++--------------
.../NewsletterSidebarWarning.module.css | 67 ----------------
.../components/NewsletterSidebarWarning.tsx | 38 ---------
frontend/src/components/PageToggle.tsx | 2 +-
...ng.module.css => WarningModule.module.css} | 10 +++
frontend/src/components/WarningModule.tsx | 44 +++++++++++
11 files changed, 101 insertions(+), 211 deletions(-)
delete mode 100644 frontend/src/components/NewsletterDeleteWarning.tsx
delete mode 100644 frontend/src/components/NewsletterSidebarWarning.module.css
delete mode 100644 frontend/src/components/NewsletterSidebarWarning.tsx
rename frontend/src/components/{NewsletterDeleteWarning.module.css => WarningModule.module.css} (81%)
create mode 100644 frontend/src/components/WarningModule.tsx
diff --git a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.module.css b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.module.css
index 99c149e8..d190c7da 100644
--- a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.module.css
+++ b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.module.css
@@ -32,12 +32,13 @@
}
.content {
+ width: 100%;
+ white-space: pre-wrap;
font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
font-weight: 400;
- font-size: 21px;
- display: flex;
- flex-direction: column;
- gap: 12px;
+ line-height: 24px;
}
.description {
diff --git a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx
index 692c12b7..c308a9dd 100644
--- a/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx
+++ b/frontend/src/app/(web app)/newsletter/[newsletterID]/page.tsx
@@ -85,9 +85,7 @@ export default function NewsletterDisplay({ params }: Props) {
Here’s Our Story
-
- {newsletter?.content.split("\n").map((line, index) =>
{line}
)}
-
+ {newsletter?.content}
Share This Post
diff --git a/frontend/src/app/admin/newsletter-creator/page.tsx b/frontend/src/app/admin/newsletter-creator/page.tsx
index b1edad65..74a938e4 100644
--- a/frontend/src/app/admin/newsletter-creator/page.tsx
+++ b/frontend/src/app/admin/newsletter-creator/page.tsx
@@ -65,9 +65,10 @@ export default function NewsletterCreator() {
const [rowsCurrent, setRowsCurrent] = React.useState(rows);
const [currentNewsletters, setCurrentNewsletters] = useState
([]);
const [archiveNewsletters, setArchiveNewsletters] = useState([]);
+ const [pageToggle, setPageToggle] = useState(0);
const [selectedRow, setSelectedRow] = useState(null);
- const [currentPage, setCurrentPage] = useState(1); // Track current page
- const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14)); // Calculate total pages
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(Math.ceil(rows.length / 14));
const [selectedNewsletter, setSelectedNewsletter] = useState(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [rerenderKey, setRerenderKey] = useState(0);
@@ -144,6 +145,7 @@ export default function NewsletterCreator() {
} else if (index === 1) {
setRowsCurrent(archiveNewsletters);
}
+ setPageToggle(index);
};
const openNewsletter = (createNew: boolean) => {
@@ -258,7 +260,7 @@ export default function NewsletterCreator() {
diff --git a/frontend/src/components/NewsletterDeleteWarning.tsx b/frontend/src/components/NewsletterDeleteWarning.tsx
deleted file mode 100644
index 14b4ba03..00000000
--- a/frontend/src/components/NewsletterDeleteWarning.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import React from "react";
-
-import styles from "./NewsletterDeleteWarning.module.css";
-
-type NewsletterDeleteWarningProps = {
- save: () => void;
- discard: () => void;
- onClose: () => void;
-};
-
-export const NewsletterDeleteWarning = ({ save, discard }: NewsletterDeleteWarningProps) => {
- return (
-
-
-
-
-
Are you sure you want to delete this newsletter?
-
This action is permanent and cannot be undone.
-
-
- {" "}
- No, cancel{" "}
-
-
- {" "}
- Delete newsletter{" "}
-
-
-
- );
-};
diff --git a/frontend/src/components/NewsletterSidebar.module.css b/frontend/src/components/NewsletterSidebar.module.css
index f3bac86b..cd63f841 100644
--- a/frontend/src/components/NewsletterSidebar.module.css
+++ b/frontend/src/components/NewsletterSidebar.module.css
@@ -18,7 +18,8 @@
position: absolute;
top: 0;
left: 0;
- width: 30%;
+ width: 100%;
+ height: 100%;
}
.closeWindow {
@@ -106,12 +107,14 @@
}
.cancelButton {
+ font: var(--font-body);
background: #fff;
border: 1px solid #694c97;
color: #694c97;
}
.saveButton {
+ font: var(--font-body);
background: #694c97;
color: #fff;
}
@@ -163,5 +166,16 @@
width: 454px;
height: 288px;
padding: 12px;
- border: 1px solid grey;
+ border: 1px solid #d8d8d8;
+ border-radius: 4px;
+}
+
+.content {
+ width: 100%;
+ white-space: pre-wrap;
+ font: var(--font-body);
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 24px;
}
diff --git a/frontend/src/components/NewsletterSidebar.tsx b/frontend/src/components/NewsletterSidebar.tsx
index a2ad0be7..3dbf5017 100644
--- a/frontend/src/components/NewsletterSidebar.tsx
+++ b/frontend/src/components/NewsletterSidebar.tsx
@@ -7,9 +7,8 @@ import { CreateNewsletterRequest, Newsletter, deleteNewsletter } from "../api/ne
import styles from "./NewsletterSidebar.module.css";
import AlertBanner from "@/components/AlertBanner";
-import { NewsletterDeleteWarning } from "@/components/NewsletterDeleteWarning";
-import { NewsletterSidebarWarning } from "@/components/NewsletterSidebarWarning";
import { TextField } from "@/components/TextField";
+import { WarningModule } from "@/components/WarningModule";
type newsletterSidebarProps = {
newsletter: null | Newsletter;
@@ -186,11 +185,7 @@ const NewsletterSidebar = ({
Newsletter Cover
Placeholder - to be replaced with image
Newsletter Content
- {content.split(".").map((paragraph: string, index: number) => (
-
- {paragraph}
-
- ))}
+ {content}
{/* Delete button */}
@@ -199,9 +194,13 @@ const NewsletterSidebar = ({
-
@@ -214,9 +213,13 @@ const NewsletterSidebar = ({
{warningOpen &&
}
{warningOpen && (
-
{
setWarningOpen(false);
}}
@@ -268,53 +271,14 @@ const NewsletterSidebar = ({
Placeholder - to be replaced with image
Newsletter Content
@@ -373,11 +337,7 @@ const NewsletterSidebar = ({
Newsletter Cover
Placeholder - to be replaced with image
Newsletter Content
- {content.split("\n").map((paragraph: string, index: number) => (
-
- {paragraph}
-
- ))}
+ {content}
{/* Delete button */}
diff --git a/frontend/src/components/NewsletterSidebarWarning.module.css b/frontend/src/components/NewsletterSidebarWarning.module.css
deleted file mode 100644
index 2cf152c3..00000000
--- a/frontend/src/components/NewsletterSidebarWarning.module.css
+++ /dev/null
@@ -1,67 +0,0 @@
-.wrapper {
- position: absolute;
- top: 200px;
- padding: 24px;
- background-color: #fff;
- border-radius: 5px;
- margin: 52px;
-}
-
-.wrapper button img {
- padding: 4px;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
-}
-
-.wrapper button p {
- font: var(--text-body);
- font-size: 20px;
- font-weight: 700;
- line-height: 150%; /* 30px */
- letter-spacing: 0.7px;
-}
-
-.closeButton {
- position: relative;
- float: right;
-}
-
-.saveButton {
- background: #694c97;
- color: #fff;
-}
-
-.deleteButton {
- background: #fff;
- border: 1px solid #b93b3b;
- color: #b93b3b;
-}
-
-.wrapper h1 {
- color: #000;
- font: var(--font-small-subtitle);
- font-size: 24px;
- font-style: normal;
- font-weight: 700;
- line-height: 150%; /* 36px */
- letter-spacing: 0.48px;
-}
-
-.wrapper p {
- color: #000;
- font: var(--font-body);
- font-size: 16px;
- font-style: normal;
- font-weight: 400;
- line-height: 24px; /* 150% */
-}
-
-.buttonWrapper {
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
- gap: 18px;
- margin-top: 18px;
-}
diff --git a/frontend/src/components/NewsletterSidebarWarning.tsx b/frontend/src/components/NewsletterSidebarWarning.tsx
deleted file mode 100644
index c52256ee..00000000
--- a/frontend/src/components/NewsletterSidebarWarning.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-"use client";
-
-import Image from "next/image";
-import React from "react";
-
-import styles from "./NewsletterSidebarWarning.module.css";
-
-type NewsletterSidebarWarningProps = {
- save: () => void;
- discard: () => void;
- onClose: () => void;
-};
-
-export const NewsletterSidebarWarning = ({
- save,
- discard,
- onClose,
-}: NewsletterSidebarWarningProps) => {
- return (
-
-
-
-
-
You have unsaved changes!
-
Do you want to save the changes you made to this event?
-
-
- {" "}
- Discard changes{" "}
-
-
- {" "}
- Save changes{" "}
-
-
-
- );
-};
diff --git a/frontend/src/components/PageToggle.tsx b/frontend/src/components/PageToggle.tsx
index 79917be9..512a3557 100644
--- a/frontend/src/components/PageToggle.tsx
+++ b/frontend/src/components/PageToggle.tsx
@@ -30,7 +30,7 @@ const PageToggle = ({ pages, links, onTogglePage, currPage }: PageToggleProps) =
);
} else if (pages && onTogglePage) {
return (
-
+
{pages.map((page, index) => (
void;
+ action: () => void;
+ onClose: () => void;
+};
+
+export const WarningModule = ({
+ titleText,
+ subtitleText,
+ cancelText,
+ actionText,
+ cancel,
+ action,
+ onClose,
+}: WarningModuleProps) => {
+ return (
+
+
+
+
+
{titleText}
+
{subtitleText}
+
+
+ {cancelText}
+
+
+ {actionText}
+
+
+
+ );
+};