Skip to content

Commit 7047520

Browse files
authored
Merge pull request #210 from Code-4-Community/190-dev---camila/implement-frontend-for-notifications
190 dev camila/implement frontend for notifications
2 parents 4f35646 + 6360bf7 commit 7047520

File tree

8 files changed

+228
-43
lines changed

8 files changed

+228
-43
lines changed

frontend/src/external/bcanSatchel/actions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@ export const updateSearchQuery = action(
6666
'updateSearchQuery',
6767
(searchQuery: string) => ({searchQuery})
6868
)
69+
70+
export const setNotifications = action(
71+
'setNotifications',
72+
(notifications: {id: number; title: string; message: string }[]) => ({
73+
notifications,
74+
})
75+
)

frontend/src/external/bcanSatchel/mutators.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
updateFilter,
88
updateStartDateFilter, updateEndDateFilter,
99
updateSearchQuery,
10-
updateYearFilter
10+
updateYearFilter,
11+
setNotifications
1112
} from './actions';
1213
import { getAppStore } from './store';
1314

@@ -80,3 +81,8 @@ mutator(updateYearFilter, (actionMessage) => {
8081
const store = getAppStore();
8182
store.yearFilter = actionMessage.yearFilter;
8283
})
84+
85+
mutator(setNotifications, (actionMessage) => {
86+
const store = getAppStore();
87+
store.notifications = actionMessage.notifications;
88+
})

frontend/src/external/bcanSatchel/store.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export interface AppState {
1313
startDateFilter: Date | null;
1414
endDateFilter: Date | null;
1515
searchQuery: string;
16-
yearFilter:number[] | [];
16+
yearFilter:number[] | null;
17+
notifications: { id: number; title: string; message: string; }[];
1718
}
1819

1920
// Define initial state
@@ -26,7 +27,8 @@ const initialState: AppState = {
2627
startDateFilter: null,
2728
endDateFilter: null,
2829
searchQuery: '',
29-
yearFilter: []
30+
yearFilter: null,
31+
notifications: []
3032
};
3133

3234
const store = createStore<AppState>('appStore', initialState);

frontend/src/main-page/dashboard/Dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const Dashboard = observer(() => {
3232

3333
const uniqueYears = Array.from(
3434
new Set(
35-
yearFilter?.length > 0
35+
yearFilter && yearFilter?.length > 0
3636
? yearFilter
3737
: allGrants.map((g) => new Date(g.application_deadline).getFullYear())
3838
)

frontend/src/main-page/header/Bell.tsx

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
22
import { faBell } from "@fortawesome/free-solid-svg-icons";
33
import { useEffect, useState } from "react";
4-
import { api } from "../../api";
4+
//import { api } from "../../api"; //todo: swap out dummy data with real api fetch when backend is ready
5+
import NotificationPopup from "../notifications/NotificationPopup";
6+
import { setNotifications as setNotificationsAction } from "../../external/bcanSatchel/actions";
7+
import { getAppStore } from "../../external/bcanSatchel/store";
8+
59

610
// get current user id
711
// const currUserID = sessionStorage.getItem('userId');
8-
const currUserID = "bcanuser33";
12+
// const currUserID = "bcanuser33";
913

1014
const BellButton = () => {
1115
// stores notifications for the current user
12-
const [notifications, setNotifications] = useState<any[]>([]);
16+
const store = getAppStore();
17+
const notifications = store.notifications ?? [];
1318

1419
// determines whether bell has been clicked
1520
const [isClicked, setClicked] = useState(false);
@@ -21,55 +26,45 @@ const BellButton = () => {
2126

2227
// function that handles when button is clicked and fetches notifications
2328
const handleClick = async () => {
24-
const response = await api(
25-
`/notifications/user/${currUserID}`,
26-
{
27-
method: "GET",
28-
}
29-
);
30-
console.log(response);
31-
const currNotifications = await response.json();
32-
setNotifications(currNotifications);
29+
//temporary dummy data for now
30+
const dummyNotifications = [
31+
{id: 1, title: "Grant Deadline", message: "Grant A deadline approaching in 3 days"},
32+
{id: 2, title: "Grant Deadline", message: "Grant B deadline tomorrow!"},
33+
{id: 3, title: "Grant Deadline", message: "Grant C deadline passed yesterday!"},
34+
{id: 4, title: "Grant Deadline", message: "Grant D deadline tomorrow!"}
35+
];
36+
//previous api logic (for later)
37+
//const response = await api(
38+
//`/notifications/user/${currUserID}`,
39+
//{
40+
//method: "GET",
41+
//}
42+
//);
43+
//console.log(response);
44+
//const currNotifications = await response.json();
45+
setNotificationsAction(dummyNotifications);
3346
setClicked(!isClicked);
3447
return notifications;
3548
};
3649

50+
const handleClose = () => setClicked(false);
51+
3752
return (
38-
<>
53+
<div className="bell-container">
3954
<button
4055
className={`bell-button ${isClicked ? "hovered" : ""}`}
4156
onClick={handleClick}
4257
>
4358
<FontAwesomeIcon icon={faBell} style={{ color: "black" }} />
4459
</button>
60+
4561
{isClicked && (
46-
<div className="notification-modal">
47-
<div className="notification-modal-content">
48-
<h4>
49-
{currUserID ? `Notifications for ${currUserID}` : "Notifications"}
50-
</h4>
51-
{notifications.length > 0 ? (
52-
<ul>
53-
{notifications.map((notification, index) => (
54-
<li key={index} className="notification-item">
55-
{notification.message} <br />
56-
<small>Alert Time: {notification.alertTime}</small>
57-
</li>
58-
))}
59-
</ul>
60-
) : (
61-
<p>No new notifications</p>
62-
)}
63-
<button
64-
onClick={() => setClicked(false)}
65-
className="notification-close-button"
66-
>
67-
Close
68-
</button>
69-
</div>
70-
</div>
62+
<NotificationPopup
63+
notifications={notifications}
64+
onClose={handleClose}
65+
/>
7166
)}
72-
</>
67+
</div>
7368
);
7469
};
7570

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2+
import { faBell } from "@fortawesome/free-solid-svg-icons";
3+
import { FaTrash } from "react-icons/fa";
4+
5+
interface GrantNotificationProps {
6+
title: string;
7+
message: string;
8+
}
9+
10+
const GrantNotification: React.FC<GrantNotificationProps> = ({ title, message }) => {
11+
return (
12+
<div className="grant-notification" role="listitem">
13+
<div className="bell-notif">
14+
<FontAwesomeIcon icon={faBell} style={{ color: "gray"}} />
15+
</div>
16+
<div className="notification-text">
17+
<div className="notification-title">{title}</div>
18+
<div className="notification-message">{message}</div>
19+
</div>
20+
<FaTrash
21+
className="notification-trash-icon"
22+
title="Delete notification"
23+
/>
24+
</div>
25+
);
26+
};
27+
28+
export default GrantNotification;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createPortal } from 'react-dom';
2+
import GrantNotification from "./GrantNotification";
3+
import '../../styles/notification.css';
4+
5+
interface NotificationPopupProps {
6+
notifications: { id: number; title: string; message: string }[];
7+
onClose: () => void;
8+
}
9+
10+
const NotificationPopup: React.FC<NotificationPopupProps> = ({
11+
notifications,
12+
onClose
13+
}) => {
14+
return createPortal(
15+
<div className="notification-popup" role="dialog" aria-label="Notifications">
16+
<div className="popup-header">
17+
<h3>Alerts</h3>
18+
<button className="close-button" onClick={onClose} aria-label="Close notifications">
19+
20+
</button>
21+
</div>
22+
23+
<div className="notification-list">
24+
{notifications && notifications.length > 0 ? (
25+
notifications.map((n) => (
26+
<GrantNotification key={n.id} title={n.title} message={n.message} />
27+
))
28+
) : (
29+
<p className="empty-text">No new notifications</p>
30+
)}
31+
</div>
32+
</div>,
33+
document.body
34+
);
35+
};
36+
37+
export default NotificationPopup;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
.notification-popup {
2+
position: absolute;
3+
right: 7.5rem;
4+
top: 75px;
5+
width: min(340px, 70%);
6+
background-color: white;
7+
border: 1px solid #343131;
8+
border-radius: 6px;
9+
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
10+
padding: 0.5rem;
11+
z-index: 1000;
12+
}
13+
14+
15+
.popup-header {
16+
display: flex;
17+
justify-content: space-between;
18+
align-items: center;
19+
padding: 0.5rem 0.5rem;
20+
border-bottom: 1px solid #ddd;
21+
}
22+
23+
24+
.popup-header h3 {
25+
font-size: 1.1rem;
26+
font-weight: 600;
27+
color: #333;
28+
margin: 0;
29+
}
30+
31+
32+
.close-button {
33+
background: none;
34+
border: none;
35+
font-size: 1rem;
36+
cursor: pointer;
37+
color: #777;
38+
}
39+
.close-button:hover {
40+
color: #e74c3c;
41+
}
42+
43+
44+
.notification-list {
45+
max-height: 200px;
46+
overflow-y: auto;
47+
margin-top: 10px;
48+
}
49+
50+
51+
.grant-notification {
52+
position: relative;
53+
border-bottom: 0.2px solid #dfd9d9;
54+
border-radius: 0px;
55+
padding: 8px 10px;
56+
padding-right: 35px;
57+
margin-bottom: 2px;
58+
font-size: 0.9rem;
59+
transition: background-color 0.2s ease;
60+
display: flex;
61+
}
62+
63+
.bell-notif {
64+
margin-right: 10px;
65+
background-color: rgb(225, 225, 225);
66+
padding: 10px;
67+
border-radius: 100%;
68+
scale: 73%;
69+
flex-shrink: 0;
70+
width: 40px;
71+
height: 40px;
72+
display: flex;
73+
align-items: center;
74+
justify-content: center;
75+
}
76+
77+
.notification-text {
78+
flex: 1;
79+
}
80+
81+
.notification-title {
82+
font-weight: 600;
83+
font-size: 13px;
84+
color: #101010;
85+
line-height: 1.15;
86+
}
87+
88+
.notification-message {
89+
font-size: 12.5px;
90+
color: #555;
91+
margin-top: 4px;
92+
line-height: 1.2;
93+
}
94+
95+
.grant-notification:hover {
96+
background-color: #f1f1f1;
97+
}
98+
99+
.notification-trash-icon {
100+
position: absolute;
101+
bottom: 8px;
102+
right: 10px;
103+
color: #aaa;
104+
cursor: pointer;
105+
font-size: 0.9rem
106+
}
107+
108+
.notification-trash-icon:hover {
109+
color: #e74c3c
110+
}

0 commit comments

Comments
 (0)