From 116d638211fd6d8acc48cc309e536049ab380e78 Mon Sep 17 00:00:00 2001 From: lyannne Date: Wed, 3 Dec 2025 20:48:18 -0500 Subject: [PATCH 01/10] logout button still appears now! and added email once account is approved --- frontend/src/main-page/header/AccountInfo.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/main-page/header/AccountInfo.tsx b/frontend/src/main-page/header/AccountInfo.tsx index b35e54a..c609895 100644 --- a/frontend/src/main-page/header/AccountInfo.tsx +++ b/frontend/src/main-page/header/AccountInfo.tsx @@ -54,18 +54,19 @@ const AccountInfo: React.FC = ({ {role} - {isAdmin &&
- + {isAdmin && + + }
- } , document.body From 38ece5889ecd627e385da562c539fef050a20407 Mon Sep 17 00:00:00 2001 From: lyannne Date: Wed, 3 Dec 2025 20:48:46 -0500 Subject: [PATCH 02/10] comment --- backend/src/user/user.service.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index c52fd1d..032a373 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -305,6 +305,29 @@ export class UserService { .promise(); this.logger.log(`✓ User ${username} added to Cognito group ${groupName}`); + + // Send verification email if moving from Inactive to employee group + if ( + previousGroup === UserStatus.Inactive && + groupName === UserStatus.Employee + ) { + try { + await this.sendVerificationEmail(user.email); + this.logger.log( + `✓ Verification email sent to ${user.email} upon group change to ${groupName}` + ); + } catch (emailError) { + this.logger.error( + `Failed to send verification email to ${username}:`, + emailError + ); + } + } + else { + this.logger.log( + `No verification email sent to ${username}. Previous group: ${previousGroup}, New group: ${groupName}` + ); + } } catch (cognitoError: any) { this.logger.error( `Failed to add ${username} to Cognito group ${groupName}:`, @@ -507,9 +530,9 @@ export class UserService { // sends email to user once account is approved, used in method above when a user // is added to the Employee or Admin group from Inactive async sendVerificationEmail(userEmail: string): Promise { - // may want to have the default be the BCAN email or something else + // remove actual email and add to env later!! const fromEmail = process.env.NOTIFICATION_EMAIL_SENDER || - 'u&@nveR1ified-failure@dont-send.com'; + 'c4cneu.bcan@gmail.com'; const params: AWS.SES.SendEmailRequest = { Source: fromEmail, From b0b4833d21ab53fabef39d78af8d6456053faf3d Mon Sep 17 00:00:00 2001 From: prooflesben Date: Thu, 4 Dec 2025 01:57:14 -0500 Subject: [PATCH 03/10] Made patches to the restricted page flow --- frontend/src/Login.tsx | 2 +- frontend/src/animations/AnimatedRoutes.tsx | 60 +++++++++++++--------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/frontend/src/Login.tsx b/frontend/src/Login.tsx index 090b571..9de2154 100644 --- a/frontend/src/Login.tsx +++ b/frontend/src/Login.tsx @@ -41,7 +41,7 @@ const Login = observer(() => {
{ const location = useLocation(); const { isAuthenticated } = useAuthContext(); + const user = getAppStore().user; return ( - : } - /> - : - } - /> - - } - /> - } /> - - } - /> - + : } + /> + : + } + /> + } + /> + } /> + + {/* Check user status and render MainPage or redirect */} + + : + } + /> + + + } + /> + ); }); From 3fdca6257d02306202f8e57358aaccedf169b0ab Mon Sep 17 00:00:00 2001 From: prooflesben Date: Thu, 4 Dec 2025 02:37:24 -0500 Subject: [PATCH 04/10] Fixed login by just adding navigate to the /login page --- frontend/src/main-page/grants/filter-bar/processGrantData.ts | 1 + frontend/src/main-page/header/AccountInfo.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/main-page/grants/filter-bar/processGrantData.ts b/frontend/src/main-page/grants/filter-bar/processGrantData.ts index 6a8fd6e..e7c3f62 100644 --- a/frontend/src/main-page/grants/filter-bar/processGrantData.ts +++ b/frontend/src/main-page/grants/filter-bar/processGrantData.ts @@ -30,6 +30,7 @@ export const fetchGrants = async () => { // stores state for list of grants/filter export const ProcessGrantData = () => { const { allGrants, filterStatus, startDateFilter, endDateFilter, yearFilter, searchQuery } = getAppStore(); + // fetch grants on mount if empty useEffect(() => { diff --git a/frontend/src/main-page/header/AccountInfo.tsx b/frontend/src/main-page/header/AccountInfo.tsx index c609895..066c736 100644 --- a/frontend/src/main-page/header/AccountInfo.tsx +++ b/frontend/src/main-page/header/AccountInfo.tsx @@ -33,6 +33,7 @@ const AccountInfo: React.FC = ({ const handleLogoutClick = () => { logoutUser(); setOpenModal(null); + navigate('/login') }; return createPortal( From d79143bf2054878d5247f3c67de9fc4b638f8880 Mon Sep 17 00:00:00 2001 From: prooflesben Date: Thu, 4 Dec 2025 10:50:27 -0500 Subject: [PATCH 05/10] remove extra fluff from the old version of doing the restricted page --- frontend/src/main-page/dashboard/Dashboard.tsx | 12 ++---------- .../main-page/grants/filter-bar/processGrantData.ts | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/frontend/src/main-page/dashboard/Dashboard.tsx b/frontend/src/main-page/dashboard/Dashboard.tsx index 2774b41..4e79ecf 100644 --- a/frontend/src/main-page/dashboard/Dashboard.tsx +++ b/frontend/src/main-page/dashboard/Dashboard.tsx @@ -45,9 +45,7 @@ const Dashboard = observer(() => { const { grants } = ProcessGrantData(); - return user ? ( - user?.position !== UserStatus.Inactive ? ( -
+ return(
@@ -81,13 +79,7 @@ const Dashboard = observer(() => {
-
- ) : ( - - ) - ) : ( - - ); +
) }); export default Dashboard; diff --git a/frontend/src/main-page/grants/filter-bar/processGrantData.ts b/frontend/src/main-page/grants/filter-bar/processGrantData.ts index e7c3f62..e3d9f5b 100644 --- a/frontend/src/main-page/grants/filter-bar/processGrantData.ts +++ b/frontend/src/main-page/grants/filter-bar/processGrantData.ts @@ -31,7 +31,6 @@ export const fetchGrants = async () => { export const ProcessGrantData = () => { const { allGrants, filterStatus, startDateFilter, endDateFilter, yearFilter, searchQuery } = getAppStore(); - // fetch grants on mount if empty useEffect(() => { if (allGrants.length === 0) fetchGrants(); @@ -45,6 +44,7 @@ export const ProcessGrantData = () => { searchFilter(searchQuery) ]); + // sorting callback const onSort = (header: keyof Grant, asc: boolean) => { return sortGrants(filteredGrants, header, asc); From d27919904014ee731c99177d8994d569323a929d Mon Sep 17 00:00:00 2001 From: prooflesben Date: Thu, 4 Dec 2025 11:00:55 -0500 Subject: [PATCH 06/10] Added bcan poc drop down back into place --- frontend/src/external/bcanSatchel/store.ts | 1 - .../grants/new-grant/NewGrantModal.tsx | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/frontend/src/external/bcanSatchel/store.ts b/frontend/src/external/bcanSatchel/store.ts index 4953e07..8d60b6c 100644 --- a/frontend/src/external/bcanSatchel/store.ts +++ b/frontend/src/external/bcanSatchel/store.ts @@ -91,6 +91,5 @@ export function persistToSessionStorage() { */ export function getAppStore() { const state = store(); - console.log('Current store.user:', state.user); // Debug: log current user when accessed return state; } diff --git a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx index 18d34ad..675147d 100644 --- a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx +++ b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx @@ -10,6 +10,7 @@ import { Status } from "../../../../../middle-layer/types/Status"; import { createNewGrant, saveGrantEdits } from "../new-grant/processGrantDataEditSave"; import { fetchGrants } from "../filter-bar/processGrantData"; import { observer } from "mobx-react-lite"; +import UserDropdown from "./UserDropdown"; /** Attachment type from your middle layer */ enum AttachmentType { @@ -532,15 +533,24 @@ const [reportDates, setReportDates] = useState<(TDateISO | "")[]>( BCAN POC * {/*Box div*/} + {/*Box div*/}
- setBcanPocName(e.target.value)}/> + { + setBcanPocName(user.name); + setBcanPocEmail(user.email) + }} + placeholder="Name" + /> setBcanPocEmail(e.target.value)}/> + placeholder="e-mail" + value={bcanPocEmail} + readOnly + />
From 28887cf109bf0b5ecc9d0c9b7c5c9a247367af16 Mon Sep 17 00:00:00 2001 From: lyannne Date: Thu, 4 Dec 2025 17:08:04 -0500 Subject: [PATCH 07/10] added field to notification for whether an email has been sent or not --- backend/src/grant/grant.service.ts | 2 ++ backend/src/notifications/notification.service.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 6ea1b12..23b096f 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -256,6 +256,7 @@ async makeGrantsInactive(grantId: number): Promise { userId, message, alertTime: alertTime as TDateISO, + sent: false, }; await this.notificationService.createNotification(notification); } @@ -272,6 +273,7 @@ async makeGrantsInactive(grantId: number): Promise { userId, message, alertTime: alertTime as TDateISO, + sent: false, }; await this.notificationService.createNotification(notification); } diff --git a/backend/src/notifications/notification.service.ts b/backend/src/notifications/notification.service.ts index ade1ec9..bca2326 100644 --- a/backend/src/notifications/notification.service.ts +++ b/backend/src/notifications/notification.service.ts @@ -19,6 +19,7 @@ export class NotificationService { Item: { ...notification, alertTime: alertTime.toISOString(), + sent: false // initialize sent to false when creating a new notification }, }; await this.dynamoDb.put(params).promise(); From be5b9468872206b1e40998ededcd29706ab6e0ca Mon Sep 17 00:00:00 2001 From: lyannne Date: Thu, 4 Dec 2025 17:08:59 -0500 Subject: [PATCH 08/10] changed Notification.ts in middle layer --- middle-layer/types/Notification.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/middle-layer/types/Notification.ts b/middle-layer/types/Notification.ts index 70e5081..b5e771b 100644 --- a/middle-layer/types/Notification.ts +++ b/middle-layer/types/Notification.ts @@ -8,4 +8,5 @@ export interface Notification { userId: string; message: string; alertTime: TDateISO; // Sort + sent: boolean; // email has been sent for this notification } \ No newline at end of file From 6c0e71de2f7c658ca291ae2305d898106d2835eb Mon Sep 17 00:00:00 2001 From: lyannne Date: Thu, 4 Dec 2025 17:10:00 -0500 Subject: [PATCH 09/10] changed notification tests to have new sent field --- .../__test__/notification.service.test.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/backend/src/notifications/__test__/notification.service.test.ts b/backend/src/notifications/__test__/notification.service.test.ts index 791af14..5755439 100644 --- a/backend/src/notifications/__test__/notification.service.test.ts +++ b/backend/src/notifications/__test__/notification.service.test.ts @@ -80,28 +80,32 @@ describe('NotificationController', () => { notificationId: '1', userId: 'user-1', message: 'New Grant Created 🎉 ', - alertTime: '2024-01-15T10:30:00.000Z' + alertTime: '2024-01-15T10:30:00.000Z', + sent: false } as Notification; mockNotification_id1_user2 = { notificationId: '1', userId: 'user-2', message: 'New Grant Created', - alertTime: '2025-01-15T10:30:00.000Z' + alertTime: '2025-01-15T10:30:00.000Z', + sent: false } as Notification; mockNotification_id2_user1= { notificationId: '2', userId: 'user-1', message: 'New Grant Created', - alertTime: '2025-01-15T10:30:00.000Z' + alertTime: '2025-01-15T10:30:00.000Z', + sent: false } as Notification; mockNotification_id2_user2= { notificationId: '2', userId: 'user-2', message: 'New Grant Created', - alertTime: '2025-01-15T10:30:00.000Z' + alertTime: '2025-01-15T10:30:00.000Z', + sent: false } as Notification; mockPut.mockReturnValue({ promise: mockPromise }); @@ -292,6 +296,7 @@ describe('NotificationController', () => { userId : 'user-456', message : 'Test notification', alertTime : '2024-01-15T10:30:00.000Z', + sent: false } as Notification; const result = await notificationService.createNotification(mockNotification); expect(mockPut).toHaveBeenCalledWith({ @@ -300,7 +305,8 @@ describe('NotificationController', () => { notificationId: '123', userId : 'user-456', message : 'Test notification', - alertTime : '2024-01-15T10:30:00.000Z' + alertTime : '2024-01-15T10:30:00.000Z', + sent: false }, }); expect(result).toEqual(mockNotification); @@ -315,7 +321,8 @@ describe('NotificationController', () => { notificationId: '123', userId: 'user-456', message: 'Test notification', - alertTime: '2024-01-15T10:30:00.000Z' + alertTime: '2024-01-15T10:30:00.000Z', + sent: false } as Notification; // Act @@ -329,7 +336,8 @@ describe('NotificationController', () => { notificationId: '123', userId: 'user-456', message: 'Test notification', - alertTime: '2024-01-15T10:30:00.000Z' + alertTime: '2024-01-15T10:30:00.000Z', + sent: false }, }); }); From 12e3636562806f78a1bccfbdbec535866d7ac1f9 Mon Sep 17 00:00:00 2001 From: prooflesben Date: Fri, 5 Dec 2025 01:31:11 -0500 Subject: [PATCH 10/10] Ficxed errors cause d by the merge --- frontend/src/main-page/dashboard/Dashboard.tsx | 4 +--- .../src/main-page/grants/new-grant/NewGrantModal.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/main-page/dashboard/Dashboard.tsx b/frontend/src/main-page/dashboard/Dashboard.tsx index 4e79ecf..b780b94 100644 --- a/frontend/src/main-page/dashboard/Dashboard.tsx +++ b/frontend/src/main-page/dashboard/Dashboard.tsx @@ -18,8 +18,6 @@ import GanttYearGrantTimeline from "./Charts/GanttYearGrantTimeline"; import DonutMoneyApplied from "./Charts/DonutMoneyApplied"; import { ProcessGrantData } from "../grants/filter-bar/processGrantData"; import KPICards from "./Charts/KPICards"; -import { Navigate } from "react-router-dom"; -import { UserStatus } from "../../../../middle-layer/types/UserStatus"; const Dashboard = observer(() => { // reset filters on initial render @@ -30,7 +28,7 @@ const Dashboard = observer(() => { updateStartDateFilter(null); }, []); - const { yearFilter, allGrants, user } = getAppStore(); + const { yearFilter, allGrants } = getAppStore(); const uniqueYears = Array.from( new Set( diff --git a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx index 5f5a3cf..285a6e4 100644 --- a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx +++ b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx @@ -222,6 +222,9 @@ const NewGrantModal: React.FC<{ /** Basic validations based on your screenshot fields */ const validateInputs = (): boolean => { + // Timeline check + + // Organization validation if (!organization || organization.trim() === "") { setErrorMessage("Organization Name is required."); @@ -321,11 +324,11 @@ const NewGrantModal: React.FC<{ return false; } // Estimated completion time validation - if (estimatedCompletionTimeInHours < 0) { + if (estimatedCompletionTimeInHours <= 0) { setErrorMessage("Estimated Completion Time cannot be negative."); return false; } - if (estimatedCompletionTimeInHours === 0) { + if (estimatedCompletionTimeInHours <= 0) { setErrorMessage("Estimated Completion Time must be greater than 0."); return false; } @@ -718,7 +721,6 @@ const NewGrantModal: React.FC<{ BCAN POC * {/*Box div*/} - {/*Box div*/}
@@ -739,7 +741,6 @@ const NewGrantModal: React.FC<{
- {/*Grant Provider POC div*/}