ducky/web
ducky/web/src/pages/register.jsx
Update to hosted URL, use oauth-refresh in profiles. When syncing the profiles, use our oauth-refresh sync helper, so it won't fail because of an expired OAuth token. Also, update our URL to use the nginx-fronted URL.
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 'conflict': 'Hm, that email already has an account. Did you forget your password?',
33 },
35 emailConfirmationValidationOutputs: {
36 'invalid_value': 'Oops! Those emails don’t match. Maybe double-check them?',
37 },
39 passphraseValidationOutputs: {
40 'insufficient': 'A longer passphrase would be better.',
41 'missing': 'Looks like you forgot to enter a passphrase. You need one!',
42 'overflow': 'We can’t store that long a passphrase. Try a shorter one.',
43 },
45 passphraseConfirmationValidationOutputs: {
46 'invalid_value': 'Oops! Those passphrases don’t match. Maybe double-check them?',
47 },
49 catchAllValidationOutputs: {
50 'act_of_god': 'Hm, something went wrong. Try again? Or let support know.',
51 'invalid_form': 'Uh oh, things went really wrong. Let support know you saw the dreaded invalid_format error!',
52 'conflict': 'Something went wrong, but trying again will probably fix it.',
53 'access_denied': 'Oops, something’s gone awry. Let support know your client isn’t working.',
54 },
56 debouncedValidateForm (event) {
57 this._validateForm(event)
58 },
60 _validateForm (event) {
61 const fields = {
62 email: this.state.email,
63 emailConfirmation: this.state.emailConfirmation,
64 passphrase: this.state.passphrase,
65 passphraseConfirmation: this.state.passphraseConfirmation,
66 }
67 if (event != null) {
68 if (event.target.id == 'emailInput') {
69 fields.email = event.target.value
70 }
71 else if (event.target.id == 'emailConfirmationInput') {
72 fields.emailConfirmation = event.target.value
73 }
74 else if (event.target.id == 'passphraseInput') {
75 fields.passphrase = event.target.value
76 }
77 else if (event.target.id == 'passphraseConfirmationInput') {
78 fields.passphraseConfirmation = event.target.value
79 }
80 }
81 const errors = this.validate(fields, event == null)
82 this.setState({clientErrors: errors, serverErrors: []})
83 return errors.length <= 0
84 },
86 validateForm (event) {
87 event.persist()
88 this.debouncedValidateForm(event)
89 },
91 validate (fields, all) {
92 const errors = []
93 if (fields.email != null) {
94 if (fields.email.length == 0) {
95 errors.push({'error': 'missing', 'field': '/email'})
96 } else if (fields.email.length > 64) {
97 errors.push({'error': 'overflow', 'field': '/email'})
98 } else if (!fields.email.match(/.+@.+\..+/)) {
99 errors.push({'error': 'invalid_format', 'field': '/email'})
100 }
101 } else if (all) {
102 errors.push({'error': 'missing', 'field': '/email'})
103 }
104 if (fields.emailConfirmation != null || all) {
105 if (fields.emailConfirmation != fields.email && (fields.email || fields.emailConfirmation)) {
106 errors.push({'error': 'invalid_value', 'field': '/email_confirmation'})
107 }
108 }
109 if (fields.passphrase != null) {
110 if (fields.passphrase.length == 0) {
111 errors.push({'error': 'missing', 'field': '/passphrase'})
112 } else if (fields.passphrase.length < 6) {
113 errors.push({'error': 'insufficient', 'field': '/passphrase'})
114 } else if (fields.passphrase.length > 64) {
115 errors.push({'error': 'overflow', 'field': '/passphrase'})
116 }
117 } else if (all) {
118 errors.push({'error': 'missing', 'field': '/passphrase'})
119 }
120 if (fields.passphraseConfirmation != null || all) {
121 if (fields.passphraseConfirmation != fields.passphrase && (fields.passphrase || fields.passphraseConfirmation)) {
122 errors.push({'error': 'invalid_value', 'field': '/passphrase_confirmation'})
123 }
124 }
125 return errors
126 },
128 componentDidMount () {
129 app.profiles.on('request', (moc, xhr, options) => {
130 this.setState({active: true})
131 })
132 app.profiles.on('error', (moc, xhr, options) => {
133 let state = {active: false}
134 let resp = {}
135 if (xhr && xhr.response) {
136 resp = JSON.parse(xhr.response)
137 }
138 if (resp.errors && resp.errors.length) {
139 state.serverErrors = resp.errors
140 } else {
141 state.serverErrors = [{'error': 'act_of_god'}]
142 }
143 this.setState(state)
144 })
145 app.profiles.on('sync', (moc, xhr, options) => {
146 app.me.login(this.state.email, this.state.passphrase)
147 })
148 app.me.on('sync', (moc, xhr, options) => {
149 this.setState({active: false})
150 app.router.navigate('/register/payment')
151 })
152 app.me.on('error', (moc, xhr, options) => {
153 let state = {active: false}
154 let resp = {}
155 if (xhr && xhr.response) {
156 resp = JSON.parse(xhr.response)
157 }
158 console.log(resp)
159 if (resp.errors && resp.errors.length) {
160 state.serverErrors = resp.errors
161 } else if (resp.error && resp.error == 'invalid_client') {
162 state.serverErrors = [{'error': 'access_denied'}]
163 } else {
164 state.serverErrors = [{'error': 'act_of_god'}]
165 }
166 this.setState(state)
167 })
168 },
170 componentWillMount () {
171 this.debouncedValidateForm = debounce(this.debouncedValidateForm, 900)
172 },
174 register (e) {
175 e.preventDefault()
176 const success = this._validateForm(null)
177 if (!success) {
178 return
179 }
180 app.profiles.register(this.state.email, this.state.passphrase)
181 },
183 onBackClick (event) {
184 event.preventDefault()
185 window.history.back()
186 },
188 render () {
189 return (
190 <div className='container'>
191 <HeroUnit title='Create an Account'>We’d like to get to know you better.</HeroUnit>
192 <article className='onboarding register'>
193 <form onSubmit={this.register}>
194 <div>
195 <label htmlFor='emailInput'>Email</label>
196 <input id='emailInput' type='email' placeholder='Ours is quack@useducky.com' valueLink={this.linkState('email')} disabled={this.state.active} onInput={this.validateForm} />
197 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/email' outputs={this.emailValidationOutputs} />
199 <label htmlFor='emailConfirmationInput'>Verify Email</label>
200 <input id='emailConfirmationInput' type='email' placeholder='Typos are the absolute worst.' valueLink={this.linkState('emailConfirmation')} disabled={this.state.active} onInput={this.validateForm} />
201 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/email_confirmation' outputs={this.emailConfirmationValidationOutputs} />
203 <label htmlFor='passphraseInput'>Passphrase</label>
204 <input id='passphraseInput' type='password' placeholder='We use a sentence. Try it!' valueLink={this.linkState('passphrase')} disabled={this.state.active} onInput={this.validateForm} />
205 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/passphrase' outputs={this.passphraseValidationOutputs} />
207 <label htmlFor='passphraseConfirmationInput'>Verify Passphrase</label>
208 <input id='passphraseConfirmationInput' type='password' placeholder='Just to make sure you know it.' valueLink={this.linkState('passphraseConfirmation')} disabled={this.state.active} onInput={this.validateForm} />
209 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/passphrase_confirmation' outputs={this.passphraseConfirmationValidationOutputs} />
211 <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} notFields={['/email', '/email_confirmation', '/passphrase', '/passphrase_confirmation']} notHeaders={[]} notParams={[]} outputs={this.catchAllValidationOutputs} />
212 </div>
213 <div className='actionbuttons'>
214 <button onClick={this.onBackClick} disabled={this.state.active} type='button' className='ladda-button'>Back</button>
215 <LaddaButton style='expand-right' active={this.state.active}>
216 <button type='submit' className='primary' disabled={this.state.active || this.state.clientErrors.length}>Register</button>
217 </LaddaButton>
218 </div>
219 </form>
220 </article>
221 </div>
222 )
223 }
224 })