Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/backend/src/donationItems/donationItems.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { CreateMultipleDonationItemsDto } from './dtos/create-donation-items.dto
export class DonationItemsController {
constructor(private donationItemsService: DonationItemsService) {}

@Get('/get-donation-items/:donationId')
async getAllDonationIdItems(
@Get('/:donationId/all')
async getAllDonationItemsForDonation(
@Param('donationId', ParseIntPipe) donationId: number,
): Promise<DonationItem[]> {
return this.donationItemsService.getAllDonationItems(donationId);
Expand Down
13 changes: 7 additions & 6 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
OrderDetails,
Assignments,
FoodRequestSummaryDto,
PantryWithUser,
} from 'types/types';

const defaultBaseUrl =
Expand Down Expand Up @@ -156,7 +157,7 @@ export class ApiClient {
return this.get(`/api/pantries/${pantryId}/ssf-contact`) as Promise<User>;
}

public async getAllPendingPantries(): Promise<Pantry[]> {
public async getAllPendingPantries(): Promise<PantryWithUser[]> {
return this.axiosInstance
.get('/api/pantries/pending')
.then((response) => response.data);
Expand All @@ -168,8 +169,8 @@ export class ApiClient {
.then((response) => response.data);
}

public async getPantry(pantryId: number): Promise<Pantry> {
return this.get(`/api/pantries/${pantryId}`) as Promise<Pantry>;
public async getPantry(pantryId: number): Promise<PantryWithUser> {
return this.get(`/api/pantries/${pantryId}`) as Promise<PantryWithUser>;
}

public async postPantry(
Expand Down Expand Up @@ -212,9 +213,9 @@ export class ApiClient {
public async getDonationItemsByDonationId(
donationId: number,
): Promise<DonationItem[]> {
return this.get(
`/api/donation-items/get-donation-items/${donationId}`,
) as Promise<DonationItem[]>;
return this.get(`/api/donation-items/${donationId}/all`) as Promise<
DonationItem[]
>;
}

public async getManufacturerFromOrder(
Expand Down
29 changes: 19 additions & 10 deletions apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PantryApplication from '@containers/pantryApplication';
import ApplicationSubmitted from '@containers/applicationSubmitted';
import { submitPantryApplicationForm } from '@components/forms/pantryApplicationForm';
import ApprovePantries from '@containers/approvePantries';
import PantryApplicationDetails from '@containers/pantryApplicationDetails';
import VolunteerManagement from '@containers/volunteerManagement';
import FoodManufacturerOrderDashboard from '@containers/foodManufacturerOrderDashboard';
import AdminDonation from '@containers/adminDonation';
Expand Down Expand Up @@ -146,41 +147,49 @@ const router = createBrowserRouter([
),
},
{
path: '/volunteer-management',
path: '/approve-pantries',
element: (
<ProtectedRoute>
<VolunteerManagement />
<ApprovePantries />
</ProtectedRoute>
),
},
{
path: '/admin-order-management',
path: '/pantry-application-details/:applicationId',
element: (
<ProtectedRoute>
<AdminOrderManagement />
<PantryApplicationDetails />
</ProtectedRoute>
),
},
{
path: '/confirm-delivery',
action: submitDeliveryConfirmationFormModal,
path: '/admin-donation',
element: (
<ProtectedRoute>
<AdminDonation />
</ProtectedRoute>
),
},
{
path: '/approve-pantries',
path: '/volunteer-management',
element: (
<ProtectedRoute>
<ApprovePantries />
<VolunteerManagement />
</ProtectedRoute>
),
},
{
path: '/admin-donation',
path: '/admin-order-management',
element: (
<ProtectedRoute>
<AdminDonation />
<AdminOrderManagement />
</ProtectedRoute>
),
},
{
path: '/confirm-delivery',
action: submitDeliveryConfirmationFormModal,
},
{
path: '/volunteer-assigned-pantries',
element: (
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/floatingAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function FloatingAlert({
p={2}
>
<Alert.Indicator />
<Alert.Title textStyle="p2" fontWeight={500}>
<Alert.Title textStyle="p2" fontWeight={500} whiteSpace="pre-line">
{message}
</Alert.Title>
</Alert.Root>
Expand Down
88 changes: 88 additions & 0 deletions apps/frontend/src/components/forms/confirmPantryDecisionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Dialog, Text, Box, Button, CloseButton } from '@chakra-ui/react';
import { capitalize } from '@utils/utils';

interface ConfirmPantryDecisionModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
decision: string;
pantryName: string;
dateApplied: string;
}

const ConfirmPantryDecisionModal: React.FC<ConfirmPantryDecisionModalProps> = ({
isOpen,
onClose,
onConfirm,
decision,
pantryName,
dateApplied,
}) => {
return (
<Dialog.Root
open={isOpen}
onOpenChange={(e: { open: boolean }) => !e.open && onClose()}
>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content maxW="500px">
<Dialog.Header pb={0}>
<Dialog.Title fontSize="lg" fontWeight={600}>
Confirm Action
</Dialog.Title>
</Dialog.Header>

<Dialog.Body pb={4}>
<Text mb={4} color="gray.dark">
Are you sure you want to {decision} this application?
</Text>

<Box
p={6}
borderRadius="md"
border="1px solid"
borderColor="neutral.100"
>
<Text textStyle="p2">{pantryName}</Text>
<Text textStyle="p3" color="neutral.600">
Applied {dateApplied}
</Text>
</Box>
<Dialog.CloseTrigger asChild>
<CloseButton />
</Dialog.CloseTrigger>
</Dialog.Body>

<Dialog.Footer gap={2}>
<Button
variant="outline"
onClick={onClose}
borderColor="neutral.200"
color="neutral.800"
textStyle="p2"
fontWeight={600}
>
Cancel
</Button>
<Button
bg="blue.hover"
color="white"
onClick={() => {
onConfirm();
onClose();
}}
_hover={{ bg: 'neutral.800' }}
px={12}
textStyle="p2"
fontWeight={600}
>
{capitalize(decision)}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
);
};

export default ConfirmPantryDecisionModal;
18 changes: 12 additions & 6 deletions apps/frontend/src/components/forms/donationDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ApiClient from '@api/apiClient';
import { Donation, DonationItem, FoodType } from 'types/types';
import { formatDate } from '@utils/utils';
import { FloatingAlert } from '@components/floatingAlert';
import { useAlert } from '../../hooks/alert';

interface DonationDetailsModalProps {
donation: Donation;
Expand All @@ -25,7 +26,7 @@ const DonationDetailsModal: React.FC<DonationDetailsModalProps> = ({
}) => {
const [items, setItems] = useState<DonationItem[]>([]);

const [alertMessage, setAlertMessage] = useState<string>('');
const [alertState, setAlertMessage] = useAlert();

const donationId = donation.donationId;

Expand All @@ -39,13 +40,13 @@ const DonationDetailsModal: React.FC<DonationDetailsModalProps> = ({
);

setItems(itemsData);
} catch (err) {
setAlertMessage('Error fetching donation details: ' + err);
} catch {
setAlertMessage('Error fetching donation details');
}
};

fetchData();
}, [isOpen, donationId]);
}, [isOpen, donationId, setAlertMessage]);

// Group items by food type
const groupedItems = items.reduce((acc, item) => {
Expand All @@ -63,8 +64,13 @@ const DonationDetailsModal: React.FC<DonationDetailsModalProps> = ({
closeOnInteractOutside
scrollBehavior="inside"
>
{alertMessage && (
<FloatingAlert message={alertMessage} status="error" timeout={6000} />
{alertState && (
<FloatingAlert
key={alertState.id}
message={alertState.message}
status="error"
timeout={6000}
/>
)}
<Portal>
<Dialog.Backdrop bg="blackAlpha.300" />
Expand Down
60 changes: 39 additions & 21 deletions apps/frontend/src/components/forms/manufacturerApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import {
ActionFunctionArgs,
Form,
redirect,
useActionData,
} from 'react-router-dom';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { USPhoneInput } from '@components/forms/usPhoneInput';
import { TagGroup } from '@components/forms/tagGroup';
import { ManufacturerApplicationDto } from '../../types/types';
Expand All @@ -33,6 +34,8 @@ import {
DonateWastedFood,
ManufacturerAttribute,
} from '../../types/manufacturerEnums';
import { FloatingAlert } from '@components/floatingAlert';
import { useAlert } from '../../hooks/alert';

const ManufacturerApplicationForm: React.FC = () => {
const [contactPhone, setContactPhone] = useState<string>('');
Expand All @@ -44,6 +47,8 @@ const ManufacturerApplicationForm: React.FC = () => {
const [facilityFreeAllergens, setFacilityFreeAllergens] = useState<
Allergen[]
>([]);
const [alertState, setAlertMessage] = useAlert();
const actionData = useActionData() as { error?: string } | undefined;

const sectionTitleStyles = {
fontFamily: 'inter',
Expand All @@ -68,9 +73,23 @@ const ManufacturerApplicationForm: React.FC = () => {
fontWeight: '600',
};

useEffect(() => {
if (actionData?.error) {
setAlertMessage(actionData.error);
}
}, [actionData, setAlertMessage]);

return (
<Box width="100%" mx="11em" my="4em">
<Box as="section" mb="2.75em">
{alertState && (
<FloatingAlert
key={alertState.id}
message={alertState.message}
status="error"
timeout={6000}
/>
)}
<Heading textStyle="h1" fontWeight="normal" mb=".5em">
Partner Manufacturer Application
</Heading>
Expand Down Expand Up @@ -699,28 +718,27 @@ export const submitManufacturerApplicationForm: ActionFunction = async ({
});

const data = Object.fromEntries(manufacturerApplicationData);
let submissionSuccessful = false;

await ApiClient.postManufacturer(data as ManufacturerApplicationDto).then(
() => (submissionSuccessful = true),
(error) => {
if (axios.isAxiosError(error) && error.response?.status === 400) {
alert(
try {
await ApiClient.postManufacturer(data as ManufacturerApplicationDto);
return redirect('/application-submitted');
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 400) {
return {
error:
'Form submission failed with the following errors: \n\n' +
// Creates a bullet-point list of the errors
// returned from the backend
error.response?.data?.message
.map((line: string) => '- ' + line)
.join('\n'),
);
} else {
alert('Form submission failed; please try again');
console.log(error);
}
},
);

return submissionSuccessful ? redirect('/application-submitted') : null;
// Creates a bullet-point list of the errors
// returned from the backend
error.response?.data?.message
.map((line: string) => '- ' + line)
.join('\n'),
};
} else {
return {
error: 'Form submission failed; please try again',
};
}
}
};

export default ManufacturerApplicationForm;
Loading
Loading