MetaPhase-Consulting/State-TalentMAP

View on GitHub
src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx

Summary

Maintainability
A
25 mins
Test Coverage
A
95%
import { Component } from 'react';
import PropTypes from 'prop-types';
import { EMPTY_FUNCTION, GLOSSARY_ERROR_OBJECT, GLOSSARY_OBJECT } from '../../../Constants/PropTypes';
import TextEditor from '../../TextEditor';
import InteractiveElement from '../../InteractiveElement';
import GlossaryEditorCardBottom from '../GlossaryEditorCardBottom';
import BoxShadow from '../../BoxShadow';
import { isUrl } from '../../../utilities';

const isEmpty = value => (value || '').length === 0;

class GlossaryEditorCard extends Component {
  constructor(props) {
    super(props);

    this.state = {
      editorHidden: true,
      newTitle: null,
      newLink: null,
      newDefinition: null,
      displayZeroLengthAlert: false,
      newIsArchived: this.props.term.is_archived || false,
    };
  }

  get hasTitleChanged() {
    const { term } = this.props;
    return (this.state.newTitle !== null) && (term.title !== this.state.newTitle);
  }

  get hasLinkChanged() {
    const { term } = this.props;
    return (this.state.newLink !== null) && (term.Link !== this.state.newLink);
  }

  get hasDefinitionChanged() {
    const { term } = this.props;
    return (this.state.newDefinition !== null) && (term.definition !== this.state.newDefinition);
  }

  get hasChanged() {
    return (this.hasTitleChanged || this.hasDefinitionChanged || this.hasLinkChanged);
  }

  get valid() {
    const { newTitle, newDefinition } = this.state;
    // Check if there's a title and definition, as well as if either the
    // title or definition are present but not changed via the text editor.
    return !isEmpty(newTitle) && !this.showInvalidLinkWarning() && !isEmpty(newDefinition);
  }

  get editorClasses() {
    const { isNewTerm } = this.props;
    const { editorHidden } = this.state;
    const shouldHideEditor = editorHidden && !isNewTerm;
    const editorHiddenClass = shouldHideEditor ? 'editor-hidden' : 'editor-visible';
    const editorContainerHiddenClass = shouldHideEditor ? 'editor-container-hidden' : 'editor-container-visible';
    const definitionContainerClass = shouldHideEditor ? 'editor-hidden--definition' : 'editor-visible--definition';
    const titleContainerClass = shouldHideEditor ? 'editor-hidden--title' : 'editor-visible--title';

    return {
      editorHiddenClass,
      editorContainerHiddenClass,
      definitionContainerClass,
      titleContainerClass,
    };
  }

  get showEmptyWarning() {
    const { displayZeroLengthAlert } = this.state;
    const emptyTitleWarning = displayZeroLengthAlert.title;
    const emptyDefinitionWarning = displayZeroLengthAlert.definition;
    return emptyTitleWarning || emptyDefinitionWarning;
  }

  getTextToRender = () => {
    const { term } = this.props;
    const {
      newTitle,
      newLink,
      newDefinition,
    } = this.state;
    const renderedTitle = newTitle || term.title;
    const renderedLink = newLink || term.link;
    const renderedDefinition = newDefinition || term.definition;
    return { renderedTitle, renderedLink, renderedDefinition };
  };

  showInvalidLinkWarning = () => {
    const { newLink } = this.state;
    if (isEmpty(newLink)) {
      return false;
    }
    return !isUrl(newLink);
  };

  toggleEditorState = () => {
    this.setState({ editorHidden: !this.state.editorHidden, displayZeroLengthAlert: false });
  };

  toggleEmptyAlert = (displayZeroLengthAlert = true) => {
    this.setState({ displayZeroLengthAlert });
  };

  updateTitle = newTitle => {
    this.setState({ newTitle });
    this.toggleEmptyAlert(false);
  };

  updateLink = newLink => {
    this.setState({ newLink });
    this.toggleEmptyAlert(false);
  };

  updateDefinition = newDefinition => {
    this.setState({ newDefinition });
    this.toggleEmptyAlert(false);
  };

  cancel = () => {
    this.props.onCancel(this.props.term.id);
    this.setState({
      editorHidden: true,
      newTitle: null,
      newDefinition: null,
      displayZeroLengthAlert: false,
    });
  };

  submitDefinition = () => {
    const { term, isNewTerm } = this.props;
    const { newTitle, newLink, newDefinition, newIsArchived } = this.state;

    if (this.valid) {
      if (this.hasChanged) {
        this.props.submitGlossaryTerm({
          id: term.id,
          title: newTitle,
          link: newLink,
          definition: newDefinition,
          is_archived: newIsArchived,
        }, () => {
          // Toggle submitted state on success
          this.setState({ editorHidden: true });

          if (isNewTerm) {
            this.props.onCancel();
          }
        });
      } else {
        // No changes made so it's fine to use our cancel fn
        this.cancel();
      }
    } else {
      this.toggleEmptyAlert({
        title: isEmpty(newTitle),
        definition: isEmpty(newDefinition),
      });
    }
  };

  render() {
    const { term, isNewTerm, hasErrored, submitGlossaryTerm } = this.props;
    const { editorHidden } = this.state;

    const { renderedTitle, renderedLink, renderedDefinition } = this.getTextToRender();
    const shouldHideEditor = editorHidden && !isNewTerm;

    const {
      editorHiddenClass,
      editorContainerHiddenClass,
      definitionContainerClass,
      titleContainerClass,
    } = this.editorClasses;

    return (
      <BoxShadow className={`usa-grid-full section-padded-inner-container glossary-editor-card ${editorContainerHiddenClass}`}>
        <div className="usa-grid-full glossary-editor-card-top">
          <div className={`title-container ${editorHiddenClass} ${titleContainerClass}`}>
            {
              shouldHideEditor ?
                <h4>{renderedTitle}</h4> :
                <TextEditor
                  initialText={renderedTitle}
                  onSubmitText={this.submitDefinition}
                  cancel={this.cancel}
                  hideButtons
                  onChangeText={this.updateTitle}
                  draftJsProps={{ placeholder: 'Title' }}
                />
            }
          </div>
          {
            !isNewTerm &&
              <div className="actions-container">
                <div className="actions-inner-container">
                  <InteractiveElement role="link" onClick={this.toggleEditorState}>{shouldHideEditor ? 'Edit' : 'Cancel'}</InteractiveElement>
                </div>
              </div>
          }
        </div>
        <div className="usa-grid-full glossary-editor-card-top">
          <div className={`title-container link-container ${editorHiddenClass} ${titleContainerClass}`}>
            {
              shouldHideEditor ?
                <h4>{renderedLink || <i>There is no link for this term</i>}</h4> :
                <TextEditor
                  initialText={renderedLink}
                  onSubmitText={this.submitDefinition}
                  cancel={this.cancel}
                  hideButtons
                  onChangeText={this.updateLink}
                  draftJsProps={{ placeholder: 'https://www.state.gov' }}
                />
            }
          </div>
        </div>
        <div className={`usa-grid-full glossary-editor-card-definition ${editorHiddenClass} ${definitionContainerClass}`}>
          {
            shouldHideEditor ?
              renderedDefinition :
              <TextEditor
                id="input-error"
                initialText={renderedDefinition}
                onSubmitText={this.submitDefinition}
                cancel={this.cancel}
                onChangeText={this.updateDefinition}
                draftJsProps={{ placeholder: 'Definition' }}
              />
          }
        </div>
        <GlossaryEditorCardBottom
          isNewTerm={isNewTerm}
          hasErrored={hasErrored}
          showEmptyWarning={this.showEmptyWarning}
          showInvalidLinkWarning={this.showInvalidLinkWarning()}
          dateUpdated={term.date_updated}
          updatedBy={term.last_editing_user}
          isArchived={term.is_archived}
          id={term.id || null}
          submitGlossaryTerm={submitGlossaryTerm}
        />
      </BoxShadow>
    );
  }
}

GlossaryEditorCard.propTypes = {
  term: GLOSSARY_OBJECT.isRequired,
  submitGlossaryTerm: PropTypes.func.isRequired,
  isNewTerm: PropTypes.bool,
  onCancel: PropTypes.func,
  hasErrored: PropTypes.oneOfType([GLOSSARY_ERROR_OBJECT, PropTypes.bool]),
};

GlossaryEditorCard.defaultProps = {
  isNewTerm: false,
  onCancel: EMPTY_FUNCTION,
  onSuccess: {},
  hasErrored: false,
};

export default GlossaryEditorCard;