From 027ce17e220973d528af35c32b3f0d32f2dcc169 Mon Sep 17 00:00:00 2001 From: mai-vu <96965521+mai-vu@users.noreply.github.com> Date: Tue, 28 May 2024 11:53:29 -0700 Subject: [PATCH] Feature/send email (#105) * Added admin page to send email * added send email api * added Circle Check icon * updated sengrid dependency * migrated API URLs to .env * debugged redirect to login in home * added table component * added admin page * updated API URL for sent status update * added sent field * added cron job to send email * added contact-stat schema * added API endpoints for contact-stats * updated cron job to interact with contact-stats collection * changed func name * migrated code to emailHandler * removed sent const * added Mass send feature * changed button hover color * added dependency * aded addEmailModal * added total number of emails added in response * add function add emails * implemented api endpoint * update UI when new email is added * added Delete endpoint and integration * added delete feature * improved UI * added more detailed alert * clear input field upon opening * renamed button * uncomment send email function call * updated email sender * added confirmation alert * renamed file and updated run instruction * updated more descriptive messege * updated dependency * deleted ununsed file * deleted unused import * added dependecy * return number of new emails added * send email using Sendgrid * Added email cron job * Checks if user is admin and redirects to email panel * Only allow admins on email page * added loading and unauthorized access html --------- Co-authored-by: shye <113232835+justshye@users.noreply.github.com> --- src/app/admin-panel/home/page.js | 7 ++ src/app/admin/emailHandler.js | 129 +++++++++++++++++++ src/app/admin/layout.js | 15 +++ src/app/admin/page.js | 140 +++++++++++++++++++++ src/components/component/addEmailModal.jsx | 127 +++++++++++++++++++ src/components/component/adminPage.jsx | 106 ++++++++++++++++ src/components/icons.js | 39 ++++++ src/components/ui/addJobPostingForm.jsx | 1 + src/components/ui/dialog.jsx | 97 ++++++++++++++ src/components/ui/table.jsx | 95 ++++++++++++++ 10 files changed, 756 insertions(+) create mode 100644 src/app/admin/emailHandler.js create mode 100644 src/app/admin/layout.js create mode 100644 src/app/admin/page.js create mode 100644 src/components/component/addEmailModal.jsx create mode 100644 src/components/component/adminPage.jsx create mode 100644 src/components/ui/dialog.jsx create mode 100644 src/components/ui/table.jsx diff --git a/src/app/admin-panel/home/page.js b/src/app/admin-panel/home/page.js index f03c3bf..742c05a 100644 --- a/src/app/admin-panel/home/page.js +++ b/src/app/admin-panel/home/page.js @@ -37,6 +37,13 @@ export default function Home() { if (response.ok) { const userData = await response.json(); setUser(userData); + console.log('User:', userData); + if (userData.data && userData.data.admin === true) { + console.log('User is admin'); + window.location.href = '/admin'; + } else { + console.log('User is not admin'); + } } else { console.error('Failed to fetch user:', response.statusText); window.location.href = '/'; diff --git a/src/app/admin/emailHandler.js b/src/app/admin/emailHandler.js new file mode 100644 index 0000000..a9dc32b --- /dev/null +++ b/src/app/admin/emailHandler.js @@ -0,0 +1,129 @@ +const CONTACT_STAT_API_URL = process.env.NEXT_PUBLIC_CONTACT_STAT_API_URL; +const SEND_EMAIL_API_URL = process.env.NEXT_PUBLIC_SEND_EMAIL_API_URL; + +const getContactStat = async () => { + try { + const response = await fetch(CONTACT_STAT_API_URL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const data = await response.json(); + return data.contactStats; + } catch (error) { + console.error('Error fetching contact stats:', error); + return []; + } +}; + +// Function to send emails +const sendEmail = async recipient => { + try { + console.log(`Sending email to ${recipient}`); + const apiUrl = SEND_EMAIL_API_URL; + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + to: recipient, + subject: 'Expand Your Hiring Reach with [Job Panel]!', + html: ` +
Hello from our team at [Job Panel],
+We are here to revolutionize your hiring process and help you reach a diverse pool of talent. Our web app offers a seamless solution to distribute your job postings to multiple platforms, including newcomers, indigenous communities, students, asylum-seekers, and individuals with disabilities.
+Here's what makes our job panel stand out:
+Ready to take your hiring to the next level? Sign up now via the link below and start reaching the talent you've been looking for!
+ +Don't miss out on the opportunity to find your next great hire effortlessly. Join us today and experience the power of our job panel!
+Best regards,
+The [Job Panel] Team
`, + }), + }); + if (response.ok) { + // Update the status of the email in the contactStat collection + await updateSentStatus(recipient, true); + } else { + throw new Error('Failed to send email'); + } + } catch (error) { + console.error('Error sending email:', error); + } +}; + +const updateSentStatus = async (email, sent) => { + try { + const response = await fetch(CONTACT_STAT_API_URL, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, sent }), + }); + + if (!response.ok) { + throw new Error('Failed to update sent status'); + } + } catch (error) { + console.error('Error updating sent status:', error); + } +}; + +// Function to add email objects to the contactStat collection, take an array of email objects with email and sent properties +const addEmailObjects = async emailObjects => { + try { + const response = await fetch(CONTACT_STAT_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(emailObjects), + }); + + if (!response.ok) { + throw new Error('Failed to add email objects'); + } + + const responseBody = await response.json(); + const { emailsAdded } = responseBody; + return emailsAdded; + } catch (error) { + console.error('Error adding email objects:', error); + } +}; + +const deleteEmail = async email => { + try { + const response = await fetch(CONTACT_STAT_API_URL, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email }), + }); + + if (!response.ok) { + throw new Error('Failed to delete email'); + } + } catch (error) { + console.error('Error deleting email:', error); + } +}; + +const emailHandler = { + getContactStat, + sendEmail, + updateSentStatus, + addEmailObjects, + deleteEmail, +}; + +export default emailHandler; diff --git a/src/app/admin/layout.js b/src/app/admin/layout.js new file mode 100644 index 0000000..f161cea --- /dev/null +++ b/src/app/admin/layout.js @@ -0,0 +1,15 @@ +import { Libre_Franklin } from 'next/font/google'; + +const libre_franklin = Libre_Franklin({ + subsets: ['latin'], + display: 'swap', + variable: '--font-libre_franklin', +}); + +export default function Layout({ children }) { + return ( + + {children} + + ); +} diff --git a/src/app/admin/page.js b/src/app/admin/page.js new file mode 100644 index 0000000..2a230ed --- /dev/null +++ b/src/app/admin/page.js @@ -0,0 +1,140 @@ +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import emailHandler from './emailHandler'; +import { AdminPage } from '@/components/component/adminPage'; +import Navbar from '@/components/ui/navbar'; + +export default function Home() { + const [emails, setEmails] = useState([]); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [unauthorized, setUnauthorized] = useState(false); + + const links = [{ text: 'Logout', url: '/api/auth/logout' }]; + + const fetchUser = useCallback(async () => { + try { + const response = await fetch('/api/auth/me'); + if (response.ok) { + const userData = await response.json(); + if (userData.data && userData.data.admin === true) { + setUser(userData); + console.log('User is admin'); + } else { + console.log('User is not admin'); + setUnauthorized(true); + setTimeout(() => { + window.location.href = '/'; + }, 3000); // Redirect after 3 seconds + } + } else { + console.error('Failed to fetch user:', response.statusText); + setUnauthorized(true); + setTimeout(() => { + window.location.href = '/'; + }, 3000); // Redirect after 3 seconds + } + } catch (error) { + console.error('Error fetching user:', error); + setUnauthorized(true); + setTimeout(() => { + window.location.href = '/'; + }, 3000); // Redirect after 3 seconds + } finally { + setLoading(false); + } + }, []); + + const fetchEmails = async () => { + try { + const data = await emailHandler.getContactStat(); + setEmails(data); + } catch (error) { + console.error('Error fetching emails:', error); + } + }; + + useEffect(() => { + fetchUser(); + }, [fetchUser]); + + useEffect(() => { + if (user) { + fetchEmails(); + } + }, [user]); + + const updateEmails = (emailObj, isAdd) => { + if (isAdd) { + setEmails(prevEmails => [...prevEmails, emailObj]); + } else if (!isAdd) { + const updatedEmails = emails.filter( + existingEmail => existingEmail.email !== emailObj.email + ); + setEmails(updatedEmails); + } + }; + + const sendEmail = async recipient => { + try { + await emailHandler.sendEmail(recipient); + } catch (error) { + console.error('Error sending email:', error); + } + + setEmails(prevEmails => + prevEmails.map(emailObj => + emailObj.email === recipient ? { ...emailObj, sent: true } : emailObj + ) + ); + }; + + // Function to send emails to all recipients + const massSendEmails = async () => { + try { + let emailSent = 0; + // Loop through all emails and send email to each recipient + for (const emailObj of emails) { + // if email has not been sent, send email + if (!emailObj.sent) { + await sendEmail(emailObj.email); + emailSent++; + } + } + alert(`${emailSent} emails have been sent.`); + } catch (error) { + console.error('Error sending emails:', error); + } + }; + + if (loading) { + return ( +