Commit 7855f6c3 authored by Marek Veselý's avatar Marek Veselý
Browse files

Merge branch '189-templates-conflicts-free' into 'master'

bring templates back from the dead

Closes #181, #131, #55, and #189

See merge request inject/frontend!167
parents ccb2837d e9674a00
Loading
Loading
Loading
Loading
+7 −12
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ import { memo, useCallback, useMemo } from 'react'

interface EmailContactSelectorProps {
  emailContacts: EmailParticipant[]
  selfemail?: string | undefined
  senderAddress?: string | undefined
  selectedContacts: string[]
  setSelectedContacts: Dispatch<SetStateAction<string[]>>
}
@@ -15,7 +15,7 @@ interface EmailContactSelectorProps {
const EmailContactSelector: FC<EmailContactSelectorProps> = ({
  emailContacts,
  selectedContacts,
  selfemail,
  senderAddress,
  setSelectedContacts,
}) => {
  const suggestedItems = useMemo(
@@ -41,8 +41,8 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({
  )

  const itemDisabled = useCallback(
    (i: string) => i === selfemail || selectedContacts.includes(i),
    [selectedContacts, selfemail]
    (i: string) => i === senderAddress || selectedContacts.includes(i),
    [selectedContacts, senderAddress]
  )
  const onItemSelect = useCallback(
    (i: string) =>
@@ -59,11 +59,6 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({
    [setSelectedContacts]
  )

  const selectedItems = useMemo(
    () => (selfemail ? [selfemail, ...selectedContacts] : selectedContacts),
    [selectedContacts, selfemail]
  )

  return (
    <MultiSelect<string>
      placeholder='Recipients'
@@ -77,20 +72,20 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({
        minimal: true,
      }}
      resetOnSelect
      selectedItems={selectedItems}
      selectedItems={selectedContacts}
      tagRenderer={i => i}
    />
  )
}

EmailContactSelector.defaultProps = {
  selfemail: undefined,
  senderAddress: undefined,
}

export default memo(EmailContactSelector, (prev, next) => {
  return (
    prev.emailContacts === next.emailContacts &&
    prev.selectedContacts === next.selectedContacts &&
    prev.selfemail === next.selfemail
    prev.senderAddress === next.senderAddress
  )
})
+27 −11
Original line number Diff line number Diff line
@@ -14,17 +14,26 @@ import notEmpty from '@inject/shared/utils/notEmpty'
import { useGetEmailContacts } from '@inject/graphql/queries/GetEmailContacts.generated'
import useThreadSubmission from './useThreadSubmission'

const InstructorEmailForm: FC<EmailFormProps> = ({
interface InstructorEmailFormProps extends EmailFormProps {
  exerciseId: string
}

const InstructorEmailForm: FC<InstructorEmailFormProps> = ({
  exerciseId,
  emailThread,
  threadId,
  teamId,
  onFinish,
}) => {
  const formState = useFormState({
    emailThread,
    threadId: threadId || '-1',
  })
  const {
    file,
    setFile,
    fileName,
    setFilename,
    setFileName,
    activateMilestone,
    deactivateMilestone,
    setActivateMilestone,
@@ -39,10 +48,9 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
    setSelectedContacts,
    subject,
    setSubject,
  } = useFormState({
    emailThread,
    threadId: threadId || '-1',
  })
    template,
    setTemplate,
  } = formState

  const [instructorMutate, { loading }] = useInstructorSendEmail()

@@ -71,8 +79,6 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
    },
  })

  const fileInputText = file ? file.name : 'Attach a file'

  const { data: emailAddresses } = useGetEmailAddresses({
    variables: {
      threadId: emailThread?.id || '',
@@ -146,8 +152,17 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
              subject,
              setSubject,
            })}
        senderAddress={senderAddress}
        senderList={senderList}
        setSenderAddress={setSenderAddress}
        setContent={setContent}
        setActivateMilestone={setActivateMilestone}
        setDeactivateMilestone={setDeactivateMilestone}
        setFileName={setFileName}
        template={template}
        setTemplate={setTemplate}
        teamId={teamId}
        exerciseId={exerciseId}
      />

      <Divider style={{ margin: '0.5rem 0' }} />
@@ -158,6 +173,7 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
        setActivateMilestone={setActivateMilestone}
        deactivateMilestone={deactivateMilestone}
        setDeactivateMilestone={setDeactivateMilestone}
        template={template}
      />

      <ContentArea content={content} setContent={setContent} />
@@ -170,10 +186,10 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
          justifyContent: 'space-between',
        }}>
        <FileArea
          file={file}
          fileInputText={fileInputText}
          fileName={fileName || undefined}
          fileInputText={'Attach a file'}
          setFile={setFile}
          setFilename={setFilename}
          setFileName={setFileName}
        />

        <FormGroup disabled={loading} style={{ margin: '0' }}>
+11 −7
Original line number Diff line number Diff line
@@ -12,7 +12,11 @@ import notEmpty from '@inject/shared/utils/notEmpty'
import { useGetEmailContacts } from '@inject/graphql/queries/GetEmailContacts.generated'
import useThreadSubmission from './useThreadSubmission'

const TraineeEmailForm: FC<EmailFormProps> = ({
interface TraineeEmailFormProps extends EmailFormProps {
  teamAddress: string
}

const TraineeEmailForm: FC<TraineeEmailFormProps> = ({
  emailThread,
  teamAddress,
  threadId,
@@ -23,7 +27,7 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
    file,
    setFile,
    fileName,
    setFilename,
    setFileName,
    content,
    setContent,
    discard,
@@ -43,7 +47,7 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
    file,
    fileName,
    subject: subject || '',
    senderAddress: teamAddress!,
    senderAddress: teamAddress,
    selectedContacts,
    threadId,
    teamId,
@@ -51,7 +55,7 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
      traineeMutate({
        variables: {
          threadId,
          senderAddress: teamAddress!,
          senderAddress: teamAddress,
          content,
          fileName,
        },
@@ -97,7 +101,7 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
              subject,
              setSubject,
            })}
        senderAddress={teamAddress!}
        senderAddress={teamAddress}
      />
      <Divider style={{ margin: '0.5rem 0' }} />

@@ -111,10 +115,10 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
          justifyContent: 'space-between',
        }}>
        <FileArea
          file={file}
          fileName={fileName}
          fileInputText={fileInputText}
          setFile={setFile}
          setFilename={setFilename}
          setFileName={setFileName}
        />

        <FormGroup disabled={loading} style={{ margin: '0' }}>
+12 −13
Original line number Diff line number Diff line
@@ -9,43 +9,42 @@ import {
} from 'react'

interface FileAreaProps {
  file: File | undefined
  fileName: string | undefined
  setFile: Dispatch<SetStateAction<File | undefined>>
  setFilename: (fileName: string) => void
  setFileName: Dispatch<SetStateAction<string | undefined>>
  fileInputText: string
}

const FileArea: FC<FileAreaProps> = ({
  file,
  fileName,
  fileInputText,
  setFile,
  setFilename,
  setFileName,
}) => {
  const handleFileChange = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      if (e.target.files) {
        setFile(e.target.files[0])
        setFilename(e.target.files[0].name || '')
        setFileName(e.target.files[0].name)
        const element = e.target as HTMLInputElement
        element.value = ''
      }
    },
    [setFile, setFilename]
    [setFile, setFileName]
  )

  const resetFile = useCallback(() => {
    setFile(undefined)
    setFilename('')
  }, [setFile, setFilename])
    setFileName(undefined)
  }, [setFile, setFileName])

  return (
    <div>
      {!file && (
      {fileName === undefined ? (
        <FileInput text={fileInputText} onInputChange={handleFileChange} />
      )}
      {file && (
      ) : (
        <Button minimal icon='cross' onClick={resetFile}>
          {file.name}
          {fileName}
        </Button>
      )}
    </div>
@@ -55,5 +54,5 @@ const FileArea: FC<FileAreaProps> = ({
export default memo(
  FileArea,
  (prev, next) =>
    prev.file === next.file && prev.fileInputText === next.fileInputText
    prev.fileName === next.fileName && prev.fileInputText === next.fileInputText
)
+50 −4
Original line number Diff line number Diff line
import EmailContactSelector from '@/email/EmailContactSelector'
import EmailTemplates from '@/email/EmailTemplates'
import { HTMLSelect, InputGroup } from '@blueprintjs/core'
import { css } from '@emotion/css'
import { EmailParticipant } from '@inject/graphql/fragments/EmailParticipant.generated'
import { EmailTemplate } from '@inject/graphql/fragments/EmailTemplate.generated'
import { EmailThread } from '@inject/graphql/fragments/EmailThread.generated'
import notEmpty from '@inject/shared/utils/notEmpty'
import {
@@ -17,7 +19,6 @@ import {
const gridContainer = css`
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: repeat(3, auto);
  grid-column-gap: 10px;
`

@@ -51,15 +52,31 @@ type HeaderAreaProps = (
  (
    | {
        // instructor
        senderAddress?: undefined
        senderAddress: string
        senderList: string[]
        setSenderAddress: Dispatch<SetStateAction<string>>
        setContent: Dispatch<SetStateAction<string>>
        setActivateMilestone: Dispatch<SetStateAction<string>>
        setDeactivateMilestone: Dispatch<SetStateAction<string>>
        setFileName: Dispatch<SetStateAction<string | undefined>>
        template: EmailTemplate | undefined
        setTemplate: Dispatch<SetStateAction<EmailTemplate | undefined>>
        teamId: string
        exerciseId: string
      }
    | {
        // trainee
        senderAddress: string
        senderList?: undefined
        setSenderAddress?: undefined
        setContent?: undefined
        setActivateMilestone?: undefined
        setDeactivateMilestone?: undefined
        setFileName?: undefined
        template?: undefined
        setTemplate?: undefined
        teamId?: undefined
        exerciseId?: undefined
      }
  )

@@ -70,9 +87,17 @@ const HeaderArea: FC<HeaderAreaProps> = ({
  contacts,
  selectedContacts,
  setSelectedContacts,
  setSenderAddress,
  subject,
  setSubject,
  setSenderAddress,
  setContent,
  setActivateMilestone,
  setDeactivateMilestone,
  setFileName,
  template,
  setTemplate,
  teamId,
  exerciseId,
}) => {
  const handleSenderChange = useCallback(
    (e: React.ChangeEvent<HTMLSelectElement>) => {
@@ -115,6 +140,7 @@ const HeaderArea: FC<HeaderAreaProps> = ({
              : senderList
          }
          disabled={senderList.length === 0}
          value={senderAddress}
          onChange={handleSenderChange}
        />
      ) : (
@@ -128,7 +154,7 @@ const HeaderArea: FC<HeaderAreaProps> = ({
        <EmailContactSelector
          emailContacts={contacts}
          selectedContacts={selectedContacts}
          selfemail={senderAddress}
          senderAddress={senderAddress}
          setSelectedContacts={setSelectedContacts}
        />
      )}
@@ -143,6 +169,26 @@ const HeaderArea: FC<HeaderAreaProps> = ({
          onChange={event => setSubject(event.currentTarget.value)}
        />
      )}

      {senderList !== undefined && (
        <>
          <div className={gridItem}>Template:</div>
          <EmailTemplates
            setSenderAddress={setSenderAddress}
            setContent={setContent}
            setActivateMilestone={setActivateMilestone}
            setDeactivateMilestone={setDeactivateMilestone}
            setFileName={setFileName}
            template={template}
            setTemplate={setTemplate}
            teamId={teamId}
            exerciseId={exerciseId}
            {...(emailThread
              ? { threadId: emailThread.id }
              : { emailAddresses: senderList })}
          />
        </>
      )}
    </div>
  )
}
Loading