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/__test__/notification.service.test.ts b/backend/src/notifications/__test__/notification.service.test.ts index 3837560..dd24047 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 }, }); }); diff --git a/backend/src/notifications/notification.service.ts b/backend/src/notifications/notification.service.ts index 2a369be..44d0cfe 100644 --- a/backend/src/notifications/notification.service.ts +++ b/backend/src/notifications/notification.service.ts @@ -21,6 +21,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(); 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, 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 */} + + : + } + /> + + + } + /> + ); }); diff --git a/frontend/src/main-page/dashboard/Dashboard.tsx b/frontend/src/main-page/dashboard/Dashboard.tsx index 2774b41..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( @@ -45,9 +43,7 @@ const Dashboard = observer(() => { const { grants } = ProcessGrantData(); - return user ? ( - user?.position !== UserStatus.Inactive ? ( -
+ return(
@@ -81,13 +77,7 @@ const Dashboard = observer(() => {
-
- ) : ( - - ) - ) : ( - - ); +
) }); export default Dashboard; diff --git a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx index 299f410..285a6e4 100644 --- a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx +++ b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx @@ -13,6 +13,7 @@ import { } 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 { @@ -221,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."); @@ -320,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; } @@ -713,55 +717,30 @@ const NewGrantModal: React.FC<{
{/*BCAN POC div*/}
- - {/*Box div*/} -
- -
- + BCAN POC * + + {/*Box div*/} +
+ +
+ { + setBcanPocName(user.name); + setBcanPocEmail(user.email) }} - className="font-family-helvetica w-full text-gray-700 rounded" - id="grid-city" placeholder="Name" - value={bcanPocName} - onChange={(e) => setBcanPocName(e.target.value)} - /> - setBcanPocEmail(e.target.value)} - /> + /> + +
-
{/*Grant Provider POC div*/}
diff --git a/frontend/src/main-page/header/AccountInfo.tsx b/frontend/src/main-page/header/AccountInfo.tsx index b80d4d4..066c736 100644 --- a/frontend/src/main-page/header/AccountInfo.tsx +++ b/frontend/src/main-page/header/AccountInfo.tsx @@ -33,7 +33,7 @@ const AccountInfo: React.FC = ({ const handleLogoutClick = () => { logoutUser(); setOpenModal(null); - navigate('login'); + navigate('/login') }; return createPortal( @@ -55,18 +55,19 @@ const AccountInfo: React.FC = ({ {role}
- {isAdmin &&
- + {isAdmin && + + }
- }
, document.body 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