SumOfUs/Champaign

View on GitHub
app/javascript/plugins/fundraiser/index.tsx

Summary

Maintainability
B
6 hrs
Test Coverage
import { isBoolean } from 'lodash';
import * as React from 'react';
import { render } from 'react-dom';
import { Store } from 'redux';
import ComponentWrapper from '../../components/ComponentWrapper';
import {
  changeAmount,
  changeCurrency,
  setPaymentType,
  setRecurring,
  setStoreInVault,
  updateForm,
} from '../../state/fundraiser/actions';
import { resetMember } from '../../state/member/reducer';
import { IAppState } from '../../types';
import { IFundraiserPluginConfig } from '../../window';
import Plugin, { IPluginOptions } from '../plugin';
import FundraiserView from './FundraiserView';
import { configureStore, fundraiserData } from './utils';

export function init(options: any) {
  if (!options.el) {
    options.el = document.getElementById('fundraiser-component');
  }

  configureStore(fundraiserData(options.config), options.store.dispatch);

  return new Fundraiser({
    ...options,
    namespace: 'fundraiser',
  });
}

export class Fundraiser extends Plugin<IFundraiserPluginConfig> {
  public store: Store<IAppState>;
  public customRenderer: (instance: Fundraiser) => any | undefined;

  constructor(options: IPluginOptions<IFundraiserPluginConfig>) {
    super(options);
    this.render();
  }

  get state() {
    return this.store.getState().fundraiser;
  }

  get formValues() {
    return this.state.form;
  }

  get amount() {
    return this.state.donationAmount;
  }

  set amount(amount: any) {
    this.setAmount(amount);
  }

  public setAmount(amount: any) {
    if (Number.isInteger(amount) && amount > 0) {
      this.store.dispatch(changeAmount(amount));
    }
    return this;
  }

  get currency() {
    return this.state.currency;
  }

  set currency(currency: any) {
    this.setCurrency(currency);
  }

  public setCurrency(currency: any) {
    this.store.dispatch(changeCurrency(currency));
    return this;
  }

  public updateForm(fieldName: string, value: any) {
    this.store.dispatch(
      updateForm({ ...this.formValues, ...{ [fieldName]: value } })
    );
    return this;
  }

  public resetMember() {
    this.store.dispatch(resetMember());
    return this;
  }

  // addPaymentMethod will allow us to add an object that contains:
  //   - label / name combo to show up in the list. Label can be HTML
  //   - a `setup` function, optional
  //   - a number of callbacks: onSubmit, onFailure, onSuccess
  public addPaymentMethod(paymentMethodData: any) {
    throw new Error('Not implemented');
  }

  public validateForm() {
    return new Promise((resolve, reject) => {
      this.events.once('fundraiser:form:success', resolve);
      this.events.once('fundraiser:form:error', reject);
      this.events.emit('fundraiser:actions:validate_form');
    });
  }

  // TODO: Move the logic behind this event (check Payment.js)
  // to this class. The braintree client should also live here,
  // or perhaps in a braintree service, but not in the react component.
  // The <Payment/> react component has become quite complex and practically
  // unmaintainable
  public makePayment(callback: (data: any, formData: any) => void) {
    return new Promise((resolve, reject) => {
      this.events.once('fundraiser:transaction_success', resolve);
      this.events.once('fundraiser:transaction_error', reject);
      this.events.emit('fundraiser:actions:make_payment');
    });
  }

  public configureHostedFields(config: any) {
    return new Promise((resolve, reject) => {
      this.events.once('fundraiser:configure:hosted_fields:success', resolve);
      this.events.once('fundraiser:configure:hosted_fields:error', reject);
      this.events.emit('fundraiser:configure:hosted_fields', config);
    });
  }

  get recurring() {
    return this.state.recurring;
  }

  set recurring(recurring: any) {
    this.setRecurring(recurring);
  }

  public setRecurring(value: any) {
    if (isBoolean(value)) {
      this.store.dispatch(setRecurring(value));
    }
    return this;
  }

  get storeInVault() {
    return this.state.storeInVault;
  }

  set storeInVault(value: any) {
    this.setStoreInVault(value);
  }

  public setStoreInVault(value: any) {
    if (isBoolean(value)) {
      this.store.dispatch(setStoreInVault(value));
    }
    return this;
  }

  // Sets the payment type (gocardless, paypal, card, etc). If the given payment
  // type is not supported, it will be set to the default payment type.
  public setPaymentType(paymentType: string) {
    this.store.dispatch(setPaymentType(paymentType));
    return this;
  }

  public render() {
    if (this.customRenderer) {
      return this.customRenderer(this);
    }

    return render(
      <ComponentWrapper store={this.store} locale={window.I18n.locale}>
        <FundraiserView />
      </ComponentWrapper>,
      this.el
    );
  }
}