From 9e74adabb5b420d76781e584c103183b0e6e007e Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:20:42 -0500 Subject: [PATCH 01/19] rebase main --- apps/backend/src/orders/order.controller.ts | 1 + .../pantries/dtos/pantry-application.dto.ts | 5 +- apps/backend/src/pantries/pantries.service.ts | 3 +- apps/frontend/src/app.tsx | 5 + .../forms/pantryApplicationForm.tsx | 1081 ++++++++++------- .../containers/pantryApplicationSubmitted.tsx | 47 + apps/frontend/src/theme.ts | 16 + package.json | 1 + 8 files changed, 717 insertions(+), 442 deletions(-) create mode 100644 apps/frontend/src/containers/pantryApplicationSubmitted.tsx diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 31ea1eb7..b43b8501 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -13,6 +13,7 @@ import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; import { FoodManufacturer } from '../foodManufacturers/manufacturer.entity'; import { FoodRequest } from '../foodRequests/request.entity'; +import { Donation } from '../donations/donations.entity'; import { AllocationsService } from '../allocations/allocations.service'; import { OrderStatus } from './types'; diff --git a/apps/backend/src/pantries/dtos/pantry-application.dto.ts b/apps/backend/src/pantries/dtos/pantry-application.dto.ts index fff83b4d..134477d2 100644 --- a/apps/backend/src/pantries/dtos/pantry-application.dto.ts +++ b/apps/backend/src/pantries/dtos/pantry-application.dto.ts @@ -3,7 +3,6 @@ import { IsBoolean, IsEmail, IsEnum, - IsIn, IsNotEmpty, IsOptional, IsPhoneNumber, @@ -122,6 +121,6 @@ export class PantryApplicationDto { needMoreOptions: string; @IsOptional() - @IsIn(['Yes', 'No']) - newsletterSubscription?: string; + @IsBoolean() + newsletterSubscription?: boolean; } diff --git a/apps/backend/src/pantries/pantries.service.ts b/apps/backend/src/pantries/pantries.service.ts index 2e10535b..101b3aa1 100644 --- a/apps/backend/src/pantries/pantries.service.ts +++ b/apps/backend/src/pantries/pantries.service.ts @@ -62,8 +62,7 @@ export class PantriesService { pantry.activitiesComments = pantryData.activitiesComments; pantry.itemsInStock = pantryData.itemsInStock; pantry.needMoreOptions = pantryData.needMoreOptions; - pantry.newsletterSubscription = - pantryData?.newsletterSubscription === 'Yes'; + pantry.newsletterSubscription = pantryData.newsletterSubscription; // pantry contact is automatically added to User table await this.repo.save(pantry); diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index 89e0fccf..90be09d1 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -14,6 +14,7 @@ import { submitFoodRequestFormModal } from '@components/forms/requestFormModal'; import { submitDeliveryConfirmationFormModal } from '@components/forms/deliveryConfirmationModal'; import FormRequests from '@containers/FormRequests'; import PantryApplication from '@containers/pantryApplication'; +import PantryApplicationSubmitted from '@containers/pantryApplicationSubmitted'; import { submitPantryApplicationForm } from '@components/forms/pantryApplicationForm'; import ApprovePantries from '@containers/approvePantries'; import VolunteerManagement from '@containers/volunteerManagement'; @@ -59,6 +60,10 @@ const router = createBrowserRouter([ element: , action: submitPantryApplicationForm, }, + { + path: '/pantry-application/submitted', + element: , + }, { path: '/food-manufacturer-order-dashboard', element: , diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 4c7325bf..49a6088a 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -2,7 +2,6 @@ import { Box, Button, Checkbox, - CheckboxGroup, Heading, Input, RadioGroup, @@ -10,7 +9,14 @@ import { Text, Field, Textarea, - Fieldset, + SimpleGrid, + Portal, + NativeSelect, + NativeSelectIndicator, + Combobox, + Wrap, + createListCollection, + Tag, } from '@chakra-ui/react'; import { ActionFunction, @@ -18,491 +24,687 @@ import { Form, redirect, } from 'react-router-dom'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { USPhoneInput } from '@components/forms/usPhoneInput'; import { PantryApplicationDto } from '../../types/types'; import ApiClient from '@api/apiClient'; import { Activity } from '../../types/pantryEnums'; import axios from 'axios'; +const otherRestrictionsOptions: string[] = [ + 'Other allergy (e.g., yeast, sunflower, etc.)', + 'Other allergic illness (e.g., eosinophilic esophagitis, FPIES, oral allergy syndrome)', + 'Other dietary restriction', +]; + +const dietaryRestrictionOptions = [ + 'Egg allergy', + 'Fish allergy', + 'Milk allergy', + 'Lactose intolerance/dairy sensitivity', + 'Peanut allergy', + 'Shellfish allergy', + 'Soy allergy', + 'Sesame allergy', + 'Tree nut allergy', + 'Wheat allergy', + 'Celiac disease', + 'Gluten sensitivity (not celiac disease)', + "Gastrointestinal illness (IBS, Crohn's, gastroparesis, etc.)", + ...otherRestrictionsOptions, + 'Unsure', +]; + +const activityOptions = [ + 'Create a labeled, allergy-friendly shelf or shelves', + 'Provide clients and staff/volunteers with educational pamphlets', + "Use a spreadsheet to track clients' medical dietary needs and distribution of SSF items per month", + 'Post allergen-free resource flyers throughout pantry', + 'Survey your clients to determine their medical dietary needs', + 'Collect feedback from allergen-avoidant clients on SSF foods', + 'Something else', +]; + const PantryApplicationForm: React.FC = () => { const [contactPhone, setContactPhone] = useState(''); - - // We need to keep track of the activities selected so we can provide custom - // validation (at least one activity chosen). const [activities, setActivities] = useState([]); - const noActivitiesSelected: boolean = activities.length === 0; - - // Option values and state below are for options that, when selected - const allergenClientsExactOption: string = 'I have an exact number'; - const otherRestrictionsOptions: string[] = [ - 'Other allergy (e.g., yeast, sunflower, etc.)', - 'Other allergic illness (e.g., eosinophilic esophagitis, FPIES, oral allergy syndrome)', - 'Other dietary restriction', - ]; - const reserveFoodForAllergicYesOption: string = 'Yes'; - const reserveFoodForAllergicSomeOption: string = 'Some'; const [allergenClients, setAllergenClients] = useState(); const [restrictions, setRestrictions] = useState([]); - const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState< + const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); + const [dedicatedAllergyFriendly, setDedicatedAllergyFriendly] = useState< + string | undefined + >(); + const [clientVisitFrequency, setClientVisitFrequency] = useState< + string | undefined + >(); + const [identifyAllergensConfidence, setIdentifyAllergensConfidence] = useState< + string | undefined + >(); + const [serveAllergicChildren, setServeAllergicChildren] = useState< string | undefined >(); + const [refrigeratedDonation, setRefrigeratedDonation] = useState(); + const [newsletterSubscription, setNewsletterSubscription] = useState(); + const [searchRestriction, setSearchRestriction] = useState(''); + const [searchActivity, setSearchActivity] = useState(''); + + const sectionTitleStyles = { + fontFamily: "'Inter', sans-serif", + fontWeight: '600', + fontSize: 'md', + }; + + const sectionSubtitleStyles = { + fontFamily: "'Inter', sans-serif", + fontWeight: '400', + color: 'gray', + mb: '2em', + fontSize: 'sm', + } + + const fieldHeaderStyles = { + color: 'neutral.800', + fontFamily: "'Inter', sans-serif", + fontSize: 'sm', + fontWeight: '600', + }; + + const filteredRestrictions = useMemo( + () => + dietaryRestrictionOptions.filter((option) => + option.toLowerCase().includes(searchRestriction.toLowerCase()), + ), + [searchRestriction], + ); + + const restrictionsCollection = useMemo( + () => createListCollection({ items: filteredRestrictions}), + [filteredRestrictions], + ); + + const filteredActivities = useMemo( + () => + activityOptions.filter((option) => + option.toLowerCase().includes(searchActivity.toLowerCase()), + ), + [searchActivity], + ); + + const activitiesCollection = useMemo( + () => createListCollection({ items: filteredActivities}), + [filteredActivities], + ); return ( - - - - SSF Pantry Sign-Up Form + + + + Pantry Sign Up Form - - - Welcome! We are excited to have you join us in our mission to secure - allergen-safe food and promote food equity. - - Please fill out the following information to get started. - - - - First Name - - - - Whom should we contact at your pantry? - - - - - - Last Name - - - - Whom should we contact at your pantry? - - - - - - Email Address - - - - Please provide the email address of the pantry contact listed above. - - - - - - Phone Number - - - - Please provide the phone number of the pantry contact listed above. - - - - - - - Food Pantry Name - - - - - - - - Address * + + Welcome! We are so excited to have you join us in our mission to secure allergen-safe food and promote food equity. + + + + + + Pantry Application - + + Please fill out the folllowing information to get started. + + + Point of Contact Information + + + Please provide information about whom we should contact at your pantry. + + + + + First Name + + + + + + + Last Name + + + + + + + Phone Number + + + + + + + Email Address + + + + + + + + Address + + Please list your address for food shipments. + + + + Address Line 1 + + + + + + + Address Line 2 + + + + + + City/Town + + + + + + + State/Region/Province + + + + + + + Zip/Post Code + + + + + + + Country + + + + + + + Pantry Details + + + + + Pantry Name + + + + + - - Address Line 1 - + + Approximately how many allergen-avoidant clients does your pantry + serve? + - + + setAllergenClients(e.target.value)} + placeholder="Select an option" + borderColor="neutral.100" + name="allergenClients" + > + {[ + '< 10', + '10 to 20', + '20 to 50', + '50 to 100', + '> 100', + "I'm not sure", + allergenClientsExactOption, + ].map((value) => ( + + {value} + + ))} + + + + {allergenClients === allergenClientsExactOption && ( + + + Please provide the exact number, if known + + + + )} - - Address Line 2 + + Which food allergies or other medical dietary restrictions do + clients at your pantry report? - + setRestrictions(e.value)} + onInputValueChange={(e: {inputValue: string}) => setSearchRestriction(e.inputValue)} + > + + + + + + + + + + + + {filteredRestrictions.map((value) => ( + ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} + onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} + > + {value} + + + ))} + No dietary restrictions found + + + + + + + {restrictions.map((value) => ( + <> + + + {value} + + + setRestrictions((prev) => + prev.filter((item) => item !== value) + ) + } + style={{ cursor: 'pointer' }} + /> + + + > + ))} + + + + {restrictions.find((option) => + otherRestrictionsOptions.includes(option), + ) && ( + + + If you selected "Other," please specify: + + + + )} - - City/Town - + + Would you be able to accept refrigerated/frozen donations from us? + - + + setRefrigeratedDonation(e.target.value)} + placeholder="Select an option" + borderColor="neutral.100" + name="refrigeratedDonation" + > + {['Yes', 'Small quantities only', 'No'].map((value) => ( + + {value} + + ))} + + + + - - State/Region/Province - - - + setReserveFoodForAllergic(e.checked)} + variant="outline" + name="reserveFoodForAllergic" + > + + + + Are you willing to reserve our food shipments for allergen-avoidant + individuals?{' '} + + + + {reserveFoodForAllergic && ( + + + Please explain how you would do this. + + + + + For example: keeping allergen-friendly items on a separate shelf, encouraging non-allergic + clients to save these items for clients who do not have other safe food options. + + + )} + - - Zip/Post Code - + + Do you have a dedicated shelf or section of your pantry for + allergy-friendly items? + - + + setDedicatedAllergyFriendly(e.target.value)} + placeholder="Select an option" + borderColor="neutral.100" + name="dedicatedAllergyFriendly" + > + {[ + 'Yes, we have a dedicated shelf or box', + 'Yes, we keep allergy-friendly items in a back room', + 'No, we keep allergy-friendly items throughout the pantry, depending on the type of item', + ].map((value) => ( + + {value} + + ))} + + + - - Country + + How often do allergen-avoidant clients visit your food pantry? - + + setClientVisitFrequency(e.target.value)} + placeholder="Select an option" + name="clientVisitFrequency" + borderColor="neutral.100" + > + {[ + 'Daily', + 'More than once a week', + 'Once a week', + 'A few times a month', + 'Once a month', + ].map((value) => ( + + {value} + + ))} + + + - - - - Approximately how many allergen-avoidant clients does your pantry - serve? - - - - Please note that our target population is NOT individuals with - diabetic, low sugar/sodium, halal, vegan/vegetarian, or kosher - needs. - - - - {[ - '< 10', - '10 to 20', - '20 to 50', - '50 to 100', - '> 100', - "I'm not sure", - allergenClientsExactOption, - ].map((value) => ( - - - - {value} - - ))} - - - - {allergenClients === allergenClientsExactOption && ( - - Please provide the exact number, if known: + + Are you confident in identifying the top 9 allergens in an + ingredient list? - + + setIdentifyAllergensConfidence(e.target.value)} + placeholder="Select an option" + name="identifyAllergensConfidence" + borderColor="neutral.100" + > + {[ + 'Very confident', + 'Somewhat confident', + 'Not very confident (we need more education!)', + ].map((value) => ( + + {value} + + ))} + + + + + The top 9 allergens are milk, egg, peanut, tree nuts, wheat, soy, + fish, shellfish, and sesame. + - )} - - - Which food allergies or other medical dietary restrictions do - clients at your pantry report? - - - Please select all that apply. - - - - {[ - 'Egg allergy', - 'Fish allergy', - 'Milk allergy', - 'Lactose intolerance/dairy sensitivity', - 'Peanut allergy', - 'Shellfish allergy', - 'Soy allergy', - 'Sesame allergy', - 'Tree nut allergy', - 'Wheat allergy', - 'Celiac disease', - 'Gluten sensitivity (not celiac disease)', - "Gastrointestinal illness (IBS, Crohn's, gastroparesis, etc.)", - ...otherRestrictionsOptions, - 'Unsure', - ].map((value) => ( - - - - {value} - - ))} - - - - {restrictions.find((option) => - otherRestrictionsOptions.includes(option), - ) && ( - - If you selected "Other," please specify: + + Do you serve allergen-avoidant or food-allergic children at your + pantry? - + + setServeAllergicChildren(e.target.value)} + placeholder="Select an option" + name="serveAllergicChildren" + borderColor="neutral.100" + > + {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( + + {value} + + ))} + + + + + "Children" is defined as any individual under the age of 18 either + living independently or as part of a household. + - )} - - - Would you be able to accept refrigerated/frozen donations from us? - - - - - {['Yes', 'Small quantities only', 'No'].map((value) => ( - - - - {value} - - ))} - - - - - - Are you willing to reserve our food shipments for allergen-avoidant - individuals? - - - - For example: keeping allergen-friendly items on a separate shelf, - encouraging non-allergic clients to save these items for clients who - do not have other safe food options. - - - - {[ - reserveFoodForAllergicYesOption, - reserveFoodForAllergicSomeOption, - 'No', - ].map((value) => ( - - - - {value} - - ))} - - - - {reserveFoodForAllergic === reserveFoodForAllergicYesOption && ( - - Please explain how you would do this: - + + What activities are you open to doing with SSF?{" "} + - + setActivities(e.value)} + onInputValueChange={(e) => setSearchActivity(e.inputValue)} + required={noActivitiesSelected} + > + + + + + + + + + + + + {filteredActivities.map((value) => ( + ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} + onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} + > + {value} + + + ))} + No activities found + + + + + + + {activities.map((value) => ( + <> + + + {value} + + + setActivities((prev) => + prev.filter((item) => item !== value) + ) + } + style={{ cursor: 'pointer' }} + /> + + + > + ))} + + + + Food donations are one part of being a partner pantry. The + following are additional ways to help us better support you! + Please select all that apply. + - )} - {reserveFoodForAllergic === reserveFoodForAllergicSomeOption && ( + - - If you chose "some," please explain: + + Please list any comments/concerns related to the previous question. - + + + If you answered "Something Else", please elaborate. + - )} - - - Do you have a dedicated shelf or section of your pantry for - allergy-friendly items? - - - - If not, we would love to have a conversation and offer resources to - help you build one! - - - - {[ - 'Yes, we have a dedicated shelf or box', - 'Yes, we keep allergy-friendly items in a back room', - 'No, we keep allergy-friendly items throughout the pantry, depending on the type of item', - ].map((value) => ( - - - - {value} - - ))} - - - - - - How often do allergen-avoidant clients visit your food pantry? - - - - {[ - 'Daily', - 'More than once a week', - 'Once a week', - 'A few times a month', - 'Once a month', - ].map((value) => ( - - - - {value} - - ))} - - - - - - Are you confident in identifying the top 9 allergens in an - ingredient list? - - - The top 9 allergens are milk, egg, peanut, tree nuts, wheat, soy, - fish, shellfish, and sesame. - - - - {[ - 'Very confident', - 'Somewhat confident', - 'Not very confident (we need more education!)', - ].map((value) => ( - - - - {value} - - ))} - - - - - - Do you serve allergen-avoidant or food-allergic children at your - pantry? - - - "Children" is defined as any individual under the age of 18 either - living independently or as part of a household. - - - - {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( - - - - {value} - - ))} - - - - - - What activities are you open to doing with SSF?{' '} - - * - - - - - Food donations are one part of being a partner pantry. The - following are additional ways to help us better support you! - (Please select all that apply.) - - Please select at least one option! - - {/* TODO: Fix input validation message */} - - setActivities(activities as string[]) - } - > - - {[ - 'Create a labeled, allergy-friendly shelf or shelves', - 'Provide clients and staff/volunteers with educational pamphlets', - "Use a spreadsheet to track clients' medical dietary needs and distribution of SSF items per month", - 'Post allergen-free resource flyers throughout pantry', - 'Survey your clients to determine their medical dietary needs', - 'Collect feedback from allergen-avoidant clients on SSF foods', - 'Something else', - ].map((value) => ( - - - - {value} - - ))} - - - - - - Please list any comments/concerns related to the previous question. - - - If you answered "something else," please elaborate! - - - - - - What types of allergen-free items, if any, do you currently have in - stock? (i.e., gluten-free breads, sunflower seed butters, non-dairy - beverages, etc.) - - - - - - - Do allergen-avoidant clients at your pantry ever request a greater - variety of items or not have enough options? - - - - - - - Would you like to subscribe to our quarterly newsletter? - - - - {['Yes', 'No'].map((value) => ( - - - - {value} - - ))} - - - - Submit - + + + What types of allergen-free items, if any, do you currently have in + stock? + + + + + For example, gluten-free breads, sunflower seed butters, nondairy beverages, etc. + + + + + Do allergen-avoidant clients at your pantry ever request a greater + variety of items or not have enough options? Please explain. + + + + + + + + Would you like to subscribe to our quarterly newsletter? + + setNewsletterSubscription(e.value === 'Yes')} + > + + {['Yes', 'No'].map((value) => ( + + + + + + {value} + + ))} + + + + + + Cancel + + + Submit Application + + + + ); }; @@ -534,6 +736,8 @@ export const submitPantryApplicationForm: ActionFunction = async ({ restrictions.push(restrictionsOther); } + console.log('restrictions: ', restrictions); + pantryApplicationData.set('restrictions', restrictions); form.delete('restrictions'); @@ -548,6 +752,9 @@ export const submitPantryApplicationForm: ActionFunction = async ({ // Handle all other questions form.forEach((value, key) => pantryApplicationData.set(key, value)); + pantryApplicationData.set('reserveFoodForAllergic', form.get("reserveFoodForAllergic") === "true"); + pantryApplicationData.set('newsletterSubscription', form.get("reserveFoodForAllergic") === "Yes"); + // Replace the answer for allergenClients with the answer // for allergenClientsExact if it is given @@ -581,7 +788,7 @@ export const submitPantryApplicationForm: ActionFunction = async ({ }, ); - return submissionSuccessful ? redirect('/') : null; + return submissionSuccessful ? redirect('/pantry-application/submitted') : null; }; export default PantryApplicationForm; diff --git a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx new file mode 100644 index 00000000..c80f7b1d --- /dev/null +++ b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx @@ -0,0 +1,47 @@ +import { + Box, + Center, + Heading, + Icon, + Text, + VStack, +} from '@chakra-ui/react'; +import { FileCheck } from 'lucide-react'; + +import React from 'react'; + +const PantryApplicationSubmitted: React.FC = () => { + return ( + + + + + Thank you! + + + Your application has been submitted. + + + + + + + Application Submitted + + + Please check your inbox for status updates. + + + + + + ); +}; + +export default PantryApplicationSubmitted; diff --git a/apps/frontend/src/theme.ts b/apps/frontend/src/theme.ts index c472e41f..1a228d0e 100644 --- a/apps/frontend/src/theme.ts +++ b/apps/frontend/src/theme.ts @@ -69,6 +69,11 @@ const customConfig = defineConfig({ 800: { value: '#414141' }, 900: { value: '#212529' }, }, + gray: { value: '#515151' }, + teal: { + 400: { value: '#A9D5DB' }, + 100: { value: '#E9F4F6' }, + } }, fonts: { instrument: { value: `'Instrument Serif', serif` }, @@ -77,6 +82,17 @@ const customConfig = defineConfig({ }, }, }, + globalCss: { + 'html, body': { + fontFamily: 'body', + }, + 'h1, h2, h3, h4, h5, h6': { + fontFamily: 'heading', + }, + 'body': { + bg: '#FAFAFA', + }, + }, }); export const system = createSystem(defaultConfig, customConfig); \ No newline at end of file diff --git a/package.json b/package.json index 6af7be3b..eadf2701 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "dotenv": "^16.4.5", "global": "^4.4.0", "google-libphonenumber": "^3.2.40", + "jest-mock-extended": "^4.0.0", "jwks-rsa": "^3.1.0", "lucide-react": "^0.544.0", "mongodb": "^6.1.0", From d8fc4dc41df10c983363cf1248b83f4aabd906f1 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:21:09 -0500 Subject: [PATCH 02/19] remove console logs --- apps/frontend/src/components/forms/pantryApplicationForm.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 49a6088a..b2b77f04 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -567,7 +567,7 @@ const PantryApplicationForm: React.FC = () => { value={activities} collection={activitiesCollection} onValueChange={(e: {value: string[]}) => setActivities(e.value)} - onInputValueChange={(e) => setSearchActivity(e.inputValue)} + onInputValueChange={(e: {inputValue: string}) => setSearchActivity(e.inputValue)} required={noActivitiesSelected} > @@ -736,8 +736,6 @@ export const submitPantryApplicationForm: ActionFunction = async ({ restrictions.push(restrictionsOther); } - console.log('restrictions: ', restrictions); - pantryApplicationData.set('restrictions', restrictions); form.delete('restrictions'); From b7c6466d5f4ee206bdec776d31237b0a9eca6859 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 2 Nov 2025 18:46:23 -0500 Subject: [PATCH 03/19] review comments and changing db schema --- .../1762125223723-UpdatePantryFields.ts | 19 +++ .../pantries/dtos/pantry-application.dto.ts | 7 + apps/backend/src/pantries/pantries.entity.ts | 16 +- .../forms/pantryApplicationForm.tsx | 158 ++++++++++++------ .../containers/pantryApplicationSubmitted.tsx | 64 ++++--- 5 files changed, 171 insertions(+), 93 deletions(-) create mode 100644 apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts diff --git a/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts b/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts new file mode 100644 index 00000000..ecaa7648 --- /dev/null +++ b/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdatePantryFields1740000000000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE pantries + ADD COLUMN accept_food_deliveries boolean NOT NULL DEFAULT false, + ADD COLUMN delivery_window_instructions text + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "pantries" + DROP COLUMN delivery_window_instructions, + DROP COLUMN accept_food_deliveries + `); + } +} diff --git a/apps/backend/src/pantries/dtos/pantry-application.dto.ts b/apps/backend/src/pantries/dtos/pantry-application.dto.ts index 134477d2..0e34e4fd 100644 --- a/apps/backend/src/pantries/dtos/pantry-application.dto.ts +++ b/apps/backend/src/pantries/dtos/pantry-application.dto.ts @@ -81,6 +81,13 @@ export class PantryApplicationDto { @IsEnum(RefrigeratedDonation) refrigeratedDonation: RefrigeratedDonation; + @IsBoolean() + acceptFoodDeliveries: boolean; + + @IsOptional() + @IsString() + deliveryWindowInstructions?: string; + @IsEnum(ReserveFoodForAllergic) reserveFoodForAllergic: ReserveFoodForAllergic; diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 20ddacff..6350b60b 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -63,11 +63,17 @@ export class Pantry { }) refrigeratedDonation: RefrigeratedDonation; - @Column({ - name: 'reserve_food_for_allergic', - type: 'enum', - enum: ReserveFoodForAllergic, - enumName: 'reserve_food_for_allergic_enum', + @Column({ name: 'accept_food_deliveries', type: 'boolean' }) + acceptFoodDeliveries: boolean; + + @Column({ name: 'delivery_window_instructions', type: 'text', nullable: true }) + deliveryWindowInstructions?: string; + + @Column({ + name: 'reserve_food_for_allergic', + type: 'enum', + enum: ReserveFoodForAllergic, + enumName: 'reserve_food_for_allergic_enum' }) reserveFoodForAllergic: string; diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index b2b77f04..0dfaf0e5 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -1,7 +1,6 @@ import { Box, Button, - Checkbox, Heading, Input, RadioGroup, @@ -73,10 +72,7 @@ const PantryApplicationForm: React.FC = () => { const [allergenClients, setAllergenClients] = useState(); const [restrictions, setRestrictions] = useState([]); - const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); - const [dedicatedAllergyFriendly, setDedicatedAllergyFriendly] = useState< - string | undefined - >(); + const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); const [clientVisitFrequency, setClientVisitFrequency] = useState< string | undefined >(); @@ -87,7 +83,6 @@ const PantryApplicationForm: React.FC = () => { string | undefined >(); const [refrigeratedDonation, setRefrigeratedDonation] = useState(); - const [newsletterSubscription, setNewsletterSubscription] = useState(); const [searchRestriction, setSearchRestriction] = useState(''); const [searchActivity, setSearchActivity] = useState(''); @@ -142,10 +137,15 @@ const PantryApplicationForm: React.FC = () => { - Pantry Sign Up Form + Welcome to the Securing Safe Food Partner Pantry Application. - Welcome! We are so excited to have you join us in our mission to secure allergen-safe food and promote food equity. + Thank you for your interest in partnering with Securing Safe Food (SSF) to help serve clients + with food allergies and other adverse reactions to foods. This application helps us understand + your pantry’s capacity and interest in distributing allergen-friendly food. We’ll ask about + your pantry’s current practices, storage capabilities, and communication preferences. Please + answer as accurately as possible. If you have any questions or need help, don’t hesitate to + contact the SSF team. { {allergenClients === allergenClientsExactOption && ( - + Please provide the exact number, if known + { - setReserveFoodForAllergic(e.checked)} - variant="outline" - name="reserveFoodForAllergic" + + Would your pantry be able to accept food deliveries during standard business hours?{' '} + + + - - - - Are you willing to reserve our food shipments for allergen-avoidant + + {['Yes', 'No'].map((value) => ( + + + + + + {value} + + ))} + + + + + + + Please note any delivery window instructions. + + + + + + + Are you willing to reserve our food shipments for allergen-avoidant individuals?{' '} - - - + + + setReserveFoodForAllergic(e.value)} + > + + {['Yes', 'Some', 'No'].map((value) => ( + + + + + + {value} + + ))} + + - {reserveFoodForAllergic && ( + + {reserveFoodForAllergic && ['Some', 'Yes'].includes(reserveFoodForAllergic) && ( Please explain how you would do this. - + For example: keeping allergen-friendly items on a separate shelf, encouraging non-allergic clients to save these items for clients who do not have other safe food options. @@ -452,27 +494,27 @@ const PantryApplicationForm: React.FC = () => { allergy-friendly items? - - setDedicatedAllergyFriendly(e.target.value)} - placeholder="Select an option" - borderColor="neutral.100" - name="dedicatedAllergyFriendly" - > - {[ - 'Yes, we have a dedicated shelf or box', - 'Yes, we keep allergy-friendly items in a back room', - 'No, we keep allergy-friendly items throughout the pantry, depending on the type of item', - ].map((value) => ( - - {value} - + + + {['Yes', 'No'].map((value) => ( + + + + + + {value} + ))} - - - + + + How often do allergen-avoidant clients visit your food pantry? @@ -636,7 +678,7 @@ const PantryApplicationForm: React.FC = () => { Please list any comments/concerns related to the previous question. - + If you answered "Something Else", please elaborate. @@ -647,7 +689,7 @@ const PantryApplicationForm: React.FC = () => { stock? - + For example, gluten-free breads, sunflower seed butters, nondairy beverages, etc. @@ -658,7 +700,7 @@ const PantryApplicationForm: React.FC = () => { variety of items or not have enough options? Please explain. - + @@ -668,8 +710,6 @@ const PantryApplicationForm: React.FC = () => { setNewsletterSubscription(e.value === 'Yes')} > {['Yes', 'No'].map((value) => ( @@ -748,10 +788,19 @@ export const submitPantryApplicationForm: ActionFunction = async ({ form.delete('dedicatedAllergyFriendly'); // Handle all other questions - form.forEach((value, key) => pantryApplicationData.set(key, value)); + form.forEach((value, key) => { + if (value === '') { + pantryApplicationData.set(key, null); + } else { + pantryApplicationData.set(key, value) + } + }); + + pantryApplicationData.set('newsletterSubscription', form.get("newsletterSubscription") === "Yes"); + pantryApplicationData.set('acceptFoodDeliveries', form.get("acceptFoodDeliveries") === "Yes"); + pantryApplicationData.set('dedicatedAllergyFriendly', form.get("dedicatedAllergyFriendly") === "Yes"); - pantryApplicationData.set('reserveFoodForAllergic', form.get("reserveFoodForAllergic") === "true"); - pantryApplicationData.set('newsletterSubscription', form.get("reserveFoodForAllergic") === "Yes"); + console.log('Pantry Application Data:', Object.fromEntries(pantryApplicationData)); // Replace the answer for allergenClients with the answer // for allergenClientsExact if it is given @@ -782,6 +831,7 @@ export const submitPantryApplicationForm: ActionFunction = async ({ ); } else { alert('Form submission failed; please try again'); + console.log(error) } }, ); diff --git a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx index c80f7b1d..1894de46 100644 --- a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx +++ b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx @@ -1,11 +1,4 @@ -import { - Box, - Center, - Heading, - Icon, - Text, - VStack, -} from '@chakra-ui/react'; +import { Box, Center, Heading, Icon, Text, VStack } from '@chakra-ui/react'; import { FileCheck } from 'lucide-react'; import React from 'react'; @@ -13,33 +6,36 @@ import React from 'react'; const PantryApplicationSubmitted: React.FC = () => { return ( - - - - Thank you! - - - Your application has been submitted. - - - - - - - Application Submitted - - - Please check your inbox for status updates. - - - + + + + Thank you! + + Your application has been submitted. + + + + + Application Submitted + + + Please check your inbox for status updates. + + + + ); }; From f7596a176681ed723fd6f90106e25b223d24b7cb Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:24:35 -0500 Subject: [PATCH 04/19] prettier --- .../src/migrations/1762125223723-UpdatePantryFields.ts | 2 +- apps/backend/src/pantries/pantries.entity.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts b/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts index ecaa7648..0150a701 100644 --- a/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts +++ b/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts @@ -1,4 +1,4 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class UpdatePantryFields1740000000000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 6350b60b..c9355860 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -66,7 +66,11 @@ export class Pantry { @Column({ name: 'accept_food_deliveries', type: 'boolean' }) acceptFoodDeliveries: boolean; - @Column({ name: 'delivery_window_instructions', type: 'text', nullable: true }) + @Column({ + name: 'delivery_window_instructions', + type: 'text', + nullable: true, + }) deliveryWindowInstructions?: string; @Column({ From a773aa78d21ac43bdd9e651a9cb8a90969502928 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:55:05 -0500 Subject: [PATCH 05/19] prettier --- .../donationItems/donationItems.controller.ts | 9 ++++++--- .../src/donations/donations.controller.ts | 4 ++-- apps/backend/src/donations/types.ts | 2 +- .../src/foodRequests/request.controller.ts | 9 ++++++--- ...1763963056712-AllergyFriendlyToBoolType.ts | 20 +++++++++---------- apps/backend/src/orders/order.service.spec.ts | 8 +++++++- apps/backend/src/pantries/pantries.entity.ts | 10 +++++----- apps/backend/src/pantries/types.ts | 2 +- apps/frontend/src/types/pantryEnums.ts | 4 ++-- apps/frontend/src/types/types.ts | 11 +++++----- 10 files changed, 45 insertions(+), 34 deletions(-) diff --git a/apps/backend/src/donationItems/donationItems.controller.ts b/apps/backend/src/donationItems/donationItems.controller.ts index 96381fc0..96be5673 100644 --- a/apps/backend/src/donationItems/donationItems.controller.ts +++ b/apps/backend/src/donationItems/donationItems.controller.ts @@ -38,8 +38,8 @@ export class DonationItemsController { status: { type: 'string', example: 'available' }, ozPerItem: { type: 'integer', example: 5 }, estimatedValue: { type: 'integer', example: 100 }, - foodType: { - type: 'string', + foodType: { + type: 'string', enum: Object.values(FoodType), example: FoodType.DAIRY_FREE_ALTERNATIVES, }, @@ -59,7 +59,10 @@ export class DonationItemsController { foodType: FoodType; }, ): Promise { - if (body.foodType && !Object.values(FoodType).includes(body.foodType as FoodType)) { + if ( + body.foodType && + !Object.values(FoodType).includes(body.foodType as FoodType) + ) { throw new BadRequestException('Invalid foodtype'); } return this.donationItemsService.create( diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index d748df66..6bcd2a7e 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -46,8 +46,8 @@ export class DonationsController { type: 'string', format: 'date-time', }, - status: { - type: 'string', + status: { + type: 'string', enum: Object.values(DonationStatus), example: DonationStatus.AVAILABLE, }, diff --git a/apps/backend/src/donations/types.ts b/apps/backend/src/donations/types.ts index 549ee6c6..16387987 100644 --- a/apps/backend/src/donations/types.ts +++ b/apps/backend/src/donations/types.ts @@ -2,4 +2,4 @@ export enum DonationStatus { AVAILABLE = 'available', FULFILLED = 'fulfilled', MATCHING = 'matching', -} \ No newline at end of file +} diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index c01eda5b..e3a93727 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -58,8 +58,8 @@ export class FoodRequestsController { type: 'object', properties: { pantryId: { type: 'integer', example: 1 }, - requestedSize: { - type: 'string', + requestedSize: { + type: 'string', enum: Object.values(RequestSize), example: RequestSize.LARGE, }, @@ -166,7 +166,10 @@ export class FoodRequestsController { ); const request = await this.requestsService.findOne(requestId); - await this.ordersService.updateStatus(request.order.orderId, OrderStatus.DELIVERED); + await this.ordersService.updateStatus( + request.order.orderId, + OrderStatus.DELIVERED, + ); return this.requestsService.updateDeliveryDetails( requestId, diff --git a/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts b/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts index 079362d6..3a14c4d4 100644 --- a/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts +++ b/apps/backend/src/migrations/1763963056712-AllergyFriendlyToBoolType.ts @@ -1,21 +1,21 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AllergyFriendlyToBoolType1763963056712 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(` +export class AllergyFriendlyToBoolType1763963056712 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE pantries ALTER COLUMN dedicated_allergy_friendly TYPE BOOLEAN USING (FALSE), ALTER COLUMN dedicated_allergy_friendly SET NOT NULL; `); - } + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(` + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE pantries ALTER COLUMN dedicated_allergy_friendly TYPE VARCHAR(255), ALTER COLUMN dedicated_allergy_friendly DROP NOT NULL; `); - } - + } } diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 4c64ac10..b65e335b 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -6,7 +6,13 @@ import { OrdersService } from './order.service'; import { mock } from 'jest-mock-extended'; import { Pantry } from '../pantries/pantries.entity'; import { User } from '../users/user.entity'; -import { AllergensConfidence, ClientVisitFrequency, PantryStatus, RefrigeratedDonation, ServeAllergicChildren } from '../pantries/types'; +import { + AllergensConfidence, + ClientVisitFrequency, + PantryStatus, + RefrigeratedDonation, + ServeAllergicChildren, +} from '../pantries/types'; import { OrderStatus } from './types'; const mockOrdersRepository = mock>(); diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index c9355860..0ab34601 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -73,11 +73,11 @@ export class Pantry { }) deliveryWindowInstructions?: string; - @Column({ - name: 'reserve_food_for_allergic', - type: 'enum', - enum: ReserveFoodForAllergic, - enumName: 'reserve_food_for_allergic_enum' + @Column({ + name: 'reserve_food_for_allergic', + type: 'enum', + enum: ReserveFoodForAllergic, + enumName: 'reserve_food_for_allergic_enum', }) reserveFoodForAllergic: string; diff --git a/apps/backend/src/pantries/types.ts b/apps/backend/src/pantries/types.ts index f776991b..cdf8b671 100644 --- a/apps/backend/src/pantries/types.ts +++ b/apps/backend/src/pantries/types.ts @@ -33,7 +33,7 @@ export enum PantryStatus { export enum Activity { CREATE_LABELED_SHELF = 'Create labeled shelf', PROVIDE_EDUCATIONAL_PAMPHLETS = 'Provide educational pamphlets', - TRACK_DIETARY_NEEDS ='Spreadsheet to track dietary needs', + TRACK_DIETARY_NEEDS = 'Spreadsheet to track dietary needs', POST_RESOURCE_FLYERS = 'Post allergen-free resource flyers', SURVEY_CLIENTS = 'Survey clients to determine medical dietary needs', COLLECT_FEEDBACK = 'Collect feedback from allergen-avoidant clients', diff --git a/apps/frontend/src/types/pantryEnums.ts b/apps/frontend/src/types/pantryEnums.ts index e5f13a6f..cdf8b671 100644 --- a/apps/frontend/src/types/pantryEnums.ts +++ b/apps/frontend/src/types/pantryEnums.ts @@ -33,7 +33,7 @@ export enum PantryStatus { export enum Activity { CREATE_LABELED_SHELF = 'Create labeled shelf', PROVIDE_EDUCATIONAL_PAMPHLETS = 'Provide educational pamphlets', - TRACK_DIETARY_NEEDS ='Spreadsheet to track dietary needs', + TRACK_DIETARY_NEEDS = 'Spreadsheet to track dietary needs', POST_RESOURCE_FLYERS = 'Post allergen-free resource flyers', SURVEY_CLIENTS = 'Survey clients to determine medical dietary needs', COLLECT_FEEDBACK = 'Collect feedback from allergen-avoidant clients', @@ -44,4 +44,4 @@ export enum ReserveFoodForAllergic { YES = 'Yes', SOME = 'Some', NO = 'No', -} \ No newline at end of file +} diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index cb901272..619bdf4f 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -1,12 +1,12 @@ -import { - RefrigeratedDonation, - ReserveFoodForAllergic, - ClientVisitFrequency, +import { + RefrigeratedDonation, + ReserveFoodForAllergic, + ClientVisitFrequency, ServeAllergicChildren, AllergensConfidence, PantryStatus, Activity, -} from "./pantryEnums"; +} from './pantryEnums'; // Note: The API calls as currently written do not // return a pantry's SSF representative or pantry @@ -223,4 +223,3 @@ export enum DonationStatus { FULFILLED = 'fulfilled', MATCHING = 'matching', } - From 21e5d07c10907363b4d73a9a38d42b508be8d6d9 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:13:09 -0500 Subject: [PATCH 06/19] pantry app pt. 2 --- .../1762125223723-UpdatePantryFields.ts | 19 - .../1763762628431-UpdatePantryFields.ts | 83 ++++ .../pantries/dtos/pantry-application.dto.ts | 67 ++- apps/backend/src/pantries/pantries.entity.ts | 76 ++- apps/backend/src/pantries/pantries.service.ts | 36 +- .../forms/pantryApplicationForm.tsx | 454 +++++++++++++----- .../src/components/forms/usPhoneInput.tsx | 4 +- .../containers/pantryApplicationSubmitted.tsx | 13 +- apps/frontend/src/theme.ts | 12 +- 9 files changed, 599 insertions(+), 165 deletions(-) delete mode 100644 apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts create mode 100644 apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts diff --git a/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts b/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts deleted file mode 100644 index 0150a701..00000000 --- a/apps/backend/src/migrations/1762125223723-UpdatePantryFields.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class UpdatePantryFields1740000000000 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(` - ALTER TABLE pantries - ADD COLUMN accept_food_deliveries boolean NOT NULL DEFAULT false, - ADD COLUMN delivery_window_instructions text - `); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(` - ALTER TABLE "pantries" - DROP COLUMN delivery_window_instructions, - DROP COLUMN accept_food_deliveries - `); - } -} diff --git a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts new file mode 100644 index 00000000..cb82081a --- /dev/null +++ b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts @@ -0,0 +1,83 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdatePantryFields1763762628431 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE pantries + ADD COLUMN accept_food_deliveries boolean NOT NULL DEFAULT false, + ADD COLUMN delivery_window_instructions text, + ADD COLUMN mailing_address_line_1 varchar(255), + ADD COLUMN mailing_address_line_2 varchar(255), + ADD COLUMN mailing_address_city varchar(255), + ADD COLUMN mailing_address_state varchar(255), + ADD COLUMN mailing_address_zip varchar(255), + ADD COLUMN mailing_address_country varchar(255), + ALTER COLUMN newsletter_subscription DROP NOT NULL, + ADD COLUMN has_email_contact BOOLEAN NOT NULL, + ADD COLUMN has_email_contact_other TEXT, + ADD COLUMN secondary_contact_first_name VARCHAR(255), + ADD COLUMN secondary_contact_last_name VARCHAR(255), + ADD COLUMN secondary_contact_email VARCHAR(255), + ADD COLUMN secondary_contact_phone VARCHAR(20); + + ALTER TABLE pantries + RENAME COLUMN address_line_1 TO shipment_address_line_1; + + ALTER TABLE pantries + RENAME COLUMN address_line_2 TO shipment_address_line_2; + + ALTER TABLE pantries + RENAME COLUMN address_city TO shipment_address_city; + + ALTER TABLE pantries + RENAME COLUMN address_state TO shipment_address_state; + + ALTER TABLE pantries + RENAME COLUMN address_zip TO shipment_address_zip; + + ALTER TABLE pantries + RENAME COLUMN address_country TO shipment_address_country; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "pantries" + DROP COLUMN IF EXISTS delivery_window_instructions, + DROP COLUMN IF EXISTS accept_food_deliveries, + DROP COLUMN IF EXISTS mailing_address_line_1, + DROP COLUMN IF EXISTS mailing_address_line_2, + DROP COLUMN IF EXISTS mailing_address_city, + DROP COLUMN IF EXISTS mailing_address_state, + DROP COLUMN IF EXISTS mailing_address_zip, + DROP COLUMN IF EXISTS mailing_address_country, + ALTER COLUMN newsletter_subscription SET NOT NULL, + DROP COLUMN IF EXISTS has_email_contact, + DROP COLUMN IF EXISTS has_email_contact_other, + DROP COLUMN IF EXISTS secondary_contact_first_name, + DROP COLUMN IF EXISTS secondary_contact_last_name, + DROP COLUMN IF EXISTS secondary_contact_email, + DROP COLUMN IF EXISTS secondary_contact_phone; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_line_1 TO address_line_1; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_line_2 TO address_line_2; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_city TO address_city; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_state TO address_state; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_zip TO address_zip; + + ALTER TABLE pantries + RENAME COLUMN shipment_address_country TO address_country; + `); + } + +} diff --git a/apps/backend/src/pantries/dtos/pantry-application.dto.ts b/apps/backend/src/pantries/dtos/pantry-application.dto.ts index 0e34e4fd..cd985531 100644 --- a/apps/backend/src/pantries/dtos/pantry-application.dto.ts +++ b/apps/backend/src/pantries/dtos/pantry-application.dto.ts @@ -39,35 +39,90 @@ export class PantryApplicationDto { }) contactPhone: string; + @IsBoolean() + hasEmailContact: boolean; + + @IsOptional() + @IsString() + @IsNotEmpty() + hasEmailContactOther?: string; + + @IsOptional() + @IsString() + @IsNotEmpty() + secondaryContactFirstName?: string; + + @IsOptional() + @IsString() + @IsNotEmpty() + secondaryContactLastName?: string; + + @IsOptional() + @IsEmail() + secondaryContactEmail?: string; + + @IsOptional() + @IsPhoneNumber('US', { + message: + 'secondaryContactPhone must be a valid phone number (make sure all the digits are correct)', + }) + secondaryContactPhone?: string; + @IsString() @Length(1, 255) pantryName: string; @IsString() @Length(1, 255) - addressLine1: string; + shipmentAddressLine1: string; + + @IsOptional() + @IsString() + @MaxLength(255) + shipmentAddressLine2?: string; + + @IsString() + @Length(1, 255) + shipmentAddressCity: string; + + @IsString() + @Length(1, 255) + shipmentAddressState: string; + + @IsString() + @Length(1, 255) + shipmentAddressZip: string; + + @IsOptional() + @IsString() + @MaxLength(255) + shipmentAddressCountry?: string; + + @IsString() + @Length(1, 255) + mailingAddressLine1: string; @IsOptional() @IsString() @MaxLength(255) - addressLine2?: string; + mailingAddressLine2?: string; @IsString() @Length(1, 255) - addressCity: string; + mailingAddressCity: string; @IsString() @Length(1, 255) - addressState: string; + mailingAddressState: string; @IsString() @Length(1, 255) - addressZip: string; + mailingAddressZip: string; @IsOptional() @IsString() @MaxLength(255) - addressCountry?: string; + mailingAddressCountry?: string; @IsString() @Length(1, 25) diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 0ab34601..bb0904bc 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -24,33 +24,61 @@ export class Pantry { @Column({ name: 'pantry_name', type: 'varchar', length: 255 }) pantryName: string; - @Column({ name: 'address_line_1', type: 'varchar', length: 255 }) - addressLine1: string; + @Column({ name: 'shipment_address_line_1', type: 'varchar', length: 255 }) + shipmentAddressLine1: string; @Column({ - name: 'address_line_2', + name: 'shipment_address_line_2', type: 'varchar', length: 255, nullable: true, }) - addressLine2?: string; + shipmentAddressLine2?: string; - @Column({ name: 'address_city', type: 'varchar', length: 255 }) - addressCity: string; + @Column({ name: 'shipment_address_city', type: 'varchar', length: 255 }) + shipmentAddressCity: string; - @Column({ name: 'address_state', type: 'varchar', length: 255 }) - addressState: string; + @Column({ name: 'shipment_address_state', type: 'varchar', length: 255 }) + shipmentAddressState: string; - @Column({ name: 'address_zip', type: 'varchar', length: 255 }) - addressZip: string; + @Column({ name: 'shipment_address_zip', type: 'varchar', length: 255 }) + shipmentAddressZip: string; @Column({ - name: 'address_country', + name: 'shipment_address_country', type: 'varchar', length: 255, nullable: true, }) - addressCountry?: string; + shipmentAddressCountry?: string; + + @Column({ name: 'mailing_address_line_1', type: 'varchar', length: 255 }) + mailingAddressLine1: string; + + @Column({ + name: 'mailing_address_line_2', + type: 'varchar', + length: 255, + nullable: true, + }) + mailingAddressLine2?: string; + + @Column({ name: 'mailing_address_city', type: 'varchar', length: 255 }) + mailingAddressCity: string; + + @Column({ name: 'mailing_address_state', type: 'varchar', length: 255 }) + mailingAddressState: string; + + @Column({ name: 'mailing_address_zip', type: 'varchar', length: 255 }) + mailingAddressZip: string; + + @Column({ + name: 'mailing_address_country', + type: 'varchar', + length: 255, + nullable: true, + }) + mailingAddressCountry?: string; @Column({ name: 'allergen_clients', type: 'varchar', length: 25 }) allergenClients: string; @@ -116,12 +144,34 @@ export class Pantry { }) serveAllergicChildren?: ServeAllergicChildren; - @Column({ name: 'newsletter_subscription', type: 'boolean' }) + @Column({ name: 'newsletter_subscription', type: 'boolean', nullable: true }) newsletterSubscription: boolean; @Column({ name: 'restrictions', type: 'text', array: true }) restrictions: string[]; + @Column({ name: 'has_email_contact', type: 'boolean' }) + hasEmailContact: boolean; + + @Column({ name: 'has_email_contact_other', type: 'text', nullable: true }) + hasEmailContactOther?: string; + + @Column({ name: 'secondary_contact_first_name', type: 'varchar', length: 255, nullable: true }) + secondaryContactFirstName?: string; + + @Column({ name: 'secondary_contact_last_name', type: 'varchar', length: 255, nullable: true }) + secondaryContactLastName?: string; + + @Column({ name: 'secondary_contact_email', type: 'varchar', length: 255, nullable: true }) + secondaryContactEmail?: string; + + @Column({ + name: 'secondary_contact_phone', + type: 'varchar', + length: 20, + }) + secondaryContactPhone?: string; + // cascade: ['insert'] means that when we create a new // pantry, the pantry user will automatically be added // to the User table diff --git a/apps/backend/src/pantries/pantries.service.ts b/apps/backend/src/pantries/pantries.service.ts index 101b3aa1..19db0891 100644 --- a/apps/backend/src/pantries/pantries.service.ts +++ b/apps/backend/src/pantries/pantries.service.ts @@ -34,6 +34,7 @@ export class PantriesService { const pantryContact: User = new User(); const pantry: Pantry = new Pantry(); + // primary contact information pantryContact.role = Role.PANTRY; pantryContact.firstName = pantryData.contactFirstName; pantryContact.lastName = pantryData.contactLastName; @@ -41,20 +42,39 @@ export class PantriesService { pantryContact.phone = pantryData.contactPhone; pantry.pantryUser = pantryContact; - + pantry.hasEmailContact = pantryData.hasEmailContact; + pantry.hasEmailContactOther = pantryData.hasEmailContactOther; + + // secondary contact information + pantry.secondaryContactFirstName = pantryData.secondaryContactFirstName; + pantry.secondaryContactLastName = pantryData.secondaryContactLastName; + pantry.secondaryContactEmail = pantryData.secondaryContactEmail; + pantry.secondaryContactPhone = pantryData.secondaryContactPhone; + + // food shipment address information + pantry.shipmentAddressLine1 = pantryData.shipmentAddressLine1; + pantry.shipmentAddressLine2 = pantryData.shipmentAddressLine2; + pantry.shipmentAddressCity = pantryData.shipmentAddressCity; + pantry.shipmentAddressState = pantryData.shipmentAddressState; + pantry.shipmentAddressZip = pantryData.shipmentAddressZip; + pantry.shipmentAddressCountry = pantryData.shipmentAddressCountry; + + // mailing address information + pantry.mailingAddressLine1 = pantryData.mailingAddressLine1; + pantry.mailingAddressLine2 = pantryData.mailingAddressLine2; + pantry.mailingAddressCity = pantryData.mailingAddressCity; + pantry.mailingAddressState = pantryData.mailingAddressState; + pantry.mailingAddressZip = pantryData.mailingAddressZip; + pantry.mailingAddressCountry = pantryData.mailingAddressCountry; + + // pantry details information pantry.pantryName = pantryData.pantryName; - pantry.addressLine1 = pantryData.addressLine1; - pantry.addressLine2 = pantryData.addressLine2; - pantry.addressCity = pantryData.addressCity; - pantry.addressState = pantryData.addressState; - pantry.addressZip = pantryData.addressZip; - pantry.addressCountry = pantryData.addressCountry; pantry.allergenClients = pantryData.allergenClients; pantry.restrictions = pantryData.restrictions; pantry.refrigeratedDonation = pantryData.refrigeratedDonation; + pantry.dedicatedAllergyFriendly = pantryData.dedicatedAllergyFriendly; pantry.reserveFoodForAllergic = pantryData.reserveFoodForAllergic; pantry.reservationExplanation = pantryData.reservationExplanation; - pantry.dedicatedAllergyFriendly = pantryData.dedicatedAllergyFriendly; pantry.clientVisitFrequency = pantryData.clientVisitFrequency; pantry.identifyAllergensConfidence = pantryData.identifyAllergensConfidence; pantry.serveAllergicChildren = pantryData.serveAllergicChildren; diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 0dfaf0e5..308b7189 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -16,6 +16,8 @@ import { Wrap, createListCollection, Tag, + Separator, + Checkbox, } from '@chakra-ui/react'; import { ActionFunction, @@ -66,6 +68,7 @@ const activityOptions = [ const PantryApplicationForm: React.FC = () => { const [contactPhone, setContactPhone] = useState(''); + const [secondaryContactPhone, setSecondaryContactPhone] = useState(''); const [activities, setActivities] = useState([]); const noActivitiesSelected: boolean = activities.length === 0; const allergenClientsExactOption: string = 'I have an exact number'; @@ -82,27 +85,29 @@ const PantryApplicationForm: React.FC = () => { const [serveAllergicChildren, setServeAllergicChildren] = useState< string | undefined >(); - const [refrigeratedDonation, setRefrigeratedDonation] = useState(); + const [differentMailingAddress, setDifferentMailingAddress] = useState(); const [searchRestriction, setSearchRestriction] = useState(''); const [searchActivity, setSearchActivity] = useState(''); const sectionTitleStyles = { - fontFamily: "'Inter', sans-serif", + fontFamily: "inter", fontWeight: '600', fontSize: 'md', + color: 'gray.dark', + mb: '1.75em', }; const sectionSubtitleStyles = { - fontFamily: "'Inter', sans-serif", + fontFamily: "inter", fontWeight: '400', - color: 'gray', - mb: '2em', + color: 'gray.light', + mb: '2.25em', fontSize: 'sm', } const fieldHeaderStyles = { color: 'neutral.800', - fontFamily: "'Inter', sans-serif", + fontFamily: "inter", fontSize: 'sm', fontWeight: '600', }; @@ -135,39 +140,40 @@ const PantryApplicationForm: React.FC = () => { return ( - - - Welcome to the Securing Safe Food Partner Pantry Application. + + + Partner Pantry Application - - Thank you for your interest in partnering with Securing Safe Food (SSF) to help serve clients - with food allergies and other adverse reactions to foods. This application helps us understand - your pantry’s capacity and interest in distributing allergen-friendly food. We’ll ask about - your pantry’s current practices, storage capabilities, and communication preferences. Please - answer as accurately as possible. If you have any questions or need help, don’t hesitate to - contact the SSF team. + + Thank you for your interest in partnering with Securing Safe Food (SSF) to help + serve clients with food allergies and other adverse reactions to foods. - - Pantry Application + + Pantry Application Form - - Please fill out the folllowing information to get started. - + + + This application helps us understand your pantry’s capacity and interest in + distributing allergen-friendly food. We’ll ask about your pantry’s current + practices, storage capabilities, and communication preferences. + + + Please answer as accurately as possible. If you have any questions or need help, + don’t hesitate to contact the SSF team. + + + + - Point of Contact Information - - - Please provide information about whom we should contact at your pantry. + Primary Contact Information - + First Name @@ -201,57 +207,261 @@ const PantryApplicationForm: React.FC = () => { + + + Is there someone at your pantry who can regularly check and respond to emails from SSF as needed?{' '} + + + + + {['Yes', 'No', 'Other'].map((value) => ( + + + + + + + {value} + + + ))} + + + + + + + + - Address + Secondary Contact Information + + + + + First Name + + + + + + Last Name + + + + + + Phone Number + + + + + + Email Address + + + + + + + + + Food Shipment Address - Please list your address for food shipments. + Please list your address for food shipments. - + Address Line 1 - + Address Line 2 - + City/Town - + State/Region/Province - + Zip/Post Code - + Country - + + + + Does this address differ from your pantry's mailing address for documents?{' '} + + + setDifferentMailingAddress(e.value === 'Yes')} + name="differentMailingAddress" + > + + {['Yes', 'No'].map((value) => ( + + + + + + + {value} + + + ))} + + + + + + Would your pantry be able to accept food deliveries + during standard business hours Mon-Fri?{' '} + + + + + {['Yes', 'No'].map((value) => ( + + + + + + + {value} + + + ))} + + + + + + Please note any delivery window instructions. + + + + + + + {differentMailingAddress && ( + <> + + Mailing Address + + + Please list your mailing address for documents. + + + + + Address Line 1 + + + + + + + Address Line 2 + + + + + + City/Town + + + + + + + State/Region/Province + + + + + + + Zip/Post Code + + + + + + + Country + + + + - + + > + )} + + Pantry Details @@ -263,7 +473,7 @@ const PantryApplicationForm: React.FC = () => { - + Approximately how many allergen-avoidant clients does your pantry serve? @@ -274,6 +484,7 @@ const PantryApplicationForm: React.FC = () => { value={allergenClients} onChange={(e) => setAllergenClients(e.target.value)} placeholder="Select an option" + color = {allergenClients ? 'neutral.800' : 'neutral.300'} borderColor="neutral.100" name="allergenClients" > @@ -291,7 +502,7 @@ const PantryApplicationForm: React.FC = () => { ))} - + {allergenClients === allergenClientsExactOption && ( @@ -323,7 +534,11 @@ const PantryApplicationForm: React.FC = () => { onInputValueChange={(e: {inputValue: string}) => setSearchRestriction(e.inputValue)} > - + @@ -337,7 +552,6 @@ const PantryApplicationForm: React.FC = () => { ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} > @@ -392,129 +606,114 @@ const PantryApplicationForm: React.FC = () => { )} - Would you be able to accept refrigerated/frozen donations from us? - - - - setRefrigeratedDonation(e.target.value)} - placeholder="Select an option" - borderColor="neutral.100" - name="refrigeratedDonation" - > - {['Yes', 'Small quantities only', 'No'].map((value) => ( - - {value} - - ))} - - - - - - - - Would your pantry be able to accept food deliveries during standard business hours?{' '} + Would you be able to accept frozen donations that require refrigeration or freezing? - {['Yes', 'No'].map((value) => ( + {['Yes, always', 'No', 'Sometimes (check in before sending)'].map((value) => ( - + - {value} + + {value} + ))} - - - Please note any delivery window instructions. - - - - - Are you willing to reserve our food shipments for allergen-avoidant - individuals?{' '} + Do you have a dedicated shelf or section of your pantry for + allergy-friendly items? setReserveFoodForAllergic(e.value)} > - {['Yes', 'Some', 'No'].map((value) => ( + {['Yes', 'No'].map((value) => ( - + - {value} + + {value} + ))} - {reserveFoodForAllergic && ['Some', 'Yes'].includes(reserveFoodForAllergic) && ( - - - Please explain how you would do this. - - - - - For example: keeping allergen-friendly items on a separate shelf, encouraging non-allergic - clients to save these items for clients who do not have other safe food options. - - - )} - - Do you have a dedicated shelf or section of your pantry for - allergy-friendly items? + Are you willing to reserve our food shipments for allergen-avoidant + individuals?{' '} + + For example, grouping allergen-friendly items on a separate shelf or + in separate bins and encouraging non-allergic clients to save these + items for clients who do not have other safe food options. + setReserveFoodForAllergic(e.value)} > - {['Yes', 'No'].map((value) => ( + {['Yes', 'Some', 'No'].map((value) => ( - + - {value} + + {value} + ))} + {reserveFoodForAllergic === 'Some' && ( + + + Please explain why you selected "Some." + + + + )} + How often do allergen-avoidant clients visit your food pantry? @@ -526,6 +725,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="clientVisitFrequency" borderColor="neutral.100" + _placeholder={{ color: "neutral.300" }} > {[ 'Daily', @@ -554,6 +754,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="identifyAllergensConfidence" borderColor="neutral.100" + _placeholder={{ color: "neutral.300" }} > {[ 'Very confident', @@ -584,6 +785,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="serveAllergicChildren" borderColor="neutral.100" + _placeholder={{ color: "neutral.300" }} > {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( @@ -613,7 +815,11 @@ const PantryApplicationForm: React.FC = () => { required={noActivitiesSelected} > - + @@ -680,7 +886,7 @@ const PantryApplicationForm: React.FC = () => { - If you answered "Something Else", please elaborate. + If you answered "Something Else," please elaborate. @@ -721,18 +927,38 @@ const PantryApplicationForm: React.FC = () => { borderColor="neutral.100" /> - {value} + + {value} + ))} - + + + + + + By submitting this form, you agree to our Privacy Policy.{' '} + + + + + Cancel @@ -799,6 +1025,16 @@ export const submitPantryApplicationForm: ActionFunction = async ({ pantryApplicationData.set('newsletterSubscription', form.get("newsletterSubscription") === "Yes"); pantryApplicationData.set('acceptFoodDeliveries', form.get("acceptFoodDeliveries") === "Yes"); pantryApplicationData.set('dedicatedAllergyFriendly', form.get("dedicatedAllergyFriendly") === "Yes"); + pantryApplicationData.set('hasEmailContact', form.get("hasEmailContact") === "Yes"); + + if (form.get("differentMailingAddress") === "No") { + pantryApplicationData.set('mailingAddressLine1', pantryApplicationData.get('shipmentAddressLine1')); + pantryApplicationData.set('mailingAddressLine2', pantryApplicationData.get('shipmentAddressLine2')); + pantryApplicationData.set('mailingAddressCity', pantryApplicationData.get('shipmentAddressCity')); + pantryApplicationData.set('mailingAddressState', pantryApplicationData.get('shipmentAddressState')); + pantryApplicationData.set('mailingAddressZip', pantryApplicationData.get('shipmentAddressZip')); + pantryApplicationData.set('mailingAddressCountry', pantryApplicationData.get('shipmentAddressCountry')); + } console.log('Pantry Application Data:', Object.fromEntries(pantryApplicationData)); diff --git a/apps/frontend/src/components/forms/usPhoneInput.tsx b/apps/frontend/src/components/forms/usPhoneInput.tsx index 4b075858..edc16aef 100644 --- a/apps/frontend/src/components/forms/usPhoneInput.tsx +++ b/apps/frontend/src/components/forms/usPhoneInput.tsx @@ -24,6 +24,7 @@ const isPhoneValid = (phone: string): boolean => { export interface USPhoneInputProps { value: string; onChange: (phone: string) => void; + allowEmpty?: boolean; inputProps?: object; } @@ -34,6 +35,7 @@ export interface USPhoneInputProps { export const USPhoneInput: React.FC = ({ value, onChange, + allowEmpty = false, inputProps, }) => { const phoneInput = usePhoneInput({ @@ -45,7 +47,7 @@ export const USPhoneInput: React.FC = ({ if (isPhoneValid(data.phone)) { phoneInput.inputRef.current?.setCustomValidity(''); - } else { + } else if (!allowEmpty) { phoneInput.inputRef.current?.setCustomValidity('Invalid phone number.'); } }, diff --git a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx index 1894de46..8c7e57d1 100644 --- a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx +++ b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx @@ -8,10 +8,10 @@ const PantryApplicationSubmitted: React.FC = () => { - + Thank you! - Your application has been submitted. + Your application has been submitted. { borderColor="neutral.200" rounded="sm" alignItems="center" - color="neutral.800" width="100%" > - - - + + + Application Submitted - + Please check your inbox for status updates. diff --git a/apps/frontend/src/theme.ts b/apps/frontend/src/theme.ts index 1a228d0e..4e666f9d 100644 --- a/apps/frontend/src/theme.ts +++ b/apps/frontend/src/theme.ts @@ -10,25 +10,28 @@ const textStyles = defineTextStyles({ value: { fontFamily: 'instrument', fontSize: '32px', - fontWeight: '400' + fontWeight: '400', }, }, h2: { value: { fontFamily: 'ibm', fontSize: '28px', + fontWeight: '600', }, }, h3: { value: { fontFamily: 'ibm', fontSize: '24px', + fontWeight: '600', }, }, h4: { value: { fontFamily: 'inter', fontSize: '20px', + fontWeight: '400', }, }, p: { @@ -42,6 +45,7 @@ const textStyles = defineTextStyles({ value: { fontFamily: 'inter', fontSize: '14px', + fontWeight: '400', }, }, }); @@ -64,12 +68,16 @@ const customConfig = defineConfig({ 50: { value: '#FAFAFA' }, 100: { value: '#E7E7E7' }, 200: { value: '#CFCFCF' }, + 300: { value: '#B8B8B8' }, 600: { value: '#707070' }, 700: { value: '#585858' }, 800: { value: '#414141' }, 900: { value: '#212529' }, }, - gray: { value: '#515151' }, + gray: { + light: { value: '#515151' }, + dark: { value: '#111' }, + }, teal: { 400: { value: '#A9D5DB' }, 100: { value: '#E9F4F6' }, From 0f6c288ae6d1703c1d16164641360cca94c573ee Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:13:35 -0500 Subject: [PATCH 07/19] prettier --- .../1763762628431-UpdatePantryFields.ts | 16 +++++------ apps/backend/src/pantries/pantries.entity.ts | 27 ++++++++++++++----- .../containers/pantryApplicationSubmitted.tsx | 6 +++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts index cb82081a..276d792c 100644 --- a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts +++ b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts @@ -1,9 +1,8 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class UpdatePantryFields1763762628431 implements MigrationInterface { - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(` + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE pantries ADD COLUMN accept_food_deliveries boolean NOT NULL DEFAULT false, ADD COLUMN delivery_window_instructions text, @@ -39,10 +38,10 @@ export class UpdatePantryFields1763762628431 implements MigrationInterface { ALTER TABLE pantries RENAME COLUMN address_country TO shipment_address_country; `); - } + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(` + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` ALTER TABLE "pantries" DROP COLUMN IF EXISTS delivery_window_instructions, DROP COLUMN IF EXISTS accept_food_deliveries, @@ -78,6 +77,5 @@ export class UpdatePantryFields1763762628431 implements MigrationInterface { ALTER TABLE pantries RENAME COLUMN shipment_address_country TO address_country; `); - } - + } } diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index bb0904bc..515d3da2 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -155,14 +155,29 @@ export class Pantry { @Column({ name: 'has_email_contact_other', type: 'text', nullable: true }) hasEmailContactOther?: string; - - @Column({ name: 'secondary_contact_first_name', type: 'varchar', length: 255, nullable: true }) + + @Column({ + name: 'secondary_contact_first_name', + type: 'varchar', + length: 255, + nullable: true, + }) secondaryContactFirstName?: string; - - @Column({ name: 'secondary_contact_last_name', type: 'varchar', length: 255, nullable: true }) + + @Column({ + name: 'secondary_contact_last_name', + type: 'varchar', + length: 255, + nullable: true, + }) secondaryContactLastName?: string; - - @Column({ name: 'secondary_contact_email', type: 'varchar', length: 255, nullable: true }) + + @Column({ + name: 'secondary_contact_email', + type: 'varchar', + length: 255, + nullable: true, + }) secondaryContactEmail?: string; @Column({ diff --git a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx index 8c7e57d1..7add3b0b 100644 --- a/apps/frontend/src/containers/pantryApplicationSubmitted.tsx +++ b/apps/frontend/src/containers/pantryApplicationSubmitted.tsx @@ -11,7 +11,9 @@ const PantryApplicationSubmitted: React.FC = () => { Thank you! - Your application has been submitted. + + Your application has been submitted. + { width="100%" > - + Application Submitted From de76192fc6779c54e04cd979f7fb226e81c1683a Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:15:15 -0500 Subject: [PATCH 08/19] remove duplicate jest-mock-extended --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index eadf2701..6af7be3b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "dotenv": "^16.4.5", "global": "^4.4.0", "google-libphonenumber": "^3.2.40", - "jest-mock-extended": "^4.0.0", "jwks-rsa": "^3.1.0", "lucide-react": "^0.544.0", "mongodb": "^6.1.0", From 7379ca592ba48df4677bb64a5a347fdd3a469de0 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:37:37 -0500 Subject: [PATCH 09/19] review comments --- .../1763762628431-UpdatePantryFields.ts | 4 +- .../pantries/dtos/pantry-application.dto.ts | 37 ++++++++++++- apps/backend/src/pantries/pantries.entity.ts | 4 +- apps/backend/src/pantries/pantries.service.ts | 2 +- apps/backend/src/users/dtos/userSchema.dto.ts | 5 +- .../forms/pantryApplicationForm.tsx | 27 +++++++--- apps/frontend/src/types/types.ts | 53 ++++++++++++++----- 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts index 276d792c..9573f725 100644 --- a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts +++ b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts @@ -14,7 +14,7 @@ export class UpdatePantryFields1763762628431 implements MigrationInterface { ADD COLUMN mailing_address_country varchar(255), ALTER COLUMN newsletter_subscription DROP NOT NULL, ADD COLUMN has_email_contact BOOLEAN NOT NULL, - ADD COLUMN has_email_contact_other TEXT, + ADD COLUMN email_contact_other TEXT, ADD COLUMN secondary_contact_first_name VARCHAR(255), ADD COLUMN secondary_contact_last_name VARCHAR(255), ADD COLUMN secondary_contact_email VARCHAR(255), @@ -53,7 +53,7 @@ export class UpdatePantryFields1763762628431 implements MigrationInterface { DROP COLUMN IF EXISTS mailing_address_country, ALTER COLUMN newsletter_subscription SET NOT NULL, DROP COLUMN IF EXISTS has_email_contact, - DROP COLUMN IF EXISTS has_email_contact_other, + DROP COLUMN IF EXISTS email_contact_other, DROP COLUMN IF EXISTS secondary_contact_first_name, DROP COLUMN IF EXISTS secondary_contact_last_name, DROP COLUMN IF EXISTS secondary_contact_email, diff --git a/apps/backend/src/pantries/dtos/pantry-application.dto.ts b/apps/backend/src/pantries/dtos/pantry-application.dto.ts index cd985531..42510915 100644 --- a/apps/backend/src/pantries/dtos/pantry-application.dto.ts +++ b/apps/backend/src/pantries/dtos/pantry-application.dto.ts @@ -22,17 +22,22 @@ import { export class PantryApplicationDto { @IsString() @IsNotEmpty() + @Length(1, 255) contactFirstName: string; @IsString() @IsNotEmpty() + @Length(1, 255) contactLastName: string; @IsEmail() + @Length(1, 255) contactEmail: string; // This validation is very strict and won't accept phone numbers // that look right but aren't actually possible phone numbers + @IsString() + @IsNotEmpty() @IsPhoneNumber('US', { message: 'contactPhone must be a valid phone number (make sure all the digits are correct)', @@ -45,92 +50,114 @@ export class PantryApplicationDto { @IsOptional() @IsString() @IsNotEmpty() - hasEmailContactOther?: string; + @MaxLength(255) + emailContactOther?: string; @IsOptional() @IsString() @IsNotEmpty() + @MaxLength(255) secondaryContactFirstName?: string; @IsOptional() @IsString() @IsNotEmpty() + @MaxLength(255) secondaryContactLastName?: string; @IsOptional() @IsEmail() + @IsNotEmpty() + @MaxLength(255) secondaryContactEmail?: string; @IsOptional() + @IsString() @IsPhoneNumber('US', { message: 'secondaryContactPhone must be a valid phone number (make sure all the digits are correct)', }) + @IsNotEmpty() secondaryContactPhone?: string; @IsString() + @IsNotEmpty() @Length(1, 255) pantryName: string; @IsString() + @IsNotEmpty() @Length(1, 255) shipmentAddressLine1: string; @IsOptional() @IsString() @MaxLength(255) + @IsNotEmpty() shipmentAddressLine2?: string; @IsString() + @IsNotEmpty() @Length(1, 255) shipmentAddressCity: string; @IsString() + @IsNotEmpty() @Length(1, 255) shipmentAddressState: string; @IsString() + @IsNotEmpty() @Length(1, 255) shipmentAddressZip: string; @IsOptional() @IsString() @MaxLength(255) + @IsNotEmpty() shipmentAddressCountry?: string; @IsString() + @IsNotEmpty() @Length(1, 255) mailingAddressLine1: string; @IsOptional() @IsString() @MaxLength(255) + @IsNotEmpty() mailingAddressLine2?: string; @IsString() + @IsNotEmpty() @Length(1, 255) mailingAddressCity: string; @IsString() + @IsNotEmpty() @Length(1, 255) mailingAddressState: string; @IsString() + @IsNotEmpty() @Length(1, 255) mailingAddressZip: string; @IsOptional() @IsString() @MaxLength(255) + @IsNotEmpty() mailingAddressCountry?: string; @IsString() + @IsNotEmpty() @Length(1, 25) allergenClients: string; @IsOptional() @IsString({ each: true }) @IsNotEmpty({ each: true }) + @MaxLength(255, { each: true }) restrictions?: string[]; @IsEnum(RefrigeratedDonation) @@ -141,6 +168,8 @@ export class PantryApplicationDto { @IsOptional() @IsString() + @IsNotEmpty() + @MaxLength(255) deliveryWindowInstructions?: string; @IsEnum(ReserveFoodForAllergic) @@ -149,6 +178,8 @@ export class PantryApplicationDto { // TODO: Really, this validation should be different depending on the value of reserveFoodForAllergic @IsOptional() @IsString() + @IsNotEmpty() + @MaxLength(255) reservationExplanation?: string; @IsBoolean() @@ -172,14 +203,18 @@ export class PantryApplicationDto { @IsOptional() @IsString() + @IsNotEmpty() + @MaxLength(255) activitiesComments?: string; @IsString() @IsNotEmpty() + @Length(1, 255) itemsInStock: string; @IsString() @IsNotEmpty() + @Length(1, 255) needMoreOptions: string; @IsOptional() diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index 515d3da2..ed8e6f5c 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -153,8 +153,8 @@ export class Pantry { @Column({ name: 'has_email_contact', type: 'boolean' }) hasEmailContact: boolean; - @Column({ name: 'has_email_contact_other', type: 'text', nullable: true }) - hasEmailContactOther?: string; + @Column({ name: 'email_contact_other', type: 'text', nullable: true }) + emailContactOther?: string; @Column({ name: 'secondary_contact_first_name', diff --git a/apps/backend/src/pantries/pantries.service.ts b/apps/backend/src/pantries/pantries.service.ts index 19db0891..bca4d680 100644 --- a/apps/backend/src/pantries/pantries.service.ts +++ b/apps/backend/src/pantries/pantries.service.ts @@ -43,7 +43,7 @@ export class PantriesService { pantry.pantryUser = pantryContact; pantry.hasEmailContact = pantryData.hasEmailContact; - pantry.hasEmailContactOther = pantryData.hasEmailContactOther; + pantry.emailContactOther = pantryData.emailContactOther; // secondary contact information pantry.secondaryContactFirstName = pantryData.secondaryContactFirstName; diff --git a/apps/backend/src/users/dtos/userSchema.dto.ts b/apps/backend/src/users/dtos/userSchema.dto.ts index b6905ea2..ac8cd1ec 100644 --- a/apps/backend/src/users/dtos/userSchema.dto.ts +++ b/apps/backend/src/users/dtos/userSchema.dto.ts @@ -3,22 +3,25 @@ import { IsEnum, IsNotEmpty, IsString, - IsOptional, IsPhoneNumber, + Length, } from 'class-validator'; import { Role } from '../types'; export class userSchemaDto { @IsEmail() @IsNotEmpty() + @Length(1, 255) email: string; @IsString() @IsNotEmpty() + @Length(1, 255) firstName: string; @IsString() @IsNotEmpty() + @Length(1, 255) lastName: string; @IsString() diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 308b7189..033a7b64 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -239,7 +239,7 @@ const PantryApplicationForm: React.FC = () => { { {allergenClients === allergenClientsExactOption && ( - + Please provide the exact number, if known @@ -705,6 +705,16 @@ const PantryApplicationForm: React.FC = () => { + {reserveFoodForAllergic === 'Yes' && ( + + + How would you work to ensure that allergen-friendly foods are distributed to + clients with food allergies or other adverse reactions to foods? + + + + )} + {reserveFoodForAllergic === 'Some' && ( @@ -725,7 +735,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="clientVisitFrequency" borderColor="neutral.100" - _placeholder={{ color: "neutral.300" }} + color = {clientVisitFrequency ? 'neutral.800' : 'neutral.300'} > {[ 'Daily', @@ -754,7 +764,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="identifyAllergensConfidence" borderColor="neutral.100" - _placeholder={{ color: "neutral.300" }} + color = {identifyAllergensConfidence ? 'neutral.800' : 'neutral.300'} > {[ 'Very confident', @@ -785,7 +795,7 @@ const PantryApplicationForm: React.FC = () => { placeholder="Select an option" name="serveAllergicChildren" borderColor="neutral.100" - _placeholder={{ color: "neutral.300" }} + color = {serveAllergicChildren ? 'neutral.800' : 'neutral.300'} > {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( @@ -884,7 +894,12 @@ const PantryApplicationForm: React.FC = () => { Please list any comments/concerns related to the previous question. - + If you answered "Something Else," please elaborate. diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index 619bdf4f..bf2d4506 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -15,14 +15,22 @@ import { export interface Pantry { pantryId: number; pantryName: string; - addressLine1: string; - addressLine2?: string; - addressCity: string; - addressState: string; - addressZip: string; - addressCountry?: string; + shippingAddressLine1: string; + shippingAddressLine2?: string; + shippingAddressCity: string; + shippingAddressState: string; + shippingAddressZip: string; + shippingAddressCountry?: string; + mailingAddressLine1: string; + mailingAddressLine2?: string; + mailingAddressCity: string; + mailingAddressState: string; + mailingAddressZip: string; + mailingAddressCountry?: string; allergenClients: string; refrigeratedDonation: RefrigeratedDonation; + acceptFoodDeliveries: boolean; + deliveryWindowInstructions?: string; reserveFoodForAllergic: ReserveFoodForAllergic; reservationExplanation?: string; dedicatedAllergyFriendly: boolean; @@ -31,6 +39,13 @@ export interface Pantry { serveAllergicChildren?: ServeAllergicChildren; newsletterSubscription: boolean; restrictions: string[]; + hasEmailContact: boolean; + emailContactOther?: string; + secondaryContactFirstName?: string; + secondaryContactLastName?: string; + secondaryContactEmail?: string; + secondaryContactPhone?: string; + pantryUser: User status: PantryStatus; dateApplied: Date; activities: Activity[]; @@ -44,16 +59,30 @@ export interface PantryApplicationDto { contactLastName: string; contactEmail: string; contactPhone: string; + hasEmailContact: boolean; + emailContactOther?: string; + secondaryContactFirstName?: string; + secondaryContactLastName?: string; + secondaryContactEmail?: string; + secondaryContactPhone?: string; pantryName: string; - addressLine1: string; - addressLine2?: string; - addressCity: string; - addressState: string; - addressZip: string; - addressCountry?: string; + shippingAddressLine1: string; + shippingAddressLine2?: string; + shippingAddressCity: string; + shippingAddressState: string; + shippingAddressZip: string; + shippingAddressCountry?: string; + mailingAddressLine1: string; + mailingAddressLine2?: string; + mailingAddressCity: string; + mailingAddressState: string; + mailingAddressZip: string; + mailingAddressCountry?: string; allergenClients: string; restrictions?: string[]; refrigeratedDonation: RefrigeratedDonation; + acceptFoodDeliveries: boolean; + deliveryWindowInstructions?: string; reserveFoodForAllergic: ReserveFoodForAllergic; reservationExplanation?: string; dedicatedAllergyFriendly: boolean; From 30e382f1ce71d62d7342ee31abd4451fe19c2640 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:38:02 -0500 Subject: [PATCH 10/19] prettier --- apps/frontend/src/types/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index bf2d4506..a7e6cc91 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -45,7 +45,7 @@ export interface Pantry { secondaryContactLastName?: string; secondaryContactEmail?: string; secondaryContactPhone?: string; - pantryUser: User + pantryUser: User; status: PantryStatus; dateApplied: Date; activities: Activity[]; From 33120e1f70af09c5f753fcc4d80622c89e4816ca Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 29 Nov 2025 10:38:06 -0500 Subject: [PATCH 11/19] review comments --- .../src/components/forms/pantryApplicationForm.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 033a7b64..7a04e777 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -88,6 +88,7 @@ const PantryApplicationForm: React.FC = () => { const [differentMailingAddress, setDifferentMailingAddress] = useState(); const [searchRestriction, setSearchRestriction] = useState(''); const [searchActivity, setSearchActivity] = useState(''); + const [otherEmailContact, setOtherEmailContact] = useState(false); const sectionTitleStyles = { fontFamily: "inter", @@ -215,6 +216,7 @@ const PantryApplicationForm: React.FC = () => { setOtherEmailContact(e.value === 'Other')} > {['Yes', 'No', 'Other'].map((value) => ( @@ -237,7 +239,7 @@ const PantryApplicationForm: React.FC = () => { - + { - + Please list any comments/concerns related to the previous question. + {activities.includes('Something else') && ( + + )} If you answered "Something Else," please elaborate. From 24a758cdc0c85263d2dc1d581312d3c1c5ddf3e7 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:29:00 -0500 Subject: [PATCH 12/19] review comments --- .../forms/pantryApplicationForm.tsx | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 7a04e777..d7dad713 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -75,6 +75,7 @@ const PantryApplicationForm: React.FC = () => { const [allergenClients, setAllergenClients] = useState(); const [restrictions, setRestrictions] = useState([]); + const noRestrictionsSelected: boolean = restrictions.length === 0; const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); const [clientVisitFrequency, setClientVisitFrequency] = useState< string | undefined @@ -430,21 +431,21 @@ const PantryApplicationForm: React.FC = () => { - + City/Town - + State/Region/Province - + Zip/Post Code @@ -522,10 +523,11 @@ const PantryApplicationForm: React.FC = () => { /> )} - + Which food allergies or other medical dietary restrictions do clients at your pantry report? + { collection={restrictionsCollection} onValueChange={(e: {value: string[]}) => setRestrictions(e.value)} onInputValueChange={(e: {inputValue: string}) => setSearchRestriction(e.inputValue)} + required={noRestrictionsSelected} > { {restrictions.find((option) => otherRestrictionsOptions.includes(option), ) && ( - + otherRestrictionsOptions.includes(option))} + mb="2em" + > If you selected "Other," please specify: + @@ -998,7 +1005,6 @@ export const submitPantryApplicationForm: ActionFunction = async ({ request, }: ActionFunctionArgs) => { const form = await request.formData(); - const pantryApplicationData = new Map(); const ActivityStorageMap: Record = { @@ -1011,62 +1017,58 @@ export const submitPantryApplicationForm: ActionFunction = async ({ 'Something else': Activity.SOMETHING_ELSE, }; - // Handle questions with checkboxes (we create an array of all - // selected options) - const restrictions = form.getAll('restrictions'); const restrictionsOther = form.get('restrictionsOther'); - if (restrictionsOther !== null && restrictionsOther !== '') { - restrictions.push(restrictionsOther); + restrictions.push(restrictionsOther as string); } - pantryApplicationData.set('restrictions', restrictions); - form.delete('restrictions'); + form.delete('restrictions') + form.delete('restrictionsOther'); const selectedActivities = form.getAll('activities') as string[]; const convertedActivities = selectedActivities.map((activity) => ActivityStorageMap[activity]); pantryApplicationData.set('activities', convertedActivities); form.delete('activities'); - pantryApplicationData.set('dedicatedAllergyFriendly', form.get('dedicatedAllergyFriendly')); - form.delete('dedicatedAllergyFriendly'); - - // Handle all other questions - form.forEach((value, key) => { - if (value === '') { - pantryApplicationData.set(key, null); - } else { - pantryApplicationData.set(key, value) - } - }); - pantryApplicationData.set('newsletterSubscription', form.get("newsletterSubscription") === "Yes"); pantryApplicationData.set('acceptFoodDeliveries', form.get("acceptFoodDeliveries") === "Yes"); pantryApplicationData.set('dedicatedAllergyFriendly', form.get("dedicatedAllergyFriendly") === "Yes"); pantryApplicationData.set('hasEmailContact', form.get("hasEmailContact") === "Yes"); + form.delete('newsletterSubscription') + form.delete('acceptFoodDeliveries') + form.delete('dedicatedAllergyFriendly') + form.delete('hasEmailContact') + // Handle mailing address if (form.get("differentMailingAddress") === "No") { - pantryApplicationData.set('mailingAddressLine1', pantryApplicationData.get('shipmentAddressLine1')); - pantryApplicationData.set('mailingAddressLine2', pantryApplicationData.get('shipmentAddressLine2')); - pantryApplicationData.set('mailingAddressCity', pantryApplicationData.get('shipmentAddressCity')); - pantryApplicationData.set('mailingAddressState', pantryApplicationData.get('shipmentAddressState')); - pantryApplicationData.set('mailingAddressZip', pantryApplicationData.get('shipmentAddressZip')); - pantryApplicationData.set('mailingAddressCountry', pantryApplicationData.get('shipmentAddressCountry')); + pantryApplicationData.set('mailingAddressLine1', form.get('shipmentAddressLine1')); + pantryApplicationData.set('mailingAddressLine2', form.get('shipmentAddressLine2') || null); + pantryApplicationData.set('mailingAddressCity', form.get('shipmentAddressCity')); + pantryApplicationData.set('mailingAddressState', form.get('shipmentAddressState')); + pantryApplicationData.set('mailingAddressZip', form.get('shipmentAddressZip')); + pantryApplicationData.set('mailingAddressCountry', form.get('shipmentAddressCountry') || null); } - - console.log('Pantry Application Data:', Object.fromEntries(pantryApplicationData)); + form.delete('differentMailingAddress') // Replace the answer for allergenClients with the answer // for allergenClientsExact if it is given - - const allergenClientsExact = pantryApplicationData.get( + const allergenClientsExact = form.get( 'allergenClientsExact', ); - if ((allergenClientsExact ?? '') !== '') { pantryApplicationData.set('allergenClients', allergenClientsExact); } + form.delete('allergenClientsExact'); + + // Copy all form data to Map + form.forEach((value, key) => { + if (value === '') { + pantryApplicationData.set(key, null); + } else { + pantryApplicationData.set(key, value) + } + }); const data = Object.fromEntries(pantryApplicationData); From a4cb1a5f0696b270a1c854590a07568562fba06d Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:03:10 -0500 Subject: [PATCH 13/19] fix text colors --- .../forms/pantryApplicationForm.tsx | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index d7dad713..3353667f 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -77,15 +77,6 @@ const PantryApplicationForm: React.FC = () => { const [restrictions, setRestrictions] = useState([]); const noRestrictionsSelected: boolean = restrictions.length === 0; const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); - const [clientVisitFrequency, setClientVisitFrequency] = useState< - string | undefined - >(); - const [identifyAllergensConfidence, setIdentifyAllergensConfidence] = useState< - string | undefined - >(); - const [serveAllergicChildren, setServeAllergicChildren] = useState< - string | undefined - >(); const [differentMailingAddress, setDifferentMailingAddress] = useState(); const [searchRestriction, setSearchRestriction] = useState(''); const [searchActivity, setSearchActivity] = useState(''); @@ -246,7 +237,7 @@ const PantryApplicationForm: React.FC = () => { type="text" borderColor="neutral.100" placeholder="If you selected other, please specify." - _placeholder={{ color: "neutral.300" }} + _placeholder={{ color: "neutral.800" }} maxW="30em" /> @@ -487,7 +478,7 @@ const PantryApplicationForm: React.FC = () => { value={allergenClients} onChange={(e) => setAllergenClients(e.target.value)} placeholder="Select an option" - color = {allergenClients ? 'neutral.800' : 'neutral.300'} + color="neutral.800" borderColor="neutral.100" name="allergenClients" > @@ -518,6 +509,7 @@ const PantryApplicationForm: React.FC = () => { maxW="10em" name="allergenClientsExact" type="number" + color="neutral.800" borderColor="neutral.100" min="0" /> @@ -542,7 +534,7 @@ const PantryApplicationForm: React.FC = () => { @@ -557,6 +549,7 @@ const PantryApplicationForm: React.FC = () => { ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} > @@ -739,12 +732,10 @@ const PantryApplicationForm: React.FC = () => { setClientVisitFrequency(e.target.value)} placeholder="Select an option" name="clientVisitFrequency" borderColor="neutral.100" - color = {clientVisitFrequency ? 'neutral.800' : 'neutral.300'} + color="neutral.800" > {[ 'Daily', @@ -768,12 +759,10 @@ const PantryApplicationForm: React.FC = () => { setIdentifyAllergensConfidence(e.target.value)} placeholder="Select an option" name="identifyAllergensConfidence" borderColor="neutral.100" - color = {identifyAllergensConfidence ? 'neutral.800' : 'neutral.300'} + color="neutral.800" > {[ 'Very confident', @@ -799,12 +788,10 @@ const PantryApplicationForm: React.FC = () => { setServeAllergicChildren(e.target.value)} placeholder="Select an option" name="serveAllergicChildren" borderColor="neutral.100" - color = {serveAllergicChildren ? 'neutral.800' : 'neutral.300'} + color="neutral.800" > {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( @@ -837,7 +824,7 @@ const PantryApplicationForm: React.FC = () => { @@ -852,6 +839,7 @@ const PantryApplicationForm: React.FC = () => { ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} > From 17863238dfaee0e30e1900b5bf9321a8f412a7b3 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:34:12 -0500 Subject: [PATCH 14/19] add back migration --- apps/backend/src/config/typeorm.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/backend/src/config/typeorm.ts b/apps/backend/src/config/typeorm.ts index b356b698..31caa730 100644 --- a/apps/backend/src/config/typeorm.ts +++ b/apps/backend/src/config/typeorm.ts @@ -22,6 +22,7 @@ import { UpdatePantriesTable1742739750279 } from '../migrations/1742739750279-up import { RemoveOrdersDonationId1761500262238 } from '../migrations/1761500262238-RemoveOrdersDonationId'; import { AllergyFriendlyToBoolType1763963056712 } from '../migrations/1763963056712-AllergyFriendlyToBoolType'; import { UpdatePantryUserFieldsFixed1764350314832 } from '../migrations/1764350314832-UpdatePantryUserFieldsFixed'; +import { UpdatePantryFields1763762628431 } from '../migrations/1763762628431-UpdatePantryFields'; const config = { type: 'postgres', @@ -55,6 +56,7 @@ const config = { UpdateColsToUseEnumType1760886499863, UpdatePantriesTable1742739750279, RemoveOrdersDonationId1761500262238, + UpdatePantryFields1763762628431, AllergyFriendlyToBoolType1763963056712, UpdatePantryUserFieldsFixed1764350314832, ], From e49ef20bd77a9c356bccaf377e670c0f660c78f2 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:16:11 -0500 Subject: [PATCH 15/19] review comments --- .../1763762628431-UpdatePantryFields.ts | 10 +++---- apps/backend/src/pantries/pantries.entity.ts | 4 ++- .../forms/pantryApplicationForm.tsx | 26 ++++++++++--------- .../forms/pantryApplicationModal.tsx | 24 ++++++++--------- .../src/components/forms/usPhoneInput.tsx | 8 ++++-- apps/frontend/src/types/types.ts | 2 +- 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts index 9573f725..54ac6e94 100644 --- a/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts +++ b/apps/backend/src/migrations/1763762628431-UpdatePantryFields.ts @@ -6,14 +6,14 @@ export class UpdatePantryFields1763762628431 implements MigrationInterface { ALTER TABLE pantries ADD COLUMN accept_food_deliveries boolean NOT NULL DEFAULT false, ADD COLUMN delivery_window_instructions text, - ADD COLUMN mailing_address_line_1 varchar(255), + ADD COLUMN mailing_address_line_1 varchar(255) NOT NULL DEFAULT 'A', ADD COLUMN mailing_address_line_2 varchar(255), - ADD COLUMN mailing_address_city varchar(255), - ADD COLUMN mailing_address_state varchar(255), - ADD COLUMN mailing_address_zip varchar(255), + ADD COLUMN mailing_address_city varchar(255) NOT NULL DEFAULT 'A', + ADD COLUMN mailing_address_state varchar(255) NOT NULL DEFAULT 'A', + ADD COLUMN mailing_address_zip varchar(255) NOT NULL DEFAULT 'A', ADD COLUMN mailing_address_country varchar(255), ALTER COLUMN newsletter_subscription DROP NOT NULL, - ADD COLUMN has_email_contact BOOLEAN NOT NULL, + ADD COLUMN has_email_contact BOOLEAN NOT NULL DEFAULT false, ADD COLUMN email_contact_other TEXT, ADD COLUMN secondary_contact_first_name VARCHAR(255), ADD COLUMN secondary_contact_last_name VARCHAR(255), diff --git a/apps/backend/src/pantries/pantries.entity.ts b/apps/backend/src/pantries/pantries.entity.ts index ed8e6f5c..33e853bc 100644 --- a/apps/backend/src/pantries/pantries.entity.ts +++ b/apps/backend/src/pantries/pantries.entity.ts @@ -141,11 +141,12 @@ export class Pantry { type: 'enum', enum: ServeAllergicChildren, enumName: 'serve_allergic_children_enum', + nullable: true, }) serveAllergicChildren?: ServeAllergicChildren; @Column({ name: 'newsletter_subscription', type: 'boolean', nullable: true }) - newsletterSubscription: boolean; + newsletterSubscription?: boolean; @Column({ name: 'restrictions', type: 'text', array: true }) restrictions: string[]; @@ -184,6 +185,7 @@ export class Pantry { name: 'secondary_contact_phone', type: 'varchar', length: 20, + nullable: true, }) secondaryContactPhone?: string; diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 3353667f..24a7ca32 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -393,7 +393,7 @@ const PantryApplicationForm: React.FC = () => { - Please note any delivery window instructions. + Please note any delivery window restrictions. @@ -491,7 +491,7 @@ const PantryApplicationForm: React.FC = () => { "I'm not sure", allergenClientsExactOption, ].map((value) => ( - + {value} ))} @@ -565,8 +565,8 @@ const PantryApplicationForm: React.FC = () => { {restrictions.map((value) => ( - <> - + + { /> - > + ))} @@ -744,7 +744,7 @@ const PantryApplicationForm: React.FC = () => { 'A few times a month', 'Once a month', ].map((value) => ( - + {value} ))} @@ -769,7 +769,7 @@ const PantryApplicationForm: React.FC = () => { 'Somewhat confident', 'Not very confident (we need more education!)', ].map((value) => ( - + {value} ))} @@ -794,7 +794,7 @@ const PantryApplicationForm: React.FC = () => { color="neutral.800" > {['Yes, many (> 10)', 'Yes, a few (< 10)', 'No'].map((value) => ( - + {value} ))} @@ -855,8 +855,8 @@ const PantryApplicationForm: React.FC = () => { {activities.map((value) => ( - <> - + + { /> - > + ))} @@ -914,7 +914,7 @@ const PantryApplicationForm: React.FC = () => { For example, gluten-free breads, sunflower seed butters, nondairy beverages, etc. - + Do allergen-avoidant clients at your pantry ever request a greater variety of items or not have enough options? Please explain. @@ -923,6 +923,8 @@ const PantryApplicationForm: React.FC = () => { + + Would you like to subscribe to our quarterly newsletter? diff --git a/apps/frontend/src/components/forms/pantryApplicationModal.tsx b/apps/frontend/src/components/forms/pantryApplicationModal.tsx index 4b7b201a..bc59ebb4 100644 --- a/apps/frontend/src/components/forms/pantryApplicationModal.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationModal.tsx @@ -70,34 +70,34 @@ const PantryApplicationModal: React.FC = ({ {pantry.pantryName} - Address Line 1 + Shipping AddressLine 1 - {pantry.addressLine1} + {pantry.shippingAddressLine1} - Address Line 2 + Shipping AddressLine 2 - {pantry.addressLine2 ?? ''} + {pantry.shippingAddressLine2 ?? ''} - Address City + Shipping AddressCity - {pantry.addressCity} + {pantry.shippingAddressCity} - Address State + Shipping AddressState - {pantry.addressState} + {pantry.shippingAddressState} - Address Zip + Shipping AddressZip - {pantry.addressZip} + {pantry.shippingAddressZip} - Address Country + Shipping AddressCountry - {pantry.addressCountry ?? ''} + {pantry.shippingAddressCountry ?? ''} Allergen Clients diff --git a/apps/frontend/src/components/forms/usPhoneInput.tsx b/apps/frontend/src/components/forms/usPhoneInput.tsx index edc16aef..f3af28e4 100644 --- a/apps/frontend/src/components/forms/usPhoneInput.tsx +++ b/apps/frontend/src/components/forms/usPhoneInput.tsx @@ -45,9 +45,13 @@ export const USPhoneInput: React.FC = ({ onChange: (data) => { onChange(data.phone); - if (isPhoneValid(data.phone)) { + const isEmpty = !data.phone || data.phone.trim() === ''; + + if (isEmpty && allowEmpty) { + phoneInput.inputRef.current?.setCustomValidity(''); + } else if (isPhoneValid(data.phone)) { phoneInput.inputRef.current?.setCustomValidity(''); - } else if (!allowEmpty) { + } else { phoneInput.inputRef.current?.setCustomValidity('Invalid phone number.'); } }, diff --git a/apps/frontend/src/types/types.ts b/apps/frontend/src/types/types.ts index a7e6cc91..1d297f11 100644 --- a/apps/frontend/src/types/types.ts +++ b/apps/frontend/src/types/types.ts @@ -45,7 +45,7 @@ export interface Pantry { secondaryContactLastName?: string; secondaryContactEmail?: string; secondaryContactPhone?: string; - pantryUser: User; + pantryUser?: User; status: PantryStatus; dateApplied: Date; activities: Activity[]; From ef9ec151491b3dbdfb0837738e37593fcc8b5629 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Thu, 4 Dec 2025 07:11:15 -0500 Subject: [PATCH 16/19] fix phone logic --- .../src/components/forms/pantryApplicationModal.tsx | 12 ++++++------ apps/frontend/src/components/forms/usPhoneInput.tsx | 9 ++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/frontend/src/components/forms/pantryApplicationModal.tsx b/apps/frontend/src/components/forms/pantryApplicationModal.tsx index bc59ebb4..ceb8179c 100644 --- a/apps/frontend/src/components/forms/pantryApplicationModal.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationModal.tsx @@ -70,32 +70,32 @@ const PantryApplicationModal: React.FC = ({ {pantry.pantryName} - Shipping AddressLine 1 + Shipping Address Line 1 {pantry.shippingAddressLine1} - Shipping AddressLine 2 + Shipping Address Line 2 {pantry.shippingAddressLine2 ?? ''} - Shipping AddressCity + Shipping Address City {pantry.shippingAddressCity} - Shipping AddressState + Shipping Address State {pantry.shippingAddressState} - Shipping AddressZip + Shipping Address Zip {pantry.shippingAddressZip} - Shipping AddressCountry + Shipping Address Country {pantry.shippingAddressCountry ?? ''} diff --git a/apps/frontend/src/components/forms/usPhoneInput.tsx b/apps/frontend/src/components/forms/usPhoneInput.tsx index f3af28e4..b0b302cf 100644 --- a/apps/frontend/src/components/forms/usPhoneInput.tsx +++ b/apps/frontend/src/components/forms/usPhoneInput.tsx @@ -43,13 +43,12 @@ export const USPhoneInput: React.FC = ({ disableDialCodeAndPrefix: true, value, onChange: (data) => { - onChange(data.phone); + const digits = data.phone.replace(/\D/g, '') + const isEmpty = !data.phone || data.phone.trim() === '' || digits.length <= 1; - const isEmpty = !data.phone || data.phone.trim() === ''; + onChange(isEmpty ? '' : data.phone); - if (isEmpty && allowEmpty) { - phoneInput.inputRef.current?.setCustomValidity(''); - } else if (isPhoneValid(data.phone)) { + if ((isEmpty && allowEmpty) || isPhoneValid(data.phone)) { phoneInput.inputRef.current?.setCustomValidity(''); } else { phoneInput.inputRef.current?.setCustomValidity('Invalid phone number.'); From efee6f40fef753bba8559bd58257c44de5cca700 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 5 Dec 2025 01:29:47 -0500 Subject: [PATCH 17/19] switch multi-checkbox components --- apps/backend/src/orders/order.service.spec.ts | 20 +- .../forms/pantryApplicationForm.tsx | 362 ++++++++++-------- 2 files changed, 209 insertions(+), 173 deletions(-) diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 69ef277b..50bd9c8d 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -17,15 +17,9 @@ import { OrderStatus } from './types'; const mockOrdersRepository = mock>(); -const mockPantry: Pantry = { +const mockPantry: Partial = { pantryId: 1, pantryName: 'Test Pantry', - addressLine1: '123 Test St', - addressLine2: 'Apt. 1', - addressCity: 'Boston', - addressState: 'MA', - addressZip: '02115', - addressCountry: 'US', allergenClients: '', refrigeratedDonation: RefrigeratedDonation.NO, reserveFoodForAllergic: 'Yes', @@ -114,17 +108,17 @@ describe('OrdersService', () => { { orderId: 3, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry' }, }, { orderId: 4, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry 2' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, }, { orderId: 5, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry 3' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 3' }, }, ]; @@ -162,17 +156,17 @@ describe('OrdersService', () => { { orderId: 3, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry 1' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 1' }, }, { orderId: 4, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry 2' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, }, { orderId: 5, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry, pantryName: 'Test Pantry 2' }, + pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, }, ]; diff --git a/apps/frontend/src/components/forms/pantryApplicationForm.tsx b/apps/frontend/src/components/forms/pantryApplicationForm.tsx index 24a7ca32..94abfe3a 100644 --- a/apps/frontend/src/components/forms/pantryApplicationForm.tsx +++ b/apps/frontend/src/components/forms/pantryApplicationForm.tsx @@ -9,15 +9,13 @@ import { Field, Textarea, SimpleGrid, - Portal, NativeSelect, NativeSelectIndicator, - Combobox, - Wrap, - createListCollection, Tag, Separator, Checkbox, + Menu, + Flex, } from '@chakra-ui/react'; import { ActionFunction, @@ -31,6 +29,7 @@ import { PantryApplicationDto } from '../../types/types'; import ApiClient from '@api/apiClient'; import { Activity } from '../../types/pantryEnums'; import axios from 'axios'; +import { ChevronDownIcon } from 'lucide-react'; const otherRestrictionsOptions: string[] = [ 'Other allergy (e.g., yeast, sunflower, etc.)', @@ -70,16 +69,12 @@ const PantryApplicationForm: React.FC = () => { const [contactPhone, setContactPhone] = useState(''); const [secondaryContactPhone, setSecondaryContactPhone] = useState(''); const [activities, setActivities] = useState([]); - const noActivitiesSelected: boolean = activities.length === 0; const allergenClientsExactOption: string = 'I have an exact number'; const [allergenClients, setAllergenClients] = useState(); const [restrictions, setRestrictions] = useState([]); - const noRestrictionsSelected: boolean = restrictions.length === 0; const [reserveFoodForAllergic, setReserveFoodForAllergic] = useState(); const [differentMailingAddress, setDifferentMailingAddress] = useState(); - const [searchRestriction, setSearchRestriction] = useState(''); - const [searchActivity, setSearchActivity] = useState(''); const [otherEmailContact, setOtherEmailContact] = useState(false); const sectionTitleStyles = { @@ -105,32 +100,6 @@ const PantryApplicationForm: React.FC = () => { fontWeight: '600', }; - const filteredRestrictions = useMemo( - () => - dietaryRestrictionOptions.filter((option) => - option.toLowerCase().includes(searchRestriction.toLowerCase()), - ), - [searchRestriction], - ); - - const restrictionsCollection = useMemo( - () => createListCollection({ items: filteredRestrictions}), - [filteredRestrictions], - ); - - const filteredActivities = useMemo( - () => - activityOptions.filter((option) => - option.toLowerCase().includes(searchActivity.toLowerCase()), - ), - [searchActivity], - ); - - const activitiesCollection = useMemo( - () => createListCollection({ items: filteredActivities}), - [filteredActivities], - ); - return ( @@ -521,75 +490,112 @@ const PantryApplicationForm: React.FC = () => { clients at your pantry report? - setRestrictions(e.value)} - onInputValueChange={(e: {inputValue: string}) => setSearchRestriction(e.inputValue)} - required={noRestrictionsSelected} - > - - ( + + ))} + + + + - - - - + justifyContent="space-between" + textStyle="p2" + size="sm" + > + {restrictions.length > 0 + ? `Select more restrictions` + : 'Select restrictions'} + - - - - - {filteredRestrictions.map((value) => ( - 0 ? 'selected' : ''} + required + style={{ + position: 'absolute', + opacity: 0, + pointerEvents: 'none' + }} + /> + + + + + + {dietaryRestrictionOptions.map((value) => { + const isChecked = restrictions.includes(value); + return ( + { + setRestrictions((prev) => + checked + ? [...prev, value] + : prev.filter((i) => i !== value), + ); + }} + display="flex" + alignItems="center" + > + + + ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} - onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} + fontWeight={500} + fontFamily="Inter" > {value} - - - ))} - No dietary restrictions found - - - - + + + ); + })} + + + - + {restrictions.length > 0 && ( + {restrictions.map((value) => ( - - - - {value} - - - setRestrictions((prev) => - prev.filter((item) => item !== value) - ) - } - style={{ cursor: 'pointer' }} - /> - - - + + {value} + + + setRestrictions((prev) => + prev.filter((item) => item !== value) + ) + } + style={{ cursor: 'pointer' }} + /> + + ))} - - + + )} {restrictions.find((option) => @@ -808,85 +814,121 @@ const PantryApplicationForm: React.FC = () => { - What activities are you open to doing with SSF?{" "} - + What activities are you open to doing with SSF? + - setActivities(e.value)} - onInputValueChange={(e: {inputValue: string}) => setSearchActivity(e.inputValue)} - required={noActivitiesSelected} - > - - ( + + ))} + + + + - - - - + justifyContent="space-between" + textStyle="p2" + size="sm" + > + {activities.length > 0 + ? `Select more activities` + : 'Select activities'} + - - - - - {filteredActivities.map((value) => ( - 0 ? 'selected' : ''} + required + style={{ + position: 'absolute', + opacity: 0, + pointerEvents: 'none' + }} + /> + + + + + + {activityOptions.map((value) => { + const isChecked = activities.includes(value); + return ( + { + setActivities((prev) => + checked + ? [...prev, value] + : prev.filter((i) => i !== value), + ); + }} + display="flex" + alignItems="center" + > + + + ) => (e.currentTarget.style.backgroundColor = '#f5f5f5')} - onMouseLeave={(e: React.MouseEvent) => (e.currentTarget.style.backgroundColor = '')} + fontWeight={500} + fontFamily="Inter" > {value} - - - ))} - No activities found - - - - + + + ); + })} + + + - + {activities.length > 0 && ( + {activities.map((value) => ( - - - - {value} - - - setActivities((prev) => - prev.filter((item) => item !== value) - ) - } - style={{ cursor: 'pointer' }} - /> - - - + + {value} + + + setActivities((prev) => + prev.filter((item) => item !== value) + ) + } + style={{ cursor: 'pointer' }} + /> + + ))} - - + + )} Food donations are one part of being a partner pantry. The following are additional ways to help us better support you! Please select all that apply. - - + Please list any comments/concerns related to the previous question. From 8e8545729f3c780a679008c0a286ba59bd708e69 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 5 Dec 2025 01:30:06 -0500 Subject: [PATCH 18/19] prettier --- apps/backend/src/orders/order.service.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 50bd9c8d..ce653481 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -108,17 +108,17 @@ describe('OrdersService', () => { { orderId: 3, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry' }, }, { orderId: 4, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' }, }, { orderId: 5, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 3' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 3' }, }, ]; @@ -156,17 +156,17 @@ describe('OrdersService', () => { { orderId: 3, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 1' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 1' }, }, { orderId: 4, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' }, }, { orderId: 5, status: OrderStatus.DELIVERED, - pantry: { ...mockPantry as Pantry, pantryName: 'Test Pantry 2' }, + pantry: { ...(mockPantry as Pantry), pantryName: 'Test Pantry 2' }, }, ]; From 1a42d8429d81d6ba8fc51dbfd72eba63f7cd65d1 Mon Sep 17 00:00:00 2001 From: amywng <147568742+amywng@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:39:49 -0500 Subject: [PATCH 19/19] add comment about phone logic --- apps/frontend/src/components/forms/usPhoneInput.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/frontend/src/components/forms/usPhoneInput.tsx b/apps/frontend/src/components/forms/usPhoneInput.tsx index b0b302cf..7dd3dc16 100644 --- a/apps/frontend/src/components/forms/usPhoneInput.tsx +++ b/apps/frontend/src/components/forms/usPhoneInput.tsx @@ -43,6 +43,9 @@ export const USPhoneInput: React.FC = ({ disableDialCodeAndPrefix: true, value, onChange: (data) => { + // +1 country code is automatically added to data.phone, so after removing + // all non-digits, a length of 0-1 means the field is empty + // (the input does not allow non-digit characters) const digits = data.phone.replace(/\D/g, '') const isEmpty = !data.phone || data.phone.trim() === '' || digits.length <= 1;
- Food donations are one part of being a partner pantry. The - following are additional ways to help us better support you! - (Please select all that apply.) -
Please select at least one option!