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:
+
+ Reach: Expand your reach and connect with a diverse range of candidates from various backgrounds and skillsets.
+ Simplicity: Easily distribute your job postings to multiple platforms with just a few clicks, saving you time and effort.
+ Diversity: Embrace diversity and inclusivity by reaching out to candidates from underrepresented communities.
+ Payment Plans: Choose from flexible payment plans that suit your budget and hiring needs.
+
+ 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!
+ Sign Up Now
+ 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 (
+
+ );
+ }
+
+ if (unauthorized) {
+ return (
+
+
+ You do not have access to this page. Redirecting...
+
+
+ );
+ }
+
+ return user ? (
+
+ ) : null;
+}
diff --git a/src/components/component/addEmailModal.jsx b/src/components/component/addEmailModal.jsx
new file mode 100644
index 0000000..e42f863
--- /dev/null
+++ b/src/components/component/addEmailModal.jsx
@@ -0,0 +1,127 @@
+import { useState, useEffect } from 'react';
+import {
+ DialogTitle,
+ DialogContent,
+ Dialog,
+ DialogOverlay,
+} from '@/components/ui/dialog';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+import { Button } from '@/components/ui/button';
+import emailHandler from '@/app/admin/emailHandler';
+
+// CloseButton component for the close button
+const CloseButton = ({ onClick }) => (
+
+
+
+
+
+);
+
+export function AddEmailModal({ open, onClose, updateEmails }) {
+ const [email, setEmail] = useState('');
+ const [emailsAdded, setEmailsAdded] = useState(1);
+
+ // Reset email state when the modal is opened
+ useEffect(() => {
+ if (open) {
+ setEmail('');
+ }
+ }, [open]);
+
+ const validateEmail = email => {
+ // Regular expression to validate email format
+ const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+ return emailPattern.test(email);
+ };
+
+ const handleAddEmail = async () => {
+ try {
+ if (!validateEmail(email)) {
+ alert('Please enter a valid email address');
+ return;
+ }
+ // Call the addEmailObjects function with an array of length 1 containing the email object
+ const emailsAdded = await emailHandler.addEmailObjects([
+ { email, sent: false },
+ ]);
+ setEmailsAdded(emailsAdded);
+ setEmail('');
+
+ // Call the callback function to update the data state in AdminPage
+ const isAdd = true;
+ updateEmails({ email, sent: false }, isAdd);
+ } catch (error) {
+ console.error('Error adding email objects:', error);
+ }
+ };
+
+ const handleAddAndSendEmail = async () => {
+ try {
+ if (!validateEmail(email)) {
+ alert('Please enter a valid email address');
+ return;
+ }
+ await emailHandler.addEmailObjects([{ email, sent: true }]);
+ await emailHandler.sendEmail(email);
+ alert('Email sent to ' + email);
+ setEmail('');
+
+ // Call the callback function to update the data state in AdminPage
+ const isAdd = true;
+ updateEmails({ email, sent: true }, isAdd);
+ } catch (error) {
+ console.error('Error adding email objects:', error);
+ }
+ };
+
+ return (
+
+
+
+
+ Add Email
+ {/* Use CloseButton component for the close button */}
+
+
+
+
+ Email Address
+ setEmail(e.target.value)}
+ />
+
+
+ Add Email
+
+ Add and Send Email
+
+
+ {/* Display warning/notification field if emailsAdded is less than 1 */}
+ {emailsAdded < 1 && (
+
+ Email address already exists on the contact list.
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/component/adminPage.jsx b/src/components/component/adminPage.jsx
new file mode 100644
index 0000000..9543ef5
--- /dev/null
+++ b/src/components/component/adminPage.jsx
@@ -0,0 +1,106 @@
+import { React, useState } from 'react';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableHeader,
+ TableRow,
+ TableHead,
+ TableCell,
+ TableBody,
+} from '@/components/ui/table';
+import { CircleCheckIcon, CircleXIcon } from '@/components/icons';
+import { AddEmailModal } from './addEmailModal';
+import emailHandler from '@/app/admin/emailHandler';
+
+const AdminPage = ({ data, sendEmail, massSendEmails, updateEmails }) => {
+ const [showAddEmailModal, setShowAddEmailModal] = useState(false);
+
+ const handleAddEmail = () => {
+ setShowAddEmailModal(true);
+ };
+
+ const handleDeleteEmail = emailObj => {
+ // Call the deleteEmail function with the email to delete
+ emailHandler.deleteEmail(emailObj.email);
+
+ // Remove row from the table
+ const isAdd = false;
+ updateEmails(emailObj, isAdd);
+ };
+
+ const handleCloseModal = () => {
+ setShowAddEmailModal(false);
+ };
+
+ return (
+
+
+
+
+ {/* Render the Add Email button */}
+
+ Add Email
+
+
+
+
+ Email Contacted Status
+ Action
+
+
+
+ {data.map(({ sent, email }, index) => (
+
+
+
+ {email}
+ {sent ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ sendEmail(email)}>
+ Send Email
+
+ handleDeleteEmail({ sent, email })}>
+ Delete
+
+
+
+
+ ))}
+
+
+
+
+ {/* Render the AddEmailModal */}
+
+
+ );
+};
+
+AdminPage.displayName = 'AdminPage';
+
+export { AdminPage };
diff --git a/src/components/icons.js b/src/components/icons.js
index 038e91b..5e1adc6 100644
--- a/src/components/icons.js
+++ b/src/components/icons.js
@@ -38,3 +38,42 @@ export function TrashIcon(props) {
);
}
+
+export function CircleCheckIcon(props) {
+ return (
+
+
+
+
+ );
+}
+
+export function CircleXIcon(props) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/components/ui/addJobPostingForm.jsx b/src/components/ui/addJobPostingForm.jsx
index 2b5bfae..63fa380 100644
--- a/src/components/ui/addJobPostingForm.jsx
+++ b/src/components/ui/addJobPostingForm.jsx
@@ -25,6 +25,7 @@ const AddJobPostingForm = ({ onSubmit, email, onClose }) => {
site3: false,
site4: false,
site5: false,
+ sent: true,
});
const sites = ['Indigenous', 'New Comers', 'Site 3', 'Site 4', 'Site 5'];
diff --git a/src/components/ui/dialog.jsx b/src/components/ui/dialog.jsx
new file mode 100644
index 0000000..aec4811
--- /dev/null
+++ b/src/components/ui/dialog.jsx
@@ -0,0 +1,97 @@
+import * as React from 'react';
+import * as DialogPrimitive from '@radix-ui/react-dialog';
+import { cn } from '@/libs/utils';
+
+const Dialog = DialogPrimitive.Root;
+
+const DialogTrigger = DialogPrimitive.Trigger;
+
+const DialogPortal = DialogPrimitive.Portal;
+
+const DialogClose = DialogPrimitive.Close;
+
+const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef(
+ ({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ )
+);
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({ className, ...props }) => (
+
+);
+DialogHeader.displayName = 'DialogHeader';
+
+const DialogFooter = ({ className, ...props }) => (
+
+);
+DialogFooter.displayName = 'DialogFooter';
+
+const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+};
diff --git a/src/components/ui/table.jsx b/src/components/ui/table.jsx
new file mode 100644
index 0000000..79f8137
--- /dev/null
+++ b/src/components/ui/table.jsx
@@ -0,0 +1,95 @@
+import * as React from 'react';
+
+import { cn } from '@/libs/utils';
+
+const Table = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+Table.displayName = 'Table';
+
+const TableHeader = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableHeader.displayName = 'TableHeader';
+
+const TableBody = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableBody.displayName = 'TableBody';
+
+const TableFooter = React.forwardRef(({ className, ...props }, ref) => (
+ tr]:last:border-b-0 dark:bg-gray-800/50',
+ className
+ )}
+ {...props}
+ />
+));
+TableFooter.displayName = 'TableFooter';
+
+const TableRow = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableRow.displayName = 'TableRow';
+
+const TableHead = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableHead.displayName = 'TableHead';
+
+const TableCell = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableCell.displayName = 'TableCell';
+
+const TableCaption = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+TableCaption.displayName = 'TableCaption';
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};