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
7 changes: 7 additions & 0 deletions apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import apiClient from '@api/apiClient';
import Root from '@containers/root';
import NotFound from '@containers/404';
import { submitFoodRequestForm } from '@components/forms/foodRequestForm';
import RequestFood from '@containers/foodRequest';
import LandingPage from '@containers/landingPage';
import PantryOverview from '@containers/pantryOverview';
import PantryPastOrders from '@containers/pantryPastOrders';
Expand Down Expand Up @@ -38,6 +40,11 @@ const router = createBrowserRouter([
},
],
},
{
path: '/food-request',
element: <RequestFood />,
action: submitFoodRequestForm,
},
]);

export const App: React.FC = () => {
Expand Down
218 changes: 218 additions & 0 deletions apps/frontend/src/components/forms/foodRequestForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import {
Flex,
Box,
Heading,
FormControl,
FormLabel,
Input,
Button,
FormHelperText,
Checkbox,
Stack,
Textarea,
SimpleGrid,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
CheckboxGroup,
} from '@chakra-ui/react';
import {
Form,
redirect,
ActionFunction,
ActionFunctionArgs,
} from 'react-router-dom';

// should be an API call, dummy data for now
const getMenu = () => {
return {
Dairy: [
'Whole Milk',
'Lactose-Free Milk',
'Salted Butter',
'Eggs',
'Yogurt',
'American Cheese',
'Mozzarella Cheese',
],
Meat: [
'Chicken Breast',
'Ground Beef',
'Ground Pork',
'Ground Turkey',
'Chicken Strips',
'Turkey Bacon',
],
Fruit: [
'Pear',
'Orange',
'Banana',
'Lychee',
'Tangerine',
'Tomato',
'Pineapple',
],
Vegetables: ['Cauliflower', '小白菜', 'Spinach', 'Broccolini', 'Seaweed'],
Snacks: [
'Oreos (20ct)',
'Potato Chips',
'Chocolate Bars (4 ct)',
'Nimbu Masala',
],
Alcohol: [
'Red Wine',
'Fireball',
'Tequila',
'Sake',
'Malt Liquor',
'Vodka',
'Rum',
],
};
};

// might be an API call, dummy data for now
const getAllergens = () => {
return [
'Milk',
'Eggs',
'Fish',
'Shellfish',
'Tree Nuts',
'Peanuts',
'Wheat',
'Soybeans',
'Sesame',
'Other (specify in notes)',
];
};

const FoodRequestForm: React.FC = () => {
const renderAllergens = () => {
return getAllergens().map((a) => (
<Checkbox name="restrictions" value={a}>
{a}
</Checkbox>
));
};

const renderMenuSection = (sectionItems: Array<string>) => {
return (
<SimpleGrid spacing={4} columns={4}>
{sectionItems.map((x) => (
<Flex gap={6}>
<NumberInput
step={1}
defaultValue={0}
min={0}
max={100}
size="s"
maxW="4em"
name={x}
>
<NumberInputField size={2} />
<NumberInputStepper boxSize={0}>
<NumberIncrementStepper border={0} marginTop={-1} boxSize={6} />
<NumberDecrementStepper border={0} boxSize={6} />
</NumberInputStepper>
</NumberInput>
<h3>{x}</h3>
</Flex>
))}
</SimpleGrid>
);
};

const renderMenu = () => {
const menu: Map<string, Array<string>> = new Map(Object.entries(getMenu()));
const menuSections: JSX.Element[] = [];
menu.forEach((v, k) => {
menuSections.push(
<div>
<Heading size="md" paddingY={2}>
{k}
</Heading>
{renderMenuSection(v)}
</div>,
);
});
return menuSections;
};

return (
<Box minW="35em" maxW="50em" m="5em">
<Form method="post" action="/food-request">
<Heading size={'2xl'} marginY={8}>
SSF Food Request Form
</Heading>
<FormControl isRequired mb="2em">
<FormLabel fontSize={25} fontWeight={700}>
Requested Delivery Date
</FormLabel>
<Input maxW="20em" name="requestedDeliveryDate" type="date" />
<FormHelperText>
We'll reach out to confirm a date and time
</FormHelperText>
</FormControl>
<FormControl mb="2em">
<FormLabel fontSize={25} fontWeight={700}>
Dietary Restrictions
</FormLabel>
<CheckboxGroup>
<SimpleGrid spacing={2} columns={3}>
{renderAllergens()}
</SimpleGrid>
</CheckboxGroup>
</FormControl>
<FormControl mb="2em">
<FormLabel fontSize={25} fontWeight={700}>
Requested Items
</FormLabel>
<Stack>{renderMenu()}</Stack>
</FormControl>
<FormControl mb="2em">
<FormLabel fontSize={25} fontWeight={700}>
Additional Comments
</FormLabel>
<Textarea
name="notes"
placeholder="Anything else we should know about"
size="sm"
/>
</FormControl>
<Button type="submit">Submit</Button>
</Form>
</Box>
);
};

export const submitFoodRequestForm: ActionFunction = async ({
request,
}: ActionFunctionArgs) => {
const form = await request.formData();

const nonMenuNames = ['requestedDeliveryDate', 'notes'];
const foodRequestData = new Map();
for (let i = 0; i < nonMenuNames.length; i++) {
const name = nonMenuNames[i];
foodRequestData.set(name, form.get(name));
form.delete(name);
}

foodRequestData.set('restrictions', form.getAll('restrictions'));
form.delete('restrictions');

const foodItems = Array.from(form.entries())
.map((x) => [x[0], parseInt(x[1] as string)])
.filter(([k, v]) => v !== 0);
foodRequestData.set('items', Object.fromEntries(foodItems));
const data = Object.fromEntries(foodRequestData);

// TODO: API Call to update database
console.log(data);
return redirect('/');
};

export default FoodRequestForm;
12 changes: 12 additions & 0 deletions apps/frontend/src/containers/foodRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import FoodRequestForm from '@components/forms/foodRequestForm';
import { Center } from '@chakra-ui/react';

const FoodRequest: React.FC = () => {
return (
<Center>
<FoodRequestForm />
</Center>
);
};

export default FoodRequest;
5 changes: 4 additions & 1 deletion apps/frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { ChakraProvider } from '@chakra-ui/react';

import App from './app';

Expand All @@ -8,6 +9,8 @@ const root = ReactDOM.createRoot(
);
root.render(
<StrictMode>
<App />
<ChakraProvider>
<App />
</ChakraProvider>
</StrictMode>,
);
11 changes: 11 additions & 0 deletions apps/frontend/src/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { extendTheme } from '@chakra-ui/react';

const colors = {
white: '#fff',
black: '#000',
blue: '#2B5061',
red: '#CC3538',
yellow: '#F89E19',
cyan: '##2795A5',
};
export const theme = extendTheme({ colors });
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"private": true,
"dependencies": {
"@aws-sdk/client-cognito-identity-provider": "^3.410.0",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@nestjs/cli": "^10.1.17",
"@nestjs/common": "^10.0.2",
"@nestjs/config": "^3.2.3",
Expand All @@ -29,6 +32,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.4.5",
"framer-motion": "^11.5.6",
"global": "^4.4.0",
"jwks-rsa": "^3.1.0",
"mongodb": "^6.1.0",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2020", "dom"],
"lib": ["es2020", "dom", "DOM.Iterable"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
Expand Down
Loading