From 95ca7f761a6e4306ca041912da2c8fba06d7af92 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 00:39:15 -0500 Subject: [PATCH 1/8] updates to the newletter page --- .../Admin/NewsLetters/NewsLetters.module.css | 113 ++++--- .../Admin/NewsLetters/NewsLetters.tsx | 315 +++++++++++++----- 2 files changed, 307 insertions(+), 121 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 5f86d8e..8333308 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,46 +1,67 @@ -/* NewsLetters.module.css */ - -.modalButton { - margin-top: 1.5rem; - } - - .modalTitle { - font-size: 1.5rem; - font-weight: bold; - } - - .modalContent { - padding: 1rem; - } - - .formGroup { - margin-bottom: 1rem; - } - - .formLabel { - font-size: 1.1rem; - font-weight: 500; - } - - .formInput { - width: 100%; - padding: 0.5rem; - font-size: 1rem; - border-radius: 4px; - } - - .formTextArea { - width: 100%; - padding: 0.5rem; - font-size: 1rem; - border-radius: 4px; - resize: vertical; - } - - .formActions { - display: flex; - justify-content: flex-end; - gap: 10px; - margin-top: 1rem; - } - \ No newline at end of file +.wrapper { + max-width: 1100px; + margin: 40px auto; + padding: 0 20px; + font-family: Arial, sans-serif; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.homeLink { + color: #007bff; + text-decoration: none; + margin-top: 10px; +} + +.homeLink:hover { + text-decoration: underline; +} + +.createButtonWrapper { + margin-bottom: 20px; +} + +.listWrapper { + margin-top: 20px; +} + +.spinner { + display: flex; + align-items: center; + gap: 10px; + font-size: 1.1rem; + justify-content: center; +} + +.newsletterGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 20px; +} + +.newsletterCard { + border: 1px solid #ddd; + border-radius: 8px; + padding: 15px; + background-color: #fff; + transition: transform 0.2s, box-shadow 0.2s; +} + +.newsletterCard:hover { + transform: translateY(-3px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.newsletterCard h3 { + margin-bottom: 10px; +} + +.newsletterCard p { + margin: 5px 0; +} diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 06a946d..72e3303 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -1,10 +1,158 @@ +// import React, { useState, useEffect } from "react"; +// import { Link } from "react-router-dom"; +// import { Modal, Button, Form } from "react-bootstrap"; // Importing Modal components +// import styles from "./NewsLetters.module.css"; // Importing CSS module +// import axios from "axios"; + +// // Define a type for a Newsletter object +// interface Newsletter { +// id: number; +// subject: string; +// description: string; +// authorId: number; +// archived: string; +// important: string; +// } + +// function NewsLetters() { +// const [showModal, setShowModal] = useState(false); // State to control modal visibility +// const [subject, setSubject] = useState(""); // Subject input state +// const [description, setDescription] = useState(""); // Description input state +// const [newsletters, setNewsletters] = useState([]); // Type the state as an array of newsletters + +// // Fetch newsletters from the database +// useEffect(() => { +// const fetchNewsletters = async () => { +// try { +// const response = await axios.get(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`); // API endpoint to fetch newsletters +// setNewsletters(response.data); // Set newsletters from the API response +// } catch (error) { +// console.error("Error fetching newsletters:", error); +// } +// }; +// fetchNewsletters(); +// }, []); + +// const handleModalClose = () => setShowModal(false); // Close the modal +// const handleModalShow = () => setShowModal(true); // Open the modal + +// const handleSubjectChange = (e: React.ChangeEvent) => +// setSubject(e.target.value); // Handle subject change +// const handleDescriptionChange = (e: React.ChangeEvent) => +// setDescription(e.target.value); // Handle description change + +// const handleFormSubmit = async (e: React.FormEvent) => { +// e.preventDefault(); + +// // Handle form submission (e.g., saving the newsletter) +// const newsletterData = { +// subject, +// description, +// authorId: 1, // Assuming 1 is the admin's user ID +// }; + +// try { +// // Call API to create a new newsletter +// await axios.post(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, newsletterData); +// // Close the modal after submission +// setShowModal(false); +// // Refresh the newsletter list after creation +// const response = await axios.get(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`); +// setNewsletters(response.data); +// } catch (error) { +// console.error("Error creating newsletter:", error); +// } +// }; + +// return ( +//
+//

News Letters

+// Home + +//
+// +//
+ +// {/* Modal for creating a newsletter */} +// +// +// +// Create A News Letter +// +// +// +//
+// +// Subject +// +// + +// +// Description +// +// + +//
+// +// +//
+//
+//
+//
+ +// {/* Display the list of newsletters */} +//
+//

Existing Newsletters

+//
    +// {newsletters.map((newsletter) => ( +//
  • +//
    +// Subject: {newsletter.subject} +//
    +//
    +// Description: {newsletter.description} +//
    +//
    +// Author: Admin +//
    +// {/* You can also add additional fields like published date, etc. */} +//
  • +// ))} +//
+//
+//
+// ); +// } + +// export default NewsLetters; + import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import { Modal, Button, Form } from "react-bootstrap"; // Importing Modal components -import styles from "./NewsLetters.module.css"; // Importing CSS module +import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +import styles from "./NewsLetters.module.css"; import axios from "axios"; -// Define a type for a Newsletter object interface Newsletter { id: number; subject: string; @@ -15,131 +163,148 @@ interface Newsletter { } function NewsLetters() { - const [showModal, setShowModal] = useState(false); // State to control modal visibility - const [subject, setSubject] = useState(""); // Subject input state - const [description, setDescription] = useState(""); // Description input state - const [newsletters, setNewsletters] = useState([]); // Type the state as an array of newsletters + const [showModal, setShowModal] = useState(false); + const [subject, setSubject] = useState(""); + const [description, setDescription] = useState(""); + const [newsletters, setNewsletters] = useState([]); + const [loading, setLoading] = useState(false); // For loading spinner + const [error, setError] = useState(""); // Error messages + const [submitting, setSubmitting] = useState(false); // Form submit spinner - // Fetch newsletters from the database + // Fetch newsletters useEffect(() => { const fetchNewsletters = async () => { + setLoading(true); try { - const response = await axios.get(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`); // API endpoint to fetch newsletters - setNewsletters(response.data); // Set newsletters from the API response - } catch (error) { - console.error("Error fetching newsletters:", error); + const response = await axios.get( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` + ); + setNewsletters(response.data); + } catch (err) { + console.error(err); + setError("Failed to load newsletters."); + } finally { + setLoading(false); } }; fetchNewsletters(); }, []); - const handleModalClose = () => setShowModal(false); // Close the modal - const handleModalShow = () => setShowModal(true); // Open the modal - - const handleSubjectChange = (e: React.ChangeEvent) => - setSubject(e.target.value); // Handle subject change - const handleDescriptionChange = (e: React.ChangeEvent) => - setDescription(e.target.value); // Handle description change + const handleModalClose = () => setShowModal(false); + const handleModalShow = () => setShowModal(true); const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); - - // Handle form submission (e.g., saving the newsletter) - const newsletterData = { - subject, - description, - authorId: 1, // Assuming 1 is the admin's user ID - }; + setSubmitting(true); + setError(""); try { - // Call API to create a new newsletter - await axios.post(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, newsletterData); - // Close the modal after submission - setShowModal(false); - // Refresh the newsletter list after creation - const response = await axios.get(`${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`); + await axios.post( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, + { subject, description, authorId: 1 } + ); + + const response = await axios.get( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` + ); setNewsletters(response.data); - } catch (error) { - console.error("Error creating newsletter:", error); + setShowModal(false); + setSubject(""); + setDescription(""); + } catch (err) { + console.error(err); + setError("Failed to create newsletter."); + } finally { + setSubmitting(false); } }; return ( -
-

News Letters

- Home +
+
+

Newsletters

+ + Back Home + +
-
+
- {/* Modal for creating a newsletter */} - + {/* Modal */} + - - Create A News Letter - + Create a Newsletter - + + {error && {error}}
- - Subject + + Subject setSubject(e.target.value)} required /> - - Description + + Description setDescription(e.target.value)} required /> -
- -
- {/* Display the list of newsletters */} -
-

Existing Newsletters

-
    - {newsletters.map((newsletter) => ( -
  • -
    - Subject: {newsletter.subject} -
    -
    - Description: {newsletter.description} -
    -
    - Author: Admin + {/* Newsletter List */} +
    + {loading ? ( +
    + + Loading newsletters... +
    + ) : newsletters.length === 0 ? ( +

    No newsletters found.

    + ) : ( +
    + {newsletters.map((n) => ( +
    +

    {n.subject}

    +

    {n.description}

    +

    + Author: Admin +

    - {/* You can also add additional fields like published date, etc. */} -
  • - ))} -
+ ))} +
+ )}
); From 13e833c01ddfb8c117b2b3279831e3a0bfbc6839 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 00:50:26 -0500 Subject: [PATCH 2/8] testing the news --- .../Admin/NewsLetters/NewsLetters.module.css | 32 +++++++++-- .../Admin/NewsLetters/NewsLetters.tsx | 54 +++++++++++-------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 8333308..21bcc3e 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -2,7 +2,7 @@ max-width: 1100px; margin: 40px auto; padding: 0 20px; - font-family: Arial, sans-serif; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .header { @@ -17,6 +17,7 @@ color: #007bff; text-decoration: none; margin-top: 10px; + font-weight: 500; } .homeLink:hover { @@ -37,6 +38,7 @@ gap: 10px; font-size: 1.1rem; justify-content: center; + margin-top: 20px; } .newsletterGrid { @@ -47,10 +49,11 @@ .newsletterCard { border: 1px solid #ddd; - border-radius: 8px; - padding: 15px; + border-radius: 12px; + padding: 20px; background-color: #fff; transition: transform 0.2s, box-shadow 0.2s; + position: relative; } .newsletterCard:hover { @@ -60,8 +63,31 @@ .newsletterCard h3 { margin-bottom: 10px; + color: #333; } .newsletterCard p { margin: 5px 0; + color: #555; +} + +.badgeArchived, +.badgeImportant { + position: absolute; + top: 10px; + right: 10px; + padding: 4px 8px; + border-radius: 6px; + font-size: 0.75rem; + font-weight: bold; + color: #fff; + margin-left: 5px; +} + +.badgeArchived { + background-color: #6c757d; +} + +.badgeImportant { + background-color: #dc3545; } diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 72e3303..6a33258 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -150,36 +150,38 @@ import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; -import styles from "./NewsLetters.module.css"; import axios from "axios"; +import styles from "./NewsLetters.module.css"; interface Newsletter { id: number; subject: string; description: string; authorId: number; - archived: string; - important: string; + archived?: boolean; + important?: boolean; } -function NewsLetters() { +const NewsLetters: React.FC = () => { + const [newsletters, setNewsletters] = useState([]); + const [loading, setLoading] = useState(true); // Loading state for fetch + const [error, setError] = useState(""); // Error messages + const [showModal, setShowModal] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); - const [newsletters, setNewsletters] = useState([]); - const [loading, setLoading] = useState(false); // For loading spinner - const [error, setError] = useState(""); // Error messages - const [submitting, setSubmitting] = useState(false); // Form submit spinner + const [submitting, setSubmitting] = useState(false); // Loading for form submit // Fetch newsletters useEffect(() => { const fetchNewsletters = async () => { setLoading(true); + setError(""); try { const response = await axios.get( `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` ); - setNewsletters(response.data); + setNewsletters(response.data || []); } catch (err) { console.error(err); setError("Failed to load newsletters."); @@ -187,12 +189,21 @@ function NewsLetters() { setLoading(false); } }; + fetchNewsletters(); }, []); - const handleModalClose = () => setShowModal(false); - const handleModalShow = () => setShowModal(true); + // Modal handlers + const openModal = () => setShowModal(true); + const closeModal = () => { + if (!submitting) { + setShowModal(false); + setSubject(""); + setDescription(""); + } + }; + // Form submission const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); @@ -203,14 +214,12 @@ function NewsLetters() { `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, { subject, description, authorId: 1 } ); - + // Refresh newsletter list const response = await axios.get( `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` ); - setNewsletters(response.data); - setShowModal(false); - setSubject(""); - setDescription(""); + setNewsletters(response.data || []); + closeModal(); } catch (err) { console.error(err); setError("Failed to create newsletter."); @@ -229,13 +238,13 @@ function NewsLetters() {
-
{/* Modal */} - + Create a Newsletter @@ -266,7 +275,7 @@ function NewsLetters() {
-
@@ -308,6 +318,6 @@ function NewsLetters() { ); -} +}; export default NewsLetters; From 281295f1e651ecb0780a8d18363e4ce1f13eefe0 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 00:56:08 -0500 Subject: [PATCH 3/8] updates --- .../Admin/NewsLetters/NewsLetters.module.css | 14 +- .../Admin/NewsLetters/NewsLetters.tsx | 149 +++++++++++------- 2 files changed, 104 insertions(+), 59 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 21bcc3e..85601ff 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,5 +1,5 @@ .wrapper { - max-width: 1100px; + max-width: 1200px; margin: 40px auto; padding: 0 20px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; @@ -28,10 +28,6 @@ margin-bottom: 20px; } -.listWrapper { - margin-top: 20px; -} - .spinner { display: flex; align-items: center; @@ -52,8 +48,8 @@ border-radius: 12px; padding: 20px; background-color: #fff; - transition: transform 0.2s, box-shadow 0.2s; position: relative; + transition: transform 0.2s, box-shadow 0.2s; } .newsletterCard:hover { @@ -71,6 +67,12 @@ color: #555; } +.cardActions { + display: flex; + gap: 10px; + margin-top: 10px; +} + .badgeArchived, .badgeImportant { position: absolute; diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 6a33258..a8d393d 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -158,38 +158,38 @@ interface Newsletter { subject: string; description: string; authorId: number; - archived?: boolean; - important?: boolean; + archived: boolean; + important: boolean; } const NewsLetters: React.FC = () => { const [newsletters, setNewsletters] = useState([]); - const [loading, setLoading] = useState(true); // Loading state for fetch - const [error, setError] = useState(""); // Error messages + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); const [showModal, setShowModal] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); - const [submitting, setSubmitting] = useState(false); // Loading for form submit + const [submitting, setSubmitting] = useState(false); // Fetch newsletters - useEffect(() => { - const fetchNewsletters = async () => { - setLoading(true); - setError(""); - try { - const response = await axios.get( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` - ); - setNewsletters(response.data || []); - } catch (err) { - console.error(err); - setError("Failed to load newsletters."); - } finally { - setLoading(false); - } - }; + const fetchNewsletters = async () => { + setLoading(true); + setError(""); + try { + const response = await axios.get( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` + ); + setNewsletters(response.data || []); + } catch (err) { + console.error(err); + setError("Failed to load newsletters."); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchNewsletters(); }, []); @@ -203,7 +203,7 @@ const NewsLetters: React.FC = () => { } }; - // Form submission + // Create newsletter const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); @@ -214,11 +214,7 @@ const NewsLetters: React.FC = () => { `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, { subject, description, authorId: 1 } ); - // Refresh newsletter list - const response = await axios.get( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` - ); - setNewsletters(response.data || []); + await fetchNewsletters(); closeModal(); } catch (err) { console.error(err); @@ -228,6 +224,38 @@ const NewsLetters: React.FC = () => { } }; + // Archive/unarchive newsletter + const toggleArchive = async (id: number, current: boolean) => { + try { + await axios.put( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, + { archived: !current } + ); + setNewsletters((prev) => + prev.map((n) => + n.id === id ? { ...n, archived: !current } : n + ) + ); + } catch (err) { + console.error(err); + setError("Failed to update newsletter."); + } + }; + + // Delete newsletter + const handleDelete = async (id: number) => { + if (!window.confirm("Are you sure you want to delete this newsletter?")) return; + try { + await axios.delete( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` + ); + setNewsletters((prev) => prev.filter((n) => n.id !== id)); + } catch (err) { + console.error(err); + setError("Failed to delete newsletter."); + } + }; + return (
@@ -243,13 +271,53 @@ const NewsLetters: React.FC = () => {
+ {error && {error}} + + {/* Loading */} + {loading ? ( +
+ Loading newsletters... +
+ ) : ( +
+ {newsletters.length === 0 ? ( +

No newsletters found.

+ ) : ( + newsletters.map((n) => ( +
+

{n.subject}

+

{n.description}

+

Author: Admin

+
+ + +
+ {n.archived && Archived} + {n.important && Important} +
+ )) + )} +
+ )} + {/* Modal */} Create a Newsletter - {error && {error}}
Subject @@ -291,31 +359,6 @@ const NewsLetters: React.FC = () => {
- - {/* Newsletter List */} -
- {loading ? ( -
- Loading newsletters... -
- ) : newsletters.length === 0 ? ( -

No newsletters found.

- ) : ( -
- {newsletters.map((n) => ( -
-

{n.subject}

-

{n.description}

-

- Author: Admin -

- {n.archived && Archived} - {n.important && Important} -
- ))} -
- )} -
); }; From 99890d2e47b933d26e7841da50ba5db09adc162d Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 01:06:31 -0500 Subject: [PATCH 4/8] updates --- .../Admin/NewsLetters/NewsLetters.module.css | 87 ++-- .../Admin/NewsLetters/NewsLetters.tsx | 389 ++++++++++++++---- 2 files changed, 325 insertions(+), 151 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 85601ff..39628b8 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,95 +1,54 @@ -.wrapper { +.container { max-width: 1200px; - margin: 40px auto; - padding: 0 20px; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin: 0 auto; + padding: 2rem; + font-family: "Arial", sans-serif; } .header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 20px; - flex-wrap: wrap; + margin-bottom: 1.5rem; } .homeLink { - color: #007bff; text-decoration: none; - margin-top: 10px; - font-weight: 500; + color: #007bff; } .homeLink:hover { text-decoration: underline; } -.createButtonWrapper { - margin-bottom: 20px; +.modalButton { + margin-bottom: 1.5rem; } -.spinner { - display: flex; - align-items: center; - gap: 10px; - font-size: 1.1rem; - justify-content: center; - margin-top: 20px; +.newsletterList { + margin-top: 2rem; } -.newsletterGrid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 20px; +.newsletterTable { + width: 100%; + border-collapse: collapse; } -.newsletterCard { +.newsletterTable th, +.newsletterTable td { border: 1px solid #ddd; - border-radius: 12px; - padding: 20px; - background-color: #fff; - position: relative; - transition: transform 0.2s, box-shadow 0.2s; -} - -.newsletterCard:hover { - transform: translateY(-3px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.newsletterCard h3 { - margin-bottom: 10px; - color: #333; -} - -.newsletterCard p { - margin: 5px 0; - color: #555; -} - -.cardActions { - display: flex; - gap: 10px; - margin-top: 10px; + padding: 0.75rem; + text-align: left; } -.badgeArchived, -.badgeImportant { - position: absolute; - top: 10px; - right: 10px; - padding: 4px 8px; - border-radius: 6px; - font-size: 0.75rem; - font-weight: bold; - color: #fff; - margin-left: 5px; +.newsletterTable th { + background-color: #f4f4f4; } -.badgeArchived { - background-color: #6c757d; +.newsletterTable tr:nth-child(even) { + background-color: #f9f9f9; } -.badgeImportant { - background-color: #dc3545; +.newsletterTable tr:hover { + background-color: #e9ecef; } diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index a8d393d..6e02670 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -147,6 +147,224 @@ // export default NewsLetters; +// import React, { useState, useEffect } from "react"; +// import { Link } from "react-router-dom"; +// import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +// import axios from "axios"; +// import styles from "./NewsLetters.module.css"; + +// interface Newsletter { +// id: number; +// subject: string; +// description: string; +// authorId: number; +// archived: boolean; +// important: boolean; +// } + +// const NewsLetters: React.FC = () => { +// const [newsletters, setNewsletters] = useState([]); +// const [loading, setLoading] = useState(true); +// const [error, setError] = useState(""); + +// const [showModal, setShowModal] = useState(false); +// const [subject, setSubject] = useState(""); +// const [description, setDescription] = useState(""); +// const [submitting, setSubmitting] = useState(false); + +// // Fetch newsletters +// const fetchNewsletters = async () => { +// setLoading(true); +// setError(""); +// try { +// const response = await axios.get( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` +// ); +// setNewsletters(response.data || []); +// } catch (err) { +// console.error(err); +// setError("Failed to load newsletters."); +// } finally { +// setLoading(false); +// } +// }; + +// useEffect(() => { +// fetchNewsletters(); +// }, []); + +// // Modal handlers +// const openModal = () => setShowModal(true); +// const closeModal = () => { +// if (!submitting) { +// setShowModal(false); +// setSubject(""); +// setDescription(""); +// } +// }; + +// // Create newsletter +// const handleFormSubmit = async (e: React.FormEvent) => { +// e.preventDefault(); +// setSubmitting(true); +// setError(""); + +// try { +// await axios.post( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, +// { subject, description, authorId: 1 } +// ); +// await fetchNewsletters(); +// closeModal(); +// } catch (err) { +// console.error(err); +// setError("Failed to create newsletter."); +// } finally { +// setSubmitting(false); +// } +// }; + +// // Archive/unarchive newsletter +// const toggleArchive = async (id: number, current: boolean) => { +// try { +// await axios.put( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, +// { archived: !current } +// ); +// setNewsletters((prev) => +// prev.map((n) => +// n.id === id ? { ...n, archived: !current } : n +// ) +// ); +// } catch (err) { +// console.error(err); +// setError("Failed to update newsletter."); +// } +// }; + +// // Delete newsletter +// const handleDelete = async (id: number) => { +// if (!window.confirm("Are you sure you want to delete this newsletter?")) return; +// try { +// await axios.delete( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` +// ); +// setNewsletters((prev) => prev.filter((n) => n.id !== id)); +// } catch (err) { +// console.error(err); +// setError("Failed to delete newsletter."); +// } +// }; + +// return ( +//
+//
+//

Newsletters

+// +// Back Home +// +//
+ +//
+// +//
+ +// {error && {error}} + +// {/* Loading */} +// {loading ? ( +//
+// Loading newsletters... +//
+// ) : ( +//
+// {newsletters.length === 0 ? ( +//

No newsletters found.

+// ) : ( +// newsletters.map((n) => ( +//
+//

{n.subject}

+//

{n.description}

+//

Author: Admin

+//
+// +// +//
+// {n.archived && Archived} +// {n.important && Important} +//
+// )) +// )} +//
+// )} + +// {/* Modal */} +// +// +// Create a Newsletter +// +// +//
+// +// Subject +// setSubject(e.target.value)} +// required +// /> +// + +// +// Description +// setDescription(e.target.value)} +// required +// /> +// + +//
+// +// +//
+//
+//
+//
+//
+// ); +// }; + +// export default NewsLetters; + import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; @@ -164,28 +382,22 @@ interface Newsletter { const NewsLetters: React.FC = () => { const [newsletters, setNewsletters] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const [showModal, setShowModal] = useState(false); + const [submitting, setSubmitting] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); - const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(""); - // Fetch newsletters + // Fetch all newsletters const fetchNewsletters = async () => { - setLoading(true); - setError(""); try { const response = await axios.get( `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` ); - setNewsletters(response.data || []); + setNewsletters(response.data); } catch (err) { - console.error(err); + console.error("Error fetching newsletters:", err); setError("Failed to load newsletters."); - } finally { - setLoading(false); } }; @@ -193,17 +405,13 @@ const NewsLetters: React.FC = () => { fetchNewsletters(); }, []); - // Modal handlers - const openModal = () => setShowModal(true); - const closeModal = () => { - if (!submitting) { - setShowModal(false); - setSubject(""); - setDescription(""); - } + const handleModalClose = () => { + setShowModal(false); + setSubject(""); + setDescription(""); + setError(""); }; - // Create newsletter const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSubmitting(true); @@ -214,35 +422,35 @@ const NewsLetters: React.FC = () => { `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, { subject, description, authorId: 1 } ); - await fetchNewsletters(); - closeModal(); + setShowModal(false); // Close modal immediately + setSubject(""); + setDescription(""); + await fetchNewsletters(); // Refresh newsletter list } catch (err) { console.error(err); - setError("Failed to create newsletter."); + setError("Failed to create newsletter. Check console for details."); } finally { setSubmitting(false); } }; - // Archive/unarchive newsletter - const toggleArchive = async (id: number, current: boolean) => { + const handleArchiveToggle = async (id: number, archived: boolean) => { try { await axios.put( `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, - { archived: !current } + { archived: !archived } ); setNewsletters((prev) => prev.map((n) => - n.id === id ? { ...n, archived: !current } : n + n.id === id ? { ...n, archived: !archived } : n ) ); } catch (err) { console.error(err); - setError("Failed to update newsletter."); + setError("Failed to update archive status."); } }; - // Delete newsletter const handleDelete = async (id: number) => { if (!window.confirm("Are you sure you want to delete this newsletter?")) return; try { @@ -257,69 +465,30 @@ const NewsLetters: React.FC = () => { }; return ( -
+

Newsletters

- Back Home + Back to Admin
-
-
- {error && {error}} - - {/* Loading */} - {loading ? ( -
- Loading newsletters... -
- ) : ( -
- {newsletters.length === 0 ? ( -

No newsletters found.

- ) : ( - newsletters.map((n) => ( -
-

{n.subject}

-

{n.description}

-

Author: Admin

-
- - -
- {n.archived && Archived} - {n.important && Important} -
- )) - )} -
- )} - {/* Modal */} - + - Create a Newsletter + Create Newsletter
- + Subject { /> - + Description { /> -
-
+ + {/* Newsletter List */} +
+ {newsletters.length === 0 ? ( +

No newsletters found.

+ ) : ( + + + + + + + + + + + + + {newsletters.map((n) => ( + + + + + + + + + ))} + +
SubjectDescriptionAuthorArchivedImportantActions
{n.subject}{n.description}Admin + + {n.important ? "Yes" : "No"} + +
+ )} +
); }; From 3454e22fbd915711d95fd8e47f194269fff78c71 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 01:18:25 -0500 Subject: [PATCH 5/8] updates --- client/src/App.tsx | 2 +- .../Admin/NewsLetters/NewsLetters.module.css | 52 +-- .../Admin/NewsLetters/NewsLetters.tsx | 390 +++++++++++++----- 3 files changed, 313 insertions(+), 131 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 6a0aca5..3c25cc2 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -95,7 +95,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 39628b8..2251c24 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,24 +1,15 @@ .container { - max-width: 1200px; - margin: 0 auto; + max-width: 900px; + margin: auto; padding: 2rem; - font-family: "Arial", sans-serif; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; + font-family: Arial, sans-serif; } .homeLink { + display: inline-block; + margin-bottom: 1rem; text-decoration: none; - color: #007bff; -} - -.homeLink:hover { - text-decoration: underline; + color: #0d6efd; } .modalButton { @@ -29,26 +20,27 @@ margin-top: 2rem; } -.newsletterTable { - width: 100%; - border-collapse: collapse; +.newsletterItems { + list-style: none; + padding: 0; } -.newsletterTable th, -.newsletterTable td { +.newsletterItem { border: 1px solid #ddd; - padding: 0.75rem; - text-align: left; -} - -.newsletterTable th { - background-color: #f4f4f4; + border-radius: 6px; + padding: 1rem; + margin-bottom: 1rem; + background: #f9f9f9; } -.newsletterTable tr:nth-child(even) { - background-color: #f9f9f9; +.actions { + margin-top: 0.5rem; + display: flex; + gap: 10px; } -.newsletterTable tr:hover { - background-color: #e9ecef; +.error { + color: red; + margin-bottom: 1rem; + font-weight: bold; } diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 6e02670..523bbf8 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -365,9 +365,224 @@ // export default NewsLetters; +// import React, { useState, useEffect } from "react"; +// import { Link } from "react-router-dom"; +// import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +// import axios from "axios"; +// import styles from "./NewsLetters.module.css"; + +// interface Newsletter { +// id: number; +// subject: string; +// description: string; +// authorId: number; +// archived: boolean; +// important: boolean; +// } + +// const NewsLetters: React.FC = () => { +// const [newsletters, setNewsletters] = useState([]); +// const [showModal, setShowModal] = useState(false); +// const [submitting, setSubmitting] = useState(false); +// const [subject, setSubject] = useState(""); +// const [description, setDescription] = useState(""); +// const [error, setError] = useState(""); + +// // Fetch all newsletters +// const fetchNewsletters = async () => { +// try { +// const response = await axios.get( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` +// ); +// setNewsletters(response.data); +// } catch (err) { +// console.error("Error fetching newsletters:", err); +// setError("Failed to load newsletters."); +// } +// }; + +// useEffect(() => { +// fetchNewsletters(); +// }, []); + +// const handleModalClose = () => { +// setShowModal(false); +// setSubject(""); +// setDescription(""); +// setError(""); +// }; + +// const handleFormSubmit = async (e: React.FormEvent) => { +// e.preventDefault(); +// setSubmitting(true); +// setError(""); + +// try { +// await axios.post( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, +// { subject, description, authorId: 1 } +// ); +// setShowModal(false); // Close modal immediately +// setSubject(""); +// setDescription(""); +// await fetchNewsletters(); // Refresh newsletter list +// } catch (err) { +// console.error(err); +// setError("Failed to create newsletter. Check console for details."); +// } finally { +// setSubmitting(false); +// } +// }; + +// const handleArchiveToggle = async (id: number, archived: boolean) => { +// try { +// await axios.put( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, +// { archived: !archived } +// ); +// setNewsletters((prev) => +// prev.map((n) => +// n.id === id ? { ...n, archived: !archived } : n +// ) +// ); +// } catch (err) { +// console.error(err); +// setError("Failed to update archive status."); +// } +// }; + +// const handleDelete = async (id: number) => { +// if (!window.confirm("Are you sure you want to delete this newsletter?")) return; +// try { +// await axios.delete( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` +// ); +// setNewsletters((prev) => prev.filter((n) => n.id !== id)); +// } catch (err) { +// console.error(err); +// setError("Failed to delete newsletter."); +// } +// }; + +// return ( +//
+//
+//

Newsletters

+// +// Back to Admin +// +//
+ +// {error && {error}} + +//
+// +//
+ +// {/* Modal */} +// +// +// Create Newsletter +// +// +//
+// +// Subject +// setSubject(e.target.value)} +// required +// /> +// + +// +// Description +// setDescription(e.target.value)} +// required +// /> +// + +//
+// +// +//
+//
+//
+//
+ +// {/* Newsletter List */} +//
+// {newsletters.length === 0 ? ( +//

No newsletters found.

+// ) : ( +// +// +// +// +// +// +// +// +// +// +// +// +// {newsletters.map((n) => ( +// +// +// +// +// +// +// +// +// ))} +// +//
SubjectDescriptionAuthorArchivedImportantActions
{n.subject}{n.description}Admin +// +// {n.important ? "Yes" : "No"} +// +//
+// )} +//
+//
+// ); +// }; + +// export default NewsLetters; + import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +import { Modal, Button, Form, Spinner } from "react-bootstrap"; import axios from "axios"; import styles from "./NewsLetters.module.css"; @@ -380,24 +595,23 @@ interface Newsletter { important: boolean; } -const NewsLetters: React.FC = () => { - const [newsletters, setNewsletters] = useState([]); +function NewsLetters() { const [showModal, setShowModal] = useState(false); - const [submitting, setSubmitting] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); + const [newsletters, setNewsletters] = useState([]); + const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); - // Fetch all newsletters + const API_URL = process.env.REACT_APP_API_URL || "http://localhost:3000"; + + // Fetch newsletters const fetchNewsletters = async () => { try { - const response = await axios.get( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` - ); + const response = await axios.get(`${API_URL}/api/newsletter/pay`); setNewsletters(response.data); } catch (err) { console.error("Error fetching newsletters:", err); - setError("Failed to load newsletters."); } }; @@ -405,12 +619,8 @@ const NewsLetters: React.FC = () => { fetchNewsletters(); }, []); - const handleModalClose = () => { - setShowModal(false); - setSubject(""); - setDescription(""); - setError(""); - }; + const handleModalClose = () => setShowModal(false); + const handleModalShow = () => setShowModal(true); const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -418,77 +628,68 @@ const NewsLetters: React.FC = () => { setError(""); try { - await axios.post( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, - { subject, description, authorId: 1 } - ); - setShowModal(false); // Close modal immediately + await axios.post(`${API_URL}/api/newsletter/pay`, { + subject, + description, + authorId: 1, // Admin + }); + setShowModal(false); setSubject(""); setDescription(""); - await fetchNewsletters(); // Refresh newsletter list + await fetchNewsletters(); } catch (err) { console.error(err); - setError("Failed to create newsletter. Check console for details."); + setError("Failed to create newsletter. Check console."); } finally { setSubmitting(false); } }; - const handleArchiveToggle = async (id: number, archived: boolean) => { + const toggleArchive = async (id: number, current: boolean) => { try { - await axios.put( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, - { archived: !archived } - ); + await axios.put(`${API_URL}/api/newsletter/archive/${id}`, { + archived: !current, + }); setNewsletters((prev) => - prev.map((n) => - n.id === id ? { ...n, archived: !archived } : n - ) + prev.map((n) => (n.id === id ? { ...n, archived: !current } : n)) ); } catch (err) { - console.error(err); - setError("Failed to update archive status."); + console.error("Error toggling archive:", err); } }; - const handleDelete = async (id: number) => { + const deleteNewsletter = async (id: number) => { if (!window.confirm("Are you sure you want to delete this newsletter?")) return; try { - await axios.delete( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` - ); + await axios.delete(`${API_URL}/api/newsletter/delete/${id}`); setNewsletters((prev) => prev.filter((n) => n.id !== id)); } catch (err) { - console.error(err); - setError("Failed to delete newsletter."); + console.error("Error deleting newsletter:", err); } }; return (
-
-

Newsletters

- - Back to Admin - -
- - {error && {error}} +

Newsletters Admin Panel

+ + Back Home +
-
- {/* Modal */} + {/* Create Newsletter Modal */} - Create Newsletter + Create a Newsletter + {error &&
{error}
}
- + Subject { /> - + Description { /> -
- + +
+ + ))} + )}
); -}; +} export default NewsLetters; From a393f9554625c89e32de4febf057b127cafc592a Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 01:25:28 -0500 Subject: [PATCH 6/8] test --- .../Admin/NewsLetters/NewsLetters.module.css | 52 ++- .../Admin/NewsLetters/NewsLetters.tsx | 390 +++++------------- 2 files changed, 130 insertions(+), 312 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 2251c24..39628b8 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,15 +1,24 @@ .container { - max-width: 900px; - margin: auto; + max-width: 1200px; + margin: 0 auto; padding: 2rem; - font-family: Arial, sans-serif; + font-family: "Arial", sans-serif; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; } .homeLink { - display: inline-block; - margin-bottom: 1rem; text-decoration: none; - color: #0d6efd; + color: #007bff; +} + +.homeLink:hover { + text-decoration: underline; } .modalButton { @@ -20,27 +29,26 @@ margin-top: 2rem; } -.newsletterItems { - list-style: none; - padding: 0; +.newsletterTable { + width: 100%; + border-collapse: collapse; } -.newsletterItem { +.newsletterTable th, +.newsletterTable td { border: 1px solid #ddd; - border-radius: 6px; - padding: 1rem; - margin-bottom: 1rem; - background: #f9f9f9; + padding: 0.75rem; + text-align: left; } -.actions { - margin-top: 0.5rem; - display: flex; - gap: 10px; +.newsletterTable th { + background-color: #f4f4f4; +} + +.newsletterTable tr:nth-child(even) { + background-color: #f9f9f9; } -.error { - color: red; - margin-bottom: 1rem; - font-weight: bold; +.newsletterTable tr:hover { + background-color: #e9ecef; } diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 523bbf8..6e02670 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -365,224 +365,9 @@ // export default NewsLetters; -// import React, { useState, useEffect } from "react"; -// import { Link } from "react-router-dom"; -// import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; -// import axios from "axios"; -// import styles from "./NewsLetters.module.css"; - -// interface Newsletter { -// id: number; -// subject: string; -// description: string; -// authorId: number; -// archived: boolean; -// important: boolean; -// } - -// const NewsLetters: React.FC = () => { -// const [newsletters, setNewsletters] = useState([]); -// const [showModal, setShowModal] = useState(false); -// const [submitting, setSubmitting] = useState(false); -// const [subject, setSubject] = useState(""); -// const [description, setDescription] = useState(""); -// const [error, setError] = useState(""); - -// // Fetch all newsletters -// const fetchNewsletters = async () => { -// try { -// const response = await axios.get( -// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` -// ); -// setNewsletters(response.data); -// } catch (err) { -// console.error("Error fetching newsletters:", err); -// setError("Failed to load newsletters."); -// } -// }; - -// useEffect(() => { -// fetchNewsletters(); -// }, []); - -// const handleModalClose = () => { -// setShowModal(false); -// setSubject(""); -// setDescription(""); -// setError(""); -// }; - -// const handleFormSubmit = async (e: React.FormEvent) => { -// e.preventDefault(); -// setSubmitting(true); -// setError(""); - -// try { -// await axios.post( -// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, -// { subject, description, authorId: 1 } -// ); -// setShowModal(false); // Close modal immediately -// setSubject(""); -// setDescription(""); -// await fetchNewsletters(); // Refresh newsletter list -// } catch (err) { -// console.error(err); -// setError("Failed to create newsletter. Check console for details."); -// } finally { -// setSubmitting(false); -// } -// }; - -// const handleArchiveToggle = async (id: number, archived: boolean) => { -// try { -// await axios.put( -// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, -// { archived: !archived } -// ); -// setNewsletters((prev) => -// prev.map((n) => -// n.id === id ? { ...n, archived: !archived } : n -// ) -// ); -// } catch (err) { -// console.error(err); -// setError("Failed to update archive status."); -// } -// }; - -// const handleDelete = async (id: number) => { -// if (!window.confirm("Are you sure you want to delete this newsletter?")) return; -// try { -// await axios.delete( -// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` -// ); -// setNewsletters((prev) => prev.filter((n) => n.id !== id)); -// } catch (err) { -// console.error(err); -// setError("Failed to delete newsletter."); -// } -// }; - -// return ( -//
-//
-//

Newsletters

-// -// Back to Admin -// -//
- -// {error && {error}} - -//
-// -//
- -// {/* Modal */} -// -// -// Create Newsletter -// -// -// -// -// Subject -// setSubject(e.target.value)} -// required -// /> -// - -// -// Description -// setDescription(e.target.value)} -// required -// /> -// - -//
-// -// -//
-// -//
-//
- -// {/* Newsletter List */} -//
-// {newsletters.length === 0 ? ( -//

No newsletters found.

-// ) : ( -// -// -// -// -// -// -// -// -// -// -// -// -// {newsletters.map((n) => ( -// -// -// -// -// -// -// -// -// ))} -// -//
SubjectDescriptionAuthorArchivedImportantActions
{n.subject}{n.description}Admin -// -// {n.important ? "Yes" : "No"} -// -//
-// )} -//
-//
-// ); -// }; - -// export default NewsLetters; - import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import { Modal, Button, Form, Spinner } from "react-bootstrap"; +import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; import axios from "axios"; import styles from "./NewsLetters.module.css"; @@ -595,23 +380,24 @@ interface Newsletter { important: boolean; } -function NewsLetters() { +const NewsLetters: React.FC = () => { + const [newsletters, setNewsletters] = useState([]); const [showModal, setShowModal] = useState(false); + const [submitting, setSubmitting] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); - const [newsletters, setNewsletters] = useState([]); - const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); - const API_URL = process.env.REACT_APP_API_URL || "http://localhost:3000"; - - // Fetch newsletters + // Fetch all newsletters const fetchNewsletters = async () => { try { - const response = await axios.get(`${API_URL}/api/newsletter/pay`); + const response = await axios.get( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` + ); setNewsletters(response.data); } catch (err) { console.error("Error fetching newsletters:", err); + setError("Failed to load newsletters."); } }; @@ -619,8 +405,12 @@ function NewsLetters() { fetchNewsletters(); }, []); - const handleModalClose = () => setShowModal(false); - const handleModalShow = () => setShowModal(true); + const handleModalClose = () => { + setShowModal(false); + setSubject(""); + setDescription(""); + setError(""); + }; const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -628,68 +418,77 @@ function NewsLetters() { setError(""); try { - await axios.post(`${API_URL}/api/newsletter/pay`, { - subject, - description, - authorId: 1, // Admin - }); - setShowModal(false); + await axios.post( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, + { subject, description, authorId: 1 } + ); + setShowModal(false); // Close modal immediately setSubject(""); setDescription(""); - await fetchNewsletters(); + await fetchNewsletters(); // Refresh newsletter list } catch (err) { console.error(err); - setError("Failed to create newsletter. Check console."); + setError("Failed to create newsletter. Check console for details."); } finally { setSubmitting(false); } }; - const toggleArchive = async (id: number, current: boolean) => { + const handleArchiveToggle = async (id: number, archived: boolean) => { try { - await axios.put(`${API_URL}/api/newsletter/archive/${id}`, { - archived: !current, - }); + await axios.put( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, + { archived: !archived } + ); setNewsletters((prev) => - prev.map((n) => (n.id === id ? { ...n, archived: !current } : n)) + prev.map((n) => + n.id === id ? { ...n, archived: !archived } : n + ) ); } catch (err) { - console.error("Error toggling archive:", err); + console.error(err); + setError("Failed to update archive status."); } }; - const deleteNewsletter = async (id: number) => { + const handleDelete = async (id: number) => { if (!window.confirm("Are you sure you want to delete this newsletter?")) return; try { - await axios.delete(`${API_URL}/api/newsletter/delete/${id}`); + await axios.delete( + `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` + ); setNewsletters((prev) => prev.filter((n) => n.id !== id)); } catch (err) { - console.error("Error deleting newsletter:", err); + console.error(err); + setError("Failed to delete newsletter."); } }; return (
-

Newsletters Admin Panel

- - Back Home - +
+

Newsletters

+ + Back to Admin + +
+ + {error && {error}}
-
- {/* Create Newsletter Modal */} + {/* Modal */} - Create a Newsletter + Create Newsletter - {error &&
{error}
}
- + Subject - + Description -
- - -
- - ))} - + + + + + + + + + + + + + {newsletters.map((n) => ( + + + + + + + + + ))} + +
SubjectDescriptionAuthorArchivedImportantActions
{n.subject}{n.description}Admin + + {n.important ? "Yes" : "No"} + +
)}
); -} +}; export default NewsLetters; From 18e64fc55a31819c0910a467bfe44a0b6bd82b84 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 01:27:43 -0500 Subject: [PATCH 7/8] testing --- .../Admin/NewsLetters/NewsLetters.module.css | 52 +-- .../Admin/NewsLetters/NewsLetters.tsx | 393 +++++++++++++----- 2 files changed, 315 insertions(+), 130 deletions(-) diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css index 39628b8..2251c24 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.module.css +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.module.css @@ -1,24 +1,15 @@ .container { - max-width: 1200px; - margin: 0 auto; + max-width: 900px; + margin: auto; padding: 2rem; - font-family: "Arial", sans-serif; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; + font-family: Arial, sans-serif; } .homeLink { + display: inline-block; + margin-bottom: 1rem; text-decoration: none; - color: #007bff; -} - -.homeLink:hover { - text-decoration: underline; + color: #0d6efd; } .modalButton { @@ -29,26 +20,27 @@ margin-top: 2rem; } -.newsletterTable { - width: 100%; - border-collapse: collapse; +.newsletterItems { + list-style: none; + padding: 0; } -.newsletterTable th, -.newsletterTable td { +.newsletterItem { border: 1px solid #ddd; - padding: 0.75rem; - text-align: left; -} - -.newsletterTable th { - background-color: #f4f4f4; + border-radius: 6px; + padding: 1rem; + margin-bottom: 1rem; + background: #f9f9f9; } -.newsletterTable tr:nth-child(even) { - background-color: #f9f9f9; +.actions { + margin-top: 0.5rem; + display: flex; + gap: 10px; } -.newsletterTable tr:hover { - background-color: #e9ecef; +.error { + color: red; + margin-bottom: 1rem; + font-weight: bold; } diff --git a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx index 6e02670..5b89650 100644 --- a/client/src/containers/Admin/NewsLetters/NewsLetters.tsx +++ b/client/src/containers/Admin/NewsLetters/NewsLetters.tsx @@ -365,9 +365,223 @@ // export default NewsLetters; +// import React, { useState, useEffect } from "react"; +// import { Link } from "react-router-dom"; +// import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +// import axios from "axios"; +// import styles from "./NewsLetters.module.css"; + +// interface Newsletter { +// id: number; +// subject: string; +// description: string; +// authorId: number; +// archived: boolean; +// important: boolean; +// } + +// const NewsLetters: React.FC = () => { +// const [newsletters, setNewsletters] = useState([]); +// const [showModal, setShowModal] = useState(false); +// const [submitting, setSubmitting] = useState(false); +// const [subject, setSubject] = useState(""); +// const [description, setDescription] = useState(""); +// const [error, setError] = useState(""); + +// // Fetch all newsletters +// const fetchNewsletters = async () => { +// try { +// const response = await axios.get( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` +// ); +// setNewsletters(response.data); +// } catch (err) { +// console.error("Error fetching newsletters:", err); +// setError("Failed to load newsletters."); +// } +// }; + +// useEffect(() => { +// fetchNewsletters(); +// }, []); + +// const handleModalClose = () => { +// setShowModal(false); +// setSubject(""); +// setDescription(""); +// setError(""); +// }; + +// const handleFormSubmit = async (e: React.FormEvent) => { +// e.preventDefault(); +// setSubmitting(true); +// setError(""); + +// try { +// await axios.post( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, +// { subject, description, authorId: 1 } +// ); +// setShowModal(false); // Close modal immediately +// setSubject(""); +// setDescription(""); +// await fetchNewsletters(); // Refresh newsletter list +// } catch (err) { +// console.error(err); +// setError("Failed to create newsletter. Check console for details."); +// } finally { +// setSubmitting(false); +// } +// }; + +// const handleArchiveToggle = async (id: number, archived: boolean) => { +// try { +// await axios.put( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, +// { archived: !archived } +// ); +// setNewsletters((prev) => +// prev.map((n) => +// n.id === id ? { ...n, archived: !archived } : n +// ) +// ); +// } catch (err) { +// console.error(err); +// setError("Failed to update archive status."); +// } +// }; + +// const handleDelete = async (id: number) => { +// if (!window.confirm("Are you sure you want to delete this newsletter?")) return; +// try { +// await axios.delete( +// `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` +// ); +// setNewsletters((prev) => prev.filter((n) => n.id !== id)); +// } catch (err) { +// console.error(err); +// setError("Failed to delete newsletter."); +// } +// }; + +// return ( +//
+//
+//

Newsletters

+// +// Back to Admin +// +//
+ +// {error && {error}} + +//
+// +//
+ +// {/* Modal */} +// +// +// Create Newsletter +// +// +// +// +// Subject +// setSubject(e.target.value)} +// required +// /> +// + +// +// Description +// setDescription(e.target.value)} +// required +// /> +// + +//
+// +// +//
+// +//
+//
+ +// {/* Newsletter List */} +//
+// {newsletters.length === 0 ? ( +//

No newsletters found.

+// ) : ( +// +// +// +// +// +// +// +// +// +// +// +// +// {newsletters.map((n) => ( +// +// +// +// +// +// +// +// +// ))} +// +//
SubjectDescriptionAuthorArchivedImportantActions
{n.subject}{n.description}Admin +// +// {n.important ? "Yes" : "No"} +// +//
+// )} +//
+//
+// ); +// }; + +// export default NewsLetters; import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import { Modal, Button, Form, Spinner, Alert } from "react-bootstrap"; +import { Modal, Button, Form, Spinner } from "react-bootstrap"; import axios from "axios"; import styles from "./NewsLetters.module.css"; @@ -380,24 +594,27 @@ interface Newsletter { important: boolean; } -const NewsLetters: React.FC = () => { - const [newsletters, setNewsletters] = useState([]); +function NewsLetters() { const [showModal, setShowModal] = useState(false); - const [submitting, setSubmitting] = useState(false); const [subject, setSubject] = useState(""); const [description, setDescription] = useState(""); + const [newsletters, setNewsletters] = useState([]); + const [loading, setLoading] = useState(false); + const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); - // Fetch all newsletters + const API_URL = process.env.REACT_APP_SOCKET_IO_CLIENT_PORT || "http://localhost:3000"; + + // Fetch newsletters const fetchNewsletters = async () => { + setLoading(true); try { - const response = await axios.get( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay` - ); + const response = await axios.get(`${API_URL}/api/newsletter/pay`); setNewsletters(response.data); } catch (err) { console.error("Error fetching newsletters:", err); - setError("Failed to load newsletters."); + } finally { + setLoading(false); } }; @@ -405,12 +622,8 @@ const NewsLetters: React.FC = () => { fetchNewsletters(); }, []); - const handleModalClose = () => { - setShowModal(false); - setSubject(""); - setDescription(""); - setError(""); - }; + const handleModalClose = () => setShowModal(false); + const handleModalShow = () => setShowModal(true); const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -418,77 +631,66 @@ const NewsLetters: React.FC = () => { setError(""); try { - await axios.post( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay`, - { subject, description, authorId: 1 } - ); - setShowModal(false); // Close modal immediately + await axios.post(`${API_URL}/api/newsletter/pay`, { + subject, + description, + authorId: 1, + }); + setShowModal(false); setSubject(""); setDescription(""); - await fetchNewsletters(); // Refresh newsletter list + await fetchNewsletters(); } catch (err) { console.error(err); - setError("Failed to create newsletter. Check console for details."); + setError("Failed to create newsletter. Check console."); } finally { setSubmitting(false); } }; - const handleArchiveToggle = async (id: number, archived: boolean) => { + const toggleArchive = async (id: number, archived: boolean) => { try { - await axios.put( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/archive/${id}`, - { archived: !archived } - ); + await axios.put(`${API_URL}/api/newsletter/archive/${id}`, { archived: !archived }); setNewsletters((prev) => - prev.map((n) => - n.id === id ? { ...n, archived: !archived } : n - ) + prev.map((n) => (n.id === id ? { ...n, archived: !archived } : n)) ); } catch (err) { - console.error(err); - setError("Failed to update archive status."); + console.error("Error toggling archive:", err); } }; - const handleDelete = async (id: number) => { + const deleteNewsletter = async (id: number) => { if (!window.confirm("Are you sure you want to delete this newsletter?")) return; try { - await axios.delete( - `${process.env.REACT_APP_SOCKET_IO_CLIENT_PORT}/api/newsletter/pay/${id}` - ); + await axios.delete(`${API_URL}/api/newsletter/delete/${id}`); setNewsletters((prev) => prev.filter((n) => n.id !== id)); } catch (err) { - console.error(err); - setError("Failed to delete newsletter."); + console.error("Error deleting newsletter:", err); } }; return (
-
-

Newsletters

- - Back to Admin - -
- - {error && {error}} +

Admin Newsletters

+ + Back Home +
-
- {/* Modal */} + {/* Create Newsletter Modal */} Create Newsletter + {error &&
{error}
}
- + Subject { /> - + Description { /> -
- + +
+ + ))} + )}
); -}; +} export default NewsLetters; From e75e9357845c1716b77152941def880c0f42a442 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 6 Dec 2025 01:43:36 -0500 Subject: [PATCH 8/8] updates --- controllers/NewsLetterController.js | 167 +++++++++++++++++++--------- 1 file changed, 112 insertions(+), 55 deletions(-) diff --git a/controllers/NewsLetterController.js b/controllers/NewsLetterController.js index b56b138..94e51ff 100644 --- a/controllers/NewsLetterController.js +++ b/controllers/NewsLetterController.js @@ -1,78 +1,134 @@ -const express = require("express"); -const router = express.Router(); -const db = require("../models"); -// const cors = require("cors"); +// const express = require("express"); +// const router = express.Router(); +// const db = require("../models"); +// // const cors = require("cors"); -// const app = express(); +// // const app = express(); -// // Allow all domains (use cautiously in production) -// app.use(cors()); +// // // Allow all domains (use cautiously in production) +// // app.use(cors()); -// Get all newsletters -// router.get("/pay", async (req, res) => { +// // Get all newsletters +// // router.get("/pay", async (req, res) => { +// // try { +// // const newsletters = await db.Newsletter.find(); // Assuming you're using a model named "Newsletter" +// // res.json(newsletters); +// // } catch (error) { +// // res.status(500).json({ message: "Error fetching newsletters." }); +// // } +// // }); + +// router.get("/pay", (req, res) => { +// db.NewsLetter.findAll().then((allLetters) => { +// res.json(allLetters); +// }); +// }); + +// // Create a new newsletter +// // router.post("/pay", async (req, res) => { +// // try { +// // // const { subject, description } = req.body; +// // const newNewsletter = new db.Newsletter(req.body); +// // await newNewsletter.save(); +// // res.status(201).json(newNewsletter); +// // } catch (error) { +// // res.status(500).json({ message: "Error creating newsletter." }); +// // } +// // }); + +// // router.post("/pay2", (req, res) => { +// // console.log(req.body); +// // db.Newsletter.create(req.body) +// // .then((newLetter) => { +// // res.json({ +// // error: false, +// // data: newLetter, +// // message: "Successfully created a new News Letter.", +// // }); +// // }) +// // .catch((err) => { +// // console.log(err); +// // res.status(500).json({ +// // error: true, +// // data: null, +// // message: "Unable to create new ticket.", +// // }); +// // }); +// // }); + +// router.post("/pay", (req, res) => { +// db.NewsLetter.create(req.body).then((newLetter) => { +// res.json(newAdmin); +// }); +// }); + +// // Update a newsletter +// router.put("/:id", async (req, res) => { // try { -// const newsletters = await db.Newsletter.find(); // Assuming you're using a model named "Newsletter" -// res.json(newsletters); +// const { subject, description } = req.body; +// const updatedNewsletter = await db.Newsletter.findByIdAndUpdate( +// req.params.id, +// { subject, description }, +// { new: true } +// ); +// res.json(updatedNewsletter); // } catch (error) { -// res.status(500).json({ message: "Error fetching newsletters." }); +// res.status(500).json({ message: "Error updating newsletter." }); // } // }); -router.get("/pay", (req, res) => { - db.NewsLetter.findAll().then((allLetters) => { - res.json(allLetters); - }); -}); - -// Create a new newsletter -// router.post("/pay", async (req, res) => { +// // Delete a newsletter +// router.delete("/:id", async (req, res) => { // try { -// // const { subject, description } = req.body; -// const newNewsletter = new db.Newsletter(req.body); -// await newNewsletter.save(); -// res.status(201).json(newNewsletter); +// await db.Newsletter.findByIdAndDelete(req.params.id); +// res.status(204).send(); // } catch (error) { -// res.status(500).json({ message: "Error creating newsletter." }); +// res.status(500).json({ message: "Error deleting newsletter." }); // } // }); -// router.post("/pay2", (req, res) => { -// console.log(req.body); -// db.Newsletter.create(req.body) -// .then((newLetter) => { -// res.json({ -// error: false, -// data: newLetter, -// message: "Successfully created a new News Letter.", -// }); -// }) -// .catch((err) => { -// console.log(err); -// res.status(500).json({ -// error: true, -// data: null, -// message: "Unable to create new ticket.", -// }); -// }); -// }); +// module.exports = router; -router.post("/pay", (req, res) => { - db.NewsLetter.create(req.body).then((newLetter) => { - res.json(newAdmin); - }); +const express = require("express"); +const router = express.Router(); +const db = require("../models"); + +// Get all newsletters +router.get("/pay", async (req, res) => { + try { + const allLetters = await db.NewsLetter.findAll(); + res.json(allLetters); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Error fetching newsletters." }); + } +}); + +// Create a new newsletter +router.post("/pay", async (req, res) => { + try { + const newLetter = await db.NewsLetter.create(req.body); + res.status(201).json(newLetter); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Error creating newsletter." }); + } }); // Update a newsletter router.put("/:id", async (req, res) => { try { - const { subject, description } = req.body; - const updatedNewsletter = await db.Newsletter.findByIdAndUpdate( - req.params.id, - { subject, description }, - { new: true } + const { subject, description, archived, important } = req.body; + + const updatedNewsletter = await db.NewsLetter.update( + { subject, description, archived, important }, + { where: { id: req.params.id }, returning: true } ); - res.json(updatedNewsletter); + + // Sequelize returns [numberOfAffectedRows, affectedRowsArray] + res.json(updatedNewsletter[1][0]); } catch (error) { + console.error(error); res.status(500).json({ message: "Error updating newsletter." }); } }); @@ -80,9 +136,10 @@ router.put("/:id", async (req, res) => { // Delete a newsletter router.delete("/:id", async (req, res) => { try { - await db.Newsletter.findByIdAndDelete(req.params.id); + await db.NewsLetter.destroy({ where: { id: req.params.id } }); res.status(204).send(); } catch (error) { + console.error(error); res.status(500).json({ message: "Error deleting newsletter." }); } });