Commit 253b2c7f authored by Marek Veselý's avatar Marek Veselý
Browse files

Resolve "Replace 'participants' with 'to' in email form"

parent 3a0fe6f5
Loading
Loading
Loading
Loading
+14 −16
Original line number Diff line number Diff line
@@ -15,30 +15,28 @@ interface EmailContactSelectorProps {
  inInstructor: boolean
  exerciseId: string
  placeholder?: string
  teamAddress?: string
  senderAddress: string
}

// TODO: add custom tag renderer not to render the "x" button for team address
const EmailContactSelector: FC<EmailContactSelectorProps> = ({
  placeholder,
  emailContacts,
  selectedContacts: initialSelectedContacts,
  selectedContacts,
  setSelectedContacts,
  inInstructor,
  exerciseId,
  teamAddress,
  senderAddress,
}) => {
  const selectedContacts = useMemo(
  const suggestedItems = useMemo(
    () =>
      teamAddress
        ? [teamAddress, ...initialSelectedContacts]
        : initialSelectedContacts,
    [initialSelectedContacts, teamAddress]
      emailContacts
        .map(contact => contact.address)
        .filter(address => address !== senderAddress),
    [emailContacts, senderAddress]
  )

  const suggestedItems = useMemo(
    () => emailContacts.map(x => x?.address || '') || [],
    [emailContacts]
  const selectedItems = useMemo(
    () => selectedContacts.filter(contact => contact !== senderAddress),
    [selectedContacts, senderAddress]
  )

  const [validateEmailAddress] = useValidateEmailAddressLazyQuery()
@@ -88,8 +86,8 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({
  )

  const itemDisabled = useCallback(
    (i: string) => i === teamAddress,
    [teamAddress]
    (i: string) => i === senderAddress,
    [senderAddress]
  )
  const onItemSelect = useCallback(
    async (i: string) => {
@@ -154,7 +152,7 @@ const EmailContactSelector: FC<EmailContactSelectorProps> = ({
        minimal: true,
      }}
      resetOnSelect
      selectedItems={selectedContacts}
      selectedItems={selectedItems}
      tagRenderer={i => i}
      popoverContentProps={{
        style: { maxHeight: '50vh', overflowY: 'auto', overflowX: 'hidden' },
+4 −4
Original line number Diff line number Diff line
@@ -10,8 +10,8 @@ import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyCon
import notEmpty from '@inject/shared/utils/notEmpty'
import type { FC } from 'react'
import { memo, useCallback, useMemo } from 'react'
import { MainDrawer } from './css'
import HeaderArea from './modules/HeaderArea'
import InstructorHeaderArea from './InstructorHeaderArea'
import { form } from './classes'
import type { EmailFormProps, OnSendEmailInput } from './typing'
import useThreadSubmission from './useThreadSubmission'

@@ -153,8 +153,8 @@ const InstructorEmailForm: FC<EmailFormProps> = ({
  }, [onSend])

  return (
    <div className={MainDrawer}>
      <HeaderArea
    <div className={form}>
      <InstructorHeaderArea
        {...(emailThread
          ? {
              emailThread,
+55 −0
Original line number Diff line number Diff line
import { InputGroup } from '@blueprintjs/core'
import type { EmailParticipant } from '@inject/graphql/fragments/EmailParticipant.generated'
import { type Dispatch, type FC, type SetStateAction } from 'react'
import SenderSelector from '../SenderSelector'

interface InstructorFromElementProps {
  senderList: string[]
  setSenderAddress: Dispatch<SetStateAction<string>>
  senderAddress: string
  contacts: EmailParticipant[]
  selectedContacts?: string[]
  setSelectedContacts?: Dispatch<SetStateAction<string[]>>
}

const InstructorFromElement: FC<InstructorFromElementProps> = ({
  senderList,
  setSenderAddress,
  senderAddress,
  contacts,
  selectedContacts,
  setSelectedContacts,
}) => {
  if (selectedContacts === undefined) {
    // existing thread
    return senderList.length > 1 ? (
      <SenderSelector
        emailContacts={contacts}
        senderList={senderList}
        senderAddress={senderAddress}
        setSenderAddress={setSenderAddress}
      />
    ) : (
      <InputGroup readOnly value={senderAddress} />
    )
  }

  // new thread
  return (
    <SenderSelector
      emailContacts={contacts}
      senderList={contacts
        .filter(contacts => contacts.definitionAddress)
        .map(contact => contact.address)}
      senderAddress={senderAddress}
      onItemSelect={item => {
        setSelectedContacts?.(prev =>
          prev.includes(item) ? prev : [...prev, item]
        )
        setSenderAddress(item)
      }}
    />
  )
}

export default InstructorFromElement
+37 −97
Original line number Diff line number Diff line
import EmailContactSelector from '@/email/EmailContactSelector'
import EmailTemplates from '@/email/EmailTemplates'
import SenderSelector from '@/email/SenderSelector'
import { Button, InputGroup } from '@blueprintjs/core'
import { css } from '@emotion/css'
import type { EmailParticipant } from '@inject/graphql/fragments/EmailParticipant.generated'
import type { EmailTemplate } from '@inject/graphql/fragments/EmailTemplate.generated'
import type { EmailThread } from '@inject/graphql/fragments/EmailThread.generated'
import type { FileInfo } from '@inject/graphql/fragments/FileInfo.generated'
import notEmpty from '@inject/shared/utils/notEmpty'
import type { Dispatch, FC, SetStateAction } from 'react'
import { useEffect, useMemo, useState } from 'react'
import InstructorFromElement from './InstructorFromElement'
import {
  contactSelector,
  contactSelectorWrapper,
  gridContainer,
  gridItem,
} from './classes'
import type { HeaderAreaProps } from './typing'

const gridContainer = css`
  display: grid;
  grid-template-columns: auto 1fr;
  grid-column-gap: 0.5rem;
`

const gridItem = css`
  display: flex;
  align-items: center;
`

const contactSelectorWrapper = css`
  width: 100%;
  display: flex;
`

const contactSelector = css`
  flex: 1;
`

type HeaderAreaProps = (
  | {
      // new thread
      emailThread?: undefined
      selectedContacts: string[]
      setSelectedContacts: Dispatch<SetStateAction<string[]>>
      bccSelectedContacts: string[]
      setBccSelectedContacts: Dispatch<SetStateAction<string[]>>
      subject: string | undefined
      setSubject: Dispatch<SetStateAction<string | undefined>>
    }
  | {
      // existing thread
      emailThread: EmailThread
      selectedContacts?: undefined
      setSelectedContacts?: undefined
      bccSelectedContacts?: undefined
      setBccSelectedContacts?: undefined
      subject?: undefined
      setSubject?: undefined
    }
) &
  (
    | {
        // instructor
type InstructorHeaderAreaProps = HeaderAreaProps & {
  senderList: string[]
  setSenderAddress: Dispatch<SetStateAction<string>>
  setContent: Dispatch<SetStateAction<string>>
@@ -66,28 +26,9 @@ type HeaderAreaProps = (
  setTemplate: Dispatch<SetStateAction<EmailTemplate | undefined>>
  teamId: string
  senderAddress: string
        teamAddress?: never
      }
    | {
        // trainee
        senderList?: undefined
        setSenderAddress?: undefined
        setContent?: undefined
        setActivateMilestone?: undefined
        setDeactivateMilestone?: undefined
        setFileInfo?: undefined
        template?: undefined
        setTemplate?: undefined
        teamId?: undefined
        senderAddress?: never
        teamAddress: string
      }
  ) & {
    contacts: EmailParticipant[]
    exerciseId: string
}

const HeaderArea: FC<HeaderAreaProps> = ({
const InstructorHeaderArea: FC<InstructorHeaderAreaProps> = ({
  senderAddress,
  emailThread,
  senderList,
@@ -107,15 +48,15 @@ const HeaderArea: FC<HeaderAreaProps> = ({
  setTemplate,
  teamId,
  exerciseId,
  teamAddress,
}) => {
  const emailThreadParticipants = useMemo(
    () =>
      (emailThread?.participants || [])
        .filter(notEmpty)
        .map(p => p.address)
        .map(participant => participant.address)
        .filter(address => address !== senderAddress)
        .join(', '),
    [emailThread]
    [emailThread?.participants, senderAddress]
  )

  useEffect(() => {
@@ -132,31 +73,29 @@ const HeaderArea: FC<HeaderAreaProps> = ({
  return (
    <div className={gridContainer}>
      <div className={gridItem}>From:</div>
      {senderList ? (
        <SenderSelector
          emailContacts={contacts}
      <InstructorFromElement
        selectedContacts={selectedContacts}
        setSelectedContacts={setSelectedContacts}
        senderList={senderList}
        senderAddress={senderAddress}
        setSenderAddress={setSenderAddress}
        contacts={contacts}
      />
      ) : (
        <InputGroup readOnly value={teamAddress} />
      )}

      <div className={gridItem}>Participants:</div>
      <div className={gridItem}>To:</div>
      {emailThread ? (
        <InputGroup readOnly value={emailThreadParticipants} />
      ) : (
        <div className={contactSelectorWrapper}>
          <div className={contactSelector}>
            <EmailContactSelector
              placeholder='Add participants...'
              placeholder='Add recipients...'
              emailContacts={contacts}
              selectedContacts={selectedContacts}
              setSelectedContacts={setSelectedContacts}
              inInstructor={senderList !== undefined}
              exerciseId={exerciseId}
              teamAddress={teamAddress}
              senderAddress={senderAddress}
            />
          </div>
          <Button
@@ -185,6 +124,7 @@ const HeaderArea: FC<HeaderAreaProps> = ({
            setSelectedContacts={setBccSelectedContacts}
            inInstructor={senderList !== undefined}
            exerciseId={exerciseId}
            senderAddress={senderAddress}
          />
        </>
      )}
@@ -223,4 +163,4 @@ const HeaderArea: FC<HeaderAreaProps> = ({
  )
}

export default HeaderArea
export default InstructorHeaderArea
+4 −4
Original line number Diff line number Diff line
@@ -8,8 +8,8 @@ import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyCon
import notEmpty from '@inject/shared/utils/notEmpty'
import type { FC } from 'react'
import { memo, useCallback } from 'react'
import { MainDrawer } from './css'
import HeaderArea from './modules/HeaderArea'
import TraineeHeaderArea from './TraineeHeaderArea'
import { form } from './classes'
import type { EmailFormProps, OnSendEmailInput } from './typing'
import useThreadSubmission from './useThreadSubmission'

@@ -99,8 +99,8 @@ const TraineeEmailForm: FC<EmailFormProps> = ({
  }, [onSuccess, storeDraft])

  return (
    <div className={MainDrawer}>
      <HeaderArea
    <div className={form}>
      <TraineeHeaderArea
        {...(emailThread
          ? {
              emailThread,
Loading