2018-03-29 20:42:43 +00:00
|
|
|
// License: LGPL-3.0-or-later
|
2018-03-25 17:30:42 +00:00
|
|
|
// 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, card_id: resp.id, 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})
|
|
|
|
|