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