houdini/client/js/recurring_donations/edit/index.es6
2018-08-03 14:04:21 -05:00

384 lines
11 KiB
JavaScript

// License: LGPL-3.0-or-later
// npm
const flyd = require('flyd')
const mergeAll = require('flyd/module/mergeall')
const flatMap = require('flyd/module/flatmap')
const lift = require('flyd/module/lift')
const snabbdom = require('snabbdom')
const h = require('snabbdom/h')
const R = require('ramda')
const render = require('ff-core/render')
const modal = require('ff-core/modal')
const notification = require('ff-core/notification')
const button = require('ff-core/button')
const request = require('../../common/request')
// local
const cardForm = require('../../components/card-form.es6')
const readableInterval = require('../../nonprofits/recurring_donations/readable_interval')
const format = require('../../common/format')
const supporterAddressForm = require('../../components/supporter-address-form.es6')
const changeAmountWizard = require('./change-amount-wizard.es6')
function init() {
var state = {
submitPaydate$: flyd.stream()
, confirmCancel$: flyd.stream()
, changeAmount$: flyd.stream()
, error$: flyd.stream()
}
const rdPath = `/recurring_donations/${app.pageLoadData.recurring_donation.id}`
const rdUpdateAmountPath = `/recurring_donations/${app.pageLoadData.recurring_donation.id}/update_amount`
const token = utils.get_param('t')
state.donate_again_url = app.pageLoadData.miscellaneous_np_info.donate_again_url;
// Paydate update and cancellation streams
const updatePaydate$ = flatMap(updatePaydate(rdPath), state.submitPaydate$)
const cancellation$ = flatMap(reqCancel(rdPath), state.confirmCancel$)
state.changeAmountWizard = changeAmountWizard.init( {nonprofit:app.pageLoadData.nonprofit,
recurring_donation: app.pageLoadData.recurring_donation,
supporter: app.pageLoadData.supporter,
custom_amounts: app.pageLoadData.change_amount_suggestions});
state.cardForm = cardForm.init({
card: {
name: app.pageLoadData.supporter.name
, address_zip: app.pageLoadData.supporter.zip_code
}
, path: '/cards'
, payload: {
edit_token: token
, path: rdPath
, card: { holder_id: app.pageLoadData.supporter.id, holder_type: 'Supporter'}
}
})
state.addressForm = supporterAddressForm.init({
supporter: app.pageLoadData.supporter
, path: rdPath
, payload: { edit_token: token }
})
// Card update streams
// update the card id on the recurring donation after the card has been saved on CC
// (card-form.es6 component will post the card but will not update the card id on the recurring donation)
state.updateCardID$ = flatMap(
resp => request({
method: 'put'
, path: rdPath
, send: {edit_token: token, token: resp.token, card_name: resp.name}
}).load
, state.cardForm.saved$
)
// Stream of notification messages
const message$ = flyd.mergeAll([
flyd.map(R.always('Paydate successfully updated'), updatePaydate$)
, flyd.map(R.always('Address successfully updated'), state.addressForm.response$)
, flyd.map(R.always('Card successfully updated'), state.updateCardID$)
])
state.notification = notification.init({message$})
// A bunch of streams that cause the modal to close:
state.modalID$ = flyd.map(
R.always(null)
, mergeAll([
updatePaydate$
, state.updateCardID$
, cancellation$
, state.addressForm.response$
])
)
// Stream of vals that cause loading animation to show/hide
state.loading$ = mergeAll([
flyd.map(R.always(true), state.submitPaydate$)
, flyd.map(R.always(true), state.confirmCancel$)
, flyd.map(R.always(false), updatePaydate$)
, flyd.map(R.always(false), cancellation$)
])
// Simply replace old recurring donations with new ones based on ajax responses
const setNew = (old, resp) => resp.body.recurring_donation
state.recDon$ = flyd.scanMerge([
[updatePaydate$, setNew]
, [cancellation$, setNew]
, [state.updateCardID$, setNew]
, [state.updateCardAndAmount$, setNew]
], app.pageLoadData.recurring_donation)
return state
}
// -- Stream creator functions
const updatePaydate = path => ev => {
ev.preventDefault()
const paydate = Number(ev.currentTarget.querySelector('input').value)
return request({
method: 'PUT'
, path: path
, send: {edit_token: utils.get_param('t'), paydate: paydate}
}).load
}
const reqCancel = path => ev => {
ev.preventDefault()
return request({
method: 'delete'
, path: path
, send: {edit_token: utils.get_param('t')}
}).load
}
// -- Virtual DOM functions
function view(state) {
var rd = state.recDon$()
var supporter = state.addressForm.supporter$()
var status = rd.active ? 'Active' : 'Deactivated'
var interval = rd.active ? readableInterval(rd.interval, rd.time_unit) : 'Deactivated'
return h('div.u-maxWidth--600.u-margin--auto.u-marginTop--50.u-padding--15.js-view-confirm', [
h('h3.u-centered.u-marginBottom--20', ['Recurring Donation for ', String(supporter.name|| supporter.email)])
// Show deactivated notification box if deactivated
, rd.active ? '' : h('p.u-centered.pastelBox--orange.u-padding--10.u-marginBottom--20', 'This recurring donation has been deactivated')
// Recurring Donation info table
, h('table.table--striped.u-marginBottom--50', [
h('tr', [
h('td.u-strong', 'Created on')
, h('td', format.date.toSimple(rd.created_at))
])
, h('tr', [
h('td.u-strong', 'Recurring amount')
, h('td', '$' + format.centsToDollars(rd.amount))
])
, h('tr', [
h('td.u-strong', 'Organization')
, h('td', [h('a', {props: {href: `/nonprofits/${rd.nonprofit_id}`, target: '_blank'}}, String(rd.nonprofit_name))])
])
, h('tr', [
h('td.u-strong', 'Card')
, h('td', String(rd.card_name))
])
, h('tr', [
h('td.u-strong', 'Donor email')
, h('td', String(app.pageLoadData.supporter.email))
])
, h('tr', [
h('td.u-strong', 'Recurring donation status')
, h('td', String(status))
])
, h('tr', [
h('td.u-strong', 'Recurring interval')
, h('td', String(interval))
])
, rd.active
? ''
: h('tr', [
h('td.u-strong', 'Cancelled By')
, h('td', String(rd.cancelled_by))
])
, rd.active
? ''
: h('tr', [
h('td.u-strong', 'Cancelled At')
, h('td', format.date.readableWithTime(rd.cancelled_at))
])
, h('tr', [
h('td.strong', 'Address')
, h('td', [
h('small', [
[supporter.address, supporter.city].filter(R.identity).join(', ')
, h('br')
, [supporter.state_code, supporter.zip_code, supporter.country].filter(R.identity).join(', ')
])
])
])
, rd.interval === 1 && rd.time_unit === 'month'
? h('tr', [
h('td.u-strong', 'Fixed paydate')
, h('td', String(rd.paydate ? rd.paydate : 'None'))
])
: ''
])
, actions(state)
, rd.active ? '' : reactivate(rd.nonprofit_id)
, cancelModal(state)
, updateCardModal(state)
, editPaydateModal(state)
, updateAddressModal(state)
, changeAmountModal(state)
, notification.view(state.notification)
])
}
const reactivate = np_id =>
h('p.u-centered', [ h('a.button', {props: {href: `/nonprofits/${np_id}/donate`}}, 'Reactivate') ])
function actions(state) {
var rd = state.recDon$()
if(!rd.active) return ''
var modalID$ = state.modalID$
return h('div.pastelBox--looseleaf.u-padding--15.u-marginBottom--50', [
h('p.u-strong.u-centered', 'What would you like to do?')
, h('ul.hasBullets.u-maxWidth--400.u-margin--auto', [
h('li', [changeAmountBtn(modalID$)])
, h('li', [updateCardBtn(modalID$)])
, h('li', [updateAddressBtn(modalID$)])
, rd.interval === 1 && rd.time_unit === 'month'
? h('li', [updatePaydateBtn(modalID$)])
: ''
, h('li', [giveOneTimeDonationBtn(state)])
, h('li', [cancelBtn(modalID$)])
])
])
}
const changeAmountBtn = modalID$ =>
h('strong', [
h('a.test-changeAmount', {
on: {click: [modalID$, 'changeAmountModal']}
}, 'Change my donation amount')
])
const updateCardBtn = modalID$ =>
h('strong', [
h('a.test-updateCard', {
on: {click: [modalID$, 'updateCardModal']}
}, 'Update my card')
])
const cancelBtn = modalID$ =>
h('strong', [
h('a.test-cancelDonation', {
on: {click: [modalID$, 'cancelRecDonModal']}
}, 'Cancel my recurring donation')
])
const updatePaydateBtn = modalID$ =>
h('strong', [
h('a', {
on: {click: [modalID$, 'editPaydateModal']}
}, 'Change the day I\'m billed')
])
const updateAddressBtn = modalID$ =>
h('strong', [
h('a', {
on: {click: [modalID$, 'updateAddressModal']}
}, 'Update my address')
])
const giveOneTimeDonationBtn = (state) =>
h('strong', [
h('a', {
props:{href: state.donate_again_url}
}, 'Give a one-time donation')
])
const cancelModal = state =>
modal({
thisID: 'cancelRecDonModal'
, id$: state.modalID$
, body: h('div.u-marginTop--30.u-centered', [
h('p.u-marginBottom--20', 'Cancelling your recurring donation will prevent any future charges for this donation.')
, h('hr.diamonds.u-marginBottom--40')
, h('p.u-strong', state.recDon$().nonprofit_name + ' will miss your support!')
, h('hr.diamonds')
, h('div.u-marginTop--30', [confirmCancelBtn(state)])
])
})
const updateCardModal = state =>
modal({
thisID: 'updateCardModal'
, id$: state.modalID$
, title: 'Update Card'
, body: cardForm.view(state.cardForm)
})
const changeAmountModal = state =>
modal({
thisID: 'changeAmountModal'
, id$: state.modalID$
, title: 'Change Amount'
, body: changeAmountWizard.view(state.changeAmountWizard)
})
const editPaydateModal = state =>
modal({
thisID: 'editPaydateModal'
, id$: state.modalID$
, title: 'Edit Paydate'
, body: paydateForm(state)
})
const updateAddressModal = state =>
modal({
thisID: 'updateAddressModal'
, id$: state.modalID$
, title: 'Edit your address'
, body: supporterAddressForm.view(state.addressForm)
})
const paydateForm = state =>
h('form', { on: {submit: state.submitPaydate$} }, [
h('p', 'Enter a day of the month (between 1 and 28) when you want to be charged for this donation.')
, h('p', 'This will fix your donations to that date each month for all future payments.')
, h('input.input--small', {
props: {
type: 'number'
, max: 28
, min: 1
, name: 'paydate'
, value: state.recDon$().paydate || 1
}
})
, h('br')
, button(R.pick(['loading$', 'error$'], state))
])
const confirmCancelBtn = state =>
h('form', { on: { submit: state.confirmCancel$ } }, [
button({
buttonText: 'Cancel My Donation'
, loading$: state.loading$
, error$: state.error$
, buttonClass: 'red'
})
])
// -- Render to the page
var container = document.querySelector('#js-main')
const patch = snabbdom.init([
require('snabbdom/modules/eventlisteners')
, require('snabbdom/modules/class')
, require('snabbdom/modules/props')
, require('snabbdom/modules/style')
])
var state = init()
render({patch, view, state, container})