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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ export class PantriesService {
lastName: volunteer.lastName,
email: volunteer.email,
phone: volunteer.phone,
active: volunteer.active,
})),
}));
}
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/pantries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface AssignedVolunteer {
lastName: string;
email: string;
phone: string;
active: boolean;
}

export enum RefrigeratedDonation {
Expand Down
53 changes: 49 additions & 4 deletions apps/backend/src/volunteers/volunteers.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('VolunteersService', () => {
});

describe('getVolunteersAndPantryAssignments', () => {
it('returns an empty array when there are no volunteers', async () => {
it('returns only admins when there are no volunteers', async () => {
await testDataSource.query(`DELETE FROM allocations`);
await testDataSource.query(`DELETE FROM orders`);
await testDataSource.query(
Expand All @@ -154,14 +154,59 @@ describe('VolunteersService', () => {

const result = await service.getVolunteersAndPantryAssignments();

expect(result).toEqual([]);
expect(result).toEqual([
{
id: 1,
firstName: 'John',
lastName: 'Smith',
email: 'john.smith@ssf.org',
phone: '555-010-0101',
role: 'admin',
userCognitoSub: '',
active: true,
pantryIds: [],
},
{
id: 2,
firstName: 'Sarah',
lastName: 'Johnson',
email: 'sarah.j@ssf.org',
phone: '555-010-0102',
role: 'admin',
userCognitoSub: '',
active: true,
pantryIds: [],
},
]);
});

it('returns all volunteers with their pantry assignments', async () => {
it('returns all volunteers and admins with their pantry assignments', async () => {
const result = await service.getVolunteersAndPantryAssignments();

expect(result.length).toEqual(4);
expect(result.length).toEqual(6);
expect(result).toEqual([
{
id: 1,
firstName: 'John',
lastName: 'Smith',
email: 'john.smith@ssf.org',
phone: '555-010-0101',
role: 'admin',
userCognitoSub: '',
active: true,
pantryIds: [],
},
{
id: 2,
firstName: 'Sarah',
lastName: 'Johnson',
email: 'sarah.j@ssf.org',
phone: '555-010-0102',
role: 'admin',
userCognitoSub: '',
active: true,
pantryIds: [],
},
{
id: 6,
firstName: 'James',
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/volunteers/volunteers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class VolunteersService {
async getVolunteersAndPantryAssignments(): Promise<Assignments[]> {
const volunteers = await this.usersService.findUsersByRoles([
Role.VOLUNTEER,
Role.ADMIN,
]);

return volunteers.map((v) => {
Expand Down
8 changes: 8 additions & 0 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ export class ApiClient {
await this.axiosInstance.patch(`/api/users/${userId}/promote-volunteer`);
}

public async deactivateUser(userId: number): Promise<void> {
await this.axiosInstance.patch(`/api/users/${userId}/deactivate`);
}

public async reactivateUser(userId: number): Promise<void> {
await this.axiosInstance.patch(`/api/users/${userId}/reactivate`);
}

public async getFoodRequest(requestId: number): Promise<FoodRequest> {
return this.axiosInstance
.get(`/api/requests/${requestId}`)
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/chakra-ui.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ declare module '@chakra-ui/react' {

// Menu components
export interface MenuTriggerProps extends ComponentPropsStrictChildren {}
export interface MenuContentProps extends ComponentPropsStrictChildren {}
export interface MenuContentProps extends ComponentPropsLenientChildren {}
export interface MenuItemProps extends ComponentPropsLenientChildren {}
export interface MenuPositionerProps extends ComponentPropsStrictChildren {}
export interface MenuRootProps extends ComponentPropsStrictChildren {}
Expand Down
6 changes: 2 additions & 4 deletions apps/frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ const ROLE_NAV_SECTIONS: Record<Role, NavSection[]> = {
[Role.ADMIN]: [
{
type: 'group',
label: 'Volunteers',
children: [
{ label: 'Volunteer Management', to: ROUTES.VOLUNTEER_MANAGEMENT },
],
label: 'Users',
children: [{ label: 'User Management', to: ROUTES.VOLUNTEER_MANAGEMENT }],
},
{
type: 'group',
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/components/forms/addNewVolunteerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({
fontFamily="Inter"
color="#000"
>
Add New Volunteer
Add New User
</Dialog.Title>
<CloseButton
onClick={() => setIsOpen(false)}
Expand All @@ -140,7 +140,7 @@ const NewVolunteerModal: React.FC<NewVolunteerModalProps> = ({
</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.
Complete all information in the form to register a new user.
</Text>
<Flex gap={8} justifyContent="flex-start" my={4}>
<Field.Root>
Expand Down
13 changes: 8 additions & 5 deletions apps/frontend/src/components/forms/assignVolunteersModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
AlertStatus,
ApprovedPantryResponse,
Assignments,
Role,
} from '../../types/types';
import { SearchIcon } from 'lucide-react';
import { getInitials, USER_ICON_COLORS } from '@utils/utils';
Expand Down Expand Up @@ -65,11 +66,13 @@ const AssignVolunteersModal: React.FC<AssignVolunteersModalProps> = ({

const assignedIds = new Set(pantry.volunteers.map((v) => v.userId));

const normalized: VolunteerDisplay[] = allVolunteers.map((v) => ({
userId: v.id,
firstName: v.firstName,
lastName: v.lastName,
}));
const normalized: VolunteerDisplay[] = allVolunteers
.filter((v) => v.active)
.map((v) => ({
userId: v.id,
firstName: v.firstName,
lastName: v.lastName,
}));

setVolunteers(normalized);
setSelectedIds(new Set(assignedIds));
Expand Down
80 changes: 80 additions & 0 deletions apps/frontend/src/components/forms/confirmActionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Dialog, Text, Button, CloseButton } from '@chakra-ui/react';
import { useModalBodyCleanup } from '../../hooks/modalBodyCleanup';

interface ConfirmActionModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
volunteerName: string;
action: 'activate' | 'deactivate';
}

const ConfirmActionModal: React.FC<ConfirmActionModalProps> = ({
isOpen,
onClose,
onConfirm,
volunteerName,
action,
}) => {
useModalBodyCleanup();

return (
<Dialog.Root
open={isOpen}
onOpenChange={(e: { open: boolean }) => !e.open && onClose()}
>
<Dialog.Backdrop />
<Dialog.Positioner
display="flex"
alignItems="center"
justifyContent="center"
>
<Dialog.Content maxW="500px">
<Dialog.Header pb={0}>
<Dialog.Title fontSize="lg" fontWeight={600}>
{action === 'activate' ? 'Activate user' : 'Deactivate user'}
</Dialog.Title>
<Dialog.CloseTrigger asChild>
<CloseButton />
</Dialog.CloseTrigger>
</Dialog.Header>

<Dialog.Body pb={4}>
<Text color="gray.dark">
Are you sure you want to {action} {volunteerName}?
</Text>
</Dialog.Body>

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

export default ConfirmActionModal;
4 changes: 3 additions & 1 deletion apps/frontend/src/containers/adminPantryManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ const AdminPantryManagement: React.FC = () => {
<Box display="flex" alignItems="center" minH="33px">
{pantry.volunteers && pantry.volunteers.length > 0 ? (
(() => {
const volunteers = pantry.volunteers;
const volunteers = pantry.volunteers.filter(
(volunteer) => volunteer.active,
);
const maxVisible = 3;

const hasOverflow = volunteers.length > maxVisible;
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/containers/homepage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const Homepage: React.FC = () => {
<ListItem textAlign="center">
<Link asChild color="teal.500">
<RouterLink to={ROUTES.VOLUNTEER_MANAGEMENT}>
Volunteer Management
User Management
</RouterLink>
</Link>
</ListItem>
Expand Down
7 changes: 7 additions & 0 deletions apps/frontend/src/containers/loginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const LoginPage: React.FC = () => {
navigate(from, { replace: true });
return;
}
if (error.message === 'User is disabled.') {
setAlertMessage(
'Your account has been deactivated.',
AlertStatus.ERROR,
);
return;
}
if (
error.name === 'NotAuthorizedException' ||
error.name === 'UserNotFoundException'
Expand Down
Loading
Loading