From c3b3d8d70ca93c0e8eeb750f3c5d6d5d4ba69a41 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Wed, 20 May 2026 10:19:56 -0700 Subject: [PATCH 1/9] added auth gating and modified donations controller/service for backend fixes --- .../src/donations/donations.controller.ts | 18 ++++++++++++++---- .../backend/src/donations/donations.service.ts | 11 +++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 19657b089..89741ee84 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -9,6 +9,7 @@ import { ParseArrayPipe, Put, Delete, + Req, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { Donation } from './donations.entity'; @@ -23,10 +24,14 @@ import { Role } from '../users/types'; import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; +import { AuthenticatedRequest } from '../auth/authenticated-request'; @Controller('donations') export class DonationsController { - constructor(private donationService: DonationService) {} + constructor( + private donationService: DonationService, + private foodManufacturersService: FoodManufacturersService, + ) {} @Get() async getAllDonations(): Promise { @@ -93,11 +98,16 @@ export class DonationsController { }, }, }) + @Roles(Role.FOODMANUFACTURER) async createDonation( - @Body() - body: CreateDonationDto, + @Req() req: AuthenticatedRequest, + @Body() body: CreateDonationDto, ): Promise { - return this.donationService.create(body); + const manufacturer = await this.foodManufacturersService.findByUserId( + req.user.id, + ); + const foodManufacturerId = manufacturer.foodManufacturerId; + return this.donationService.create(body, foodManufacturerId); } @Patch('/:donationId/fulfill') diff --git a/apps/backend/src/donations/donations.service.ts b/apps/backend/src/donations/donations.service.ts index 88263b43c..92a9a51aa 100644 --- a/apps/backend/src/donations/donations.service.ts +++ b/apps/backend/src/donations/donations.service.ts @@ -58,15 +58,18 @@ export class DonationService { return this.repo.count(); } - async create(donationData: CreateDonationDto): Promise { - validateId(donationData.foodManufacturerId, 'Food Manufacturer'); + async create( + donationData: CreateDonationDto, + foodManufacturerId: number, + ): Promise { + validateId(foodManufacturerId, 'Food Manufacturer'); const manufacturer = await this.manufacturerRepo.findOne({ - where: { foodManufacturerId: donationData.foodManufacturerId }, + where: { foodManufacturerId }, }); if (!manufacturer) { throw new NotFoundException( - `Food Manufacturer ${donationData.foodManufacturerId} not found`, + `Food Manufacturer ${foodManufacturerId} not found`, ); } From da98675b90182bbbcba6d421a40645b0f5d60bc7 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Wed, 20 May 2026 13:18:38 -0700 Subject: [PATCH 2/9] updated tests --- .../donations/donations.controller.spec.ts | 16 ++- .../src/donations/donations.service.spec.ts | 101 ++++++++++-------- apps/backend/src/users/users.service.spec.ts | 2 +- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 20400d45a..8f8e7794b 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -9,7 +9,10 @@ import { DonationStatus, RecurrenceEnum } from './types'; import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donation-item-details.dto'; import { ReplaceDonationItemsDto } from '../donationItems/dtos/create-donation-items.dto'; import { FoodType } from '../donationItems/types'; +import { AuthenticatedRequest } from '../auth/authenticated-request'; +import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; +const mockFoodManufacturersService = mock(); const mockDonationService = mock(); const donation1: Partial = { @@ -35,6 +38,10 @@ describe('DonationsController', () => { provide: DonationService, useValue: mockDonationService, }, + { + provide: FoodManufacturersService, + useValue: mockFoodManufacturersService, + }, ], }).compile(); @@ -100,6 +107,12 @@ describe('DonationsController', () => { ] as CreateDonationItemDto[], }; + const mockReq = { user: { id: 1 } }; + + mockFoodManufacturersService.findByUserId.mockResolvedValueOnce({ + foodManufacturerId: 1, + } as any); + const createdDonation: Partial = { donationId: 1, ...createBody, @@ -112,11 +125,12 @@ describe('DonationsController', () => { ); const result = await controller.createDonation( + mockReq as AuthenticatedRequest, createBody as CreateDonationDto, ); expect(result).toEqual(createdDonation); - expect(mockDonationService.create).toHaveBeenCalledWith(createBody); + expect(mockDonationService.create).toHaveBeenCalledWith(createBody, 1); }); }); diff --git a/apps/backend/src/donations/donations.service.spec.ts b/apps/backend/src/donations/donations.service.spec.ts index 7fc1b0b67..80a035930 100644 --- a/apps/backend/src/donations/donations.service.spec.ts +++ b/apps/backend/src/donations/donations.service.spec.ts @@ -924,11 +924,14 @@ describe('DonationService', () => { ]; it('successfully creates a donation with items', async () => { - const donation = await service.create({ - foodManufacturerId: 1, - recurrence: RecurrenceEnum.NONE, - items: validItems, - }); + const donation = await service.create( + { + foodManufacturerId: 1, + recurrence: RecurrenceEnum.NONE, + items: validItems, + }, + 1, + ); expect(donation).toBeDefined(); expect(donation.donationId).toBeDefined(); @@ -960,13 +963,16 @@ describe('DonationService', () => { const before = new Date(); before.setHours(0, 0, 0, 0); - const donation = await service.create({ - foodManufacturerId: 1, - recurrence: RecurrenceEnum.MONTHLY, - recurrenceFreq: 1, - occurrencesRemaining: 3, - items: validItems, - }); + const donation = await service.create( + { + foodManufacturerId: 1, + recurrence: RecurrenceEnum.MONTHLY, + recurrenceFreq: 1, + occurrencesRemaining: 3, + items: validItems, + }, + 1, + ); const rows = await testDataSource.query( `SELECT next_donation_dates, occurrences_remaining, recurrence, recurrence_freq @@ -997,11 +1003,14 @@ describe('DonationService', () => { it('throws when foodManufacturerId does not exist', async () => { expect( - service.create({ - foodManufacturerId: 99999, - recurrence: RecurrenceEnum.NONE, - items: validItems, - }), + service.create( + { + foodManufacturerId: 99999, + recurrence: RecurrenceEnum.NONE, + items: validItems, + }, + 99999, + ), ).rejects.toThrow( new NotFoundException('Food Manufacturer 99999 not found'), ); @@ -1011,20 +1020,23 @@ describe('DonationService', () => { let donations = await testDataSource.query(`SELECT * FROM donations`); expect(donations).toHaveLength(4); await expect( - service.create({ - foodManufacturerId: 1, - recurrence: RecurrenceEnum.WEEKLY, - repeatOnDays: { - Sunday: false, - Monday: true, - Tuesday: false, - Wednesday: false, - Thursday: false, - Friday: false, - Saturday: false, + service.create( + { + foodManufacturerId: 1, + recurrence: RecurrenceEnum.WEEKLY, + repeatOnDays: { + Sunday: false, + Monday: true, + Tuesday: false, + Wednesday: false, + Thursday: false, + Friday: false, + Saturday: false, + }, + items: validItems, }, - items: validItems, - }), + 1, + ), ).rejects.toThrow( new BadRequestException( 'recurrenceFreq is required for recurring donations', @@ -1040,19 +1052,22 @@ describe('DonationService', () => { expect(donations).toHaveLength(4); await expect( - service.create({ - foodManufacturerId: 1, - recurrence: RecurrenceEnum.NONE, - items: [ - ...validItems, - { - itemName: 'a'.repeat(1000), - quantity: 5, - foodType: FoodType.DAIRY_FREE_ALTERNATIVES, - foodRescue: false, - }, - ], - }), + service.create( + { + foodManufacturerId: 1, + recurrence: RecurrenceEnum.NONE, + items: [ + ...validItems, + { + itemName: 'a'.repeat(1000), + quantity: 5, + foodType: FoodType.DAIRY_FREE_ALTERNATIVES, + foodRescue: false, + }, + ], + }, + 1, + ), ).rejects.toThrow(); donations = await testDataSource.query(`SELECT * FROM donations`); diff --git a/apps/backend/src/users/users.service.spec.ts b/apps/backend/src/users/users.service.spec.ts index a39c7faae..c7868579d 100644 --- a/apps/backend/src/users/users.service.spec.ts +++ b/apps/backend/src/users/users.service.spec.ts @@ -363,7 +363,7 @@ describe('UsersService', () => { ], }; - await donationService.create(createDonationBody as CreateDonationDto); + await donationService.create(createDonationBody as CreateDonationDto, 1); // updating existing request to have a current month requested at date const existingRequest = await foodRequestService.findOne(1); From a95b36ad1fe59dd6f629e8a40b3e70464cd7d7ac Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Thu, 21 May 2026 22:39:51 -0700 Subject: [PATCH 3/9] removed fm id --- apps/backend/src/donations/donations.controller.spec.ts | 1 - apps/backend/src/donations/donations.service.spec.ts | 5 ----- apps/backend/src/donations/dtos/create-donation.dto.ts | 4 ---- apps/backend/src/users/users.service.spec.ts | 1 - 4 files changed, 11 deletions(-) diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 8f8e7794b..486987de8 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -93,7 +93,6 @@ describe('DonationsController', () => { describe('POST /', () => { it('should call donationService.create and return the created donation', async () => { const createBody: Partial = { - foodManufacturerId: 1, recurrence: RecurrenceEnum.MONTHLY, recurrenceFreq: 3, occurrencesRemaining: 2, diff --git a/apps/backend/src/donations/donations.service.spec.ts b/apps/backend/src/donations/donations.service.spec.ts index 80a035930..12cab7b6b 100644 --- a/apps/backend/src/donations/donations.service.spec.ts +++ b/apps/backend/src/donations/donations.service.spec.ts @@ -926,7 +926,6 @@ describe('DonationService', () => { it('successfully creates a donation with items', async () => { const donation = await service.create( { - foodManufacturerId: 1, recurrence: RecurrenceEnum.NONE, items: validItems, }, @@ -965,7 +964,6 @@ describe('DonationService', () => { const donation = await service.create( { - foodManufacturerId: 1, recurrence: RecurrenceEnum.MONTHLY, recurrenceFreq: 1, occurrencesRemaining: 3, @@ -1005,7 +1003,6 @@ describe('DonationService', () => { expect( service.create( { - foodManufacturerId: 99999, recurrence: RecurrenceEnum.NONE, items: validItems, }, @@ -1022,7 +1019,6 @@ describe('DonationService', () => { await expect( service.create( { - foodManufacturerId: 1, recurrence: RecurrenceEnum.WEEKLY, repeatOnDays: { Sunday: false, @@ -1054,7 +1050,6 @@ describe('DonationService', () => { await expect( service.create( { - foodManufacturerId: 1, recurrence: RecurrenceEnum.NONE, items: [ ...validItems, diff --git a/apps/backend/src/donations/dtos/create-donation.dto.ts b/apps/backend/src/donations/dtos/create-donation.dto.ts index 523e6c085..04e7b2b25 100644 --- a/apps/backend/src/donations/dtos/create-donation.dto.ts +++ b/apps/backend/src/donations/dtos/create-donation.dto.ts @@ -65,10 +65,6 @@ export class RepeatOnDaysDto { } export class CreateDonationDto { - @IsInt() - @Min(1) - foodManufacturerId!: number; - @IsNotEmpty() @IsEnum(RecurrenceEnum) recurrence!: RecurrenceEnum; diff --git a/apps/backend/src/users/users.service.spec.ts b/apps/backend/src/users/users.service.spec.ts index c7868579d..b0557649e 100644 --- a/apps/backend/src/users/users.service.spec.ts +++ b/apps/backend/src/users/users.service.spec.ts @@ -349,7 +349,6 @@ describe('UsersService', () => { const now = new Date(); const createDonationBody: Partial = { - foodManufacturerId: 1, recurrence: RecurrenceEnum.MONTHLY, recurrenceFreq: 3, occurrencesRemaining: 2, From b226841fd88d9b8e9fc31fa7866eefd340c1baa6 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Fri, 22 May 2026 22:26:44 -0700 Subject: [PATCH 4/9] refactored alert hook + implemented success/error toast --- .../src/components/foodRequestManagement.tsx | 27 +++++---------- .../forms/assignVolunteersModal.tsx | 4 +-- .../components/forms/changePasswordModal.tsx | 15 +++++---- .../forms/completeRequiredActionsModal.tsx | 2 +- .../components/forms/createNewOrderModal.tsx | 6 ++-- .../components/forms/donationDetailsModal.tsx | 2 +- .../forms/fmCompleteRequiredActionsModal.tsx | 3 ++ .../forms/manufacturerApplicationForm.tsx | 2 +- .../components/forms/newDonationFormModal.tsx | 7 ++-- .../components/forms/orderDetailsModal.tsx | 4 +-- .../forms/orderReceivedActionModal.tsx | 2 +- .../forms/pantryApplicationForm.tsx | 2 +- .../src/components/forms/profileLeftPanel.tsx | 4 ++- .../src/components/forms/requestFormModal.tsx | 30 ++++++----------- .../components/forms/resetPasswordModal.tsx | 10 +++--- .../forms/volunteerCloseRequestModal.tsx | 2 +- .../src/containers/adminDashboard.tsx | 11 ++++--- .../frontend/src/containers/adminDonation.tsx | 2 +- .../src/containers/adminDonationStats.tsx | 8 ++--- .../src/containers/adminOrderManagement.tsx | 2 +- .../src/containers/adminPantryManagement.tsx | 9 ++--- .../containers/approveFoodManufacturers.tsx | 27 +++++---------- .../src/containers/approvePantries.tsx | 27 +++++---------- .../src/containers/donationManagement.tsx | 6 ++-- .../foodManufacturerApplicationDetails.tsx | 4 +-- .../foodManufacturerDonationManagement.tsx | 33 +++++++++---------- apps/frontend/src/containers/formRequests.tsx | 4 +-- apps/frontend/src/containers/loginPage.tsx | 12 ++++--- .../containers/pantryApplicationDetails.tsx | 4 +-- .../src/containers/pantryDashboard.tsx | 6 ++-- .../src/containers/pantryOrderManagement.tsx | 27 +++++---------- apps/frontend/src/containers/profilePage.tsx | 19 +++++++---- .../containers/volunteerAssignedPantries.tsx | 7 ++-- .../src/containers/volunteerDashboard.tsx | 6 ++-- .../src/containers/volunteerManagement.tsx | 27 +++++---------- .../containers/volunteerOrderManagement.tsx | 7 ++-- apps/frontend/src/hooks/alert.ts | 15 ++++++--- 37 files changed, 181 insertions(+), 204 deletions(-) diff --git a/apps/frontend/src/components/foodRequestManagement.tsx b/apps/frontend/src/components/foodRequestManagement.tsx index 1db69e7df..a520ef926 100644 --- a/apps/frontend/src/components/foodRequestManagement.tsx +++ b/apps/frontend/src/components/foodRequestManagement.tsx @@ -51,8 +51,7 @@ const RequestManagement: React.FC = ({ const [selectedCreateOrderRequest, setSelectedCreateOrderRequest] = useState(null); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); const navigate = useNavigate(); const location = useLocation(); @@ -62,9 +61,9 @@ const RequestManagement: React.FC = ({ const data = await fetchData(); setRequests(data); } catch { - setErrorMessage('Error fetching requests'); + setAlertMessage('Error fetching requests', 'error'); } - }, [fetchData, setErrorMessage]); + }, [fetchData, setAlertMessage]); useEffect(() => { loadRequests(); @@ -150,19 +149,11 @@ const RequestManagement: React.FC = ({ Food Request Management - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} @@ -414,7 +405,7 @@ const RequestManagement: React.FC = ({ isOpen={true} onClose={clearCloseRequest} onSuccess={() => { - setSuccessMessage('Request Closed'); + setAlertMessage('Request Closed', 'success'); loadRequests(); }} /> @@ -426,7 +417,7 @@ const RequestManagement: React.FC = ({ isOpen={true} onClose={clearCreateOrder} onSuccess={() => { - setSuccessMessage('Order Created'); + setAlertMessage('Order Created', 'success'); loadRequests(); }} /> diff --git a/apps/frontend/src/components/forms/assignVolunteersModal.tsx b/apps/frontend/src/components/forms/assignVolunteersModal.tsx index a4aeb70ba..332ba34ab 100644 --- a/apps/frontend/src/components/forms/assignVolunteersModal.tsx +++ b/apps/frontend/src/components/forms/assignVolunteersModal.tsx @@ -70,7 +70,7 @@ const AssignVolunteersModal: React.FC = ({ setVolunteers(normalized); setSelectedIds(new Set(assignedIds)); } catch { - setAlertMessage('Error fetching volunteers'); + setAlertMessage('Error fetching volunteers', 'error'); } }; @@ -112,7 +112,7 @@ const AssignVolunteersModal: React.FC = ({ onSuccess(); onClose(); } catch { - setAlertMessage('Error saving volunteer assignments'); + setAlertMessage('Error saving volunteer assignments', 'error'); } }; diff --git a/apps/frontend/src/components/forms/changePasswordModal.tsx b/apps/frontend/src/components/forms/changePasswordModal.tsx index 7893aa340..2409fd0b7 100644 --- a/apps/frontend/src/components/forms/changePasswordModal.tsx +++ b/apps/frontend/src/components/forms/changePasswordModal.tsx @@ -39,12 +39,12 @@ const ChangePasswordModal: React.FC = ({ const handleChangePassword = async () => { if (password.length < 8) { - setAlertMessage('Password must be at least 8 characters'); + setAlertMessage('Password must be at least 8 characters', 'error'); return; } if (password !== confirmPassword) { - setAlertMessage('Passwords must match'); + setAlertMessage('Passwords must match', 'error'); return; } @@ -58,11 +58,14 @@ const ChangePasswordModal: React.FC = ({ onSuccess(); } catch (err: any) { if (err.name === 'LimitExceededException') { - setAlertMessage('Limit exceeded, please try again later'); + setAlertMessage('Limit exceeded, please try again later', 'error'); } else if (err.name === 'NotAuthorizedException') { - setAlertMessage('Failed to update password, old password is incorrect'); + setAlertMessage( + 'Failed to update password, old password is incorrect', + 'error', + ); } else { - setAlertMessage('Failed to update password, please try again'); + setAlertMessage('Failed to update password, please try again', 'error'); } } }; @@ -93,7 +96,7 @@ const ChangePasswordModal: React.FC = ({ open={open} onOpenChange={(e: { open: boolean }) => { if (!e.open) { - setAlertMessage(''); + setAlertMessage('', 'error'); onClose(); } }} diff --git a/apps/frontend/src/components/forms/completeRequiredActionsModal.tsx b/apps/frontend/src/components/forms/completeRequiredActionsModal.tsx index 6c7543d99..c9427f3dd 100644 --- a/apps/frontend/src/components/forms/completeRequiredActionsModal.tsx +++ b/apps/frontend/src/components/forms/completeRequiredActionsModal.tsx @@ -46,7 +46,7 @@ const CompleteRequiredActionsModal: React.FC< await ApiClient.completeOrderAction(order.orderId, action); onActionCompleted(order.orderId, action); } catch { - setAlertMessage('Error completing action. Please try again.'); + setAlertMessage('Error completing action. Please try again.', 'error'); } finally { setLoadingAction(null); } diff --git a/apps/frontend/src/components/forms/createNewOrderModal.tsx b/apps/frontend/src/components/forms/createNewOrderModal.tsx index f08af90e3..763ff6259 100644 --- a/apps/frontend/src/components/forms/createNewOrderModal.tsx +++ b/apps/frontend/src/components/forms/createNewOrderModal.tsx @@ -62,7 +62,7 @@ const CreateNewOrderModal: React.FC = ({ ); setManufacturers(data); } catch { - setAlertMessage('Error fetching manufacturers'); + setAlertMessage('Error fetching manufacturers', 'error'); } }; fetchManufacturers(); @@ -114,7 +114,7 @@ const CreateNewOrderModal: React.FC = ({ ); setManufacturerItems(data); } catch { - setAlertMessage('Error fetching manufacturer items'); + setAlertMessage('Error fetching manufacturer items', 'error'); } }; @@ -140,7 +140,7 @@ const CreateNewOrderModal: React.FC = ({ onClose(); onSuccess(); } catch { - setAlertMessage('Error creating new order'); + setAlertMessage('Error creating new order', 'error'); } }; diff --git a/apps/frontend/src/components/forms/donationDetailsModal.tsx b/apps/frontend/src/components/forms/donationDetailsModal.tsx index 3b61ab4b7..6f5733924 100644 --- a/apps/frontend/src/components/forms/donationDetailsModal.tsx +++ b/apps/frontend/src/components/forms/donationDetailsModal.tsx @@ -36,7 +36,7 @@ const DonationDetailsModal: React.FC = ({ setItems(itemsData); } catch { - setAlertMessage('Error fetching donation details'); + setAlertMessage('Error fetching donation details', 'error'); } }; diff --git a/apps/frontend/src/components/forms/fmCompleteRequiredActionsModal.tsx b/apps/frontend/src/components/forms/fmCompleteRequiredActionsModal.tsx index 20e76d0ff..fca8aeddc 100644 --- a/apps/frontend/src/components/forms/fmCompleteRequiredActionsModal.tsx +++ b/apps/frontend/src/components/forms/fmCompleteRequiredActionsModal.tsx @@ -192,12 +192,14 @@ const FmCompleteRequiredActionsModal: React.FC< if (shippingCost !== '' && !isValidShippingCost(shippingCost)) { setAlertMessage( `Shipping cost for order ${order.orderId} must be a positive number with up to 2 decimal places.`, + 'error', ); return; } if (trackingLink.trim() !== '' && !isValidUrl(trackingLink)) { setAlertMessage( `Tracking link for order ${order.orderId} must be a valid http or https URL.`, + 'error', ); return; } @@ -296,6 +298,7 @@ const FmCompleteRequiredActionsModal: React.FC< msg ? msg.replace(/^orders\.\d+\./, '') : 'Error completing required actions. Please try again.', + 'error', ); } finally { setIsSubmitting(false); diff --git a/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx b/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx index e104581cf..dd44ab55c 100644 --- a/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx +++ b/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx @@ -76,7 +76,7 @@ const ManufacturerApplicationForm: React.FC = () => { useEffect(() => { if (actionData?.error) { - setAlertMessage(actionData.error); + setAlertMessage(actionData.error, 'error'); } }, [actionData, setAlertMessage]); diff --git a/apps/frontend/src/components/forms/newDonationFormModal.tsx b/apps/frontend/src/components/forms/newDonationFormModal.tsx index d9bf495a8..e71957d31 100644 --- a/apps/frontend/src/components/forms/newDonationFormModal.tsx +++ b/apps/frontend/src/components/forms/newDonationFormModal.tsx @@ -204,7 +204,10 @@ const NewDonationFormModal: React.FC = ({ repeatInterval === RecurrenceEnum.WEEKLY && !Object.values(repeatOn).some(Boolean) ) { - setAlertMessage('Please select at least one day for weekly recurrence.'); + setAlertMessage( + 'Please select at least one day for weekly recurrence.', + 'error', + ); return; } @@ -248,7 +251,7 @@ const NewDonationFormModal: React.FC = ({ setRepeatInterval(RecurrenceEnum.NONE); onClose(); } catch { - setAlertMessage('Error submitting new donation'); + setAlertMessage('Error submitting new donation', 'error'); } }; diff --git a/apps/frontend/src/components/forms/orderDetailsModal.tsx b/apps/frontend/src/components/forms/orderDetailsModal.tsx index 2d0b85d62..94e8185ef 100644 --- a/apps/frontend/src/components/forms/orderDetailsModal.tsx +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -52,7 +52,7 @@ const OrderDetailsModal: React.FC = ({ ); setFoodRequest(foodRequestData); } catch { - setAlertMessage('Error fetching food request details'); + setAlertMessage('Error fetching food request details', 'error'); } }; @@ -67,7 +67,7 @@ const OrderDetailsModal: React.FC = ({ const orderDetailsData = await ApiClient.getOrder(orderId); setOrderDetails(orderDetailsData); } catch { - setAlertMessage('Error fetching order details'); + setAlertMessage('Error fetching order details', 'error'); } }; diff --git a/apps/frontend/src/components/forms/orderReceivedActionModal.tsx b/apps/frontend/src/components/forms/orderReceivedActionModal.tsx index a8b002b03..88d12166a 100644 --- a/apps/frontend/src/components/forms/orderReceivedActionModal.tsx +++ b/apps/frontend/src/components/forms/orderReceivedActionModal.tsx @@ -223,7 +223,7 @@ const OrderReceivedActionModal: React.FC = ({ if (words.length <= 250) { setFeedback(e.target.value); } else { - setAlertMessage('Exceeded word limit'); + setAlertMessage('Exceeded word limit', 'error'); } }} /> diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 526de1d1d..66921ad0b 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -111,7 +111,7 @@ const PantryApplicationForm: React.FC = () => { useEffect(() => { if (actionData?.error) { - setAlertMessage(actionData.error); + setAlertMessage(actionData.error, 'error'); } }, [actionData, setAlertMessage]); diff --git a/apps/frontend/src/components/forms/profileLeftPanel.tsx b/apps/frontend/src/components/forms/profileLeftPanel.tsx index bbaa830a9..85b3b5bf3 100644 --- a/apps/frontend/src/components/forms/profileLeftPanel.tsx +++ b/apps/frontend/src/components/forms/profileLeftPanel.tsx @@ -86,7 +86,9 @@ const ProfileLeftPanel: React.FC = ({ setAlertMessage('Password successfully changed')} + onSuccess={() => + setAlertMessage('Password successfully changed', 'success') + } > ); diff --git a/apps/frontend/src/components/forms/requestFormModal.tsx b/apps/frontend/src/components/forms/requestFormModal.tsx index 4de9e6061..3b27ec73e 100644 --- a/apps/frontend/src/components/forms/requestFormModal.tsx +++ b/apps/frontend/src/components/forms/requestFormModal.tsx @@ -42,8 +42,7 @@ const FoodRequestFormModal: React.FC = ({ const [selectedFoodTypes, setSelectedFoodTypes] = useState([]); const [requestedSize, setRequestedSize] = useState(''); const [additionalNotes, setAdditionalNotes] = useState(''); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); const isFormValid = requestedSize !== '' && selectedFoodTypes.length > 0; @@ -58,10 +57,9 @@ const FoodRequestFormModal: React.FC = ({ setRequestedSize(''); setAdditionalNotes(''); } - setErrorMessage(''); - setSuccessMessage(''); + setAlertMessage('', 'error'); } - }, [isOpen, previousRequest, setErrorMessage, setSuccessMessage]); + }, [isOpen, previousRequest, setAlertMessage]); const handleSubmit = async () => { const foodRequestData: CreateFoodRequestBody = { @@ -73,11 +71,11 @@ const FoodRequestFormModal: React.FC = ({ try { await apiClient.createFoodRequest(foodRequestData); - setSuccessMessage('Request submitted'); + setAlertMessage('Request submitted', 'success'); onClose(); onSuccess(); } catch { - setErrorMessage('Request could not be submitted.'); + setAlertMessage('Request could not be submitted.', 'error'); } }; @@ -90,19 +88,11 @@ const FoodRequestFormModal: React.FC = ({ }} closeOnInteractOutside > - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} @@ -283,7 +273,7 @@ const FoodRequestFormModal: React.FC = ({ if (words.length <= 250) { setAdditionalNotes(e.target.value); } else { - setErrorMessage('Exceeded word limit'); + setAlertMessage('Exceeded word limit', 'error'); } }} /> diff --git a/apps/frontend/src/components/forms/resetPasswordModal.tsx b/apps/frontend/src/components/forms/resetPasswordModal.tsx index 557d8fbe5..8b642ebfd 100644 --- a/apps/frontend/src/components/forms/resetPasswordModal.tsx +++ b/apps/frontend/src/components/forms/resetPasswordModal.tsx @@ -43,7 +43,7 @@ const ResetPasswordModal: React.FC = () => { await resetPassword({ username: email }); setStep('new'); } catch { - setAlertMessage('Failed to send verification code'); + setAlertMessage('Failed to send verification code', 'error'); } }; @@ -51,18 +51,18 @@ const ResetPasswordModal: React.FC = () => { try { await resetPassword({ username: email }); } catch { - setAlertMessage('Failed to send verification code'); + setAlertMessage('Failed to send verification code', 'error'); } }; const handleResetPassword = async () => { if (password !== confirmPassword) { - setAlertMessage('Passwords need to match'); + setAlertMessage('Passwords need to match', 'error'); return; } if (password.length < 8) { - setAlertMessage('Password needs to be at least 8 characters'); + setAlertMessage('Password needs to be at least 8 characters', 'error'); return; } @@ -74,7 +74,7 @@ const ResetPasswordModal: React.FC = () => { }); navigate(ROUTES.LOGIN); } catch { - setAlertMessage('Failed to set new password'); + setAlertMessage('Failed to set new password', 'error'); } }; diff --git a/apps/frontend/src/components/forms/volunteerCloseRequestModal.tsx b/apps/frontend/src/components/forms/volunteerCloseRequestModal.tsx index 8d36592e4..792fbe083 100644 --- a/apps/frontend/src/components/forms/volunteerCloseRequestModal.tsx +++ b/apps/frontend/src/components/forms/volunteerCloseRequestModal.tsx @@ -34,7 +34,7 @@ const VolunteerCloseRequestActionModal: React.FC< onClose(); onSuccess(); } catch { - setAlertMessage('Error completing action. Please try again.'); + setAlertMessage('Error completing action. Please try again.', 'error'); } }; diff --git a/apps/frontend/src/containers/adminDashboard.tsx b/apps/frontend/src/containers/adminDashboard.tsx index 7ce01914e..0cced5f81 100644 --- a/apps/frontend/src/containers/adminDashboard.tsx +++ b/apps/frontend/src/containers/adminDashboard.tsx @@ -34,7 +34,7 @@ const AdminDashboard: React.FC = () => { await ApiClient.getRecentPendingApplications(); setPendingApplications(pendingApplications); } catch { - setAlertMessage('Error fetching pending applications'); + setAlertMessage('Error fetching pending applications', 'error'); } }; @@ -48,7 +48,7 @@ const AdminDashboard: React.FC = () => { const recentOrders = sortedOrders.slice(0, 2); setRecentOrders(recentOrders); } catch { - setAlertMessage('Error fetching orders'); + setAlertMessage('Error fetching orders', 'error'); } }; @@ -62,7 +62,7 @@ const AdminDashboard: React.FC = () => { const recentDonations = sortedDonations.slice(0, 2); setRecentDonations(recentDonations); } catch { - setAlertMessage('Error fetching donations'); + setAlertMessage('Error fetching donations', 'error'); } }; @@ -72,7 +72,10 @@ const AdminDashboard: React.FC = () => { user = await ApiClient.getMe(); setCurrentUser(user); } catch { - setAlertMessage('Authentication error. Please log in and try again.'); + setAlertMessage( + 'Authentication error. Please log in and try again.', + 'error', + ); return; } }; diff --git a/apps/frontend/src/containers/adminDonation.tsx b/apps/frontend/src/containers/adminDonation.tsx index 62d547bf2..1256bcca6 100644 --- a/apps/frontend/src/containers/adminDonation.tsx +++ b/apps/frontend/src/containers/adminDonation.tsx @@ -44,7 +44,7 @@ const AdminDonation: React.FC = () => { const data = await ApiClient.getAllDonations(); setDonations(data); } catch { - setAlertMessage('Error fetching donations'); + setAlertMessage('Error fetching donations', 'error'); } }; fetchDonations(); diff --git a/apps/frontend/src/containers/adminDonationStats.tsx b/apps/frontend/src/containers/adminDonationStats.tsx index 22f1d5c93..d877a4b82 100644 --- a/apps/frontend/src/containers/adminDonationStats.tsx +++ b/apps/frontend/src/containers/adminDonationStats.tsx @@ -46,14 +46,14 @@ const AdminDonationStats: React.FC = () => { const names = await ApiClient.getApprovedPantryNames(); setPantryNameOptions(names); } catch { - setAlertMessage('Error fetching pantry names'); + setAlertMessage('Error fetching pantry names', 'error'); } try { const years = await ApiClient.getPantryOrderYears(); setAvailableYears(years); } catch { - setAlertMessage('Error fetching available years'); + setAlertMessage('Error fetching available years', 'error'); } }; fetchInitialData(); @@ -70,7 +70,7 @@ const AdminDonationStats: React.FC = () => { ); setTotalStats(stats); } catch { - setAlertMessage('Error fetching total stats'); + setAlertMessage('Error fetching total stats', 'error'); } }; fetchTotalStats(); @@ -86,7 +86,7 @@ const AdminDonationStats: React.FC = () => { }); setPantryStats(stats); } catch { - setAlertMessage('Error fetching pantry stats'); + setAlertMessage('Error fetching pantry stats', 'error'); } }; fetchStats(); diff --git a/apps/frontend/src/containers/adminOrderManagement.tsx b/apps/frontend/src/containers/adminOrderManagement.tsx index 84e09c3f8..df7cbad90 100644 --- a/apps/frontend/src/containers/adminOrderManagement.tsx +++ b/apps/frontend/src/containers/adminOrderManagement.tsx @@ -133,7 +133,7 @@ const AdminOrderManagement: React.FC = () => { }; setCurrentPages(initialPages); } catch { - setAlertMessage('Error fetching orders'); + setAlertMessage('Error fetching orders', 'error'); } }; diff --git a/apps/frontend/src/containers/adminPantryManagement.tsx b/apps/frontend/src/containers/adminPantryManagement.tsx index e807b5ce4..dfbdb3e68 100644 --- a/apps/frontend/src/containers/adminPantryManagement.tsx +++ b/apps/frontend/src/containers/adminPantryManagement.tsx @@ -38,7 +38,6 @@ const AdminPantryManagement: React.FC = () => { const [selectedPantries, setSelectedPantries] = useState([]); const [alertState, setAlertMessage] = useAlert(); - const [isAlertSuccess, setIsAlertSuccess] = useState(false); const [isFilterOpen, setIsFilterOpen] = useState(false); const [ selectedPantryToAssignVolunteers, @@ -52,8 +51,7 @@ const AdminPantryManagement: React.FC = () => { const allApprovedPantries = await ApiClient.getApprovedPantries(); setPantries(allApprovedPantries); } catch { - setIsAlertSuccess(false); - setAlertMessage('Error fetching pantries'); + setAlertMessage('Error fetching pantries', 'error'); } }; @@ -62,8 +60,7 @@ const AdminPantryManagement: React.FC = () => { }, [setAlertMessage]); const handleAssignVolunteersSuccess = () => { - setIsAlertSuccess(true); - setAlertMessage('Successfully assigned volunteers'); + setAlertMessage('Successfully assigned volunteers', 'success'); fetchPantries(); }; @@ -110,7 +107,7 @@ const AdminPantryManagement: React.FC = () => { )} diff --git a/apps/frontend/src/containers/approveFoodManufacturers.tsx b/apps/frontend/src/containers/approveFoodManufacturers.tsx index 786e13137..b07505bb5 100644 --- a/apps/frontend/src/containers/approveFoodManufacturers.tsx +++ b/apps/frontend/src/containers/approveFoodManufacturers.tsx @@ -36,8 +36,7 @@ const ApproveFoodManufacturers: React.FC = () => { const [currentPage, setCurrentPage] = useState(1); const [isFilterOpen, setIsFilterOpen] = useState(false); const [searchParams, setSearchParams] = useSearchParams(); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); useEffect(() => { const fetchFoodManufacturers = async () => { @@ -45,12 +44,12 @@ const ApproveFoodManufacturers: React.FC = () => { const data = await ApiClient.getAllPendingFoodManufacturers(); setFoodManufacturers(data); } catch { - setErrorMessage('Error fetching food manufacturers'); + setAlertMessage('Error fetching food manufacturers', 'error'); } }; fetchFoodManufacturers(); - }, [setErrorMessage]); + }, [setAlertMessage]); useEffect(() => { setCurrentPage(1); @@ -116,29 +115,21 @@ const ApproveFoodManufacturers: React.FC = () => { ? `${name} - Application Accepted` : `${name} - Application Rejected`; - setSuccessMessage(message); + setAlertMessage(message, 'success'); setSearchParams({}); } - }, [searchParams, setSearchParams, setErrorMessage, setSuccessMessage]); + }, [searchParams, setSearchParams, setAlertMessage]); return ( Application Review - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} diff --git a/apps/frontend/src/containers/approvePantries.tsx b/apps/frontend/src/containers/approvePantries.tsx index 75dd9245a..5a7ac4ebe 100644 --- a/apps/frontend/src/containers/approvePantries.tsx +++ b/apps/frontend/src/containers/approvePantries.tsx @@ -32,8 +32,7 @@ const ApprovePantries: React.FC = () => { const [currentPage, setCurrentPage] = useState(1); const [isFilterOpen, setIsFilterOpen] = useState(false); const [searchParams, setSearchParams] = useSearchParams(); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); useEffect(() => { const fetchPantries = async () => { @@ -41,12 +40,12 @@ const ApprovePantries: React.FC = () => { const data = await ApiClient.getAllPendingPantries(); setPantries(data); } catch { - setErrorMessage('Error fetching pantries'); + setAlertMessage('Error fetching pantries', 'error'); } }; fetchPantries(); - }, [setErrorMessage]); + }, [setAlertMessage]); useEffect(() => { setCurrentPage(1); @@ -107,29 +106,21 @@ const ApprovePantries: React.FC = () => { ? `${name} - Application Accepted` : `${name} - Application Rejected`; - setSuccessMessage(message); + setAlertMessage(message, 'success'); setSearchParams({}); } - }, [searchParams, setSearchParams, setErrorMessage, setSuccessMessage]); + }, [searchParams, setSearchParams, setAlertMessage]); return ( Application Review - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} diff --git a/apps/frontend/src/containers/donationManagement.tsx b/apps/frontend/src/containers/donationManagement.tsx index e7d038f4c..f0ba1cedb 100644 --- a/apps/frontend/src/containers/donationManagement.tsx +++ b/apps/frontend/src/containers/donationManagement.tsx @@ -43,7 +43,7 @@ const DonationManagement: React.FC = () => { }); setDonations(sortedDonations); } catch { - setAlertMessage('Error fetching donations'); + setAlertMessage('Error fetching donations', 'error'); } }; @@ -62,7 +62,7 @@ const DonationManagement: React.FC = () => { })); }); } catch { - setAlertMessage('Error fetching donation items'); + setAlertMessage('Error fetching donation items', 'error'); } }; @@ -82,7 +82,7 @@ const DonationManagement: React.FC = () => { await ApiClient.fulfillDonation(donationId); fetchDonations(); } catch { - setAlertMessage('Failed to fulfill donation'); + setAlertMessage('Failed to fulfill donation', 'error'); } }; diff --git a/apps/frontend/src/containers/foodManufacturerApplicationDetails.tsx b/apps/frontend/src/containers/foodManufacturerApplicationDetails.tsx index f60646c8b..27064eeb5 100644 --- a/apps/frontend/src/containers/foodManufacturerApplicationDetails.tsx +++ b/apps/frontend/src/containers/foodManufacturerApplicationDetails.tsx @@ -178,7 +178,7 @@ const FoodManufacturerApplicationDetails: React.FC = () => { application.foodManufacturerName, ); } catch { - setAlertMessage('Error approving application'); + setAlertMessage('Error approving application', 'error'); } } }; @@ -198,7 +198,7 @@ const FoodManufacturerApplicationDetails: React.FC = () => { application.foodManufacturerName, ); } catch { - setAlertMessage('Error denying application'); + setAlertMessage('Error denying application', 'error'); } } }; diff --git a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx index a5b1b2a85..4359f6349 100644 --- a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx +++ b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx @@ -22,8 +22,7 @@ import { useAlert } from '../hooks/alert'; const MAX_PER_STATUS = 5; const FoodManufacturerDonationManagement: React.FC = () => { - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); const [isLogDonationOpen, setIsLogDonationOpen] = useState(false); const [manufacturerId, setManufacturerId] = useState(null); const [selectedActionDonation, setSelectedActionDonation] = @@ -84,10 +83,15 @@ const FoodManufacturerDonationManagement: React.FC = () => { }; setCurrentPages(initialPages); } catch { - setErrorMessage('Error fetching donations'); + setAlertMessage('Error fetching donations', 'error'); } }; + const handleLogNewDonationSuccess = () => { + setAlertMessage('Successfully logged new donation', 'success'); + if (manufacturerId !== null) fetchDonations(manufacturerId); + }; + // On page load, get the food manufacturer id and all appropriate donations useEffect(() => { const init = async () => { @@ -96,7 +100,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { setManufacturerId(fmId); await fetchDonations(fmId); } catch { - setErrorMessage('Error initializing donation management'); + setAlertMessage('Error initializing donation management', 'error'); } }; init(); @@ -111,19 +115,11 @@ const FoodManufacturerDonationManagement: React.FC = () => { return ( - {errorAlertState && ( - - )} - {successAlertState && ( + {alertState && ( )} @@ -151,7 +147,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { {isLogDonationOpen && manufacturerId !== null && ( fetchDonations(manufacturerId)} + onDonationSuccess={handleLogNewDonationSuccess} isOpen={isLogDonationOpen} onClose={() => setIsLogDonationOpen(false)} /> @@ -165,8 +161,9 @@ const FoodManufacturerDonationManagement: React.FC = () => { onSuccess={() => { setSelectedActionDonation(null); if (manufacturerId !== null) fetchDonations(manufacturerId); - setSuccessMessage( + setAlertMessage( 'Your details have been saved. Actions are complete once all shipment and item details are confirmed.', + 'success', ); }} /> diff --git a/apps/frontend/src/containers/formRequests.tsx b/apps/frontend/src/containers/formRequests.tsx index 5dc980bc7..ce25c5665 100644 --- a/apps/frontend/src/containers/formRequests.tsx +++ b/apps/frontend/src/containers/formRequests.tsx @@ -58,10 +58,10 @@ const FormRequests: React.FC = () => { setPreviousRequest(sortedData[0]); } } catch { - setAlertMessage('Error fetching requests'); + setAlertMessage('Error fetching requests', 'error'); } } else { - setAlertMessage('No pantry associated with this account.'); + setAlertMessage('No pantry associated with this account.', 'error'); } }, [setAlertMessage]); diff --git a/apps/frontend/src/containers/loginPage.tsx b/apps/frontend/src/containers/loginPage.tsx index d0933c365..bedd8cb7e 100644 --- a/apps/frontend/src/containers/loginPage.tsx +++ b/apps/frontend/src/containers/loginPage.tsx @@ -78,7 +78,10 @@ const LoginPage: React.FC = () => { error.name === 'NotAuthorizedException' || error.name === 'UserNotFoundException' ) { - setAlertMessage('Incorrect email or password. Please try again.'); + setAlertMessage( + 'Incorrect email or password. Please try again.', + 'error', + ); return; } } @@ -86,6 +89,7 @@ const LoginPage: React.FC = () => { navigator.onLine ? 'Login failed. The server may be unavailable. Please try again later.' : 'No internet connection. Please check your network and try again.', + 'error', ); } }; @@ -93,11 +97,11 @@ const LoginPage: React.FC = () => { // Sets the new password for the first time const handleSetNewPassword = async () => { if (newPassword !== confirmNewPassword) { - setAlertMessage('Passwords need to match'); + setAlertMessage('Passwords need to match', 'error'); return; } if (newPassword.length < 8) { - setAlertMessage('Password needs to be at least 8 characters'); + setAlertMessage('Password needs to be at least 8 characters', 'error'); return; } @@ -107,7 +111,7 @@ const LoginPage: React.FC = () => { await fetchAuthSession({ forceRefresh: true }); navigate(from, { replace: true }); } catch { - setAlertMessage('Failed to set new password'); + setAlertMessage('Failed to set new password', 'error'); } }; diff --git a/apps/frontend/src/containers/pantryApplicationDetails.tsx b/apps/frontend/src/containers/pantryApplicationDetails.tsx index 5838b7336..dfeb44c3d 100644 --- a/apps/frontend/src/containers/pantryApplicationDetails.tsx +++ b/apps/frontend/src/containers/pantryApplicationDetails.tsx @@ -179,7 +179,7 @@ const PantryApplicationDetails: React.FC = () => { application.pantryName, ); } catch { - setAlertMessage('Error approving application'); + setAlertMessage('Error approving application', 'error'); } } }; @@ -196,7 +196,7 @@ const PantryApplicationDetails: React.FC = () => { application.pantryName, ); } catch { - setAlertMessage('Error denying application'); + setAlertMessage('Error denying application', 'error'); } } }; diff --git a/apps/frontend/src/containers/pantryDashboard.tsx b/apps/frontend/src/containers/pantryDashboard.tsx index 182c9a9ef..d3cca1d86 100644 --- a/apps/frontend/src/containers/pantryDashboard.tsx +++ b/apps/frontend/src/containers/pantryDashboard.tsx @@ -31,7 +31,7 @@ const PantryDashboard: React.FC = () => { const pantryData = await ApiClient.getPantry(pantryId); setPantry(pantryData); } catch { - setAlertMessage('Error fetching pantry information'); + setAlertMessage('Error fetching pantry information', 'error'); return; } @@ -44,7 +44,7 @@ const PantryDashboard: React.FC = () => { ); setRecentFoodRequests(sortedFoodRequests.slice(0, 2)); } catch { - setAlertMessage('Error fetching pantry food requests'); + setAlertMessage('Error fetching pantry food requests', 'error'); } try { @@ -55,7 +55,7 @@ const PantryDashboard: React.FC = () => { ); setRecentOrders(sortedOrders.slice(0, 4)); } catch { - setAlertMessage('Error fetching orders'); + setAlertMessage('Error fetching orders', 'error'); } }; fetchDashboardData(); diff --git a/apps/frontend/src/containers/pantryOrderManagement.tsx b/apps/frontend/src/containers/pantryOrderManagement.tsx index c231b87a4..9d7176f4b 100644 --- a/apps/frontend/src/containers/pantryOrderManagement.tsx +++ b/apps/frontend/src/containers/pantryOrderManagement.tsx @@ -56,8 +56,7 @@ const PantryOrderManagement: React.FC = () => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); // State to hold filter state per status type FilterState = { @@ -110,9 +109,9 @@ const PantryOrderManagement: React.FC = () => { }; setCurrentPages(initialPages); } catch { - setErrorMessage('Failed to fetch orders'); + setAlertMessage('Failed to fetch orders', 'error'); } - }, [setErrorMessage]); + }, [setAlertMessage]); useEffect(() => { fetchOrders(); @@ -149,19 +148,11 @@ const PantryOrderManagement: React.FC = () => { Order Management - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} @@ -228,10 +219,10 @@ const PantryOrderManagement: React.FC = () => { onClose={() => setSelectedActionOrder(null)} onSuccess={() => { fetchOrders(); - setSuccessMessage('Delivery Confirmed'); + setAlertMessage('Delivery Confirmed', 'success'); }} onError={() => { - setErrorMessage('Delivery could not be confirmed.'); + setAlertMessage('Delivery could not be confirmed.', 'error'); }} /> )} diff --git a/apps/frontend/src/containers/profilePage.tsx b/apps/frontend/src/containers/profilePage.tsx index 2079f7167..755fc482a 100644 --- a/apps/frontend/src/containers/profilePage.tsx +++ b/apps/frontend/src/containers/profilePage.tsx @@ -33,7 +33,7 @@ const ProfilePage: React.FC = () => { const pantry = await ApiClient.getPantry(pantryId); setOrgName(pantry.pantryName); } catch { - setAlertMessage('Failed to fetch pantry data.'); + setAlertMessage('Failed to fetch pantry data.', 'error'); } } else if (user.role === Role.FOODMANUFACTURER) { try { @@ -42,11 +42,14 @@ const ProfilePage: React.FC = () => { const fm = await ApiClient.getFoodManufacturer(foodManufacturerId); setOrgName(fm.foodManufacturerName); } catch { - setAlertMessage('Failed to fetch food manufacturer data.'); + setAlertMessage('Failed to fetch food manufacturer data.', 'error'); } } } catch { - setAlertMessage('Authentication error. Please log in and try again.'); + setAlertMessage( + 'Authentication error. Please log in and try again.', + 'error', + ); } finally { setIsLoading(false); } @@ -56,7 +59,7 @@ const ProfilePage: React.FC = () => { const handleSave = async (fields: UpdateProfileFields): Promise => { if (!profile) { - setAlertMessage('Profile not found.'); + setAlertMessage('Profile not found.', 'error'); return false; } @@ -68,14 +71,18 @@ const ProfilePage: React.FC = () => { if (axios.isAxiosError(error)) { const status = error.response?.status; if (status === 400 || status === 404) { - setAlertMessage(error.response?.data?.message); + setAlertMessage(error.response?.data?.message, 'error'); } else { setAlertMessage( 'Profile unable to be edited. Please try again later.', + 'error', ); } } else { - setAlertMessage('An unexpected error occurred. Please try again.'); + setAlertMessage( + 'An unexpected error occurred. Please try again.', + 'error', + ); } return false; } diff --git a/apps/frontend/src/containers/volunteerAssignedPantries.tsx b/apps/frontend/src/containers/volunteerAssignedPantries.tsx index 53e37450a..dfae4710d 100644 --- a/apps/frontend/src/containers/volunteerAssignedPantries.tsx +++ b/apps/frontend/src/containers/volunteerAssignedPantries.tsx @@ -38,7 +38,10 @@ const AssignedPantries: React.FC = () => { user = await ApiClient.getMe(); userId = user.id; } catch { - setAlertMessage('Authentication error. Please log in and try again.'); + setAlertMessage( + 'Authentication error. Please log in and try again.', + 'error', + ); setIsLoading(false); return; } @@ -47,7 +50,7 @@ const AssignedPantries: React.FC = () => { const data = await ApiClient.getVolunteerPantries(userId); setPantries(data); } catch { - setAlertMessage('Error fetching assigned pantries'); + setAlertMessage('Error fetching assigned pantries', 'error'); } finally { setIsLoading(false); } diff --git a/apps/frontend/src/containers/volunteerDashboard.tsx b/apps/frontend/src/containers/volunteerDashboard.tsx index c33843670..d04205b06 100644 --- a/apps/frontend/src/containers/volunteerDashboard.tsx +++ b/apps/frontend/src/containers/volunteerDashboard.tsx @@ -25,7 +25,7 @@ const VolunteerDashboard: React.FC = () => { const currentUser = await ApiClient.getMe(); setUser(currentUser); } catch { - setAlertMessage('Error fetching user information'); + setAlertMessage('Error fetching user information', 'error'); return; } @@ -38,14 +38,14 @@ const VolunteerDashboard: React.FC = () => { ); setRecentFoodRequests(sorted.slice(0, 2)); } catch { - setAlertMessage('Error fetching food requests'); + setAlertMessage('Error fetching food requests', 'error'); } try { const orders = await ApiClient.getVolunteerRecentOrders(); setRecentOrders(orders); } catch { - setAlertMessage('Error fetching orders'); + setAlertMessage('Error fetching orders', 'error'); } }; fetchDashboardData(); diff --git a/apps/frontend/src/containers/volunteerManagement.tsx b/apps/frontend/src/containers/volunteerManagement.tsx index 54707fe94..2142973a6 100644 --- a/apps/frontend/src/containers/volunteerManagement.tsx +++ b/apps/frontend/src/containers/volunteerManagement.tsx @@ -26,8 +26,7 @@ const VolunteerManagement: React.FC = () => { const [volunteers, setVolunteers] = useState([]); const [searchName, setSearchName] = useState(''); - const [errorAlertState, setErrorMessage] = useAlert(); - const [successAlertState, setSuccessMessage] = useAlert(); + const [alertState, setAlertMessage] = useAlert(); const pageSize = 8; @@ -37,12 +36,12 @@ const VolunteerManagement: React.FC = () => { const allVolunteers = await ApiClient.getVolunteers(); setVolunteers(allVolunteers); } catch { - setErrorMessage('Error fetching volunteers'); + setAlertMessage('Error fetching volunteers', 'error'); } }; fetchVolunteers(); - }, [setErrorMessage]); + }, [setAlertMessage]); useEffect(() => { setCurrentPage(1); @@ -69,19 +68,11 @@ const VolunteerManagement: React.FC = () => { Volunteer Management - {errorAlertState && ( + {alertState && ( - )} - {successAlertState && ( - )} @@ -115,10 +106,10 @@ const VolunteerManagement: React.FC = () => { { - setSuccessMessage('Volunteer added.'); + setAlertMessage('Volunteer added.', 'success'); }} onSubmitFail={() => { - setErrorMessage('Volunteer could not be added.'); + setAlertMessage('Volunteer could not be added.', 'error'); }} /> diff --git a/apps/frontend/src/containers/volunteerOrderManagement.tsx b/apps/frontend/src/containers/volunteerOrderManagement.tsx index 591981416..2197e5e5c 100644 --- a/apps/frontend/src/containers/volunteerOrderManagement.tsx +++ b/apps/frontend/src/containers/volunteerOrderManagement.tsx @@ -125,7 +125,10 @@ const VolunteerOrderManagement: React.FC = () => { userId = user.id; setCurrentUser(user); } catch { - setAlertMessage('Authentication error. Please log in and try again.'); + setAlertMessage( + 'Authentication error. Please log in and try again.', + 'error', + ); setIsLoading(false); return; } @@ -162,7 +165,7 @@ const VolunteerOrderManagement: React.FC = () => { }; setCurrentPages(initialPages); } catch { - setAlertMessage('Error fetching assigned orders'); + setAlertMessage('Error fetching assigned orders', 'error'); } finally { setIsLoading(false); } diff --git a/apps/frontend/src/hooks/alert.ts b/apps/frontend/src/hooks/alert.ts index 0a2c609b3..034c8462a 100644 --- a/apps/frontend/src/hooks/alert.ts +++ b/apps/frontend/src/hooks/alert.ts @@ -2,16 +2,23 @@ import { useCallback, useRef, useState } from 'react'; export interface AlertState { message: string; + status: 'success' | 'error'; id: number; } -export function useAlert(): [AlertState | null, (message: string) => void] { +export function useAlert(): [ + AlertState | null, + (message: string, status: 'success' | 'error') => void, +] { const [alertState, setAlertState] = useState(null); const idRef = useRef(0); - const setAlertMessage = useCallback((message: string) => { - setAlertState({ message, id: idRef.current++ }); - }, []); + const setAlertMessage = useCallback( + (message: string, status: 'success' | 'error') => { + setAlertState({ message, status, id: idRef.current++ }); + }, + [], + ); return [alertState, setAlertMessage]; } From d976889f9d398f11488fe29e468fe1b1bceec7a4 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Fri, 22 May 2026 22:45:33 -0700 Subject: [PATCH 5/9] removed remaining references to FM ID --- apps/backend/src/donations/donations.controller.ts | 1 - apps/frontend/src/components/forms/newDonationFormModal.tsx | 3 --- apps/frontend/src/containers/donationManagement.tsx | 1 - .../src/containers/foodManufacturerDonationManagement.tsx | 1 - apps/frontend/src/types/types.ts | 1 - 5 files changed, 7 deletions(-) diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 89741ee84..3b9a05795 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -56,7 +56,6 @@ export class DonationsController { schema: { type: 'object', properties: { - foodManufacturerId: { type: 'integer', example: 1 }, recurrence: { type: 'string', enum: Object.values(RecurrenceEnum), diff --git a/apps/frontend/src/components/forms/newDonationFormModal.tsx b/apps/frontend/src/components/forms/newDonationFormModal.tsx index e71957d31..034ec1603 100644 --- a/apps/frontend/src/components/forms/newDonationFormModal.tsx +++ b/apps/frontend/src/components/forms/newDonationFormModal.tsx @@ -32,7 +32,6 @@ import { useAlert } from '../../hooks/alert'; import { useModalBodyCleanup } from '../../hooks/modalBodyCleanup'; interface NewDonationFormModalProps { - foodManufacturerId: number; onDonationSuccess: () => void; isOpen: boolean; onClose: () => void; @@ -104,7 +103,6 @@ const getFirstValidationError = ( }; const NewDonationFormModal: React.FC = ({ - foodManufacturerId, onDonationSuccess, isOpen, onClose, @@ -212,7 +210,6 @@ const NewDonationFormModal: React.FC = ({ } const donationBody: CreateDonationDto = { - foodManufacturerId, recurrenceFreq: isRecurring ? parseInt(repeatEvery) : undefined, recurrence: isRecurring ? repeatInterval : RecurrenceEnum.NONE, repeatOnDays: diff --git a/apps/frontend/src/containers/donationManagement.tsx b/apps/frontend/src/containers/donationManagement.tsx index f0ba1cedb..9cb25c49d 100644 --- a/apps/frontend/src/containers/donationManagement.tsx +++ b/apps/frontend/src/containers/donationManagement.tsx @@ -103,7 +103,6 @@ const DonationManagement: React.FC = () => { {manufacturerId !== null && ( { {isLogDonationOpen && manufacturerId !== null && ( setIsLogDonationOpen(false)} diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 2a1592b91..0fe34520c 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -465,7 +465,6 @@ export interface CreateFoodRequestBody { } export interface CreateDonationDto { - foodManufacturerId: number; recurrenceFreq?: number; recurrence: RecurrenceEnum; repeatOnDays?: RepeatOnState; From 6c0684b5870ceff7382120beb729667c68647890 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Sat, 23 May 2026 12:33:47 -0700 Subject: [PATCH 6/9] fixed flaky test --- .../src/foodManufacturers/manufacturers.service.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts b/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts index 8074c2981..ea69d27eb 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts @@ -611,9 +611,12 @@ describe('FoodManufacturersService', () => { it('returns next two upcoming donation reminders from same donation', async () => { const futureDate1 = new Date(); + futureDate1.setMilliseconds(0); futureDate1.setDate(futureDate1.getDate() + 30); clampDay(futureDate1); + const futureDate2 = new Date(); + futureDate2.setMilliseconds(0); futureDate2.setDate(futureDate2.getDate() + 60); clampDay(futureDate2); From ee87af2a103ec7a13e0fa4f792ab32dce5cad0b1 Mon Sep 17 00:00:00 2001 From: Heidi Jiang Date: Fri, 29 May 2026 16:57:33 -0700 Subject: [PATCH 7/9] requested change: refactored alerts + standardized status, note: will edit FM + pantry application details in a separate commit --- .../frontend/src/components/floatingAlert.tsx | 5 +- .../src/components/foodRequestManagement.tsx | 9 +-- .../components/forms/addNewVolunteerModal.tsx | 35 ++++++----- .../forms/assignVolunteersModal.tsx | 12 ++-- .../components/forms/changePasswordModal.tsx | 23 ++++--- .../forms/completeRequiredActionsModal.tsx | 8 ++- .../components/forms/createNewOrderModal.tsx | 11 ++-- .../components/forms/donationDetailsModal.tsx | 5 +- .../forms/editableFMApplication.tsx | 55 ++++++++++++----- .../forms/editablePantryApplication.tsx | 60 ++++++++++++++----- .../forms/fmCompleteRequiredActionsModal.tsx | 9 +-- .../forms/manufacturerApplicationForm.tsx | 6 +- .../components/forms/newDonationFormModal.tsx | 7 ++- .../components/forms/orderDetailsModal.tsx | 11 ++-- .../forms/orderReceivedActionModal.tsx | 12 ++-- .../forms/pantryApplicationForm.tsx | 6 +- .../src/components/forms/profileLeftPanel.tsx | 5 +- .../src/components/forms/requestFormModal.tsx | 10 ++-- .../components/forms/resetPasswordModal.tsx | 16 +++-- .../forms/volunteerCloseRequestModal.tsx | 9 ++- .../src/containers/adminDashboard.tsx | 11 ++-- .../frontend/src/containers/adminDonation.tsx | 6 +- .../src/containers/adminDonationStats.tsx | 12 ++-- .../src/containers/adminOrderManagement.tsx | 6 +- .../src/containers/adminPantryManagement.tsx | 8 +-- .../containers/approveFoodManufacturers.tsx | 8 +-- .../src/containers/approvePantries.tsx | 8 +-- .../src/containers/donationManagement.tsx | 10 ++-- .../foodManufacturerApplicationDetails.tsx | 12 ++-- .../foodManufacturerDonationManagement.tsx | 15 +++-- apps/frontend/src/containers/formRequests.tsx | 10 +++- apps/frontend/src/containers/loginPage.tsx | 16 +++-- .../containers/pantryApplicationDetails.tsx | 8 +-- .../src/containers/pantryDashboard.tsx | 12 ++-- .../src/containers/pantryOrderManagement.tsx | 13 ++-- apps/frontend/src/containers/profilePage.tsx | 21 ++++--- .../containers/volunteerAssignedPantries.tsx | 8 +-- .../src/containers/volunteerDashboard.tsx | 15 +++-- .../src/containers/volunteerManagement.tsx | 13 ++-- .../containers/volunteerOrderManagement.tsx | 7 ++- apps/frontend/src/hooks/alert.ts | 7 ++- apps/frontend/src/types/types.ts | 5 ++ 42 files changed, 336 insertions(+), 209 deletions(-) diff --git a/apps/frontend/src/components/floatingAlert.tsx b/apps/frontend/src/components/floatingAlert.tsx index 8e45294da..52e0c22bb 100644 --- a/apps/frontend/src/components/floatingAlert.tsx +++ b/apps/frontend/src/components/floatingAlert.tsx @@ -1,9 +1,10 @@ import { Alert } from '@chakra-ui/react'; import { useEffect, useState } from 'react'; +import { AlertStatus } from '../types/types'; type FloatingAlertProps = { message?: string | null; - status?: 'info' | 'error'; + status?: AlertStatus; timeout?: number; }; @@ -35,7 +36,7 @@ export function FloatingAlert({ return ( Promise; @@ -61,7 +62,7 @@ const RequestManagement: React.FC = ({ const data = await fetchData(); setRequests(data); } catch { - setAlertMessage('Error fetching requests', 'error'); + setAlertMessage('Error fetching requests', AlertStatus.ERROR); } }, [fetchData, setAlertMessage]); @@ -153,7 +154,7 @@ const RequestManagement: React.FC = ({ )} @@ -405,7 +406,7 @@ const RequestManagement: React.FC = ({ isOpen={true} onClose={clearCloseRequest} onSuccess={() => { - setAlertMessage('Request Closed', 'success'); + setAlertMessage('Request Closed', AlertStatus.INFO); loadRequests(); }} /> @@ -417,7 +418,7 @@ const RequestManagement: React.FC = ({ isOpen={true} onClose={clearCreateOrder} onSuccess={() => { - setAlertMessage('Order Created', 'success'); + setAlertMessage('Order Created', AlertStatus.INFO); loadRequests(); }} /> diff --git a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx index 5a85fa3be..e0dcafd34 100644 --- a/apps/frontend/src/components/forms/addNewVolunteerModal.tsx +++ b/apps/frontend/src/components/forms/addNewVolunteerModal.tsx @@ -9,11 +9,13 @@ import { Box, } from '@chakra-ui/react'; import { useState } from 'react'; -import { Role, UserDto } from '../../types/types'; +import { AlertStatus, Role, UserDto } from '../../types/types'; import ApiClient from '@api/apiClient'; import { USPhoneInput } from './usPhoneInput'; import { PlusIcon } from 'lucide-react'; import { useModalBodyCleanup } from '../../hooks/modalBodyCleanup'; +import { useAlert } from '../../hooks/alert'; +import { FloatingAlert } from '@components/floatingAlert'; interface NewVolunteerModalProps { onSubmitSuccess?: () => void; @@ -32,16 +34,14 @@ const NewVolunteerModal: React.FC = ({ const [isOpen, setIsOpen] = useState(false); - const [error, setError] = useState(''); + const [alertState, setAlertMessage] = useAlert(); const handleSubmit = async () => { if (!firstName || !lastName || !email || !phone || phone === '+1') { - setError('Please fill in all fields. *'); + setAlertMessage('Please fill in all fields. *', AlertStatus.ERROR); return; } - setError(''); - const newVolunteer: UserDto = { firstName, lastName, @@ -80,9 +80,12 @@ const NewVolunteerModal: React.FC = ({ } if (hasEmailError) { - setError('Please specify a valid email. *'); + setAlertMessage('Please specify a valid email. *', AlertStatus.ERROR); } else if (hasPhoneError) { - setError('Please specify a valid phone number. *'); + setAlertMessage( + 'Please specify a valid phone number. *', + AlertStatus.ERROR, + ); } else { if (onSubmitFail) onSubmitFail(); handleClear(); @@ -95,7 +98,6 @@ const NewVolunteerModal: React.FC = ({ setLastName(''); setEmail(''); setPhone(''); - setError(''); setIsOpen(false); }; @@ -200,16 +202,13 @@ const NewVolunteerModal: React.FC = ({ }} /> - {error && ( - - {error} - + {alertState && ( + )}