Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5382942
Refactoring visual elements
Juwang110 Nov 11, 2025
f745231
pagination functionality
Juwang110 Nov 11, 2025
d22d126
Removing volunteer type filtering and display
Juwang110 Nov 12, 2025
1d24d7c
visual updates
Juwang110 Nov 12, 2025
a941c41
modal setup for add volunteer
Juwang110 Nov 16, 2025
5a39ddb
Modal frontend setup
Juwang110 Nov 16, 2025
b10d9eb
callbacks and email/phone invalid handling
Juwang110 Nov 19, 2025
c85a3fa
refactoring to closer align with figma
Juwang110 Nov 30, 2025
6c9f810
Merge pull request #77 from Code-4-Community/main
Juwang110 Dec 4, 2025
91ff9fd
Aligning add volunteer modal with figma
Juwang110 Dec 4, 2025
61f722e
Merge branch 'jw/SSF-92-volunteer-management-frontend' of https://git…
Juwang110 Dec 4, 2025
21d9104
Refining submit status alerts
Juwang110 Dec 4, 2025
a5b8bf3
minor visual updates
Juwang110 Dec 6, 2025
e6f8502
Adding volunteer initial icons
Juwang110 Dec 11, 2025
66888e4
minor visual refactoring
Juwang110 Dec 12, 2025
2d286af
Refactoring add volunteer modal to use USPhoneInput
Juwang110 Dec 13, 2025
96fb6d6
visual refactoring
Juwang110 Dec 14, 2025
23d500f
visual refactoring
Juwang110 Dec 21, 2025
81da1dd
refactoring
Juwang110 Jan 11, 2026
4b01e93
Merge branch 'main' into jw/SSF-92-volunteer-management-frontend
Juwang110 Jan 11, 2026
5223983
fixing packages
Juwang110 Jan 11, 2026
68ce7c8
page is entirely white
Juwang110 Jan 12, 2026
ece3380
changing background to pure white
Juwang110 Jan 12, 2026
b31f4a3
Revert "fixing packages"
Juwang110 Jan 12, 2026
ce6e1b9
making error catching type safe
Juwang110 Jan 12, 2026
7e3dff2
yarn lock
Juwang110 Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CreateFoodRequestBody,
Pantry,
PantryApplicationDto,
UserDto,
} from 'types/types';

const defaultBaseUrl =
Expand Down Expand Up @@ -95,6 +96,10 @@ export class ApiClient {
.then((response) => response.data);
}

public async postUser(data: UserDto): Promise<User> {
return this.axiosInstance.post(`/api/users`, data);
}

public async getPantrySSFRep(pantryId: number): Promise<User> {
return this.get(`/api/pantries/${pantryId}/ssf-contact`) as Promise<User>;
}
Expand Down
159 changes: 159 additions & 0 deletions apps/frontend/src/components/forms/addNewVolunteerModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import {
Dialog,
Button,
Text,
Flex,
Field,
Input,
CloseButton,
Box
} from '@chakra-ui/react';
import { useState } from 'react';
import { Role, UserDto } from "../../types/types";
import ApiClient from '@api/apiClient';
import { USPhoneInput } from './usPhoneInput';
import { PlusIcon } from 'lucide-react';

interface NewVolunteerModalProps {
onSubmitSuccess?: () => void;
onSubmitFail?: () => void;
}

const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({ onSubmitSuccess, onSubmitFail }) => {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");

const [isOpen, setIsOpen] = useState(false);

const [error, setError] = useState("");

const handleSubmit = async () => {
console.log("RAW phone value:", phone);
if (!firstName || !lastName || !email || !phone || phone === "+1") {
setError("Please fill in all fields. *");
return;
}

setError("");

const newVolunteer: UserDto = {
firstName,
lastName,
email,
phone,
role: Role.VOLUNTEER
};

try {
await ApiClient.postUser(newVolunteer);
if (onSubmitSuccess) onSubmitSuccess();
handleClear();
} catch (error: unknown) {

let hasEmailError = false
let hasPhoneError = false

if (typeof error === "object" && error !== null) {
const e = error as { response?: { data?: { message?: string | string[] } } };
const message = e.response?.data?.message;

hasEmailError =
Array.isArray(message) &&
message.some((msg) => typeof msg === "string" && msg.toLowerCase().includes("email"));

hasPhoneError =
Array.isArray(message) &&
message.some((msg) => typeof msg === "string" && msg.toLowerCase().includes("phone"));
}

if (hasEmailError) {
setError("Please specify a valid email. *")
} else if (hasPhoneError) {
setError("Please specify a valid phone number. *")
} else {
if (onSubmitFail) onSubmitFail();
handleClear();
}
}
}

const handleClear = () => {
setFirstName("");
setLastName("");
setEmail("");
setPhone("");
setError("");
setIsOpen(false);
};

return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Trigger asChild>
<Button pl={3} borderColor="neutral.200" variant="outline" color="neutral.600" fontFamily="ibm" fontWeight="semibold" fontSize="14px" gap={1}>
<Box as={PlusIcon} boxSize="17px" strokeWidth={2.5} />
Add
</Button>
</Dialog.Trigger>
<Dialog.Backdrop />
<Dialog.Positioner alignItems="center">
<Dialog.Content maxW="40em" mt="-8">
<Dialog.Header pb={1}>
<Dialog.Title fontSize="18px" fontWeight={600} fontFamily="Inter" color="#000">
Add New Volunteer
</Dialog.Title>
<CloseButton onClick={() => setIsOpen(false)} size="md" position="absolute" top={3} right={3}/>
</Dialog.Header>
<Dialog.Body color="neutral.800" fontWeight={600} textStyle="p2">
<Text mb="1.5em" color="#52525B" fontWeight={400}>
Complete all information in the form to register a new volunteer.
</Text>
<Flex gap={8} justifyContent="flex-start" my={4}>
<Field.Root>
<Field.Label textStyle="p2" color="neutral.800" fontWeight={600}>First Name</Field.Label>
<Input color="neutral.700" textStyle="p2" fontWeight={400} value={firstName} onChange={(e) => setFirstName(e.target.value)}/>
</Field.Root>
<Field.Root>
<Field.Label textStyle="p2" color="neutral.800" fontWeight={600}>Last Name</Field.Label>
<Input color="neutral.700" textStyle="p2" fontWeight={400} value={lastName} onChange={(e) => setLastName(e.target.value)}/>
</Field.Root>
</Flex>
<Field.Root>
<Field.Label textStyle="p2" color="neutral.800" fontWeight={600}>Email</Field.Label>
<Input color="neutral.700" textStyle="p2" fontWeight={400} value={email} onChange={(e) => setEmail(e.target.value)}/>
</Field.Root>
<Field.Root my={4}>
<Field.Label textStyle="p2" color="neutral.800" fontWeight={600}>Phone Number</Field.Label>
<USPhoneInput
value={phone}
onChange={setPhone}
inputProps={{
color: 'neutral.700',
textStyle: 'p2',
fontWeight: 400,
}}
/>
</Field.Root>
{error && (
<Text color="red" mb={3} fontWeight={400} fontSize="12px" fontFamily="Inter">
{error}
</Text>
)}
<Flex justifyContent="flex-end" mt={10} gap={2.5}>
<Button textStyle="p2" fontWeight={600} color="neutral.800" variant='outline' onClick={handleClear}>Cancel</Button>
<Button textStyle="p2" fontWeight={600} bg={'#213C4A'} color={'white'} onClick={handleSubmit}>Submit</Button>
</Flex>
</Dialog.Body>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
);
};






export default NewVolunteerModal;
Loading
Loading