Add address validation

This commit is contained in:
Ray Schamp 2016-06-06 10:10:27 -04:00
parent f8037f6767
commit cfa85427b1
9 changed files with 90 additions and 16 deletions

View file

@ -47,6 +47,8 @@ env:
- SENTRY_DSN_VAR=SENTRY_DSN_$TRAVIS_BRANCH
- SENTRY_DSN=${!SENTRY_DSN_VAR}
- SENTRY_DSN=${SENTRY_DSN:-$SENTRY_DSN_STAGING}
# SMARTY_STREETS_API_KEY
- secure: "uQKNgJaJEju8ErGUxIbLE0Y6ee4j6OFFbBpqyuKrNMk6apvvvXLp3lTdGZJq6j/ZwQeQ384m5bbfmhFwr7piPFj7W/zBXVKcifbF6ShfP7skMl834Kkefs3uEWU0VZw3nURgzNInSOPqqGLsISFywpwBXUWKfL0Q87KHNU0/I0EkwvImm3SAlNpR38m3yKcMF3h0zK8Fh2mO7iyHEIhtssdWabaRjf3t6Mr5vikACeXYJg+k4oEQZtsnSNnlLYWumdEDsxwonMozGKUBqlXwhHCdYNOJ1DUGuntbXOnylLt1/LA9I9B4hWQOrRDwqjyIOI+2dpADoCN040+Zr1VSrJhk7Wb7ogeaQLzZ4W/3dX54rbsnFHa+MuKqOsAxQ0Tjfk5xWq/pbLRsAyW6Pl7Q1v4yWOQ2COnM/tfJ6UaH9bxppOyKsX8n33rFjlvZU6CtY1GGa7fpB2zOKI5B5OovLjHeokIe/Tx+4coEDZqt44qkTGWr/eWDxrvkQqpQ29F9My3wBgB3gdou+3lWExS0a9M2wwp4EIduXEKNZXLGDuVefH5f3eFy09wH+nhctmMF8uhMbPefFubEi7fqXTkxntmDTy+/pD2A2w1jJhBwLhwlik335k+Wrbl3dclt7cjJ6fRVX9b+AuBCbGr633vM4xbk90whwXizSECIt5InGSw="
- SKIP_CLEANUP=true
- NODE_ENV=production
- WWW_VERSION=${TRAVIS_COMMIT:0:5}

View file

@ -1,6 +1,7 @@
var classNames = require('classnames');
var Formsy = require('formsy-react');
var React = require('react');
var GeneralError = require('./general-error.jsx');
var validations = require('./validations.jsx').validations;
for (var validation in validations) {
@ -20,6 +21,7 @@ var Form = React.createClass({
);
return (
<Formsy.Form {... this.props} className={classes}>
<GeneralError name="all" />
{this.props.children}
</Formsy.Form>
);

View file

@ -0,0 +1,13 @@
var Formsy = require('formsy-react');
var React = require('react');
module.exports = Formsy.HOC(React.createClass({
render: function () {
if (!this.props.showError()) return null;
return (
<p className="error">
{this.props.getErrorMessage()}
</p>
);
}
}));

View file

@ -2,6 +2,7 @@
.spinner {
position: relative;
margin: 0 auto;
width: 20px;
height: 20px;
@ -17,10 +18,14 @@
animation: circleFadeDelay 1.2s infinite ease-in-out both;
margin: 0 auto;
border-radius: 100%;
background-color: darken($ui-blue, 8%);
background-color: darken($ui-white, 8%);
width: 15%;
height: 15%;
content: "";
.white & {
background-color: darken($ui-blue, 8%);
}
}
}

21
src/lib/smarty-streets.js Normal file
View file

@ -0,0 +1,21 @@
var defaults = require('lodash.defaults');
var api = require('./api');
module.exports = function smartyStreetApi (params, callback) {
defaults(params, {
'auth-id': process.env.SMARTY_STREETS_API_KEY
});
api({
host: 'https://api.smartystreets.com',
uri: '/street-address',
params: params
}, function (err, body, res) {
if (err) return callback(err);
if (res.statusCode !== 200) {
return callback(
'There was an error contacting the address validation server.'
);
}
return callback(err, body);
});
};

View file

@ -1,7 +1,4 @@
module.exports = {
// Bind environment
api_host: process.env.API_HOST || 'https://api.scratch.mit.edu',
// Search and metadata
title: 'Imagine, Program, Share',
description:

View file

@ -31,13 +31,6 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="stylesheet" href="/css/lib/normalize.min.css" />
<!-- Environment -->
<script>
window.env = {
API_HOST: "{{&api_host}}"
};
</script>
<!-- Polyfills -->
<script src="/js/polyfill.min.js"></script>
</head>

View file

@ -1,6 +1,8 @@
var React = require('react');
var countryData = require('../../lib/country-data');
var log = require('../../lib/log');
var smartyStreets = require('../../lib/smarty-streets');
var Button = require('../../components/forms/button.jsx');
var Checkbox = require('../../components/forms/checkbox.jsx');
@ -11,6 +13,7 @@ var Input = require('../../components/forms/input.jsx');
var PhoneInput = require('../../components/forms/phone-input.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx');
var Select = require('../../components/forms/select.jsx');
var Spinner = require('../../components/spinner/spinner.jsx');
var TextArea = require('../../components/forms/textarea.jsx');
var DEFAULT_COUNTRY = 'us';
@ -222,12 +225,43 @@ module.exports = {
countryChoice: (
(this.props.formData.user && this.props.formData.user.country) ||
this.props.defaultCountry
)
),
waiting: false
};
},
onChangeCountry: function (field, choice) {
this.setState({countryChoice: choice});
},
onValidSubmit: function (formData, reset, invalidate) {
if (formData.address.country !== 'us') {
return this.props.onNextStep(formData);
}
this.setState({waiting: true});
var address = {
street: formData.address.line1,
secondary: formData.address.line2 || '',
city: formData.address.city,
state: formData.address.state,
zipcode: formData.address.zip
};
smartyStreets(address, function (err, res) {
this.setState({waiting: false});
if (err) {
// We don't want to prevent registration because
// address validation isn't working. Log it and
// move on.
log.error(err);
return this.props.onNextStep(formData);
}
if (res && res.length > 0) {
return this.props.onNextStep(formData);
} else {
return invalidate({
'all': 'This doesn\'t look like a real address'
});
}
}.bind(this));
},
render: function () {
var stateOptions = countryData.subdivisionOptions[this.state.countryChoice];
var stateDefault = 'Please select...';
@ -239,7 +273,7 @@ module.exports = {
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onValidSubmit={this.props.onNextStep}>
<Form onValidSubmit={this.onValidSubmit}>
<Select label="Country"
name="address.country"
options={countryData.countryOptions}
@ -249,12 +283,17 @@ module.exports = {
<Input label="Address Line 1" type="text" name="address.line1" required />
<Input label="Address Line 2" type="text" name="address.line2" />
<Input label="City" type="text" name="address.city" required />
<Input label="ZIP Code" type="text" name="address.zip" required />
{stateOptions.length > 2 ?
<Select label="State / Province" name="address.state" options={stateOptions} required /> :
[]
}
<Button type="submit">Next Step</Button>
<Input label="ZIP Code" type="text" name="address.zip" required />
<Button type="submit" disabled={this.state.waiting}>
{this.state.waiting ?
<Spinner /> :
<span>Next Step</span>
}
</Button>
</Form>
</FormStep>
);

View file

@ -114,7 +114,9 @@ module.exports = {
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"',
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"'
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"',
'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"',
'process.env.SMARTY_STREETS_API_KEY': '"' + (process.env.SMARTY_STREETS_API_KEY || '') + '"'
}),
new webpack.optimize.CommonsChunkPlugin('common', 'js/common.bundle.js'),
new webpack.optimize.OccurenceOrderPlugin()