app/javascript/plugins/petition/index.tsx
import * as EventEmitter from 'eventemitter3';
import Cookie from 'js-cookie';
import { omit } from 'lodash';
import * as React from 'react';
import { render } from 'react-dom';
import { Store } from 'redux';
import ComponentWrapper from '../../components/ComponentWrapper';
import api from '../../modules/api';
import { DoubleOptIn } from '../../modules/double-opt-in';
import { formValues } from '../../modules/form_values';
import { transitionFromTo } from '../../modules/transition';
import { setSubmitting, updateForm } from '../../state/forms';
import { actionFormUpdated } from '../../state/fundraiser/actions';
import { resetMember } from '../../state/member/reducer';
import { IAppState } from '../../types';
import { IPetitionPluginConfig } from '../../window';
import Plugin from '../plugin';
import { PetitionComponent } from './PetitionComponent';
import './petition.css';
export const init = (options: any) => {
if (!options.el) {
return;
}
return new Petition({
el: options.el,
namespace: 'petition',
config: options.config,
store: options.store,
eventEmitter: options.eventEmitter,
customRenderer: options.customRenderer,
});
};
interface IPetitionOptions {
el: HTMLElement;
namespace: string;
config: IPetitionPluginConfig;
store: Store<IAppState>;
eventEmitter?: EventEmitter;
customRenderer?: (instance: Petition) => any;
}
export class Petition extends Plugin<IPetitionPluginConfig> {
public store: Store<IAppState>;
public customRenderer: (instance: Petition) => any | undefined;
private errors: { [key: string]: string[] };
constructor(options: IPetitionOptions) {
super(options);
this.initState();
this.render();
}
public get form() {
return this.el.getElementsByClassName('Form')[0];
}
public get formValues() {
const fieldValues = omit(
this.store.getState().forms[this.config.form_id] || {},
'submitting'
);
return {
form_id: this.config.form_id,
...fieldValues,
};
}
public updateForm(data: { [key: string]: any }) {
this.store.dispatch(
updateForm(this.config.form_id, { ...this.formValues, ...data })
);
}
public resetMember = () => {
this.store.dispatch(resetMember());
this.initState();
this.render();
this.emit('resetMember');
};
public validate = () => {
this.setSubmitting(true);
return api.pages.validateForm(this.config.page_id, this.formValues).then(
success => {
this.emit('validated', this);
return this;
},
failure => {
this.handleErrors(failure);
throw failure;
}
);
};
public submit = () => {
this.setSubmitting(true);
return api.pages.createAction(this.config.page_id, this.formValues).then(
success => {
this.triggerCompleteRegistrationEvent(success);
DoubleOptIn.handleActionSuccess(success);
this.onComplete();
return this;
},
failure => {
this.handleErrors(failure);
throw failure;
}
);
};
public submitOrValidate = () => {
// Check if this form was a validate-only form.
// The template can set a data-form-action="validate"
if (this.el.dataset.action === 'validate') {
return this.validate();
}
return this.submit()
.then(() => this.onCompleteTransition())
.then(() => this);
};
public onComplete = () => {
const listeners = [
...this.listeners('complete:before'),
...this.events.listeners(this.namespace + ':complete:before'),
];
return Promise.all(listeners.map(l => l(this)))
.then(() => this.events.emit('complete', { petition: this }))
.then(() => {
this.store.dispatch(actionFormUpdated(this.formValues));
})
.then(() => {
this.events.emit('action:submitted_success', { petition: this });
})
.then(() => this.onCompleteTransition())
.then(() => this.emit('complete', this))
.then(() => this);
};
public render() {
if (this.customRenderer) {
return this.customRenderer(this);
}
const el = this.el;
if (el) {
render(
<ComponentWrapper
locale={window.I18n.locale}
store={window.champaign.store}
>
<PetitionComponent
config={this.config}
values={this.formValues}
errors={this.errors}
resetMember={this.resetMember}
onValidate={this.validate}
onSubmit={this.submitOrValidate}
eventEmitter={this.events}
/>
</ComponentWrapper>,
el
);
}
}
private setSubmitting(submitting: boolean) {
this.store.dispatch(setSubmitting(this.config.form_id, submitting));
}
private handleErrors(response) {
if (response.errors) {
this.errors = response.errors;
}
this.setSubmitting(false);
this.render();
return response;
}
private initState() {
this.store.dispatch(
updateForm(this.config.form_id, formValues(this.config.fields))
);
this.errors = {};
}
private onCompleteTransition() {
if (!this.el.dataset.transition) {
return;
}
transitionFromTo(this.el.dataset.transition);
}
private triggerCompleteRegistrationEvent = response => {
const tracking = response.tracking;
if (tracking) {
const fbq = (window as any).fbq;
if (fbq && tracking.user_id) {
fbq('track', 'CompleteRegistration', tracking);
}
}
};
}