redbadger/website-honestly

View on GitHub
site/components/hubspot/form/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
F
27%
// @flow
import React from 'react';
import Cookies from 'js-cookie';

import FormField from './formField';
import type { HubspotFormField } from './formField';
import inputTypes from './inputTypeDefinitions';

type HubspotFormFieldValues = {
  name: string,
  value: string,
  valid: boolean,
};

export type HubspotFormProps = {
  portalId: string,
  guid: string,
  name: string,
  cssClass: string,
  consentCssClass: string,
  submitText?: string,
  inlineMessage?: string,
  formFields: Array<HubspotFormField>,
  formConsent: {
    consentMessage: string,
    checkboxes: Array<{ label: string, required: boolean }>,
  },
  pageTitle: string,
  uniqueFormId?: string, // Used when multiple forms exist on one page
};

type HubspotFormState = {
  showWarnings: boolean,
  submitted: boolean,
  fieldData: Array<HubspotFormFieldValues>,
  hubspotAPIEndpoint: string,
  isDisabled: boolean,
};

export default class HubspotForm extends React.Component<HubspotFormProps, HubspotFormState> {
  constructor(props: HubspotFormProps) {
    super(props);

    let fieldData = [];
    if (props.formFields) {
      fieldData = props.formFields
        .filter(field => {
          // Hubspot packages form fields and isolated chunks of html together
          // this removes any fields that don't correlate to an input.
          if (field.name) {
            return true;
          }
          return false;
        })
        .map(field => {
          const formField = {
            name: field.name,
            value: field.defaultValue ? field.defaultValue : '',
            valid: !field.required,
          };
          return formField;
        });
    }

    this.state = {
      showWarnings: false,
      submitted: false,
      fieldData,
      hubspotAPIEndpoint: `https://api.hsforms.com/submissions/v3/integration/submit/${props.portalId}/${props.guid}`,
      isDisabled:
        props.formConsent && props.formConsent.checkboxes
          ? props.formConsent.checkboxes.some(checkbox => checkbox.required)
          : false,
    };
    (this: any)._onSubmit = this._onSubmit.bind(this);
    (this: any)._onChange = this._onChange.bind(this);
    (this: any).formIsValid = this.formIsValid.bind(this);
    (this: any).postFormDataToHubspot = this.postFormDataToHubspot.bind(this);
    (this: any).checkConsented = this.checkConsented.bind(this);
  }

  static getFieldState(fieldData: Array<HubspotFormFieldValues>, fieldName: string) {
    return fieldData.find(field => field.name === fieldName);
  }

  static validateContent(type: string, value: string, required: boolean) {
    // for strings with special constraints like emails or phone numbers
    // If they input has a special case retrieve that validator and run
    // the string through it
    if (inputTypes[type]) {
      const { validator } = inputTypes[type];
      if (validator && (required || value)) {
        return validator(value);
      }
    }
    if (required) {
      return value.length > 0;
    }
    // If neither of the above constrains apply, the input is inherently valid.
    return true;
  }

  static getRequiredCheckboxes(uniqueFormId: ?string): any {
    return Array.from(
      document.querySelectorAll(uniqueFormId ? `#${uniqueFormId}` : '#form-legal-consent'),
    ).filter((check: any) => check.required);
  }

  static fireSubmitEvent(status: string) {
    const dataObject = {
      event: 'gtm.click',
      category: 'Gold Coin Submit',
      'gtm.element': `form_${status}`,
    };
    if (typeof window.dataLayer !== 'undefined') {
      window.dataLayer.push(dataObject);
    }
  }

  formIsValid() {
    const { fieldData } = this.state;
    const isValid = fieldData.filter(value => value.valid === false).length === 0;
    return isValid;
  }

  postFormDataToHubspot() {
    const { hubspotAPIEndpoint, fieldData } = this.state;
    const { pageTitle } = this.props;
    const postData = {
      context: {
        hutk: Cookies.get('hubspotutk'),
        pageName: pageTitle,
        pageUri: window.location.href,
      },
      fields: [],
    };
    postData.fields = fieldData.map(field => {
      return { name: field.name, value: field.value };
    });
    const headers = {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'Content-Length': Object.keys(postData).length.toString(),
    };
    fetch(hubspotAPIEndpoint, {
      method: 'POST',
      headers,
      body: JSON.stringify(postData),
      mode: 'cors',
    });
  }

  _onSubmit(event: SyntheticInputEvent<HTMLInputElement>) {
    event.preventDefault();
    if (this.formIsValid()) {
      this.postFormDataToHubspot();
      HubspotForm.fireSubmitEvent('success');
      this.setState({ submitted: true });
    } else {
      let { showWarnings } = this.state;
      showWarnings = true;
      HubspotForm.fireSubmitEvent('failure');
      this.setState({ showWarnings });
    }
  }

  _onChange(event: SyntheticInputEvent<HTMLInputElement>, fieldName: string, required: boolean) {
    const { fieldData } = this.state;
    let { showWarnings } = this.state;
    const { uniqueFormId } = this.props;
    const targetField = HubspotForm.getFieldState(fieldData, fieldName);
    if (targetField) {
      const { value } = event.target;
      targetField.value = value;
      targetField.valid = HubspotForm.validateContent(fieldName, value, required);
      if (targetField.name === 'email' && !showWarnings) {
        showWarnings = true;
        this.setState({ showWarnings });
      }

      const checkboxes = HubspotForm.getRequiredCheckboxes(uniqueFormId);
      const isDisabled = checkboxes.some((check: any) => !check.checked) || !this.formIsValid();

      this.setState({ fieldData, isDisabled });
    }
  }

  checkConsented() {
    const { uniqueFormId } = this.props;
    if (document) {
      const checkboxes = HubspotForm.getRequiredCheckboxes(uniqueFormId);
      const isDisabled = checkboxes.some((check: any) => !check.checked) || !this.formIsValid();
      const showWarnings = isDisabled;
      this.setState({ isDisabled, showWarnings });
    }
  }

  render() {
    const {
      portalId,
      guid,
      name,
      cssClass,
      consentCssClass,
      submitText,
      inlineMessage,
      formFields,
      formConsent,
      uniqueFormId,
    } = this.props;
    const { showWarnings, submitted, fieldData, isDisabled } = this.state;
    // html strings are provided by our CMS and sanitized in badger brain
    /* eslint-disable react/no-danger */
    return (
      <div id={name} className={cssClass}>
        {submitted ? (
          <p dangerouslySetInnerHTML={{ __html: inlineMessage }} />
        ) : (
          <form
            acceptCharset="UTF-8"
            action={`https://forms.hsforms.com/submissions/v3/public/submit/formsnext/multipart/${portalId}/${guid}`}
            encType="multipart/form-data"
            method="POST"
          >
            {formFields &&
              formFields.map((field, index) => {
                const fieldState = HubspotForm.getFieldState(fieldData, field.name);
                return (
                  <FormField
                    key={index}
                    richText={field.richText}
                    name={field.name}
                    label={field.label}
                    fieldType={field.fieldType}
                    description={field.description}
                    defaultValue={field.defaultValue}
                    value={fieldState ? fieldState.value : ''}
                    placeholder={field.placeholder}
                    required={field.required}
                    enabled={field.enabled}
                    hidden={field.hidden}
                    labelHidden={field.labelHidden}
                    valid={fieldState ? fieldState.valid : true}
                    showWarnings={showWarnings}
                    onChange={this._onChange}
                  />
                );
              })}
            {formConsent && (
              <div className={consentCssClass}>
                {formConsent.checkboxes.map(checkbox => {
                  return (
                    <label key={uniqueFormId || 'form-legal-consent'}>
                      <span>{checkbox.label}</span>
                      <input
                        type="checkbox"
                        id={uniqueFormId || 'form-legal-consent'}
                        required={checkbox.required}
                        onChange={this.checkConsented}
                      />
                    </label>
                  );
                })}
                <div dangerouslySetInnerHTML={{ __html: formConsent.consentMessage }} />
              </div>
            )}
            <input
              type="submit"
              disabled={isDisabled}
              onClick={this._onSubmit}
              name={submitText}
              value={submitText}
            />
          </form>
        )}
      </div>
    );
    /* eslint-enable react/no-danger */
  }
}