ducky/web

Paddy 2015-07-07 Parent:e6da0d35a533

22:21f80f56cda9 Go to Latest

ducky/web/src/pages/register.jsx

Switch to being a website instead of a Chrome app. Update our CNAME to be the more appropriate "prototype.useducky.com" when we deploy. Create a homepage and a non-onboarding page template. This mostly consisted of setting up a header component and the associated styles, and then a logged-in vs. guest flavor of said header, changing the links appropriately. We also created a simple homepage that describes what Ducky is and does, and gave a jumping-off point for it. Stubbed out a basic links page, just to get an idea for what the homepage would be like when a logged-in user navigated to the homepage (e.g., not the marketing copy). Updated our login page to _actually work_, and redirected it to the new URL for the payment setup page. Updated the payment page to actually create a subscription, and moved it from /register/payment to just /payment. Fixed a bug in our registration page that was looking for an invalid_form error when it really meant an invalid_format error. Ooops. Also, updated it to point to the new /payment endpoint instead of /register/payment. Updated our router to use the new homepage, the new links page, and updated the URL for the payment page. Updated our button styles so they should all have the right font color, padding, and border-radius, but could've potentially screwed something up. Oops. Updated our backgrounds all over to have a transparent-y white background behind the content, and a simple pattern for the rest of the body. Not sure how I feel about it just yet, but I'm not going to keep futzing with it.

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@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@22 51 'invalid_format': '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@22 150 app.router.navigate('/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 })