Skip to content

Commit

Permalink
Feature/send email (#105)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
mai-vu and justshye authored May 28, 2024
1 parent 0054e5f commit 027ce17
Show file tree
Hide file tree
Showing 10 changed files with 756 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/app/admin-panel/home/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '/';
Expand Down
129 changes: 129 additions & 0 deletions src/app/admin/emailHandler.js
Original file line number Diff line number Diff line change
@@ -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: `
<p>Hello from our team at [Job Panel],</p>
<p>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.</p>
<p>Here's what makes our job panel stand out:</p>
<ul>
<li><strong>Reach:</strong> Expand your reach and connect with a diverse range of candidates from various backgrounds and skillsets.</li>
<li><strong>Simplicity:</strong> Easily distribute your job postings to multiple platforms with just a few clicks, saving you time and effort.</li>
<li><strong>Diversity:</strong> Embrace diversity and inclusivity by reaching out to candidates from underrepresented communities.</li>
<li><strong>Payment Plans:</strong> Choose from flexible payment plans that suit your budget and hiring needs.</li>
</ul>
<p>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!</p>
<p><a href="https://job-bank-mu.vercel.app/" style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Sign Up Now</a></p>
<p>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!</p>
<p>Best regards,</p>
<p>The [Job Panel] Team</p>`,
}),
});
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;
15 changes: 15 additions & 0 deletions src/app/admin/layout.js
Original file line number Diff line number Diff line change
@@ -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 (
<html lang="en">
<body className={libre_franklin.variable}>{children}</body>
</html>
);
}
140 changes: 140 additions & 0 deletions src/app/admin/page.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex items-center justify-center min-h-screen">
<div className="text-xl">Loading...</div>
</div>
);
}

if (unauthorized) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-xl text-red-600">
You do not have access to this page. Redirecting...
</div>
</div>
);
}

return user ? (
<div>
<Navbar links={links} />
<AdminPage
data={emails}
sendEmail={sendEmail}
massSendEmails={massSendEmails}
updateEmails={updateEmails}
/>
</div>
) : null;
}
Loading

0 comments on commit 027ce17

Please sign in to comment.