Skip to content

Commit

Permalink
Merge pull request #83 from ctc-uci/67-implement-notifications-and-em…
Browse files Browse the repository at this point in the history
…ail-system

67 implement notifications and email system
  • Loading branch information
theNatePi authored Feb 23, 2025
2 parents 3d5a77f + 58a9a6b commit 0edd9f1
Show file tree
Hide file tree
Showing 21 changed files with 785 additions and 266 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@hookform/resolvers": "^3.9.0",
"@react-email/components": "^0.0.33",
"@react-pdf/renderer": "^4.1.6",
"axios": "^1.7.4",
"firebase": "10.12.5",
Expand Down
13 changes: 13 additions & 0 deletions client/src/assets/icons/CkEmail.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions client/src/components/email/SendEmailButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState } from "react";

import {
Button,
Flex,
Input,
FormControl,
FormErrorMessage
} from "@chakra-ui/react";

import { useBackendContext } from "../../contexts/hooks/useBackendContext";

const SendEmailButton = () => {
const { backend } = useBackendContext();
const [email, setEmail] = useState("");
const [error, setError] = useState("");

const validateEmail = (email) => {
const regex = /\S+@\S+\.\S+/;
return regex.test(email);
};

const sendEmail = async () => {
if (!validateEmail(email)) {
setError("Please enter a valid email address.");
return;
}

const response = await backend.post("/email/send", {
to: email,
subject: "Invoice",
text: "This is a test email.",
html: "<h1>This is a test email.</h1>"
});

console.log(response.data);
};

return (
<FormControl isInvalid={!!error}>
<Flex direction="row" gap="1rem" width="100%">
<Input
type="email"
size="lg"
placeholder="Enter invoice recipient email..."
value={email}
onChange={(e) => {
setEmail(e.target.value);
setError("");
}}
>
</Input>
<Button size="lg" onClick={sendEmail}>
Send Email
</Button>
</Flex>
<FormErrorMessage>{error}</FormErrorMessage>
</FormControl>
);
}

export { SendEmailButton };
8 changes: 8 additions & 0 deletions client/src/components/navbar/Navbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
color: #474849;
}

.navTextChosen {
margin-left: 3%;
width: 90%;
font-weight: 500;
font-size: 150%;
color: #4E4AE7;
}

.navLink, .navText, .navIcon, .navItem {
color: #474849;
}
Expand Down
32 changes: 29 additions & 3 deletions client/src/components/navbar/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ import { Logo } from "../../assets/logo/Logo";

import "./Navbar.css";

const Navbar = ({ children }) => {
const Navbar = ({ children, notificationsCount, currentPage = "" }) => {
const menuItems = [
{ name: "Programs", path: "/programs", icon: <IoMdCalendar /> },
{ name: "Invoices", path: "/invoices", icon: <TbInvoice /> },
{
name: "Notifications",
path: "/notifications",
icon: <MdNotifications />,
count: notificationsCount,
},
{ name: "Settings", path: "/settings", icon: <MdSettings /> },
];

console.log(currentPage)
return (
<div id="navbarBody">
<Box
Expand Down Expand Up @@ -48,6 +50,9 @@ const Navbar = ({ children }) => {
<div
key={item.name}
className="navItem"
color={
currentPage === item.name.toLowerCase() ? "#4441C8" : "#767778"
}
>
<Link
href={item.path}
Expand All @@ -61,16 +66,37 @@ const Navbar = ({ children }) => {
<Icon
className="navIcon"
fontSize="2xl"
color={
currentPage === item.name.toLowerCase()
? "#4441C8"
: "#767778"
}
>
{item.icon}
</Icon>
<Text className="navText">{item.name}</Text>
<Text className={currentPage === item.name.toLowerCase() ? "navTextChosen" : "navText"} >{item.name}</Text>
{item.count !== null && item.count !== undefined && (
<Box
marginLeft="10px"
height="30px"
textAlign="center"
fontSize="15px"
fontWeight="medium"
color="#FFF"
padding="5px"
borderRadius="5px"
background="#4E4AE7"
width="auto"
>
{item.count}
</Box>
)}
</Link>
</div>
))}
</VStack>
</Box>
<div style={{ width: "100%", paddingLeft: "min(18%, 250px)"}}>
<div style={{ width: "100%", paddingLeft: "min(18%, 250px)" }}>
{children}
</div>
</div>
Expand Down
2 changes: 0 additions & 2 deletions client/src/components/notifications/Counter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ export const CounterComponent = ({ count }) => {
style={{
width: "75px",
height: "31px",
borderRadius: "10px",
border: "1px solid var(--medium-light-grey, #D2D2D2)",
background: "#FFF",
marginTop: "3px",
}}
Expand Down
79 changes: 17 additions & 62 deletions client/src/components/notifications/FilterButton.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";

import {
Button,
HStack,
Expand All @@ -16,43 +17,12 @@ import styles from "./FilterButton.module.css";
import { FilterIcon } from "./FilterIcon";

export const FilterButton = ({ setFilterType, currentFilter }) => {
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const toast = useToast();
const [type, setType] = useState("all");

// Watch for changes in both dates
useEffect(() => {
console.log(startDate, endDate);
if (startDate.substring(0,1) === "2" && endDate.substring(0,1) === "2") { // checking if the date is filled out completely
const start = new Date(startDate);
const end = new Date(endDate);

console.log(start, end);
if (start > end) {
toast({
title: "Invalid Date Range",
description: "Start date must be before end date",
status: "error",
duration: 3000,
isClosable: true,
});
return;
}

// Update filter with new dates
setFilterType(prev => ({
...prev,
type: type,
startDate: startDate, // The date input already returns YYYY-MM-DD format
endDate: endDate
}));
}
}, [startDate, endDate, type, toast]);

const handleFilterSelect = (type) => {
setType(type);
setFilterType(prev => ({
setFilterType((prev) => ({
...prev,
type,
}));
Expand All @@ -68,16 +38,15 @@ export const FilterButton = ({ setFilterType, currentFilter }) => {
padding="8px 16px"
justifyContent="center"
alignItems="center"
gap="4px"
flexShrink={0}
borderRadius={15}
borderRadius={30}
border="1px solid var(--medium-light-grey, #D2D2D2)"
background="var(--white, #FFF)"
background="#F0F1F4"
color="var(--medium-grey, #767778)"
fontFamily="Inter, sans-serif"
fontSize="16px"
fontStyle="normal"
fontWeight={400}
fontSize="15px"
fontStyle="bold"
fontWeight={650}
lineHeight="normal"
>
Filters
Expand All @@ -88,30 +57,10 @@ export const FilterButton = ({ setFilterType, currentFilter }) => {
className={styles.modalContent}
boxShadow="0px 4px 6px -1px rgba(0, 0, 0, 0.1)"
border="1px solid #E2E2E2"
w={"auto"}
>
<PopoverCloseButton />
<PopoverBody className={styles.modalBody}>
<HStack spacing={2} className={styles.dateRangeHeader}>
<CalendarIcon />
<Text>Date Range</Text>
</HStack>

<div className={styles.dateInputContainer}>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className={styles.dateInput}
/>
<Text className={styles.toText}>to</Text>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className={styles.dateInput}
/>
</div>

<Text className={styles.typeHeader}>Type</Text>

<div className={styles.filterButtonGroup}>
Expand All @@ -121,21 +70,27 @@ export const FilterButton = ({ setFilterType, currentFilter }) => {
>
All
</Button>
<Button
onClick={() => handleFilterSelect("highpriority")}
className={`${styles.filterButton} ${currentFilter.type === "highpriority" ? styles.active : ""}`}
>
High Priority
</Button>
<Button
onClick={() => handleFilterSelect("overdue")}
className={`${styles.filterButton} ${currentFilter.type === "overdue" ? styles.active : ""}`}
>
Overdue
Past Due
</Button>
<Button
onClick={() => handleFilterSelect("neardue")}
className={`${styles.filterButton} ${currentFilter.type === "neardue" ? styles.active : ""}`}
>
Near Due
Email Not Sent
</Button>
</div>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
};
3 changes: 2 additions & 1 deletion client/src/components/notifications/FilterButton.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
border: 1px solid var(--medium-light-grey, #D2D2D2);
background: var(--white, #FFF);
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
width: 200px;
width: 100px;
}

.dateRangeHeader {
Expand Down Expand Up @@ -109,6 +109,7 @@

.filterButtonGroup {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
align-self: stretch;
Expand Down
33 changes: 33 additions & 0 deletions client/src/components/notifications/InboxTab.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Ckemail from "../../assets/icons/CkEmail.svg";

const EmailIcon = () => {
return (
<img
src={Ckemail}
width={24}
height={24}
/>
);
};

export const InboxTab = () => {
return (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: "10px",
fontSize: "20px",
fontWeight: 600,
color: "#7C15D4",
borderBottom: "2px solid #9747FF",
width: "200px",
height: "46px",
}}
>
<EmailIcon />
Primary Inbox
</div>
);
};
Loading

0 comments on commit 0edd9f1

Please sign in to comment.