Coursemology/coursemology2

View on GitHub
client/app/bundles/course/announcements/components/forms/AnnouncementForm.tsx

Summary

Maintainability
B
5 hrs
Test Coverage
import { FC, useState } from 'react';
import { Controller, UseFormSetError } from 'react-hook-form';
import { defineMessages } from 'react-intl';
import { RadioGroup } from '@mui/material';
import { AnnouncementFormData } from 'types/course/announcements';
import * as yup from 'yup';

import IconRadio from 'lib/components/core/buttons/IconRadio';
import FormDialog from 'lib/components/form/dialog/FormDialog';
import FormDateTimePickerField from 'lib/components/form/fields/DateTimePickerField';
import FormRichTextField from 'lib/components/form/fields/RichTextField';
import FormTextField from 'lib/components/form/fields/TextField';
import FormToggleField from 'lib/components/form/fields/ToggleField';
import useTranslation from 'lib/hooks/useTranslation';
import formTranslations from 'lib/translations/form';

export type PublishTime = 'now' | 'later';

interface Props {
  open: boolean;
  editing: boolean;
  title: string;
  initialValues: AnnouncementFormData;
  onClose: () => void;
  onSubmit: (
    data: AnnouncementFormData,
    setError: UseFormSetError<AnnouncementFormData>,
    whenToPublish: PublishTime,
  ) => Promise<void>;
  canSticky: boolean;
}

const translations = defineMessages({
  title: {
    id: 'course.announcements.AnnouncementForm.title',
    defaultMessage: 'Title',
  },
  content: {
    id: 'course.announcements.AnnouncementForm.content',
    defaultMessage: 'Content',
  },
  sticky: {
    id: 'course.announcements.AnnouncementForm.sticky',
    defaultMessage: 'Sticky',
  },
  startAt: {
    id: 'course.announcements.AnnouncementForm.startAt',
    defaultMessage: 'Start At',
  },
  endAt: {
    id: 'course.announcements.AnnouncementForm.endAt',
    defaultMessage: 'End At',
  },
  endTimeError: {
    id: 'course.announcements.AnnouncementForm.endTimeError',
    defaultMessage: 'End time cannot be earlier than start time',
  },
  publishNow: {
    id: 'course.announcements.AnnouncementForm.publishNow',
    defaultMessage: 'Publish Now',
  },
  publishAtSetDate: {
    id: 'course.announcements.AnnouncementForm.publishAtSetDate',
    defaultMessage: 'Publish At:',
  },
});

const validationSchema = (whenToPublish: PublishTime): yup.AnyObjectSchema =>
  yup.object({
    title: yup.string().required(formTranslations.required),
    content: yup.string().nullable(),
    sticky: yup.bool(),
    startAt: yup.date().nullable().typeError(formTranslations.invalidDate),
    endAt: yup
      .date()
      .nullable()
      .typeError(formTranslations.invalidDate)
      .min(
        yup.ref('startAt'),
        whenToPublish === 'now'
          ? formTranslations.earlierThanCurrentTimeError
          : formTranslations.earlierThanStartTimeError,
      ),
  });

const AnnouncementForm: FC<Props> = (props) => {
  const { open, editing, title, onClose, initialValues, onSubmit, canSticky } =
    props;
  const { t } = useTranslation();
  const [whenToPublish, setWhenToPublish] = useState<PublishTime>('now');

  return (
    <FormDialog
      editing={editing}
      formName="announcement-form"
      initialValues={initialValues}
      onClose={onClose}
      onSubmit={(
        data: AnnouncementFormData,
        setError: UseFormSetError<AnnouncementFormData>,
      ): Promise<void> => onSubmit(data, setError, whenToPublish)}
      open={open}
      title={title}
      validationSchema={validationSchema(whenToPublish)}
    >
      {(control, formState): JSX.Element => (
        <>
          <Controller
            control={control}
            name="title"
            render={({ field, fieldState }): JSX.Element => (
              <FormTextField
                disabled={formState.isSubmitting}
                field={field}
                fieldState={fieldState}
                fullWidth
                InputLabelProps={{
                  shrink: true,
                }}
                label={t(translations.title)}
                required
                variant="standard"
              />
            )}
          />

          <Controller
            control={control}
            name="content"
            render={({ field, fieldState }): JSX.Element => (
              <FormRichTextField
                disabled={formState.isSubmitting}
                field={field}
                fieldState={fieldState}
                fullWidth
                InputLabelProps={{
                  shrink: true,
                }}
                label={t(translations.content)}
                variant="standard"
              />
            )}
          />

          {canSticky && (
            <Controller
              control={control}
              name="sticky"
              render={({ field, fieldState }): JSX.Element => (
                <FormToggleField
                  disabled={formState.isSubmitting}
                  field={field}
                  fieldState={fieldState}
                  label={t(translations.sticky)}
                />
              )}
            />
          )}

          {!editing && (
            <RadioGroup
              onChange={(_, value): void =>
                setWhenToPublish(value as PublishTime)
              }
              value={whenToPublish}
            >
              <div className="flex space-x-3 max-sm:flex-col max-sm:space-x-0">
                <IconRadio
                  iconClassName="py-0"
                  label={t(translations.publishNow)}
                  value="now"
                />

                <div className="flex items-center space-x-3">
                  <IconRadio
                    iconClassName="py-0"
                    label={t(translations.publishAtSetDate)}
                    value="later"
                  />
                  <Controller
                    control={control}
                    name="startAt"
                    render={({ field, fieldState }): JSX.Element => (
                      <FormDateTimePickerField
                        disabled={
                          formState.isSubmitting || whenToPublish === 'now'
                        }
                        field={field}
                        fieldState={fieldState}
                      />
                    )}
                  />
                </div>
              </div>
            </RadioGroup>
          )}

          <div className="flex w-full max-sm:flex-col max-sm:space-y-5">
            {editing && (
              <div className="w-1/3 max-sm:w-1/2">
                <Controller
                  control={control}
                  name="startAt"
                  render={({ field, fieldState }): JSX.Element => (
                    <FormDateTimePickerField
                      disabled={
                        formState.isSubmitting || whenToPublish === 'later'
                      }
                      field={field}
                      fieldState={fieldState}
                      label={t(translations.startAt)}
                    />
                  )}
                />
              </div>
            )}
            <div className="w-1/3 max-sm:w-1/2">
              <Controller
                control={control}
                name="endAt"
                render={({ field, fieldState }): JSX.Element => (
                  <FormDateTimePickerField
                    disabled={formState.isSubmitting}
                    field={field}
                    fieldState={fieldState}
                    label={t(translations.endAt)}
                  />
                )}
              />
            </div>
          </div>
        </>
      )}
    </FormDialog>
  );
};

export default AnnouncementForm;