ducky/web

Paddy 2015-05-31 Parent:bd64a7d043d0 Child:a641906b8267

5:efdc78cbdac5 Go to Latest

ducky/web/src/pages/register.jsx

Update our ValidationError component to accept arrays. Allow our ValidationError component to match an array of fields, headers, or params. This is for components that may address multiple inputs (e.g., month/year inputs) but also lays the ground work for inverse-matching. Ideally, inverse-matching and matching ValidationErrors should mirror each other, logically, so the syntax is identical. But we also should have the common use case easily supported, so if you use field instead of fields (or header/headers, param/params) we'll automatically turn that into an array.

History
paddy@0 1 import app from 'ampersand-app'
paddy@0 2 import React from 'react/addons'
paddy@0 3 import localLinks from 'local-links'
paddy@0 4 import LaddaButton from 'react-ladda'
paddy@0 5 import LaddaCSS from '../../node_modules/ladda/dist/ladda.min.css'
paddy@0 6 import HeroUnit from '../components/hero'
paddy@2 7 import ValidationError from '../components/validation-error'
paddy@0 8 import onboardingStyles from '../styles/onboarding.scss'
paddy@2 9 import flashStyles from '../styles/_flashes.scss'
paddy@2 10 import debounce from 'lodash.debounce'
paddy@0 11
paddy@0 12 export default React.createClass({
paddy@0 13 displayName: 'RegisterPage',
paddy@0 14 mixins: [React.addons.LinkedStateMixin],
paddy@0 15
paddy@0 16 getInitialState () {
paddy@0 17 return {
paddy@0 18 email: null,
paddy@0 19 emailConfirmation: null,
paddy@0 20 passphrase: null,
paddy@0 21 passphraseConfirmation: null,
paddy@0 22 active: false,
paddy@4 23 clientErrors: [],
paddy@4 24 serverErrors: [],
paddy@0 25 }
paddy@0 26 },
paddy@0 27
paddy@2 28 emailValidationOutputs: {
paddy@2 29 'missing': 'We need to know how to contact you. We promise not to share it.',
paddy@2 30 'overflow': 'Hm, that’s a bit long. Do you have a shorter email address?',
paddy@2 31 'invalid_format': 'That doesn’t look like an email address… Double check it?',
paddy@2 32 },
paddy@2 33
paddy@2 34 emailConfirmationValidationOutputs: {
paddy@2 35 'invalid_value': 'Oops! Those emails don’t match. Maybe double-check them?',
paddy@2 36 },
paddy@2 37
paddy@2 38 passphraseValidationOutputs: {
paddy@2 39 'insufficient': 'A longer passphrase would be better.',
paddy@2 40 'missing': 'Looks like you forgot to enter a passphrase. You need one!',
paddy@2 41 'overflow': 'We can’t store that long a passphrase. Try a shorter one.',
paddy@2 42 },
paddy@2 43
paddy@2 44 passphraseConfirmationValidationOutputs: {
paddy@2 45 'invalid_value': 'Oops! Those passphrases don’t match. Maybe double-check them?',
paddy@2 46 },
paddy@2 47
paddy@2 48 debouncedValidateForm (event) {
paddy@2 49 this._validateForm(event)
paddy@2 50 },
paddy@2 51
paddy@2 52 _validateForm (event) {
paddy@2 53 const fields = {
paddy@2 54 email: this.state.email,
paddy@2 55 emailConfirmation: this.state.emailConfirmation,
paddy@2 56 passphrase: this.state.passphrase,
paddy@2 57 passphraseConfirmation: this.state.passphraseConfirmation,
paddy@2 58 }
paddy@2 59 if (event != null) {
paddy@2 60 if (event.target.id == 'emailInput') {
paddy@2 61 fields.email = event.target.value
paddy@2 62 }
paddy@2 63 else if (event.target.id == 'emailConfirmationInput') {
paddy@2 64 fields.emailConfirmation = event.target.value
paddy@2 65 }
paddy@2 66 else if (event.target.id == 'passphraseInput') {
paddy@2 67 fields.passphrase = event.target.value
paddy@2 68 }
paddy@2 69 else if (event.target.id == 'passphraseConfirmationInput') {
paddy@2 70 fields.passphraseConfirmation = event.target.value
paddy@2 71 }
paddy@2 72 }
paddy@2 73 const errors = this.validate(fields, event == null)
paddy@4 74 this.setState({clientErrors: errors})
paddy@2 75 return errors.length <= 0
paddy@2 76 },
paddy@2 77
paddy@2 78 validateForm (event) {
paddy@2 79 event.persist()
paddy@2 80 this.debouncedValidateForm(event)
paddy@2 81 },
paddy@2 82
paddy@2 83 validate (fields, all) {
paddy@2 84 const errors = []
paddy@2 85 if (fields.email != null) {
paddy@2 86 if (fields.email.length == 0) {
paddy@2 87 errors.push({'error': 'missing', 'field': '/email'})
paddy@2 88 } else if (fields.email.length > 64) {
paddy@2 89 errors.push({'error': 'overflow', 'field': '/email'})
paddy@2 90 } else if (!fields.email.match(/.+@.+\..+/)) {
paddy@2 91 errors.push({'error': 'invalid_format', 'field': '/email'})
paddy@2 92 }
paddy@2 93 } else if (all) {
paddy@2 94 errors.push({'error': 'missing', 'field': '/email'})
paddy@2 95 }
paddy@2 96 if (fields.emailConfirmation != null || all) {
paddy@2 97 if (fields.emailConfirmation != fields.email && (fields.email || fields.emailConfirmation)) {
paddy@2 98 errors.push({'error': 'invalid_value', 'field': '/email_confirmation'})
paddy@2 99 }
paddy@2 100 }
paddy@2 101 if (fields.passphrase != null) {
paddy@2 102 if (fields.passphrase.length == 0) {
paddy@2 103 errors.push({'error': 'missing', 'field': '/passphrase'})
paddy@2 104 } else if (fields.passphrase.length < 6) {
paddy@2 105 errors.push({'error': 'insufficient', 'field': '/passphrase'})
paddy@2 106 } else if (fields.passphrase.length > 64) {
paddy@2 107 errors.push({'error': 'overflow', 'field': '/passphrase'})
paddy@2 108 }
paddy@2 109 } else if (all) {
paddy@2 110 errors.push({'error': 'missing', 'field': '/passphrase'})
paddy@2 111 }
paddy@2 112 if (fields.passphraseConfirmation != null || all) {
paddy@2 113 if (fields.passphraseConfirmation != fields.passphrase && (fields.passphrase || fields.passphraseConfirmation)) {
paddy@2 114 errors.push({'error': 'invalid_value', 'field': '/passphrase_confirmation'})
paddy@2 115 }
paddy@2 116 }
paddy@2 117 return errors
paddy@2 118 },
paddy@2 119
paddy@0 120 componentDidMount () {
paddy@0 121 app.profiles.on('request', (moc, xhr, options) => {
paddy@0 122 this.setState({active: true})
paddy@0 123 })
paddy@0 124 app.profiles.on('error', (moc, xhr, options) => {
paddy@4 125 let state = {active: false}
paddy@4 126 if (xhr.errors && xhr.errors.length) {
paddy@4 127 state.serverErrors = this.state.serverErrors.concat(xhr.errors)
paddy@4 128 } else {
paddy@4 129 state.serverErrors = [{'error': 'act_of_god'}]
paddy@4 130 }
paddy@4 131 this.setState(state)
paddy@0 132 })
paddy@0 133 app.profiles.on('sync', (moc, xhr, options) => {
paddy@0 134 app.me.login(this.state.email, this.state.passphrase)
paddy@0 135 })
paddy@0 136 app.me.on('sync', (moc, xhr, options) => {
paddy@0 137 this.setState({active: false})
paddy@2 138 app.router.navigate('/register/payment')
paddy@0 139 })
paddy@4 140 app.me.on('error', (moc, xhr, options) => {
paddy@4 141 let state = {active: false}
paddy@4 142 if (xhr.errors && xhr.errors.length) {
paddy@4 143 state.serverErrors = this.state.serverErrors.concat(xhr.errors)
paddy@4 144 } else {
paddy@4 145 state.serverErrors = [{'error': 'act_of_god'}]
paddy@4 146 }
paddy@4 147 this.setState(state)
paddy@4 148 })
paddy@0 149 },
paddy@0 150
paddy@2 151 componentWillMount () {
paddy@2 152 this.debouncedValidateForm = debounce(this.debouncedValidateForm, 900)
paddy@2 153 },
paddy@2 154
paddy@0 155 register (e) {
paddy@0 156 e.preventDefault()
paddy@2 157 const success = this._validateForm(null)
paddy@2 158 if (!success) {
paddy@2 159 return
paddy@2 160 }
paddy@0 161 app.profiles.register(this.state.email, this.state.passphrase)
paddy@0 162 },
paddy@0 163
paddy@0 164 onBackClick (event) {
paddy@0 165 event.preventDefault()
paddy@0 166 window.history.back()
paddy@0 167 },
paddy@0 168
paddy@0 169 render () {
paddy@0 170 return (
paddy@0 171 <div className='container'>
paddy@0 172 <HeroUnit title='Create an Account'>We’d like to get to know you better.</HeroUnit>
paddy@0 173 <article className='onboarding register'>
paddy@0 174 <form onSubmit={this.register}>
paddy@0 175 <div>
paddy@2 176 <label htmlFor='emailInput'>Email</label>
paddy@2 177 <input id='emailInput' type='email' placeholder='Ours is quack@useducky.com' valueLink={this.linkState('email')} disabled={this.state.active} onInput={this.validateForm} />
paddy@4 178 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/email' outputs={this.emailValidationOutputs} />
paddy@0 179
paddy@2 180 <label htmlFor='emailConfirmationInput'>Verify Email</label>
paddy@2 181 <input id='emailConfirmationInput' type='email' placeholder='Typos are the absolute worst.' valueLink={this.linkState('emailConfirmation')} disabled={this.state.active} onInput={this.validateForm} />
paddy@4 182 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/email_confirmation' outputs={this.emailConfirmationValidationOutputs} />
paddy@0 183
paddy@2 184 <label htmlFor='passphraseInput'>Passphrase</label>
paddy@2 185 <input id='passphraseInput' type='password' placeholder='We use a sentence. Try it!' valueLink={this.linkState('passphrase')} disabled={this.state.active} onInput={this.validateForm} />
paddy@4 186 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/passphrase' outputs={this.passphraseValidationOutputs} />
paddy@0 187
paddy@2 188 <label htmlFor='passphraseConfirmationInput'>Verify Passphrase</label>
paddy@2 189 <input id='passphraseConfirmationInput' type='password' placeholder='Just to make sure you know it.' valueLink={this.linkState('passphraseConfirmation')} disabled={this.state.active} onInput={this.validateForm} />
paddy@4 190 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/passphrase_confirmation' outputs={this.passphraseConfirmationValidationOutputs} />
paddy@0 191 </div>
paddy@0 192 <div className='actionbuttons'>
paddy@0 193 <button onClick={this.onBackClick} disabled={this.state.active} type='button' className='ladda-button'>Back</button>
paddy@0 194 <LaddaButton style='expand-right' active={this.state.active}>
paddy@4 195 <button type='submit' className='primary' disabled={this.state.active || this.state.clientErrors.length}>Register</button>
paddy@0 196 </LaddaButton>
paddy@0 197 </div>
paddy@0 198 </form>
paddy@0 199 </article>
paddy@0 200 </div>
paddy@0 201 )
paddy@0 202 }
paddy@0 203 })