Loading backend @ b0d99e75 Compare caf6b1c1 to b0d99e75 Original line number Diff line number Diff line Subproject commit caf6b1c1038b43f45124e4a9220e63d822a8e900 Subproject commit b0d99e75be6486619ae5abddcfc347c79c72f54a codegen/gql/queries/ValidateEmailAddress.graphql 0 → 100644 +3 −0 Original line number Diff line number Diff line query ValidateEmailAddress($exerciseId: ID!, $address: String!) { validateEmailAddress(exerciseId: $exerciseId, address: $address) } frontend/src/email/EmailContactSelector/index.tsx +23 −4 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ import { MenuItem, Tooltip } from '@blueprintjs/core' import type { ItemRenderer } from '@blueprintjs/select' import { MultiSelect } from '@blueprintjs/select' import type { EmailParticipant } from '@inject/graphql/fragments/EmailParticipant.generated' import { useValidateEmailAddressLazyQuery } from '@inject/graphql/queries/ValidateEmailAddress.generated' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import type { Dispatch, FC, MouseEventHandler, SetStateAction } from 'react' import { memo, useCallback, useMemo } from 'react' import type { ExtendedItemRenderer } from '../typing' Loading @@ -12,6 +14,7 @@ interface EmailContactSelectorProps { selectedContacts: string[] setSelectedContacts: Dispatch<SetStateAction<string[]>> inInstructor: boolean exerciseId: string } const EmailContactSelector: FC<EmailContactSelectorProps> = ({ Loading @@ -20,12 +23,17 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({ senderAddress, setSelectedContacts, inInstructor, exerciseId, }) => { const suggestedItems = useMemo( () => emailContacts.map(x => x?.address || '') || [], [emailContacts] ) const [validateEmailAddress] = useValidateEmailAddressLazyQuery() const { notify } = useNotifyContext() const itemRenderer: ExtendedItemRenderer<string> = useCallback( (i, { handleClick, handleFocus, modifiers, hasTooltip }) => ( <MenuItem Loading Loading @@ -66,15 +74,26 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({ [selectedContacts, senderAddress] ) const onItemSelect = useCallback( (i: string) => // TODO: add validation async (i: string) => { const { data: validateData } = await validateEmailAddress({ variables: { exerciseId, address: i, }, }) if (!validateData?.validateEmailAddress) { notify('Invalid email address', { intent: 'danger' }) return } setSelectedContacts(prev => { if (prev.includes(i)) { return prev.filter(x => x !== i) } return [...prev, i] }), [setSelectedContacts] }) }, [exerciseId, notify, setSelectedContacts, validateEmailAddress] ) const onRemove = useCallback( (i: string) => setSelectedContacts(prev => prev.filter(x => x !== i)), Loading frontend/src/email/EmailForm/TraineeEmailForm.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ const TraineeEmailForm: FC<TraineeEmailFormProps> = ({ })} contacts={traineeList} senderAddress={teamAddress} exerciseId={exerciseId} /> <Divider style={{ margin: '0.5rem 0' }} /> Loading frontend/src/email/EmailForm/modules/HeaderArea.tsx +2 −2 Original line number Diff line number Diff line Loading @@ -54,7 +54,6 @@ type HeaderAreaProps = ( template: EmailTemplate | undefined setTemplate: Dispatch<SetStateAction<EmailTemplate | undefined>> teamId: string exerciseId: string } | { // trainee Loading @@ -67,11 +66,11 @@ type HeaderAreaProps = ( template?: undefined setTemplate?: undefined teamId?: undefined exerciseId?: undefined } ) & { senderAddress: string contacts: EmailParticipant[] exerciseId: string } const HeaderArea: FC<HeaderAreaProps> = ({ Loading Loading @@ -136,6 +135,7 @@ const HeaderArea: FC<HeaderAreaProps> = ({ senderAddress={senderAddress} setSelectedContacts={setSelectedContacts} inInstructor={senderList !== undefined} exerciseId={exerciseId} /> )} Loading Loading
backend @ b0d99e75 Compare caf6b1c1 to b0d99e75 Original line number Diff line number Diff line Subproject commit caf6b1c1038b43f45124e4a9220e63d822a8e900 Subproject commit b0d99e75be6486619ae5abddcfc347c79c72f54a
codegen/gql/queries/ValidateEmailAddress.graphql 0 → 100644 +3 −0 Original line number Diff line number Diff line query ValidateEmailAddress($exerciseId: ID!, $address: String!) { validateEmailAddress(exerciseId: $exerciseId, address: $address) }
frontend/src/email/EmailContactSelector/index.tsx +23 −4 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ import { MenuItem, Tooltip } from '@blueprintjs/core' import type { ItemRenderer } from '@blueprintjs/select' import { MultiSelect } from '@blueprintjs/select' import type { EmailParticipant } from '@inject/graphql/fragments/EmailParticipant.generated' import { useValidateEmailAddressLazyQuery } from '@inject/graphql/queries/ValidateEmailAddress.generated' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import type { Dispatch, FC, MouseEventHandler, SetStateAction } from 'react' import { memo, useCallback, useMemo } from 'react' import type { ExtendedItemRenderer } from '../typing' Loading @@ -12,6 +14,7 @@ interface EmailContactSelectorProps { selectedContacts: string[] setSelectedContacts: Dispatch<SetStateAction<string[]>> inInstructor: boolean exerciseId: string } const EmailContactSelector: FC<EmailContactSelectorProps> = ({ Loading @@ -20,12 +23,17 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({ senderAddress, setSelectedContacts, inInstructor, exerciseId, }) => { const suggestedItems = useMemo( () => emailContacts.map(x => x?.address || '') || [], [emailContacts] ) const [validateEmailAddress] = useValidateEmailAddressLazyQuery() const { notify } = useNotifyContext() const itemRenderer: ExtendedItemRenderer<string> = useCallback( (i, { handleClick, handleFocus, modifiers, hasTooltip }) => ( <MenuItem Loading Loading @@ -66,15 +74,26 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({ [selectedContacts, senderAddress] ) const onItemSelect = useCallback( (i: string) => // TODO: add validation async (i: string) => { const { data: validateData } = await validateEmailAddress({ variables: { exerciseId, address: i, }, }) if (!validateData?.validateEmailAddress) { notify('Invalid email address', { intent: 'danger' }) return } setSelectedContacts(prev => { if (prev.includes(i)) { return prev.filter(x => x !== i) } return [...prev, i] }), [setSelectedContacts] }) }, [exerciseId, notify, setSelectedContacts, validateEmailAddress] ) const onRemove = useCallback( (i: string) => setSelectedContacts(prev => prev.filter(x => x !== i)), Loading
frontend/src/email/EmailForm/TraineeEmailForm.tsx +1 −0 Original line number Diff line number Diff line Loading @@ -110,6 +110,7 @@ const TraineeEmailForm: FC<TraineeEmailFormProps> = ({ })} contacts={traineeList} senderAddress={teamAddress} exerciseId={exerciseId} /> <Divider style={{ margin: '0.5rem 0' }} /> Loading
frontend/src/email/EmailForm/modules/HeaderArea.tsx +2 −2 Original line number Diff line number Diff line Loading @@ -54,7 +54,6 @@ type HeaderAreaProps = ( template: EmailTemplate | undefined setTemplate: Dispatch<SetStateAction<EmailTemplate | undefined>> teamId: string exerciseId: string } | { // trainee Loading @@ -67,11 +66,11 @@ type HeaderAreaProps = ( template?: undefined setTemplate?: undefined teamId?: undefined exerciseId?: undefined } ) & { senderAddress: string contacts: EmailParticipant[] exerciseId: string } const HeaderArea: FC<HeaderAreaProps> = ({ Loading Loading @@ -136,6 +135,7 @@ const HeaderArea: FC<HeaderAreaProps> = ({ senderAddress={senderAddress} setSelectedContacts={setSelectedContacts} inInstructor={senderList !== undefined} exerciseId={exerciseId} /> )} Loading