// License: LGPL-3.0-or-later import * as React from 'react'; import {observer} from 'mobx-react'; import {InjectedIntlProps, injectIntl} from 'react-intl'; import {action, computed} from "mobx"; import {FieldDefinition} from "mobx-react-form"; import {HoudiniForm} from "../../lib/houdini_form"; import ProgressableButton from "../common/ProgressableButton"; import {centsToDollars, dollarsToCents, readableInterval, readableKind} from "../../lib/format"; import {NonprofitTimezonedDates} from '../../lib/date'; import {UpdateDonationModel, PutDonation} from "../../lib/api/put_donation"; import {ApiManager} from "../../lib/api_manager"; import * as CustomAPIS from "../../lib/apis"; import {CSRFInterceptor} from "../../lib/csrf_interceptor"; import {BasicField, CurrencyField, SelectField, TextareaField} from '../common/fields'; import {TwoColumnFields} from "../common/layout"; import {Validations} from "../../lib/vjf_rules"; import _ = require("lodash"); import {Dedication, parseDedication, serializeDedication} from '../../lib/dedication'; import blacklist = require("validator/lib/blacklist"); import {createFieldDefinition} from "../../lib/mobx_utils"; import Modal from "../common/Modal"; import ReactInput from "../common/form/ReactInput"; interface Charge { status: string } interface RecurringDonation { interval?: number time_unit?: string created_at: string } interface Donation { designation?: string comment?: string event?: { id: number } campaign?: { id: number } dedication?: string recurring_donation?: RecurringDonation id: number } interface PaymentData { gross_amount: number fee_total: number date: string offsite_payment: OffsitePayment donation: Donation kind: string id: string refund_total: number net_amount: number origin_url?: string charge?: Charge, nonprofit: { id: number } } interface OffsitePayment { check_number: string kind: string } export interface EditPaymentPaneProps { data: PaymentData events: FundraiserInfo[] campaigns: FundraiserInfo[] nonprofitTimezone?: string preupdateDonationAction: () => void postUpdateSuccess: () => void //from ModalProps onClose: () => void modalActive: boolean } export interface FundraiserInfo { id: number name: string } class EditPaymentPaneForm extends HoudiniForm { } @observer class EditPaymentPane extends React.Component { constructor(props: EditPaymentPaneProps & InjectedIntlProps) { super(props); this.putDonation = new ApiManager(CustomAPIS.APIS as Array, CSRFInterceptor).get(PutDonation); this.loadFormFromData() } @computed get nonprofitTimezonedDates(): NonprofitTimezonedDates { return new NonprofitTimezonedDates(this.props.nonprofitTimezone) } @computed get dedication(): Dedication | null { return parseDedication(this.props.data && this.props.data.donation && this.props.data.donation.dedication) } putDonation: PutDonation; @action.bound async updateDonation() { if (this.props.preupdateDonationAction) { this.props.preupdateDonationAction() } let updateData: UpdateDonationModel = { id: Number(this.props.data.donation.id), donation: { designation: this.form.$('designation').value, comment: this.form.$('comment').value, campaign_id: this.form.$('campaign').value, event_id: this.form.$('event').value, gross_amount: dollarsToCents(this.form.$('gross_amount').get('value')), fee_total: dollarsToCents(this.form.$('fee_total').get('value')), date: this.form.$('date').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({ type: this.form.$('dedication.type').get('value'), supporter_id: this.form.$('dedication.supporter_id').get('value'), name: this.form.$('dedication.name').get('value'), contact: contact, note: this.form.$('dedication.note').get('value') }); } else { updateData.donation.dedication = ""; } if (this.form.has('check_number')) { updateData.donation.check_number = this.form.$('check_number').value } await this.putDonation.putDonation(updateData, this.props.data.nonprofit.id); if (this.props.postUpdateSuccess) { try { this.props.postUpdateSuccess() } catch { } } this.props.onClose() } @action.bound loadFormFromData() { 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; let params: { [name: string]: FieldDefinition } = { 'event': {name: 'event', label: 'Event', value: eventId}, 'campaign': {name: 'campaign', label: 'Campaign', value: campaignId}, 'gross_amount': createFieldDefinition({ name: 'gross_amount', label: 'Gross Amount', value: this.props.data.gross_amount, 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, input: (amount: number) => centsToDollars(amount), output: (dollarString: string) => parseFloat(blacklist(dollarString, '$,')) }), 'date': createFieldDefinition({ name: 'date', label: 'Date', value: this.props.data.date, input: (isoTime: string) => this.nonprofitTimezonedDates.readable_date(isoTime), output: (date: string) => this.nonprofitTimezonedDates.readable_date_time_to_iso(date) }), 'dedication': { name: 'dedication', label: 'Dedication', fields: [ 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}), createFieldDefinition({name: 'name', label: 'Person dedicated for', value: this.dedication && this.dedication.name}), 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') { 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.gross_amount.validators = [Validations.isGreaterThanOrEqualTo(0.01)]; params.fee_total.validators = [Validations.optional(Validations.isLessThanOrEqualTo(0))]; } return new EditPaymentPaneForm({fields: _.values(params)}, { hooks: { onSuccess: async () => { await this.updateDonation() } } }) } @computed get form(): EditPaymentPaneForm { //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) } @action.bound innerRender() { let rd = this.props.data && this.props.data.donation && this.props.data.donation.recurring_donation; let initialTable = { this.props.data.kind === 'RecurringDonation' ? : false } { this.props.data.refund_total && this.props.data.refund_total > 0 ? : false } { this.props.data.origin_url ? : false } { this.props.data.charge ? : false } {this.props.data.offsite_payment && this.props.data.offsite_payment.check_number ? : false }
Payment Info
Date {this.dateFormatter.readable_date(this.props.data.date)}
Type {readableKind(this.props.data.kind)} { this.props.data.offsite_payment && this.props.data.offsite_payment && this.props.data.offsite_payment.kind ?   ({this.props.data.offsite_payment.kind}) : undefined }
Recurring {rd ? readableInterval(rd.interval, rd.time_unit) : false} since {rd ? this.dateFormatter.readable_date(rd.created_at) : false}
Gross Amount ${centsToDollars(this.props.data.gross_amount)}
Processing Fees ${centsToDollars(this.props.data.fee_total)}
Total Refunds ${centsToDollars(this.props.data.fee_total)}
Net Amount ${centsToDollars(this.props.data.net_amount)}
Origin {this.props.data.origin_url}
Status {this.props.data.charge.status}
Check # {this.props.data.offsite_payment.check_number}
ID {this.props.data.id}
; let checkNumber = this.props.data.offsite_payment ? : false; let offsitePayment = this.props.data.kind === "OffsitePayment" ? (
{checkNumber}
) : undefined; return
{initialTable}
{offsitePayment}
{this.form.$('dedication.type').get('value') != '' ?
Name
Supporter ID {this.dedication.supporter_id}
Full Address
Phone Number
Email Address
: undefined}
} render() { //force it to check the form values so this updates this.form.values() const modal = this.innerRender()}/> return modal; } } export default injectIntl(observer(EditPaymentPane))