houdini/client/js/common/onboard.js
Bradley M. Kuhn fc77ee76d6 Relicense Javascript code in accordance with project's new license
The primary license of the project is changing to:
  AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later

The Additional Permission is designed to permit publicly distributed
Javascript code to be relicensed under LGPL-3.0-or-later, but not server-side
Javascript code.  As such, we've relicensed here static Javscript files under
LGPL-3.0-or-later, and those that run as part of build and/or server side
under AGPL-3.0-or-later.

Note that in future, Javascript files may be updated to be stronger copyleft
license with the Additional Permission, particularly if they adapted to run
on server side and/or turned into templates.  Of course, we'd seek public
discussion with the contributor community about such changes.

This commit is one of the many steps to relicense the entire codebase.

Documentation granting permission for this relicensing (from all past
contributors who hold copyrights) is on file with Software Freedom
Conservancy, Inc.
2018-03-25 15:10:40 -04:00

280 lines
9.7 KiB
JavaScript

// License: LGPL-3.0-or-later
const flyd = require('flimflam/flyd')
const h = require('flimflam/h')
const R = require('ramda')
const modal = require('flimflam/ui/modal')
const render = require('flimflam/render')
const wizard = require('flimflam/ui/wizard')
const validatedForm = require('flimflam/ui/validated-form')
const request = require('./request')
const notification = require('flimflam/ui/notification')
const fieldWithError = require('../components/field-with-error')
const init = () => {
const orgForm = validatedForm.init({constraints: constraints.org})
const contactForm = validatedForm.init({constraints: constraints.contact})
const infoForm = validatedForm.init({constraints: constraints.info})
const currentStep$ = flyd.mergeAll([
flyd.stream(0)
, flyd.map(R.always(1), orgForm.validSubmit$)
, flyd.map(R.always(2), infoForm.validSubmit$)
])
const wiz = wizard.init({currentStep$})
const openModal$ = flyd.stream()
document.querySelectorAll('[data-ff-open-onboard]')
.forEach(x => {x.addEventListener('click', openModal$)})
//flyd.map(trackGA, openModal$)
const response$ = flyd.flatMap(postData(orgForm, infoForm, contactForm), contactForm.validData$)
const respOk$ = flyd.filter(resp => resp.status === 200, response$)
const respErr$ = flyd.filter(resp => resp.status !== 200, response$)
const loading$ = flyd.mergeAll([
flyd.map(R.always(true), contactForm.validSubmit$)
, flyd.map(R.always(false), response$)
])
const message$ = flyd.mergeAll([
flyd.map(R.always("Saving your data..."), contactForm.validSubmit$)
, flyd.map(R.always("Thank you! Now redirecting..."), respOk$)
, flyd.map(resp => `There was an error: ${resp.body.error}`, respErr$)
])
const notif = notification.init({message$, hideDelay: 20000})
flyd.map(resp => {setTourCookies(resp.body.nonprofit); window.location = '/'}, respOk$)
return {
openModal$
, currentStep$
, wiz
, orgForm
, contactForm
, infoForm
, loading$
, notif
}
}
// const trackGA = () => {
// if(!ga) return
// ga('send', {
// hitType: 'event',
// eventCategory: 'ClickSignUp',
// eventAction: 'click',
// eventLabel: location.pathname
// })
// }
const setTourCookies = nonprofit => {
document.cookie = `tour_dashboard=${nonprofit.id};path=/`
document.cookie = `tour_campaign=${nonprofit.id};path=/`
document.cookie = `tour_event=${nonprofit.id};path=/`
document.cookie = `tour_profile=${nonprofit.id};path=/`
document.cookie = `tour_transactions=${nonprofit.id};path=/`
document.cookie = `tour_supporters=${nonprofit.id};path=/`
document.cookie = `tour_subscribers=${nonprofit.id};path=/`
}
const postData = (orgForm, infoForm) => contactFormData => {
const send = {
nonprofit: orgForm.validData$()
, extraInfo: infoForm.validData$()
, user: contactFormData
}
return request({
method: 'post'
, path: '/nonprofits/onboard'
, send
}).load
}
const constraints = {
org: {
name: {required: true}
, city: {required: true}
, state_code: {required: true}
, zip_code: {required: true}
}
, contact: {
email: {required: true, email: true}
, name: {required: true}
, phone: {required: true}
, password: {required: true, minLength: 7}
, password_confirmation: {required: true, matchesField: 'password'}
}
, info: {}
}
const view = state => {
return h('div', [
modal({
show$: state.openModal$
, body: onboardWizard(state)
, title: 'Get started'
})
, notification.view(state.notif)
])
}
const onboardWizard = state => {
const labels = [ 'Org', 'Info', 'Contact' ]
const steps = [ orgForm(state) , infoForm(state), contactForm(state) ]
return h('div', [
wizard.labels(state.wiz, labels)
, wizard.content(state.wiz, steps)
])
}
const pricingDetails = h('div.u-marginTop--15.u-padding--10.u-background--fog', [
h('p', [
"CommitChange uses "
, h('a.strong', {props: {href: 'https://www.stripe.com/', target :'_blank'}}, 'Stripe')
, ' to process transactions. Stripe takes a '
, h('strong', `${ENV.feeRate}% + ${ENV.perTransaction}¢`)
, ' processing fee on every transaction.'])
, h('p', [
'In order to support operations, feature development, and community building, '
, 'CommitChange takes an additional fee of '
, h('strong', `${ENV.platformFeeRate}%.`)
])
, h('p.u-marginBottom--0', [
"Our fee scales down as your transaction volume scales up. "
, h('a.strong', {props: {href: 'mailto:support@commitchange.com'}}, 'Contact us')
, " to chat about volume discounts."
])
])
const orgForm = state => {
const form = validatedForm.form(state.orgForm)
const field = fieldWithError(state.orgForm)
return h('div', [
form(h('form', [
h('fieldset', [
h('label', 'Organization Name')
, field(h('input', {props: {type: 'text', name: 'name', placeholder: ''}}))
])
, h('fieldset', [
h('label', 'Website URL')
, field(h('input', {props: {type: 'text', name: 'website', placeholder: 'https://your-website.org'}}))
])
, h('div.clearfix', [
h('fieldset.col-left-6.u-paddingRight--10', [
h('label', 'Org Email (public)')
, field(h('input', {props: {type: 'email', name: 'email', placeholder: 'example@name.org'}}))
])
, h('fieldset.col-left-6', [
h('label', 'Org Phone (public)')
, field(h('input', {props: {type: 'text', name: 'phone', placeholder: '(XXX) XXX-XXXX'}}))
])
])
, h('div.clearfix', [
h('fieldset.col-left-6.u-paddingRight--10', [
h('label', 'City')
, field(h('input', {props: {type: 'text', name: 'city', placeholder: ''}}))
])
, h('fieldset.col-left-3.u-paddingRight--10', [
h('label', 'State')
, field(h('input', {props: {type: 'text', name: 'state_code', placeholder: 'NY'}}))
])
, h('fieldset.col-left-3', [
h('label', 'Zip Code')
, field(h('input', {props: {type: 'text', name: 'zip_code', placeholder: ''}}))
])
])
, h('div', [
h('button.button', 'Next')
])
]))
])
}
const infoForm = state => {
const form = validatedForm.form(state.infoForm)
const field = fieldWithError(state.infoForm)
return h('div', [
form(h('form', [
h('div.u-marginBottom--20', [
h('fieldset', [
h('label', {props: {htmlFor: 'registered-npo-checkbox'}}, 'What kind of entity are you fundraising for?')
])
, h('fieldset', [
h('input', {props: {type: 'radio', name: 'entity_type', value: 'nonprofit', id: 'onboard-entity-nonprofit'}})
, h('label', {props: {htmlFor: 'onboard-entity-nonprofit'}}, 'A registered nonprofit')
])
, h('fieldset', [
h('input', {props: {type: 'radio', name: 'entity_type', value: 'forprofit', id: 'onboard-entity-forprofit'}})
, h('label', {props: {htmlFor: 'onboard-entity-forprofit'}}, 'A for-profit company')
])
, h('fieldset', [
h('input', {props: {type: 'radio', name: 'entity_type', value: 'unregistered', id: 'onboard-entity-unregistered'}})
, h('label', {props: {htmlFor: 'onboard-entity-unregistered'}}, 'An unregistered project, group, club, or other cause')
])
])
, h('div.u-marginBottom--20', [
h('fieldset', [
h('label', 'How do you want to use CommitChange?')
])
, h('fieldset', [
h('input', {props: {type: 'checkbox', name: 'use_donations', id: 'onboard-use-donations'}})
, h('label', {props: {htmlFor: 'onboard-use-donations'}}, 'Donation processing')
])
, h('fieldset', [
h('input', {props: {type: 'checkbox', name: 'use_crm', id: 'onboard-use-crm'}})
, h('label', {props: {htmlFor: 'onboard-use-crm'}}, 'Supporter relationship management')
])
, h('fieldset', [
h('input', {props: {type: 'checkbox', name: 'use_campaigns', id: 'onboard-use-campaigns'}})
, h('label', {props: {htmlFor: 'onboard-use-campaigns'}}, 'Campaign fundriasing')
])
, h('fieldset', [
h('input', {props: {type: 'checkbox', name: 'use_events', id: 'onboard-use-events'}})
, h('label', {props: {htmlFor: 'onboard-use-events'}}, 'Event pages and ticketing')
])
])
, h('fieldset', [
h('label', 'How did you hear about CommitChange?')
, field(h('input', {props: {type: 'text', name: 'how_they_heard', placeholder: 'Google, radio, referral, etc'}}))
])
, h('button.button', 'Next')
]))
])
}
const contactForm = state => {
const form = validatedForm.form(state.contactForm)
const field = fieldWithError(state.contactForm)
return h('div', [
form(h('form', [
h('div.clearfix', [
h('fieldset.col-left-6.u-paddingRight--10', [
h('label', 'Your Name')
, field(h('input', {props: {type: 'text', name: 'name', placeholder: 'Full Name'}}))
])
, h('fieldset.col-left-6', [
h('label', 'Your Email (used for login)')
, field(h('input', {props: {type: 'email', name: 'email', placeholder: 'youremail@example.com'}}))
])
])
, h('fieldset', [
h('label', 'New Password')
, field(h('input', {props: {type: 'password', name: 'password', placeholder: ''}}))
])
, h('fieldset', [
h('label', 'Retype Password')
, field(h('input', {props: {type: 'password', name: 'password_confirmation', placeholder: ''}}))
])
, h('fieldset', [
h('label', ['Your Phone', h('small', ' (for account recovery)')])
, field(h('input', {props: {type: 'text', name: 'phone', placeholder: '(XXX) XXX-XXXX'}}))
])
, h('button.button', {props: {disabled: state.loading$()}}, 'Save & Finish')
]))
])
}
const container = document.querySelector("#ff-render-onboard")
render(view, init(), container)