diff --git a/client/src/generated/graphql.tsx b/client/src/generated/graphql.tsx index ae35de1c7f..1fc5a0c1c3 100644 --- a/client/src/generated/graphql.tsx +++ b/client/src/generated/graphql.tsx @@ -729,6 +729,7 @@ export type DeleteRsvpMutation = { export type SendEventInviteMutationVariables = Exact<{ id: Scalars['Int']; + emailGroups?: Maybe>; }>; export type SendEventInviteMutation = { @@ -1892,8 +1893,8 @@ export type DeleteRsvpMutationOptions = Apollo.BaseMutationOptions< DeleteRsvpMutationVariables >; export const SendEventInviteDocument = gql` - mutation sendEventInvite($id: Int!) { - sendEventInvite(id: $id) + mutation sendEventInvite($id: Int!, $emailGroups: [String!]) { + sendEventInvite(id: $id, emailGroups: $emailGroups) } `; export type SendEventInviteMutationFn = Apollo.MutationFunction< diff --git a/client/src/modules/dashboard/Events/components/Actions.tsx b/client/src/modules/dashboard/Events/components/Actions.tsx index 1cdbea0972..29d383a485 100644 --- a/client/src/modules/dashboard/Events/components/Actions.tsx +++ b/client/src/modules/dashboard/Events/components/Actions.tsx @@ -1,9 +1,10 @@ +import { useDisclosure } from '@chakra-ui/hooks'; import { Button, HStack } from '@chakra-ui/react'; import { useConfirm, useConfirmDelete } from 'chakra-confirm'; import { LinkButton } from 'chakra-next-link'; import React, { useMemo } from 'react'; - import { EVENT, EVENTS } from '../graphql/queries'; +import SendEmailModal from './SendEmailModal'; import { Event, useCancelEventMutation, @@ -17,6 +18,7 @@ interface ActionsProps { } const Actions: React.FC = ({ event, onDelete, hideCancel }) => { + const { isOpen, onOpen, onClose } = useDisclosure(); const [cancel] = useCancelEventMutation(); const [remove] = useDeleteEventMutation(); @@ -53,6 +55,9 @@ const Actions: React.FC = ({ event, onDelete, hideCancel }) => { } }; + const clickEmailAttendees = () => { + onOpen(); + }; return ( )} + + ); }; diff --git a/client/src/modules/dashboard/Events/components/SendEmailModal.tsx b/client/src/modules/dashboard/Events/components/SendEmailModal.tsx new file mode 100644 index 0000000000..17a77b0d62 --- /dev/null +++ b/client/src/modules/dashboard/Events/components/SendEmailModal.tsx @@ -0,0 +1,114 @@ +import { Button } from '@chakra-ui/button'; +import { Checkbox } from '@chakra-ui/checkbox'; +import { Stack } from '@chakra-ui/layout'; +import { + Modal, + ModalBody, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalFooter, + ModalOverlay, +} from '@chakra-ui/modal'; +import { Alert, AlertIcon, AlertDescription } from '@chakra-ui/react'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useSendEventInviteMutation } from 'generated/graphql'; +interface SendEmailModalProps { + onClose: () => any; + isOpen: boolean; + eventId: number; +} +interface FormInputs { + confirmed: boolean; + on_waitlist: boolean; + canceled: boolean; +} +const SendEmailModal: React.FC = ({ + onClose, + isOpen, + eventId, +}) => { + const { + register, + getValues, + handleSubmit, + formState: { errors }, + } = useForm({ defaultValues: { confirmed: true } }); + + const atLeastOneChecked = () => { + return ( + getValues('confirmed') || + getValues('on_waitlist') || + getValues('canceled') + ); + }; + + const [publish] = useSendEventInviteMutation(); + const onSubmit = (data: FormInputs) => { + const emailGroups = []; + if (data.confirmed) { + emailGroups.push('confirmed'); + } + if (data.canceled) { + emailGroups.push('canceled'); + } + if (data.on_waitlist) { + emailGroups.push('on_waitlist'); + } + publish({ variables: { id: eventId, emailGroups: emailGroups } }); + onClose(); + }; + return ( + + + + Send Email to Attendees + + +
+ + + + Confirmed + + + Waitlist + + + Cancelled + + +
+ {Object.keys(errors).length == 3 && ( + + + + Please select at least one checkbox + + + )} +
+ + + + + +
+
+ ); +}; + +export default SendEmailModal; diff --git a/server/src/controllers/Events/resolver.ts b/server/src/controllers/Events/resolver.ts index 19dc7f2025..c162b2c8bf 100644 --- a/server/src/controllers/Events/resolver.ts +++ b/server/src/controllers/Events/resolver.ts @@ -273,17 +273,60 @@ ${venue.postal_code}
} @Mutation(() => Boolean) - async sendEventInvite(@Arg('id', () => Int) id: number) { + async sendEventInvite( + @Arg('id', () => Int) id: number, + @Arg('emailGroups', () => [String], { + nullable: true, + defaultValue: ['interested'], + }) + emailGroups: Array<'confirmed' | 'on_waitlist' | 'canceled' | 'interested'>, + ) { const event = await Event.findOne(id, { - relations: ['venue', 'chapter', 'chapter.users', 'chapter.users.user'], + relations: [ + 'venue', + 'chapter', + 'chapter.users', + 'chapter.users.user', + 'rsvps', + 'rsvps.user', + ], }); if (!event) throw new Error("Can't find event"); // TODO: the default should probably be to bcc everyone. - const addresses = event.chapter.users - .filter((role) => role.interested) - .map(({ user }) => user.email); + const addresses: string[] = []; + if (emailGroups.includes('interested')) { + const interestedUsers: string[] = event.chapter.users + .filter((role) => role.interested) + .map(({ user }) => user.email); + + addresses.push(...interestedUsers); + } + if (emailGroups.includes('on_waitlist')) { + const waitlistUsers: string[] = event.rsvps + .filter((rsvp) => rsvp.on_waitlist && rsvp.interested) + .map(({ user }) => user.email); + addresses.push(...waitlistUsers); + } + if (emailGroups.includes('confirmed')) { + const confirmedUsers: string[] = event.rsvps + .filter( + (rsvp) => !rsvp.on_waitlist && !rsvp.canceled && rsvp.interested, + ) + .map(({ user }) => user.email); + addresses.push(...confirmedUsers); + } + if (emailGroups.includes('canceled')) { + const confirmedUsers: string[] = event.rsvps + .filter((rsvp) => rsvp.canceled && rsvp.interested) + .map(({ user }) => user.email); + addresses.push(...confirmedUsers); + } + + if (!addresses.length) { + return true; + } const subject = `Invitation to ${event.name}.`; const chapterURL = `${process.env.CLIENT_LOCATION}/chapters/${event.chapter.id}`;