Finish the offsite_payment_pane
This commit is contained in:
parent
6b23f21b56
commit
4aaf005252
19 changed files with 692 additions and 290 deletions
|
@ -2,10 +2,42 @@
|
||||||
<% content_for(:dont_load_optimizely) {'true'} %>
|
<% content_for(:dont_load_optimizely) {'true'} %>
|
||||||
<% content_for(:footer_hidden) {'hidden'} %>
|
<% content_for(:footer_hidden) {'hidden'} %>
|
||||||
|
|
||||||
<%= content_for(:stylesheets) {stylesheet_link_tag 'nonprofits/supporters/index/page'} %>
|
<% content_for(:stylesheets) do %>
|
||||||
|
<%= stylesheet_link_tag 'nonprofits/supporters/index/page' %>
|
||||||
|
<%= IncludeAsset.css '/client/css/bootstrap.css' %>
|
||||||
|
<% end %>
|
||||||
<%= content_for :javascripts do %>
|
<%= content_for :javascripts do %>
|
||||||
<%= IncludeAsset.js '/client/js/nonprofits/supporters/index/page.js' %>
|
<%= IncludeAsset.js '/client/js/nonprofits/supporters/index/page.js' %>
|
||||||
<%= render 'common/froala' %>
|
<%= render 'common/froala' %>
|
||||||
|
<%= IncludeAsset.js '/app/react.js' %>
|
||||||
|
<%= IncludeAsset.js '/app/react-dom.js' %>
|
||||||
|
<%= IncludeAsset.js '/app/vendor.js' %>
|
||||||
|
<%= IncludeAsset.js '/app/create_new_offsite_payment_panex.js' %>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
appl.def('open_donation_modal', function(supporter_id, donation_finish_successful_state_fn) {
|
||||||
|
$('.modal').removeClass('inView')
|
||||||
|
|
||||||
|
function SetupLoadCreateOffsiteDonationPane(modalActive){
|
||||||
|
LoadReactCreateOffsiteDonationPane(document.getElementById('react-vdom-hack'),
|
||||||
|
appl.campaigns.data,
|
||||||
|
appl.events.data,
|
||||||
|
app.nonprofit_id,
|
||||||
|
supporter_id,
|
||||||
|
() => {}, //appl.start_loading,
|
||||||
|
donation_finish_successful_state_fn, //appl.update_donation__success,
|
||||||
|
() => {SetupLoadCreateOffsiteDonationPane(false)},
|
||||||
|
modalActive,
|
||||||
|
ENV.nonprofitTimezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupLoadCreateOffsiteDonationPane(true)
|
||||||
|
|
||||||
|
|
||||||
|
return appl
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render '/components/trial_bar' if nonprofit_in_trial?(@nonprofit.id) %>
|
<%= render '/components/trial_bar' if nonprofit_in_trial?(@nonprofit.id) %>
|
||||||
|
@ -58,3 +90,5 @@
|
||||||
<%= render 'donations/new_offline_modal' %>
|
<%= render 'donations/new_offline_modal' %>
|
||||||
|
|
||||||
<div id='js-vdomParty'></div>
|
<div id='js-vdomParty'></div>
|
||||||
|
|
||||||
|
<div id="react-vdom-hack"></div>
|
||||||
|
|
|
@ -31,6 +31,7 @@ const init = _ => {
|
||||||
, newNote$: flyd.stream()
|
, newNote$: flyd.stream()
|
||||||
, editNote$: flyd.stream()
|
, editNote$: flyd.stream()
|
||||||
, deleteNote$: flyd.stream()
|
, deleteNote$: flyd.stream()
|
||||||
|
, newDonation$: flyd.stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
const supporterID$ = R.compose(
|
const supporterID$ = R.compose(
|
||||||
|
@ -95,7 +96,6 @@ const init = _ => {
|
||||||
, flyd.map(()=> 'replyGmailModal', state.threadId$)
|
, flyd.map(()=> 'replyGmailModal', state.threadId$)
|
||||||
, flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$)
|
, flyd.map(()=> 'newSupporterNoteModal', state.editNoteData$)
|
||||||
, flyd.map(()=> null, state.gmail.sendResponse$)
|
, flyd.map(()=> null, state.gmail.sendResponse$)
|
||||||
, flyd.map(()=> null, state.offsiteDonationForm.saved$)
|
|
||||||
, flyd.map(()=> null, state.supporterNoteForm.saved$)
|
, flyd.map(()=> null, state.supporterNoteForm.saved$)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ const view = state => {
|
||||||
, activities.view(state)
|
, activities.view(state)
|
||||||
, composeModal(state)
|
, composeModal(state)
|
||||||
, notification.view(state.notification)
|
, notification.view(state.notification)
|
||||||
, offsiteDonationForm.view(R.merge(state.offsiteDonationForm, {modalID$: state.modalID$}))
|
, offsiteDonationForm.view(R.merge(state.offsiteDonationForm))
|
||||||
, supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$}))
|
, supporterNoteForm.view(R.merge(state.supporterNoteForm, {modalID$: state.modalID$}))
|
||||||
, replyModal(state)
|
, replyModal(state)
|
||||||
, confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?')
|
, confirm.view(state.confirmDelete, 'Are you sure you want to delete this note?')
|
||||||
|
|
|
@ -8,168 +8,26 @@ const format = require('../../../../common/format')
|
||||||
const moment = require('moment')
|
const moment = require('moment')
|
||||||
const request = require('../../../../common/request')
|
const request = require('../../../../common/request')
|
||||||
const serialize = require('form-serialize')
|
const serialize = require('form-serialize')
|
||||||
const flyd_filter = require('flyd/module/filter')
|
|
||||||
const flyd_flatMap = require('flyd/module/flatmap')
|
const flyd_flatMap = require('flyd/module/flatmap')
|
||||||
const flyd_mergeAll = require('flyd/module/mergeall')
|
const flyd_mergeAll = require('flyd/module/mergeall')
|
||||||
|
|
||||||
var rootUrl = `/nonprofits/${app.nonprofit_id}`
|
|
||||||
|
|
||||||
const getFundraisers = type => {
|
|
||||||
var response$ = request({
|
|
||||||
method: 'get'
|
|
||||||
, path: `${rootUrl}/${type}/name_and_id`
|
|
||||||
}).load
|
|
||||||
|
|
||||||
var body$ = R.compose(
|
|
||||||
flyd.map(x => x.body)
|
|
||||||
, flyd_filter(x => x.status === 200 || x.status === 304)
|
|
||||||
)(response$)
|
|
||||||
|
|
||||||
return flyd.merge(flyd.stream([]), body$)
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(parentState) {
|
function init(parentState) {
|
||||||
var state = {
|
var state = {
|
||||||
submit$: flyd.stream()
|
submit$: flyd.stream()
|
||||||
, supporter$: parentState.supporter$
|
, supporter$: parentState.supporter$
|
||||||
, campaigns$: getFundraisers('campaigns')
|
, saved$: flyd.stream(Math.random())
|
||||||
, events$: getFundraisers('events')
|
|
||||||
}
|
}
|
||||||
const resp$ = flyd_flatMap(
|
|
||||||
form => request({
|
|
||||||
method: 'post'
|
|
||||||
, path: `${rootUrl}/donations/create_offsite`
|
|
||||||
, send: {donation: setDefaults(serialize(form, {hash: true}))}
|
|
||||||
}).load
|
|
||||||
, state.submit$ )
|
|
||||||
state.saved$ = flyd_filter(resp => resp.status === 200, resp$)
|
|
||||||
state.error$ = flyd_filter(resp => resp.status !== 200, resp$)
|
|
||||||
state.loading$ = flyd_mergeAll([
|
|
||||||
flyd.map(()=> true, state.submit$)
|
|
||||||
, flyd.map(() => false, resp$)
|
|
||||||
])
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
const setDefaults = formData =>
|
|
||||||
R.evolve({
|
|
||||||
amount: format.dollarsToCents
|
|
||||||
, date: d => moment(d).format("YYYY-MM-DD")
|
|
||||||
}, formData)
|
|
||||||
|
|
||||||
function view(state) {
|
function view(state) {
|
||||||
var body = form(state)
|
|
||||||
|
return h('div', {id$: 'offsite_donation_form_modal'})
|
||||||
return h('div', [
|
|
||||||
modal({
|
|
||||||
id$: state.modalID$
|
|
||||||
, thisID: 'newOffsiteDonationModal'
|
|
||||||
, title: 'New Offsite Contribution'
|
|
||||||
, body
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = state => {
|
|
||||||
return h('form', {
|
|
||||||
on: {submit: ev => {ev.preventDefault(); state.submit$(ev.currentTarget)}}
|
|
||||||
}, [
|
|
||||||
h('input', {
|
|
||||||
props: {
|
|
||||||
type: 'hidden'
|
|
||||||
, name: 'nonprofit_id'
|
|
||||||
, value: app.nonprofit_id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
, h('input', {
|
|
||||||
props: {
|
|
||||||
type: 'hidden'
|
|
||||||
, name: 'supporter_id'
|
|
||||||
, value: state.supporter$().id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
, h('div.layout--four', [
|
|
||||||
h('fieldset', [
|
|
||||||
h('label', 'Amount')
|
|
||||||
, h('div.prepend--dollar', [
|
|
||||||
h('input', {
|
|
||||||
props: {
|
|
||||||
name: 'amount'
|
|
||||||
, step: 'any'
|
|
||||||
, type: 'number'
|
|
||||||
, min: 0
|
|
||||||
, required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', 'Date')
|
|
||||||
, h('input', {
|
|
||||||
props: {
|
|
||||||
id: 'js-offsiteDonationDate'
|
|
||||||
, name: 'date'
|
|
||||||
, type: 'text'
|
|
||||||
, placeholder: 'MM/DD/YYYY'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', 'Type')
|
|
||||||
, h('select', {props: {name: 'offsite_payment[kind]'}}, [
|
|
||||||
h('option', {props: {selected: true, value: 'check'}}, 'Check')
|
|
||||||
, h('option', {props: {value: 'cash'}}, 'Cash')
|
|
||||||
, h('option', {props: {value: ''}}, 'Other')
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', 'Check Number')
|
|
||||||
, h('input', {
|
|
||||||
props: {
|
|
||||||
name: 'offsite_payment[check_number]'
|
|
||||||
, type: 'text'
|
|
||||||
, placeholder: '1234'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('div.layout--two', [
|
|
||||||
h('fieldset', [
|
|
||||||
h('label', ['Towards an Event', h('small', ' (optional) ')])
|
|
||||||
, fundraiserSelects('event', state.events$())
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', ['Towards a Campaign ', h('small', ' (optional) ')])
|
|
||||||
, fundraiserSelects('campaign', state.campaigns$())
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('div.layout--two', [
|
|
||||||
h('fieldset', [
|
|
||||||
h('label', ['In Memory/Honor Of ', h('small', ' (optional) ')])
|
|
||||||
, h('textarea', {props: {rows: 3, name: 'dedication', placeholder: 'In Memory/Honor Of'}})
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', ['Designation ', h('small', ' (optional) ')])
|
|
||||||
, h('textarea', {props: {rows: 3, name: 'designation', placeholder: 'Designation'}})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
, h('fieldset', [
|
|
||||||
h('label', ['Notes ', h('small', ' (optional) ')])
|
|
||||||
, h('textarea', {props: {rows: 3, name: 'comment', placeholder: 'Notes'}})
|
|
||||||
])
|
|
||||||
, h('div.u-centered', [
|
|
||||||
button({loading$: state.loading$, error$: state.error$})
|
|
||||||
])
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
const fundraiserSelects = (type, arr) =>
|
|
||||||
h('select', {props: {name: `${type}_id`}}
|
|
||||||
, R.concat(
|
|
||||||
[h('option', {props: {value: ''}}, 'Select One')]
|
|
||||||
, R.map(x => h('option', {props: {value: x.id}}, x.name), arr)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {init, view}
|
module.exports = {init, view}
|
||||||
|
|
|
@ -5,6 +5,7 @@ const flyd = require('flyd')
|
||||||
const h = require('snabbdom/h')
|
const h = require('snabbdom/h')
|
||||||
flyd.mergeAll = require('flyd/module/mergeall')
|
flyd.mergeAll = require('flyd/module/mergeall')
|
||||||
|
|
||||||
|
|
||||||
const button = (text, stream) =>
|
const button = (text, stream) =>
|
||||||
h('button.button--tiny.u-marginRight--10', {on: {click: stream}}
|
h('button.button--tiny.u-marginRight--10', {on: {click: stream}}
|
||||||
, [h('i.fa.fa-plus.u-marginRight--5') , text ])
|
, [h('i.fa.fa-plus.u-marginRight--5') , text ])
|
||||||
|
@ -13,8 +14,12 @@ const view = state =>
|
||||||
h('section.timeline-actions.u-padding--10', [
|
h('section.timeline-actions.u-padding--10', [
|
||||||
button('Note', state.newNote$)
|
button('Note', state.newNote$)
|
||||||
, button('Email', state.clickComposing$)
|
, button('Email', state.clickComposing$)
|
||||||
, button('Donation', [state.modalID$, 'newOffsiteDonationModal'])
|
, button('Donation', () => appl.open_donation_modal(state.supporter$().id,
|
||||||
])
|
() => {state.offsiteDonationForm.saved$(Math.random())}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
module.exports = {view}
|
module.exports = {view}
|
||||||
|
|
||||||
|
|
33
javascripts/app/create_new_offsite_payment_pane.tsx
Normal file
33
javascripts/app/create_new_offsite_payment_pane.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
// require a root component here. This will be treated as the root of a webpack package
|
||||||
|
import Root from "../src/components/common/Root"
|
||||||
|
import CreateOffsitePaymentPane from "../src/components/create_offsite_payment_pane/CreateOffsitePaymentPane"
|
||||||
|
|
||||||
|
import * as ReactDOM from 'react-dom'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export interface FundraiserInfo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadReactPage(element:HTMLElement, events: FundraiserInfo[],
|
||||||
|
campaigns: FundraiserInfo[],
|
||||||
|
nonprofitId: number,
|
||||||
|
supporterId:number,
|
||||||
|
preupdateDonationAction:() => void,
|
||||||
|
postUpdateSuccess: () => void,
|
||||||
|
|
||||||
|
//from ModalProps
|
||||||
|
onClose: () => void,
|
||||||
|
modalActive: boolean,
|
||||||
|
nonprofitTimezone?: string) {
|
||||||
|
ReactDOM.render(<Root><CreateOffsitePaymentPane campaigns={campaigns}
|
||||||
|
events={events} onClose={onClose}
|
||||||
|
modalActive={modalActive} nonprofitTimezone={nonprofitTimezone}
|
||||||
|
postUpdateSuccess={postUpdateSuccess}
|
||||||
|
preupdateDonationAction={preupdateDonationAction} nonprofitId={nonprofitId} supporterId={supporterId}/></Root>, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(window as any).LoadReactCreateOffsiteDonationPane = LoadReactPage
|
10
javascripts/src/components/common/Modal.spec.tsx
Normal file
10
javascripts/src/components/common/Modal.spec.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import * as React from 'react';
|
||||||
|
import 'jest';
|
||||||
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
describe('Modal', () => {
|
||||||
|
test('your test here', () => {
|
||||||
|
expect(false).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
37
javascripts/src/components/common/Modal.tsx
Normal file
37
javascripts/src/components/common/Modal.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import * as React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import {InjectedIntlProps, injectIntl} from 'react-intl';
|
||||||
|
import AriaModal = require('react-aria-modal');
|
||||||
|
|
||||||
|
export interface ModalProps
|
||||||
|
{
|
||||||
|
onClose?: () => void
|
||||||
|
modalActive?: boolean
|
||||||
|
titleText?: string
|
||||||
|
focusDialog?:boolean
|
||||||
|
dialogStyle?:any
|
||||||
|
childGenerator:() => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
class Modal extends React.Component<ModalProps & InjectedIntlProps, {}> {
|
||||||
|
render() {
|
||||||
|
const modal = this.props.modalActive ?
|
||||||
|
<AriaModal mounted={this.props.modalActive} titleText={this.props.titleText} focusDialog={this.props.focusDialog}
|
||||||
|
onExit={this.props.onClose} dialogStyle={this.props.dialogStyle || {minWidth:'768px'}}>
|
||||||
|
<header className='modal-header'>
|
||||||
|
<h4 className='modal-header-title'>{this.props.titleText}</h4>
|
||||||
|
</header>
|
||||||
|
<div className="modal-body">
|
||||||
|
{this.props.childGenerator()}
|
||||||
|
</div>
|
||||||
|
</AriaModal> : false;
|
||||||
|
|
||||||
|
return modal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectIntl(observer(Modal))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as React from 'react';
|
||||||
import {observer} from "mobx-react";
|
import {observer} from "mobx-react";
|
||||||
import {Field} from "../../../../types/mobx-react-form";
|
import {Field} from "../../../../types/mobx-react-form";
|
||||||
import LabeledFieldComponent from "./LabeledFieldComponent";
|
import LabeledFieldComponent from "./LabeledFieldComponent";
|
||||||
import {injectIntl, InjectedIntl} from 'react-intl';
|
|
||||||
import {HoudiniField} from "../../lib/houdini_form";
|
import {HoudiniField} from "../../lib/houdini_form";
|
||||||
import ReactInput from "./form/ReactInput";
|
import ReactInput from "./form/ReactInput";
|
||||||
import ReactSelect from './form/ReactSelect';
|
import ReactSelect from './form/ReactSelect';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {TabList} from "./TabList";
|
||||||
import {TabPanel} from "./TabPanel";
|
import {TabPanel} from "./TabPanel";
|
||||||
import {Tab} from "./Tab";
|
import {Tab} from "./Tab";
|
||||||
import toJson from 'enzyme-to-json';
|
import toJson from 'enzyme-to-json';
|
||||||
import {UniqueIdMock} from "../../tests/unique_id_mock";
|
import {UniqueIdMock} from "../../test/unique_id_mock";
|
||||||
import {mountForMobx, runTestsOnConditions, TriggerAndAction} from '../../test/react_test_helpers';
|
import {mountForMobx, runTestsOnConditions, TriggerAndAction} from '../../test/react_test_helpers';
|
||||||
|
|
||||||
let uniqueIdMock = new UniqueIdMock();
|
let uniqueIdMock = new UniqueIdMock();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {ReactWrapper} from 'enzyme';
|
||||||
import {WizardPanel} from "./WizardPanel";
|
import {WizardPanel} from "./WizardPanel";
|
||||||
|
|
||||||
import {mountForMobxWithIntl, runTestsOnConditions, TriggerAndAction} from "../test/react_test_helpers";
|
import {mountForMobxWithIntl, runTestsOnConditions, TriggerAndAction} from "../test/react_test_helpers";
|
||||||
import {UniqueIdMock} from "../tests/unique_id_mock";
|
import {UniqueIdMock} from "../test/unique_id_mock";
|
||||||
|
|
||||||
let uniqueIdMock = new UniqueIdMock();
|
let uniqueIdMock = new UniqueIdMock();
|
||||||
class MockableTabPanelState extends WizardTabPanelState
|
class MockableTabPanelState extends WizardTabPanelState
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react';
|
||||||
import 'jest';
|
import 'jest';
|
||||||
import {AbstractWizardState, AbstractWizardTabPanelState} from './abstract_wizard_state'
|
import {AbstractWizardState, AbstractWizardTabPanelState} from './abstract_wizard_state'
|
||||||
import {observable, action, computed} from 'mobx'
|
import {observable, action, computed} from 'mobx'
|
||||||
import {UniqueIdMock} from "../tests/unique_id_mock";
|
import {UniqueIdMock} from "../test/unique_id_mock";
|
||||||
|
|
||||||
let uniqueIdMock = new UniqueIdMock();
|
let uniqueIdMock = new UniqueIdMock();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import * as React from 'react';
|
||||||
|
import 'jest';
|
||||||
|
import CreateNewOffsitePaymentPane from './CreateOffsitePaymentPane'
|
||||||
|
|
||||||
|
describe('CreateOffsitePaymentPane', () => {
|
||||||
|
test('your test here', () => {
|
||||||
|
expect(false).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,245 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import * as React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import {InjectedIntlProps, injectIntl} from 'react-intl';
|
||||||
|
import Modal from "../common/Modal";
|
||||||
|
import { FundraiserInfo} from "../edit_payment_pane/EditPaymentPane";
|
||||||
|
import {HoudiniForm} from "../../lib/houdini_form";
|
||||||
|
import {BasicField, CurrencyField, SelectField, TextareaField} from "../common/fields";
|
||||||
|
import ProgressableButton from "../common/ProgressableButton";
|
||||||
|
import {action, computed} from "mobx";
|
||||||
|
import {NonprofitTimezonedDates} from "../../lib/date";
|
||||||
|
import {Field, FieldDefinition} from "../../../../types/mobx-react-form";
|
||||||
|
import {createFieldDefinition} from "../../lib/mobx_utils";
|
||||||
|
import {centsToDollars, dollarsToCents} from "../../lib/format";
|
||||||
|
import {Validations} from "../../lib/vjf_rules";
|
||||||
|
import {serializeDedication} from "../../lib/dedication";
|
||||||
|
import {ApiManager} from "../../lib/api_manager";
|
||||||
|
import * as CustomAPIS from "../../lib/apis";
|
||||||
|
import {CSRFInterceptor} from "../../lib/csrf_interceptor";
|
||||||
|
import {CreateOffsiteDonation, CreateOffsiteDonationModel} from "../../lib/api/create_offsite_donation";
|
||||||
|
import blacklist = require("validator/lib/blacklist");
|
||||||
|
import * as _ from 'lodash'
|
||||||
|
import moment = require('moment');
|
||||||
|
import { castToUndefinedIfBlank } from '../../lib/utils';
|
||||||
|
|
||||||
|
export interface CreateOffsitePaymentPaneProps
|
||||||
|
{
|
||||||
|
events: FundraiserInfo[]
|
||||||
|
campaigns: FundraiserInfo[]
|
||||||
|
nonprofitId: number
|
||||||
|
supporterId:number
|
||||||
|
nonprofitTimezone?: string
|
||||||
|
preupdateDonationAction:() => void
|
||||||
|
postUpdateSuccess: () => void
|
||||||
|
|
||||||
|
//from ModalProps
|
||||||
|
onClose: () => void
|
||||||
|
modalActive: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FundraiserInfo {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateOffsitePaymentPaneForm extends HoudiniForm {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateNewOffsitePaymentPane extends React.Component<CreateOffsitePaymentPaneProps & InjectedIntlProps, {}> {
|
||||||
|
|
||||||
|
constructor(props: CreateOffsitePaymentPaneProps & InjectedIntlProps) {
|
||||||
|
super(props);
|
||||||
|
this.postOffsiteDonation = new ApiManager(CustomAPIS.APIS as Array<any>, CSRFInterceptor).get(CreateOffsiteDonation)
|
||||||
|
|
||||||
|
this.loadFormFromData()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@computed
|
||||||
|
get nonprofitTimezonedDates():NonprofitTimezonedDates {
|
||||||
|
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
postOffsiteDonation : CreateOffsiteDonation
|
||||||
|
|
||||||
|
@action.bound
|
||||||
|
async createOffsiteDonation() {
|
||||||
|
if (this.props.preupdateDonationAction) {
|
||||||
|
this.props.preupdateDonationAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let postData:CreateOffsiteDonationModel = {
|
||||||
|
nonprofit_id:this.props.nonprofitId,
|
||||||
|
supporter_id:this.props.supporterId,
|
||||||
|
amount: dollarsToCents(this.form.$('gross_amount').get('value')),
|
||||||
|
designation: this.form.$('designation').value,
|
||||||
|
comment: this.form.$('comment').value,
|
||||||
|
campaign_id: this.form.$('campaign').get('value'),
|
||||||
|
event_id: this.form.$('event').get('value'),
|
||||||
|
date: this.form.$('date').get('value')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.$('dedication.type').get('value') != '') {
|
||||||
|
postData.dedication = serializeDedication({
|
||||||
|
type: this.form.$('dedication.type').get('value'),
|
||||||
|
supporter_id: this.form.$('dedication.supporter_id').get('value'),
|
||||||
|
name: this.form.$('dedication.name').get('value'),
|
||||||
|
contact:this.form.$('dedication.contact').get('value'),
|
||||||
|
note: this.form.$('dedication.note').get('value')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
postData.dedication = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.has('check_number'))
|
||||||
|
{
|
||||||
|
postData.offsite_payment = postData.offsite_payment || {}
|
||||||
|
postData.offsite_payment.check_number = this.form.$('check_number').value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await this.postOffsiteDonation.postDonation(postData, this.props.nonprofitId)
|
||||||
|
|
||||||
|
|
||||||
|
if(this.props.postUpdateSuccess){
|
||||||
|
try {
|
||||||
|
this.props.postUpdateSuccess()
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onClose()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@action.bound
|
||||||
|
loadFormFromData() {
|
||||||
|
|
||||||
|
let params: {[name:string]:FieldDefinition} = {
|
||||||
|
'event': {name: 'event', label: 'Event',
|
||||||
|
output: (id:string) => castToUndefinedIfBlank(id)},
|
||||||
|
'campaign': {name: 'campaign', label: 'Campaign',
|
||||||
|
output: (id:string) => castToUndefinedIfBlank(id)},
|
||||||
|
'gross_amount': createFieldDefinition({name: 'gross_amount',
|
||||||
|
label: 'Gross Amount',
|
||||||
|
input: (amount:number) => centsToDollars(amount),
|
||||||
|
output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,')),
|
||||||
|
value: 0
|
||||||
|
}),
|
||||||
|
// 'fee_total': createFieldDefinition({name: 'fee_total', label: 'Fees',
|
||||||
|
// input: (amount:number) => centsToDollars(amount),
|
||||||
|
// output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,')),
|
||||||
|
// value: 0
|
||||||
|
// }),
|
||||||
|
'date': createFieldDefinition({name: 'date', label: 'Date',
|
||||||
|
input: (isoTime:string) => this.nonprofitTimezonedDates.readable_date(isoTime),
|
||||||
|
output:(date:string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date),
|
||||||
|
value: moment.utc().toISOString()
|
||||||
|
}),
|
||||||
|
'dedication': {name: 'dedication', label: 'Dedication', fields: [
|
||||||
|
createFieldDefinition({name:'type', label: 'Dedication Type'}),
|
||||||
|
createFieldDefinition({name: 'supporter_id', type: 'hidden'}),
|
||||||
|
createFieldDefinition({name:'name', label:'Person dedicated for'}),
|
||||||
|
createFieldDefinition({name: 'contact', type: 'hidden'}),
|
||||||
|
createFieldDefinition({name: 'note'})
|
||||||
|
]},
|
||||||
|
'designation': {name: 'designation', label: 'Designation'},
|
||||||
|
'comment': {name: 'comment', label: 'Note'}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
params.check_number = {name: 'check_number', label: 'Check Number'}
|
||||||
|
|
||||||
|
params.date.validators = [Validations.isDate('MM/DD/YYYY')]
|
||||||
|
|
||||||
|
params.gross_amount.validators = [Validations.isGreaterThanOrEqualTo(0.01)];
|
||||||
|
// params.fee_total.validators = [Validations.optional(Validations.isLessThanOrEqualTo(0))];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new CreateOffsitePaymentPaneForm({fields: _.values(params)}, {
|
||||||
|
hooks: {
|
||||||
|
onSuccess: async (e: Field) => {
|
||||||
|
await this.createOffsiteDonation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@computed get form(): CreateOffsitePaymentPaneForm {
|
||||||
|
//add this.props because we need to reload on prop change
|
||||||
|
return this.props && this.loadFormFromData()
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get dateFormatter(): NonprofitTimezonedDates {
|
||||||
|
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const modal =
|
||||||
|
<Modal modalActive={this.props.modalActive} titleText={'Edit Donation'} focusDialog={true}
|
||||||
|
onClose={this.props.onClose} dialogStyle={{minWidth:'768px'}} childGenerator={() => {
|
||||||
|
return <div className={"tw-bs"}>
|
||||||
|
<form className='u-marginTop--20'>
|
||||||
|
|
||||||
|
<CurrencyField field={this.form.$('gross_amount')} label={"Gross Amount"} currencySymbol={"$"}/>
|
||||||
|
{/* <CurrencyField field={this.form.$('fee_total')} label={"Processing Fees"} mustBeNegative={true}/> */}
|
||||||
|
|
||||||
|
<BasicField field={this.form.$('date')} label={"Date"} />
|
||||||
|
<SelectField field={this.form.$('campaign')}
|
||||||
|
label={"Campaign"}
|
||||||
|
options={this.props.campaigns}/>
|
||||||
|
|
||||||
|
|
||||||
|
<SelectField field={this.form.$('event')}
|
||||||
|
label={"Event"}
|
||||||
|
options={this.props.events} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<TextareaField field={this.form.$('designation')} label={"Designation"} rows={3} />
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading"><label>Dedication <small> (optional)</small></label></div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<SelectField field={this.form.$('dedication.type')} label={"Dedication Type"} options={[{id: null, name: ''}, {id: 'honor', name: 'In honor of'}, {id:'memory', name: 'In memory of'}]} />
|
||||||
|
|
||||||
|
{ this.form.$('dedication.type').get('value') != '' ? <div> <div className={"panel panel-default"}>
|
||||||
|
<div className="panel-heading"><label>Dedicated to:</label></div>
|
||||||
|
<div className={'panel-body'}>
|
||||||
|
<table className='table--small u-marginBottom--10'>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<td><input {...this.form.$('dedication.name').bind()}/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TextareaField rows={3} placeholder={"Dedication"} field={this.form.$('dedication.note')} label={"Dedication Note"}/></div>
|
||||||
|
: undefined }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<TextareaField field={this.form.$('comment')} label={"Notes"} rows={3} />
|
||||||
|
|
||||||
|
|
||||||
|
<ProgressableButton buttonText={'Save'}
|
||||||
|
buttonTextOnProgress={'Updating...'} className={'button'}
|
||||||
|
inProgress={this.form.submitting}
|
||||||
|
disabled={!this.form.isValid}
|
||||||
|
disableOnProgress={true} onClick={this.form.onSubmit}/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}} />
|
||||||
|
return <div>{modal}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectIntl(observer(CreateNewOffsitePaymentPane))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,25 +2,25 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {InjectedIntlProps, injectIntl} from 'react-intl';
|
import {InjectedIntlProps, injectIntl} from 'react-intl';
|
||||||
import {action, computed, observable, runInAction} from "mobx";
|
import {action, computed} from "mobx";
|
||||||
import {Field, FieldDefinition, Form} from "../../../../types/mobx-react-form";
|
import {FieldDefinition} from "mobx-react-form";
|
||||||
import {HoudiniForm} from "../../lib/houdini_form";
|
import {HoudiniForm} from "../../lib/houdini_form";
|
||||||
import ProgressableButton from "../common/ProgressableButton";
|
import ProgressableButton from "../common/ProgressableButton";
|
||||||
import AriaModal = require('react-aria-modal');
|
|
||||||
import {centsToDollars, dollarsToCents, readableInterval, readableKind} from "../../lib/format";
|
import {centsToDollars, dollarsToCents, readableInterval, readableKind} from "../../lib/format";
|
||||||
import {NonprofitTimezonedDates, readable_date} from '../../lib/date';
|
import {NonprofitTimezonedDates} from '../../lib/date';
|
||||||
import {UpdateDonationModel, PutDonation} from "../../lib/api/put_donation";
|
import {UpdateDonationModel, PutDonation} from "../../lib/api/put_donation";
|
||||||
import {ApiManager} from "../../lib/api_manager";
|
import {ApiManager} from "../../lib/api_manager";
|
||||||
import * as CustomAPIS from "../../lib/apis";
|
import * as CustomAPIS from "../../lib/apis";
|
||||||
import {CSRFInterceptor} from "../../lib/csrf_interceptor";
|
import {CSRFInterceptor} from "../../lib/csrf_interceptor";
|
||||||
import {BasicField, CurrencyField, SelectField, TextareaField} from '../common/fields';
|
import {BasicField, CurrencyField, SelectField, TextareaField} from '../common/fields';
|
||||||
import ReactTextarea from "../common/form/ReactTextarea";
|
|
||||||
import {TwoColumnFields} from "../common/layout";
|
import {TwoColumnFields} from "../common/layout";
|
||||||
import {Validations} from "../../lib/vjf_rules";
|
import {Validations} from "../../lib/vjf_rules";
|
||||||
import _ = require("lodash");
|
import _ = require("lodash");
|
||||||
import {Dedication, parseDedication, serializeDedication} from '../../lib/dedication';
|
import {Dedication, parseDedication, serializeDedication} from '../../lib/dedication';
|
||||||
import blacklist = require("validator/lib/blacklist");
|
import blacklist = require("validator/lib/blacklist");
|
||||||
import {createFieldDefinition} from "../../lib/mobx_utils";
|
import {createFieldDefinition} from "../../lib/mobx_utils";
|
||||||
|
import Modal from "../common/Modal";
|
||||||
|
import ReactInput from "../common/form/ReactInput";
|
||||||
|
|
||||||
|
|
||||||
interface Charge {
|
interface Charge {
|
||||||
|
@ -40,7 +40,7 @@ interface Donation {
|
||||||
campaign?: { id: number }
|
campaign?: { id: number }
|
||||||
dedication?: string
|
dedication?: string
|
||||||
recurring_donation?: RecurringDonation
|
recurring_donation?: RecurringDonation
|
||||||
id:number
|
id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaymentData {
|
interface PaymentData {
|
||||||
|
@ -55,7 +55,7 @@ interface PaymentData {
|
||||||
net_amount: number
|
net_amount: number
|
||||||
origin_url?: string
|
origin_url?: string
|
||||||
charge?: Charge,
|
charge?: Charge,
|
||||||
nonprofit: {id:number}
|
nonprofit: { id: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OffsitePayment {
|
interface OffsitePayment {
|
||||||
|
@ -67,11 +67,14 @@ export interface EditPaymentPaneProps {
|
||||||
data: PaymentData
|
data: PaymentData
|
||||||
events: FundraiserInfo[]
|
events: FundraiserInfo[]
|
||||||
campaigns: FundraiserInfo[]
|
campaigns: FundraiserInfo[]
|
||||||
|
|
||||||
|
nonprofitTimezone?: string
|
||||||
|
preupdateDonationAction: () => void
|
||||||
|
postUpdateSuccess: () => void
|
||||||
|
|
||||||
|
//from ModalProps
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
modalActive: boolean
|
modalActive: boolean
|
||||||
nonprofitTimezone?: string
|
|
||||||
preupdateDonationAction:() => void
|
|
||||||
postUpdateSuccess: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FundraiserInfo {
|
export interface FundraiserInfo {
|
||||||
|
@ -79,7 +82,7 @@ export interface FundraiserInfo {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditPaymentPanelForm extends HoudiniForm {
|
class EditPaymentPaneForm extends HoudiniForm {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,23 +92,23 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
|
||||||
|
|
||||||
constructor(props: EditPaymentPaneProps & InjectedIntlProps) {
|
constructor(props: EditPaymentPaneProps & InjectedIntlProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.putDonation = new ApiManager(CustomAPIS.APIS as Array<any>, CSRFInterceptor).get(PutDonation)
|
this.putDonation = new ApiManager(CustomAPIS.APIS as Array<any>, CSRFInterceptor).get(PutDonation);
|
||||||
|
|
||||||
this.loadFormFromData()
|
this.loadFormFromData()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get nonprofitTimezonedDates():NonprofitTimezonedDates {
|
get nonprofitTimezonedDates(): NonprofitTimezonedDates {
|
||||||
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get dedication(): Dedication|null {
|
get dedication(): Dedication | null {
|
||||||
return parseDedication(this.props.data.donation && this.props.data.donation.dedication)
|
return parseDedication(this.props.data && this.props.data.donation && this.props.data.donation.dedication)
|
||||||
}
|
}
|
||||||
|
|
||||||
putDonation : PutDonation
|
putDonation: PutDonation;
|
||||||
|
|
||||||
@action.bound
|
@action.bound
|
||||||
async updateDonation() {
|
async updateDonation() {
|
||||||
|
@ -114,106 +117,130 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let updateData:UpdateDonationModel = {
|
let updateData: UpdateDonationModel = {
|
||||||
id: Number(this.props.data.donation.id),
|
id: Number(this.props.data.donation.id),
|
||||||
donation: {
|
donation: {
|
||||||
designation: this.form.$('designation').value,
|
designation: this.form.$('designation').value,
|
||||||
comment: this.form.$('comment').value,
|
comment: this.form.$('comment').value,
|
||||||
campaign_id: this.form.$('campaign').value,
|
campaign_id: this.form.$('campaign').value,
|
||||||
event_id: this.form.$('event').value,
|
event_id: this.form.$('event').value,
|
||||||
gross_amount: dollarsToCents(this.form.$('gross_amount').get('value')),
|
gross_amount: dollarsToCents(this.form.$('gross_amount').get('value')),
|
||||||
fee_total: dollarsToCents(this.form.$('fee_total').get('value')),
|
fee_total: dollarsToCents(this.form.$('fee_total').get('value')),
|
||||||
|
|
||||||
date: this.form.$('date').get('value')
|
date: this.form.$('date').get('value')
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (this.form.$('dedication.type').get('value') != '') {
|
if (this.form.$('dedication.type').get('value') != '') {
|
||||||
|
const nameToValueForContact = ['full_address', 'phone', 'email'].map((i) => {
|
||||||
|
return {
|
||||||
|
name: i, value: this.form.$(`dedication.${i}`).get('value')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const contact = _.some(nameToValueForContact, (i) => i.value && i.value != "") ?
|
||||||
|
_.reduce(nameToValueForContact, (result: any, i) => {
|
||||||
|
result[i.name] = i.value;
|
||||||
|
return result;
|
||||||
|
}, {}) : undefined;
|
||||||
|
|
||||||
updateData.donation.dedication = serializeDedication({
|
updateData.donation.dedication = serializeDedication({
|
||||||
type: this.form.$('dedication.type').get('value'),
|
type: this.form.$('dedication.type').get('value'),
|
||||||
supporter_id: this.form.$('dedication.supporter_id').get('value'),
|
supporter_id: this.form.$('dedication.supporter_id').get('value'),
|
||||||
name: this.form.$('dedication.name').get('value'),
|
name: this.form.$('dedication.name').get('value'),
|
||||||
contact:this.form.$('dedication.contact').get('value'),
|
contact: contact,
|
||||||
note: this.form.$('dedication.note').get('value')
|
note: this.form.$('dedication.note').get('value')
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
updateData.donation.dedication = ""
|
updateData.donation.dedication = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.form.has('check_number'))
|
if (this.form.has('check_number')) {
|
||||||
{
|
|
||||||
updateData.donation.check_number = this.form.$('check_number').value
|
updateData.donation.check_number = this.form.$('check_number').value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await this.putDonation.putDonation(updateData, this.props.data.nonprofit.id)
|
await this.putDonation.putDonation(updateData, this.props.data.nonprofit.id);
|
||||||
|
|
||||||
|
|
||||||
if(this.props.postUpdateSuccess){
|
if (this.props.postUpdateSuccess) {
|
||||||
try {
|
try {
|
||||||
this.props.postUpdateSuccess()
|
this.props.postUpdateSuccess()
|
||||||
}
|
}
|
||||||
catch {}
|
catch {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onClose()
|
this.props.onClose()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@action.bound
|
@action.bound
|
||||||
loadFormFromData() {
|
loadFormFromData() {
|
||||||
const eventId = this.props.data.donation.event && this.props.data.donation.event.id;
|
const eventId = this.props.data.donation.event && this.props.data.donation.event.id;
|
||||||
const campaignId = this.props.data.donation.campaign && this.props.data.donation.campaign.id;
|
const campaignId = this.props.data.donation.campaign && this.props.data.donation.campaign.id;
|
||||||
let params: {[name:string]:FieldDefinition} = {
|
let params: { [name: string]: FieldDefinition } = {
|
||||||
'event': {name: 'event', label: 'Event', value: eventId},
|
'event': {name: 'event', label: 'Event', value: eventId},
|
||||||
'campaign': {name: 'campaign', label: 'Campaign', value: campaignId},
|
'campaign': {name: 'campaign', label: 'Campaign', value: campaignId},
|
||||||
'gross_amount': createFieldDefinition({name: 'gross_amount', label: 'Gross Amount', value: this.props.data.gross_amount,
|
'gross_amount': createFieldDefinition({
|
||||||
input: (amount:number) => centsToDollars(amount),
|
name: 'gross_amount', label: 'Gross Amount', value: this.props.data.gross_amount,
|
||||||
output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,'))
|
input: (amount: number) => centsToDollars(amount),
|
||||||
|
output: (dollarString: string) => parseFloat(blacklist(dollarString, '$,'))
|
||||||
}),
|
}),
|
||||||
'fee_total': createFieldDefinition({name: 'fee_total', label: 'Fees', value: this.props.data.fee_total,
|
'fee_total': createFieldDefinition({
|
||||||
input: (amount:number) => centsToDollars(amount),
|
name: 'fee_total', label: 'Fees', value: this.props.data.fee_total,
|
||||||
output: (dollarString:string) => parseFloat(blacklist(dollarString, '$,'))
|
input: (amount: number) => centsToDollars(amount),
|
||||||
}),
|
output: (dollarString: string) => parseFloat(blacklist(dollarString, '$,'))
|
||||||
'date': createFieldDefinition({name: 'date', label: 'Date',
|
}),
|
||||||
value: this.props.data.date,
|
'date': createFieldDefinition({
|
||||||
input: (isoTime:string) => this.nonprofitTimezonedDates.readable_date(isoTime),
|
name: 'date', label: 'Date',
|
||||||
output:(date:string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date)}),
|
value: this.props.data.date,
|
||||||
'dedication': {name: 'dedication', label: 'Dedication', fields: [
|
input: (isoTime: string) => this.nonprofitTimezonedDates.readable_date(isoTime),
|
||||||
createFieldDefinition({name:'type', label: 'Dedication Type', value: this.dedication.type}),
|
output: (date: string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date)
|
||||||
createFieldDefinition({name: 'supporter_id', type: 'hidden', value: this.dedication.supporter_id}),
|
}),
|
||||||
createFieldDefinition({name:'name', label:'Person dedicated for', value: this.dedication.name}),
|
'dedication': {
|
||||||
createFieldDefinition({name: 'contact', type: 'hidden', value: this.dedication.contact}),
|
name: 'dedication', label: 'Dedication', fields: [
|
||||||
createFieldDefinition({name: 'note', value: this.dedication.note})
|
createFieldDefinition({name: 'type', label: 'Dedication Type', value: this.dedication && this.dedication.type}),
|
||||||
]},
|
createFieldDefinition({name: 'supporter_id', type: 'hidden', value: this.dedication && this.dedication.supporter_id}),
|
||||||
'designation': {name: 'designation', label: 'Designation', value: this.props.data.donation.designation},
|
createFieldDefinition({name: 'name', label: 'Person dedicated for', value: this.dedication && this.dedication.name}),
|
||||||
'comment': {name: 'comment', label: 'Note', value: this.props.data.donation.comment}
|
createFieldDefinition({name: 'full_address', label: 'Full address', value: this.dedication && this.dedication.contact && this.dedication.contact.address}),
|
||||||
|
createFieldDefinition({name: 'phone', label: 'Phone', value: this.dedication && this.dedication.contact && this.dedication.contact.phone}),
|
||||||
|
createFieldDefinition({name: 'email', label: 'email', value: this.dedication && this.dedication.contact && this.dedication.contact.email}),
|
||||||
|
createFieldDefinition({name: 'note', value: this.dedication && this.dedication.note})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'designation': {name: 'designation', label: 'Designation', value: this.props.data.donation.designation},
|
||||||
|
'comment': {name: 'comment', label: 'Note', value: this.props.data.donation.comment}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (this.props.data.kind == 'OffsitePayment') {
|
if (this.props.data.kind == 'OffsitePayment') {
|
||||||
params.check_number = {name: 'check_number', label: 'Check Number', value: this.props.data.offsite_payment.check_number}
|
params.check_number = {
|
||||||
|
name: 'check_number',
|
||||||
|
label: 'Check Number',
|
||||||
|
value: this.props.data.offsite_payment.check_number
|
||||||
|
};
|
||||||
|
|
||||||
params.date.validators = [Validations.isDate('MM/DD/YYYY')]
|
params.date.validators = [Validations.isDate('MM/DD/YYYY')];
|
||||||
|
|
||||||
params.gross_amount.validators = [Validations.isGreaterThanOrEqualTo(0.01)];
|
params.gross_amount.validators = [Validations.isGreaterThanOrEqualTo(0.01)];
|
||||||
params.fee_total.validators = [Validations.optional(Validations.isLessThanOrEqualTo(0))];
|
params.fee_total.validators = [Validations.optional(Validations.isLessThanOrEqualTo(0))];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EditPaymentPanelForm({fields: _.values(params)}, {
|
return new EditPaymentPaneForm({fields: _.values(params)}, {
|
||||||
hooks: {
|
hooks: {
|
||||||
onSuccess: async (e: Field) => {
|
onSuccess: async () => {
|
||||||
await this.updateDonation()
|
await this.updateDonation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get form(): EditPaymentPanelForm {
|
@computed get form(): EditPaymentPaneForm {
|
||||||
//add this.props because we need to reload on prop change
|
//add this.props because we need to reload on prop change
|
||||||
return this.props && this.loadFormFromData()
|
return this.props && this.loadFormFromData()
|
||||||
}
|
}
|
||||||
|
@ -222,9 +249,9 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
|
||||||
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
return new NonprofitTimezonedDates(this.props.nonprofitTimezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
@action.bound
|
||||||
|
innerRender() {
|
||||||
let rd = this.props.data.donation && this.props.data.donation.recurring_donation;
|
let rd = this.props.data && this.props.data.donation && this.props.data.donation.recurring_donation;
|
||||||
let initialTable = <table className='table--small u-marginBottom--10'>
|
let initialTable = <table className='table--small u-marginBottom--10'>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -355,85 +382,112 @@ class EditPaymentPane extends React.Component<EditPaymentPaneProps & InjectedInt
|
||||||
|
|
||||||
</TwoColumnFields>
|
</TwoColumnFields>
|
||||||
|
|
||||||
<BasicField field={this.form.$('date')} label={"Date"} />
|
<BasicField field={this.form.$('date')} label={"Date"}/>
|
||||||
|
|
||||||
|
|
||||||
{checkNumber}
|
{checkNumber}
|
||||||
</div>) : undefined;
|
</div>) : undefined;
|
||||||
|
return <div className={"tw-bs"}>
|
||||||
|
<div>
|
||||||
|
{initialTable}
|
||||||
|
|
||||||
const modal = this.props.modalActive ?
|
<form className='u-marginTop--20'>
|
||||||
<AriaModal mounted={this.props.modalActive} titleText={'Edit Donation'} focusDialog={true}
|
|
||||||
onExit={this.props.onClose} dialogStyle={{minWidth:'768px'}}>
|
|
||||||
<header className='modal-header'>
|
|
||||||
<h4 className='modal-header-title'>Edit Donation</h4>
|
|
||||||
</header>
|
|
||||||
<div className="modal-body">
|
|
||||||
<div className={"tw-bs"}>
|
|
||||||
<div>
|
|
||||||
{initialTable}
|
|
||||||
|
|
||||||
<form className='u-marginTop--20'>
|
{offsitePayment}
|
||||||
|
|
||||||
{offsitePayment}
|
|
||||||
|
|
||||||
|
|
||||||
<SelectField field={this.form.$('campaign')}
|
<SelectField field={this.form.$('campaign')}
|
||||||
label={"Campaign"}
|
label={"Campaign"}
|
||||||
options={this.props.campaigns}/>
|
options={this.props.campaigns}/>
|
||||||
|
|
||||||
|
|
||||||
<SelectField field={this.form.$('event')}
|
<SelectField field={this.form.$('event')}
|
||||||
label={"Event"}
|
label={"Event"}
|
||||||
options={this.props.events} />
|
options={this.props.events}/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextareaField field={this.form.$('designation')} label={"Designation"} rows={3}/>
|
||||||
|
|
||||||
<TextareaField field={this.form.$('designation')} label={"Designation"} rows={3} />
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading"><label>Dedication</label></div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<SelectField field={this.form.$('dedication.type')} label={"Dedication Type"}
|
||||||
|
options={[{id: null, name: ''}, {id: 'honor', name: 'In honor of'}, {
|
||||||
|
id: 'memory',
|
||||||
|
name: 'In memory of'
|
||||||
|
}]}/>
|
||||||
|
|
||||||
<div className="panel panel-default">
|
{this.form.$('dedication.type').get('value') != '' ? <div>
|
||||||
<div className="panel-heading"><label>Dedication</label></div>
|
<div className={"panel panel-default"}>
|
||||||
<div className="panel-body">
|
<div className="panel-heading"><label>Dedicated to:</label></div>
|
||||||
<SelectField field={this.form.$('dedication.type')} label={"Dedication Type"} options={[{id: null, name: ''}, {id: 'honor', name: 'In honor of'}, {id:'memory', name: 'In memory of'}]} />
|
<div className={'panel-body'}>
|
||||||
|
<table className='table--small u-marginBottom--10'>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<td><ReactInput field={this.form.$('dedication.name')} label={'Name'} className={"form-control"}/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> Supporter
|
||||||
|
ID
|
||||||
|
</th>
|
||||||
|
<td>{this.dedication.supporter_id}<input {...this.form.$('dedication.supporter_id').bind()}/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{ this.form.$('dedication.type').get('value') != '' ? <div> <div className={"panel panel-default"}>
|
<tr>
|
||||||
<div className="panel-heading"><label>Dedicated to:</label></div>
|
<th>Full Address
|
||||||
<div className={'panel-body'}>
|
</th>
|
||||||
<table className='table--small u-marginBottom--10'>
|
<td><ReactInput field={this.form.$('dedication.full_address')} className={"form-control"}/>
|
||||||
<tr>
|
</td>
|
||||||
<th>Name</th>
|
</tr>
|
||||||
<td><input {...this.form.$('dedication.name').bind()}/></td>
|
<tr>
|
||||||
</tr>
|
<th>Phone Number
|
||||||
<tr>
|
</th>
|
||||||
<th > Supporter
|
<td><ReactInput field={this.form.$('dedication.phone')} className={"form-control"}/>
|
||||||
ID</th>
|
</td>
|
||||||
<td>{this.dedication.supporter_id}<input {...this.form.$('dedication.supporter_id').bind()}/></td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
</table>
|
<th>Email Address
|
||||||
|
</th>
|
||||||
|
<td><ReactInput field={this.form.$('dedication.email')} className={"form-control"}/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<TextareaField rows={3} placeholder={"Dedication"} field={this.form.$('dedication.note')}
|
||||||
<TextareaField rows={3} placeholder={"Dedication"} field={this.form.$('dedication.note')} label={"Dedication Note"}/></div>
|
label={"Dedication Note"}/></div>
|
||||||
: undefined }
|
: undefined}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<TextareaField field={this.form.$('comment')} label={"Notes"} rows={3} />
|
|
||||||
|
|
||||||
|
|
||||||
<ProgressableButton buttonText={'Save'}
|
|
||||||
buttonTextOnProgress={'Updating...'} className={'button'}
|
|
||||||
inProgress={this.form.submitting}
|
|
||||||
disabled={!this.form.isValid}
|
|
||||||
disableOnProgress={true} onClick={this.form.onSubmit}/>
|
|
||||||
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</AriaModal> : false;
|
|
||||||
|
|
||||||
return (<div>{modal}</div>)
|
|
||||||
|
<TextareaField field={this.form.$('comment')} label={"Notes"} rows={3}/>
|
||||||
|
|
||||||
|
|
||||||
|
<ProgressableButton buttonText={'Save'}
|
||||||
|
buttonTextOnProgress={'Updating...'} className={'button'}
|
||||||
|
inProgress={this.form.submitting}
|
||||||
|
disabled={!this.form.isValid}
|
||||||
|
disableOnProgress={true} onClick={this.form.onSubmit}/>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const modal =
|
||||||
|
<Modal modalActive={this.props.modalActive} titleText={'Edit Donation'} focusDialog={true}
|
||||||
|
onClose={this.props.onClose} dialogStyle={{minWidth: '768px'}} childGenerator={() => this.innerRender()}/>
|
||||||
|
|
||||||
|
return modal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
99
javascripts/src/lib/api/create_offsite_donation.ts
Normal file
99
javascripts/src/lib/api/create_offsite_donation.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// License: LGPL-3.0-or-later
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
import {Configuration} from "../../../api/configuration";
|
||||||
|
|
||||||
|
export class CreateOffsiteDonation {
|
||||||
|
protected basePath = '/';
|
||||||
|
public defaultHeaders: Array<string> = [];
|
||||||
|
public defaultExtraJQueryAjaxSettings?: JQueryAjaxSettings = null;
|
||||||
|
public configuration: Configuration = new Configuration();
|
||||||
|
|
||||||
|
constructor(basePath?: string, configuration?: Configuration, defaultExtraJQueryAjaxSettings?: JQueryAjaxSettings) {
|
||||||
|
if (basePath) {
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
if (configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
if (defaultExtraJQueryAjaxSettings) {
|
||||||
|
this.defaultExtraJQueryAjaxSettings = defaultExtraJQueryAjaxSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public postDonation(donation: CreateOffsiteDonationModel, nonprofitId: number, extraJQueryAjaxSettings?: JQueryAjaxSettings): Promise<any> {
|
||||||
|
let localVarPath = `${this.basePath}nonprofits/${nonprofitId}/donations/create_offsite`;
|
||||||
|
|
||||||
|
let queryParameters: any = {};
|
||||||
|
let headerParams: any = {};
|
||||||
|
// verify required parameter 'nonprofit' is not null or undefined
|
||||||
|
if (donation === null || donation === undefined) {
|
||||||
|
throw new Error('Required parameter nonprofit was null or undefined when calling postNonprofit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
localVarPath = localVarPath + "?" + $.param(queryParameters);
|
||||||
|
// to determine the Content-Type header
|
||||||
|
let consumes: string[] = [
|
||||||
|
'application/json'
|
||||||
|
];
|
||||||
|
|
||||||
|
// to determine the Accept header
|
||||||
|
let produces: string[] = [
|
||||||
|
'application/json'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
headerParams['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
let requestOptions: JQueryAjaxSettings = {
|
||||||
|
url: localVarPath,
|
||||||
|
type: 'POST',
|
||||||
|
headers: headerParams,
|
||||||
|
processData: false
|
||||||
|
};
|
||||||
|
|
||||||
|
requestOptions.data = JSON.stringify({donation:donation});
|
||||||
|
if (headerParams['Content-Type']) {
|
||||||
|
requestOptions.contentType = headerParams['Content-Type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraJQueryAjaxSettings) {
|
||||||
|
requestOptions = (<any>Object).assign(requestOptions, extraJQueryAjaxSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.defaultExtraJQueryAjaxSettings) {
|
||||||
|
requestOptions = (<any>Object).assign(requestOptions, this.defaultExtraJQueryAjaxSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dfd = $.Deferred();
|
||||||
|
$.ajax(requestOptions).then(
|
||||||
|
(data: any, textStatus: string, jqXHR: JQueryXHR) =>
|
||||||
|
dfd.resolve(jqXHR, data),
|
||||||
|
(xhr: JQueryXHR, textStatus: string, errorThrown: string) => {
|
||||||
|
|
||||||
|
|
||||||
|
dfd.reject(xhr.responseJSON)
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return dfd.promise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface CreateOffsiteDonationModel {
|
||||||
|
designation?:string
|
||||||
|
dedication?:string
|
||||||
|
comment?:string
|
||||||
|
|
||||||
|
|
||||||
|
amount?: number
|
||||||
|
supporter_id:number
|
||||||
|
nonprofit_id:number
|
||||||
|
date?:string
|
||||||
|
|
||||||
|
campaign_id?:string
|
||||||
|
event_id?: string
|
||||||
|
offsite_payment?:{check_number?: string}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// License: LGPL-3.0-or-later
|
// License: LGPL-3.0-or-later
|
||||||
import {WebUserSignInOut} from "./api/sign_in";
|
import {WebUserSignInOut} from "./api/sign_in";
|
||||||
import {PutDonation} from './api/put_donation';
|
import {PutDonation} from './api/put_donation';
|
||||||
|
import {CreateOffsiteDonation} from "./api/create_offsite_donation";
|
||||||
|
|
||||||
export const APIS = [WebUserSignInOut, PutDonation]
|
export const APIS = [WebUserSignInOut, PutDonation, CreateOffsiteDonation]
|
|
@ -3,7 +3,11 @@ export interface Dedication {
|
||||||
type?:'honor'|'memory',
|
type?:'honor'|'memory',
|
||||||
supporter_id?: number,
|
supporter_id?: number,
|
||||||
name?:string
|
name?:string
|
||||||
contact?: string,
|
contact?: {
|
||||||
|
email?: string,
|
||||||
|
phone?:string
|
||||||
|
address?:string
|
||||||
|
}
|
||||||
note?:string
|
note?:string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
// License: LGPL-3.0-or-later
|
// License: LGPL-3.0-or-later
|
||||||
export function castToNullIfUndef<T>(i:T): T | null{
|
export function castToNullIfUndef<T>(i:T): T | null{
|
||||||
return i === undefined ? null : i
|
return i === undefined ? null : i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBlank(i:null|undefined|string) : boolean {
|
||||||
|
return i === null || i === undefined || i === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFilled(i:null|undefined|string) : boolean {
|
||||||
|
return !isBlank(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function castToUndefinedIfBlank(i:null|undefined|string) :
|
||||||
|
string | undefined {
|
||||||
|
return isBlank(i) ? undefined : i;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue