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