Enable catch-all in our ValidationError component.
We're doing this an ugly, hacky way. But it works, and right now, that's what
counts.
To match our params/fields/headers properties on the ValidationError component,
we're going to add the notParams/notFields/notHeaders properties--they match any
error _not_ targeting those params/fields/headers. Basically, "any error that
wouldn't be caught by these filters". Which is an ugly, but workable, solution
for a catch-all ValidationError--just tell it to catch anything but the
params/fields/headers that are being handled by the other ValidationErrors.
Our implementation of this in the RegisterPage component validates (ha!) that
it's at least workable model, if not overly pretty. Also, I anticipate some
human error bugs in the future, where one of the field-specific ValidationErrors
gets updated and the catch-all ValidationError does not.
But whatever. For now, this is Good Enough™.
1 import app from 'ampersand-app'
2 import React from 'react'
3 import ScriptLoaderMixin from 'react-script-loader'
4 import LaddaButton from 'react-ladda'
5 import LaddaCSS from '../../node_modules/ladda/dist/ladda.min.css'
6 import HeroUnit from '../components/hero'
7 import ValidationError from '../components/validation-error'
8 import onboardStyles from '../styles/onboarding.scss'
9 import config from '../config'
11 export default React.createClass({
12 displayName: 'PaymentMethodPage',
13 mixins: [ScriptLoaderMixin.ReactScriptLoaderMixin, React.addons.LinkedStateMixin],
15 return 'https://js.stripe.com/v2/'
21 stripeFailedToLoad: false,
32 nameValidationOutputs: {},
33 numberValidationOutputs: {},
34 cvcValidationOutputs: {},
35 expirationValidationOutputs: {},
38 Stripe.setPublishableKey(config.stripeKey)
39 this.setState({stripeLoading: false})
43 this.setState({stripeFailedToLoad: true})
47 let created = new Date()
48 if (app.me && app.me.profile && app.me.profile.created && app.me.profile.created < created) {
49 console.log("using register date...")
50 created = app.me.profile.created
52 let month = created.getMonth()
53 let day = created.getDate()
59 let result = new Date(created.toString())
61 result.setMonth(month)
67 if (this.state.stripeLoading || this.state.stripeFailedToLoad) {
70 this.setState({active: true})
73 Stripe.card.createToken({
74 number: this.state.number,
76 exp_month: this.state.expireMonth,
77 exp_year: this.state.expireYear,
78 name: this.state.name,
79 }, function(status, response) {
83 if (response.error.type == 'card_error') {
84 switch (response.error.code) {
85 case 'incorrect_number':
86 errors.push({'error': 'invalid_value', 'field': '/number'})
88 case 'invalid_number':
89 errors.push({'error': 'invalid_format', 'field': '/number'})
91 case 'invalid_expiry_month':
92 errors.push({'error': 'invalid_format', 'field': '/expireMonth'})
94 case 'invalid_expiry_year':
95 errors.push({'error': 'invalid_format', 'field': '/expireYear'})
98 errors.push({'error': 'invalid_format', 'field': '/cvc'})
101 errors.push({'error': 'invalid_value', 'field': '/expiration'})
103 case 'incorrect_cvc':
104 errors.push({'error': 'invalid_value', 'field': '/cvc'})
106 case 'incorrect_zip':
107 errors.push({'error': 'invalid_value', 'field': '/zip'})
109 case 'card_declined':
110 errors.push({'error': 'insufficient', 'field': '/balance'})
113 errors.push({'error': 'missing', 'field': '/customer/card'})
115 case 'processing_error':
116 errors.push({'error': 'act_of_god', 'field': '/'})
119 errors.push({'error': 'access_denied', 'field': '/rate'})
122 errors.push({'error': 'act_of_god', 'field': '/'})
126 console.log('Error:', response.error.message)
129 console.log('Sending '+response.id+' to server to create customer')
131 t.setState({active: false, errors: errors})
137 <div className='container'>
138 <HeroUnit title='Add a Payment Method'>Gotta keep our servers online and food on the table.</HeroUnit>
139 <article className='onboarding payment'>
140 <p>Ducky costs $2 a month to use. We’ll charge the card you enter below on the first of every month until your account is disabled. You won’t be charged before {this.getChargeDate().toLocaleDateString(navigator.languages, {month: 'long', year: 'numeric', day: 'numeric'})}.</p>
141 <form onSubmit={this.addCard}>
143 <label htmlFor='name'>Cardholder Name</label>
144 <input id='name' type='text' placeholder='This is the name on your card' valueLink={this.linkState('name')} />
145 <ValidationError errors={this.state.errors} field='/name' outputs={this.nameValidationOutputs} />
147 <label htmlFor='cardNumber'>Card Number</label>
148 <input id='cardNumber' type='text' placeholder='4242 4242 4242 4242' valueLink={this.linkState('number')} />
149 <ValidationError errors={this.state.errors} field='/number' outputs={this.numberValidationOutputs} />
151 <label htmlFor='cvc'>Security Code / CVC</label>
152 <input id='cvc' className='cvc' type='password' placeholder='123' valueLink={this.linkState('cvc')} />
154 <label htmlFor='expireMonth' className='expiration'>Expires</label>
155 <input id='expireMonth' className='expiration month' type='text' placeholder='01' valueLink={this.linkState('expireMonth')} />
156 <input id='expireYear' className='expiration year' type='text' placeholder='15' valueLink={this.linkState('expireYear')} />
157 <ValidationError errors={this.state.errors} field='/cvc' outputs={this.cvcValidationOutputs} />
158 <ValidationError errors={this.state.errors} field='/expiration' outputs={this.expirationValidationOutputs} />
160 <div className='actionbuttons'>
161 <button type='button' onClick={this.skip} className='ladda-button' disabled={this.state.active}>Not Now</button>
162 <LaddaButton style='expand-right' active={this.state.active}>
163 <button type='submit' className='primary' disabled={this.state.stripeLoading || this.state.stripeFailedToLoad || this.state.active || this.state.errors.length}>Add Card</button>