From fe74208f32a2e96321101bc90862adc5f5869eac Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 2 Jun 2016 15:25:02 -0400
Subject: [PATCH] Add international phone validation
---
package.json | 2 +
src/components/forms/checkbox-group.jsx | 4 +-
src/components/forms/checkbox.jsx | 4 +-
src/components/forms/form.jsx | 2 +-
src/components/forms/input.jsx | 4 +-
src/components/forms/phone-input.jsx | 55 ++++++++++++++++++
src/components/forms/radio-group.jsx | 4 +-
src/components/forms/select.jsx | 4 +-
src/components/forms/textarea.jsx | 4 +-
src/components/forms/validateMixin.jsx | 24 --------
src/components/forms/validations.js | 10 ----
src/components/forms/validations.jsx | 46 +++++++++++++++
.../teacherregistration.jsx | 6 +-
static/images/flags.png | Bin 0 -> 70814 bytes
webpack.config.js | 7 ++-
15 files changed, 127 insertions(+), 49 deletions(-)
create mode 100644 src/components/forms/phone-input.jsx
delete mode 100644 src/components/forms/validateMixin.jsx
delete mode 100644 src/components/forms/validations.js
create mode 100644 src/components/forms/validations.jsx
create mode 100644 static/images/flags.png
diff --git a/package.json b/package.json
index 08f47b80a..a816d3ed9 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"formsy-react-components": "0.7.1",
"git-bundle-sha": "0.0.2",
"glob": "5.0.15",
+ "google-libphonenumber": "1.0.21",
"json-loader": "0.5.2",
"json2po-stream": "1.0.3",
"jsx-loader": "0.13.2",
@@ -69,6 +70,7 @@
"react-onclickoutside": "4.1.1",
"react-redux": "4.4.5",
"react-slick": "0.12.2",
+ "react-telephone-input": "3.4.5",
"redux": "3.5.2",
"redux-thunk": "2.0.1",
"sass-lint": "1.5.1",
diff --git a/src/components/forms/checkbox-group.jsx b/src/components/forms/checkbox-group.jsx
index 87b08d41e..30fd85c7a 100644
--- a/src/components/forms/checkbox-group.jsx
+++ b/src/components/forms/checkbox-group.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var CheckboxGroup = React.createClass({
type: 'CheckboxGroup',
@@ -16,4 +16,4 @@ var CheckboxGroup = React.createClass({
}
});
-module.exports = validateMixin(CheckboxGroup);
+module.exports = defaultValidationHOC(CheckboxGroup);
diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx
index 38abfdd45..4363b24fe 100644
--- a/src/components/forms/checkbox.jsx
+++ b/src/components/forms/checkbox.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var Checkbox = React.createClass({
type: 'Checkbox',
@@ -16,4 +16,4 @@ var Checkbox = React.createClass({
}
});
-module.exports = validateMixin(Checkbox);
+module.exports = defaultValidationHOC(Checkbox);
diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx
index 319c559f4..b9811b075 100644
--- a/src/components/forms/form.jsx
+++ b/src/components/forms/form.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var Formsy = require('formsy-react');
var React = require('react');
-var validations = require('./validations');
+var validations = require('./validations.jsx').validations;
for (var validation in validations) {
Formsy.addValidationRule(validation, validations[validation]);
diff --git a/src/components/forms/input.jsx b/src/components/forms/input.jsx
index 0b06c5438..23bea730e 100644
--- a/src/components/forms/input.jsx
+++ b/src/components/forms/input.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var FRCInput = require('formsy-react-components').Input;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
require('./input.scss');
@@ -22,4 +22,4 @@ var Input = React.createClass({
}
});
-module.exports = validateMixin(Input);
+module.exports = defaultValidationHOC(Input);
diff --git a/src/components/forms/phone-input.jsx b/src/components/forms/phone-input.jsx
new file mode 100644
index 000000000..16c11bdba
--- /dev/null
+++ b/src/components/forms/phone-input.jsx
@@ -0,0 +1,55 @@
+var classNames = require('classnames');
+var React = require('react');
+var FormsyMixin = require('formsy-react').Mixin;
+var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
+var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
+var Row = require('formsy-react-components').Row;
+var ComponentMixin = require('formsy-react-components').ComponentMixin;
+
+var PhoneInput = React.createClass({
+ displayName: 'PhoneInput',
+ mixins: [
+ FormsyMixin,
+ ComponentMixin
+ ],
+ getDefaultProps: function () {
+ return {
+ validations: {
+ isPhone: true
+ },
+ flagsImagePath: '/images/flags.png'
+ };
+ },
+ onChangeInput: function (number, country) {
+ var value = {national_number: number, country_code: country};
+ this.setValue(value);
+ this.props.onChange(this.props.name, value);
+ },
+ render: function () {
+ return (
+
+
+
+
+ {this.renderHelp()}
+ {this.renderErrorMessage()}
+
+ );
+ }
+});
+
+var phoneValidationHOC = validationHOCFactory({
+ isPhone: 'Please enter a valid phone number'
+});
+
+module.exports = defaultValidationHOC(phoneValidationHOC(PhoneInput));
diff --git a/src/components/forms/radio-group.jsx b/src/components/forms/radio-group.jsx
index 430b27e59..8f2a99033 100644
--- a/src/components/forms/radio-group.jsx
+++ b/src/components/forms/radio-group.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var RadioGroup = React.createClass({
type: 'RadioGroup',
@@ -16,4 +16,4 @@ var RadioGroup = React.createClass({
}
});
-module.exports = validateMixin(RadioGroup);
+module.exports = defaultValidationHOC(RadioGroup);
diff --git a/src/components/forms/select.jsx b/src/components/forms/select.jsx
index d87e76c14..9ddab75dd 100644
--- a/src/components/forms/select.jsx
+++ b/src/components/forms/select.jsx
@@ -2,7 +2,7 @@ var classNames = require('classnames');
var defaults = require('lodash.defaultsdeep');
var FRCSelect = require('formsy-react-components').Select;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
require('./select.scss');
@@ -26,4 +26,4 @@ var Select = React.createClass({
}
});
-module.exports = validateMixin(Select);
+module.exports = defaultValidationHOC(Select);
diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx
index 56c1a8c25..f317e220e 100644
--- a/src/components/forms/textarea.jsx
+++ b/src/components/forms/textarea.jsx
@@ -1,7 +1,7 @@
var classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react');
-var validateMixin = require('./validateMixin.jsx');
+var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
require('./textarea.scss');
@@ -18,4 +18,4 @@ var TextArea = React.createClass({
}
});
-module.exports = validateMixin(TextArea);
+module.exports = defaultValidationHOC(TextArea);
diff --git a/src/components/forms/validateMixin.jsx b/src/components/forms/validateMixin.jsx
deleted file mode 100644
index 72bcdc557..000000000
--- a/src/components/forms/validateMixin.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-var defaults = require('lodash.defaultsdeep');
-var React = require('react');
-
-var validateMixin = function (Component) {
- var ValidatedComponent = React.createClass({
- getDefaultValidationErrors: function () {
- return {
- isDefaultRequiredValue: 'This field is required'
- };
- },
- render: function () {
- var validationErrors = defaults(
- this.getDefaultValidationErrors(),
- this.props.validationErrors
- );
- return (
-
- );
- }
- });
- return ValidatedComponent;
-};
-
-module.exports = validateMixin;
diff --git a/src/components/forms/validations.js b/src/components/forms/validations.js
deleted file mode 100644
index 38985788c..000000000
--- a/src/components/forms/validations.js
+++ /dev/null
@@ -1,10 +0,0 @@
-var Validations = {
- notEquals: function (values, value, neq) {
- return value !== neq;
- },
- notEqualsField: function (values, value, field) {
- return value !== values[field];
- }
-};
-
-module.exports = Validations;
diff --git a/src/components/forms/validations.jsx b/src/components/forms/validations.jsx
new file mode 100644
index 000000000..db0b71632
--- /dev/null
+++ b/src/components/forms/validations.jsx
@@ -0,0 +1,46 @@
+var defaults = require('lodash.defaultsdeep');
+var libphonenumber = require('google-libphonenumber');
+var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
+var React = require('react');
+
+module.exports = {};
+
+module.exports.validations = {
+ notEquals: function (values, value, neq) {
+ return value !== neq;
+ },
+ notEqualsField: function (values, value, field) {
+ return value !== values[field];
+ },
+ isPhone: function (values, value) {
+ if (typeof value === 'undefined') return true;
+ if (value && value.national_number === '+') return true;
+ try {
+ var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
+ } catch (err) {
+ return false;
+ }
+ return phoneNumberUtil.isValidNumber(parsed);
+ }
+};
+
+module.exports.validationHOCFactory = function (defaultValidationErrors) {
+ return function (Component) {
+ var ValidatedComponent = React.createClass({
+ render: function () {
+ var validationErrors = defaults(
+ defaultValidationErrors,
+ this.props.validationErrors
+ );
+ return (
+
+ );
+ }
+ });
+ return ValidatedComponent;
+ };
+};
+
+module.exports.defaultValidationHOC = module.exports.validationHOCFactory({
+ isDefaultRequiredValue: 'This field is required'
+});
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 75091356e..74933704c 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -13,6 +13,7 @@ var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
var Page = require('../../components/page/www/page.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 TextArea = require('../../components/forms/textarea.jsx');
@@ -152,7 +153,10 @@ var PhoneNumberStep = React.createClass({
Why do we ask for this information ?
}>