From a080b2b64f995af65185667b55a99b8bb81fbf52 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 24 Mar 2016 12:46:07 -0400
Subject: [PATCH 01/55] wip
---
src/components/formcard/formcard.jsx | 55 +++++++++++++++++++++++++++
src/components/formcard/formcard.scss | 17 +++++++++
2 files changed, 72 insertions(+)
create mode 100644 src/components/formcard/formcard.jsx
create mode 100644 src/components/formcard/formcard.scss
diff --git a/src/components/formcard/formcard.jsx b/src/components/formcard/formcard.jsx
new file mode 100644
index 000000000..092563dc3
--- /dev/null
+++ b/src/components/formcard/formcard.jsx
@@ -0,0 +1,55 @@
+var React = require('react');
+var classNames = require('classnames');
+
+require('./formcard.scss');
+
+
+var CounterIndicator = React.createClass({
+ type: 'CounterIndicator',
+ propTypes: {
+ selected: false,
+ active: false
+ },
+ render: function () {
+ var classes = classNames(
+ 'counterIndicator',
+ {
+ 'selected': this.props.selected,
+ 'active': this.props.selected
+ },
+ this.props.className
+ );
+ return (
+
+ );
+ }
+})
+
+var FormCard = React.createClass({
+ type: 'FormCard',
+ propTypes: {
+
+ },
+ render: function () {
+ var classes = classNames(
+ 'formcard',
+ this.props.className
+ );
+ return (
+
+
+
+ {this.props.description}
+
+
+ {for (var i = 0; i < this.props.counterMax; i++) {
+
+ }}
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = FormCard;
diff --git a/src/components/formcard/formcard.scss b/src/components/formcard/formcard.scss
new file mode 100644
index 000000000..156be2c10
--- /dev/null
+++ b/src/components/formcard/formcard.scss
@@ -0,0 +1,17 @@
+@import "colors";
+
+.counterindicator {
+ display: inline-block;
+
+ &.active {
+ background-color: $ui-blue;
+ }
+
+ &.selected {
+ border: 2px solid $ui-blue;
+ }
+}
+
+.formcard {
+ width: 100px;
+}
From 144d6e375417bf85aa87d719acd25259fad10ee8 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Tue, 29 Mar 2016 10:07:23 -0400
Subject: [PATCH 02/55] wip
---
src/components/formcard/formcard.jsx | 55 --------
src/components/formcard/formcard.scss | 17 ---
src/components/formset/formset.jsx | 85 ++++++++++++
src/components/formset/formset.scss | 15 ++
.../stepnavigation/stepnavigation.jsx | 44 ++++++
src/routes.json | 6 +
.../teacherregistration.jsx | 129 ++++++++++++++++++
7 files changed, 279 insertions(+), 72 deletions(-)
delete mode 100644 src/components/formcard/formcard.jsx
delete mode 100644 src/components/formcard/formcard.scss
create mode 100644 src/components/formset/formset.jsx
create mode 100644 src/components/formset/formset.scss
create mode 100644 src/components/stepnavigation/stepnavigation.jsx
create mode 100644 src/views/teacherregistration/teacherregistration.jsx
diff --git a/src/components/formcard/formcard.jsx b/src/components/formcard/formcard.jsx
deleted file mode 100644
index 092563dc3..000000000
--- a/src/components/formcard/formcard.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-var React = require('react');
-var classNames = require('classnames');
-
-require('./formcard.scss');
-
-
-var CounterIndicator = React.createClass({
- type: 'CounterIndicator',
- propTypes: {
- selected: false,
- active: false
- },
- render: function () {
- var classes = classNames(
- 'counterIndicator',
- {
- 'selected': this.props.selected,
- 'active': this.props.selected
- },
- this.props.className
- );
- return (
-
- );
- }
-})
-
-var FormCard = React.createClass({
- type: 'FormCard',
- propTypes: {
-
- },
- render: function () {
- var classes = classNames(
- 'formcard',
- this.props.className
- );
- return (
-
-
-
- {this.props.description}
-
-
- {for (var i = 0; i < this.props.counterMax; i++) {
-
- }}
-
- {this.props.children}
-
- );
- }
-});
-
-module.exports = FormCard;
diff --git a/src/components/formcard/formcard.scss b/src/components/formcard/formcard.scss
deleted file mode 100644
index 156be2c10..000000000
--- a/src/components/formcard/formcard.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-@import "colors";
-
-.counterindicator {
- display: inline-block;
-
- &.active {
- background-color: $ui-blue;
- }
-
- &.selected {
- border: 2px solid $ui-blue;
- }
-}
-
-.formcard {
- width: 100px;
-}
diff --git a/src/components/formset/formset.jsx b/src/components/formset/formset.jsx
new file mode 100644
index 000000000..5b353ac10
--- /dev/null
+++ b/src/components/formset/formset.jsx
@@ -0,0 +1,85 @@
+var React = require('react');
+var classNames = require('classnames');
+
+var StepNavigationComponents = require('../stepnavigation/stepnavigation.jsx');
+var StepNavigation = StepNavigationComponents.StepNavigation;
+var StepNavigationIndicator = StepNavigationComponents.StepNavigationIndicator;
+
+require('./formset.scss');
+
+
+module.exports = {
+ FormStep: React.createClass({
+ type: 'FormStep',
+ propTypes: {
+
+ },
+ getDefaultProps: function () {
+ return {
+ navigation: null
+ }
+ },
+ render: function () {
+ var classes = classNames(
+ 'step',
+ this.props.className
+ );
+ return (
+
+
+ {this.props.description}
+ {this.props.navigation}
+ {this.props.children}
+
+ );
+ }
+ }),
+ FormSet: React.createClass({
+ type: 'FormSet',
+ propTypes: {
+ step: function(props, propName, componentName) {
+ var stepValidator = function (props, propName) {
+ if (props[propName] > -1 && props[propName] < props.children.length) {
+ return null;
+ } else {
+ return new Error('Prop `step` out of range');
+ }
+ }
+ return (
+ React.PropTypes.number.isRequired(props, propName, componentName) ||
+ stepValidator(props, propName, componentName)
+ );
+ }
+ },
+ getDefaultProps: function () {
+ return {
+ step: 0
+ };
+ },
+ render: function () {
+ var classes = classNames(
+ 'formset',
+ this.props.className
+ );
+ var navigation = (
+
+ {this.props.children.map(function (child, id) {
+ return (
+
+ );
+ }.bind(this))}
+ );
+ return (
+
+ {this.props.children.map(function (child, id) {
+ if (id === this.props.step) {
+ return child;
+ }
+ }.bind(this))}
+
+ );
+ }
+ })
+};
diff --git a/src/components/formset/formset.scss b/src/components/formset/formset.scss
new file mode 100644
index 000000000..17df75100
--- /dev/null
+++ b/src/components/formset/formset.scss
@@ -0,0 +1,15 @@
+@import "../../colors";
+
+.formset {
+ .counterindicator {
+ display: inline-block;
+
+ &.active {
+ background-color: $ui-blue;
+ }
+
+ &.selected {
+ border: 2px solid $ui-blue;
+ }
+ }
+}
diff --git a/src/components/stepnavigation/stepnavigation.jsx b/src/components/stepnavigation/stepnavigation.jsx
new file mode 100644
index 000000000..978f066c8
--- /dev/null
+++ b/src/components/stepnavigation/stepnavigation.jsx
@@ -0,0 +1,44 @@
+var classNames = require('classnames');
+var React = require('react');
+
+module.exports = {
+ StepNavigationIndicator: React.createClass({
+ type: 'StepNavigationIndicator',
+ propTypes: {
+ selected: React.PropTypes.bool,
+ active: React.PropTypes.bool
+ },
+ getDefaultProps: function () {
+ return {
+ selected: false,
+ active: false
+ };
+ },
+ render: function () {
+ var classes = classNames(
+ 'indicator',
+ {
+ 'selected': this.props.selected,
+ 'active': this.props.selected
+ },
+ this.props.className
+ );
+ return (
+
+ );
+ }
+ }),
+ StepNavigation: React.createClass({
+ type: 'Navigation',
+ render: function () {
+ var classes = classNames(
+ 'step-navigation',
+ this.props.className);
+ return (
+
+ {this.props.children}
+
+ );
+ }
+ })
+};
diff --git a/src/routes.json b/src/routes.json
index a573354f8..4d5bae599 100644
--- a/src/routes.json
+++ b/src/routes.json
@@ -67,6 +67,12 @@
"view": "jobs/jobs",
"title": "Jobs"
},
+ {
+ "name": "teacherregistration",
+ "pattern": "^/register-teacher/?$",
+ "view": "teacherregistration/teacherregistration",
+ "title": "Teacher Registration"
+ },
{
"name": "wedo2",
"pattern": "^/wedo/?$",
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
new file mode 100644
index 000000000..2ad2ccb44
--- /dev/null
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -0,0 +1,129 @@
+var classNames = require('classnames');
+var React = require('react');
+
+var render = require('../../lib/render.jsx');
+
+var formset = require('../../components/formset/formset.jsx');
+ var FormSet = formset.FormSet;
+ var FormStep = formset.FormStep;
+var Page = require('../../components/page/www/page.jsx');
+
+var TeacherRegistration = React.createClass({
+ type: 'TeacherRegistration',
+ getInitialState: function () {
+ return {
+ step: 0
+ }
+ },
+ setStep: function (step) {
+ this.setState({step: step});
+ },
+ render: function () {
+ var classes = classNames(
+ 'teacher-registration',
+ this.props.className);
+ return (
+
+
+ Creating a Teacher Account requires additional information
+ for review.
+ The approval process can take up to 24 hours
+
}
+ key="step1">
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step2">
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step3">
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step4">
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step5">
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step6">
+
+
+
+ Tell us a little how you plan to use Scratch.
+ Why do we ask for this information ?
+ }
+ key="step7">
+
+
+
+ We will send you a confirmation email that will
+ allow you to access your Scratch Teacher Account.
+ }
+ key="step8">
+
+
+
+ );
+ }
+});
+
+render( , document.getElementById('app'));
From 68d49a84dcd781f7a8f255ccc26ea071c4f57b2d Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Wed, 6 Apr 2016 09:58:38 -0400
Subject: [PATCH 03/55] Update FormSet to advance steps onSubmit
---
src/components/formset/formset.jsx | 22 ++++++++----
.../teacherregistration.jsx | 34 +++++++++++++++++++
2 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/components/formset/formset.jsx b/src/components/formset/formset.jsx
index 5b353ac10..dec795256 100644
--- a/src/components/formset/formset.jsx
+++ b/src/components/formset/formset.jsx
@@ -19,6 +19,10 @@ module.exports = {
navigation: null
}
},
+ onSubmit: function (e) {
+ e.preventDefault();
+ this.props.onNextStep();
+ },
render: function () {
var classes = classNames(
'step',
@@ -29,7 +33,11 @@ module.exports = {
{this.props.description}
{this.props.navigation}
- {this.props.children}
+ {React.Children.map(this.props.children, function (child){
+ if (child.type === 'form') {
+ return React.cloneElement(child, {onSubmit: this.onSubmit});
+ }
+ }, this)}
);
}
@@ -63,21 +71,23 @@ module.exports = {
);
var navigation = (
- {this.props.children.map(function (child, id) {
+ {React.Children.map(this.props.children, function (child, id) {
return (
);
- }.bind(this))}
+ }, this)}
);
return (
- {this.props.children.map(function (child, id) {
+ {React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
- return child;
+ return React.cloneElement(child, {onNextStep: function () {
+ this.props.onSetStep(this.props.step + 1);
+ }.bind(this)});
}
- }.bind(this))}
+ }, this)}
);
}
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 2ad2ccb44..5d0819900 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -42,6 +42,7 @@ var TeacherRegistration = React.createClass({
Confirm Password
+
+
+ Lorem ipsum dolor sit amet
+ }
+ key="step8">
+
+
Confirm Your Email
+
+ Click the link in the confirmation email that we
+ sent to the following address:
+ {this.state.email}
+
+
+
+
+
Wait for Approval
+
+ Your information is being reviewed. Please be
+ patient, the approval process can take up to 24hrs.
+
+
+
);
}
From 17a3f1ebcb10b0427d3d4a6bb65eb618a97456c7 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Wed, 6 Apr 2016 09:59:13 -0400
Subject: [PATCH 04/55] Add initial form data
---
src/views/teacherregistration/countries.json | 249 ++++++++++++++++++
.../teacherregistration.jsx | 88 ++++++-
2 files changed, 330 insertions(+), 7 deletions(-)
create mode 100644 src/views/teacherregistration/countries.json
diff --git a/src/views/teacherregistration/countries.json b/src/views/teacherregistration/countries.json
new file mode 100644
index 000000000..11a6035c3
--- /dev/null
+++ b/src/views/teacherregistration/countries.json
@@ -0,0 +1,249 @@
+{
+ "US": "United States",
+ "AF": "Afghanistan",
+ "AX": "Aland Islands",
+ "AL": "Albania",
+ "DZ": "Algeria",
+ "AS": "American Samoa",
+ "AD": "Andorra",
+ "AO": "Angola",
+ "AI": "Anguilla",
+ "AQ": "Antarctica",
+ "AG": "Antigua and Barbuda",
+ "AR": "Argentina",
+ "AM": "Armenia",
+ "AW": "Aruba",
+ "AU": "Australia",
+ "AT": "Austria",
+ "AZ": "Azerbaijan",
+ "BS": "Bahamas",
+ "BQ": "Bonaire, Sint Eustatius and Saba",
+ "BH": "Bahrain",
+ "BD": "Bangladesh",
+ "BB": "Barbados",
+ "BY": "Belarus",
+ "BE": "Belgium",
+ "BZ": "Belize",
+ "BJ": "Benin",
+ "BM": "Bermuda",
+ "BT": "Bhutan",
+ "BO": "Bolivia",
+ "BA": "Bosnia and Herzegovina",
+ "BW": "Botswana",
+ "BV": "Bouvet Island",
+ "BR": "Brazil",
+ "IO": "British Indian Ocean Territory",
+ "BN": "Brunei",
+ "BG": "Bulgaria",
+ "BF": "Burkina Faso",
+ "BI": "Burundi",
+ "KH": "Cambodia",
+ "CM": "Cameroon",
+ "CA": "Canada",
+ "CV": "Cape Verde",
+ "KY": "Cayman Islands",
+ "CF": "Central African Republic",
+ "TD": "Chad",
+ "CL": "Chile",
+ "CN": "China",
+ "CX": "Christmas Island",
+ "CC": "Cocos (Keeling) Islands",
+ "CO": "Colombia",
+ "KM": "Comoros",
+ "CG": "Congo",
+ "CD": "Congo, The Democratic Republic of the",
+ "CK": "Cook Islands",
+ "CR": "Costa Rica",
+ "CI": "Cote d'Ivoire",
+ "HR": "Croatia",
+ "CU": "Cuba",
+ "CY": "Cyprus",
+ "CZ": "Czech Republic",
+ "DK": "Denmark",
+ "DJ": "Djibouti",
+ "DM": "Dominica",
+ "DO": "Dominican Republic",
+ "EC": "Ecuador",
+ "EG": "Egypt",
+ "SV": "El Salvador",
+ "GQ": "Equatorial Guinea",
+ "ER": "Eritrea",
+ "EE": "Estonia",
+ "ET": "Ethiopia",
+ "FK": "Falkland Islands (Malvinas)",
+ "FO": "Faroe Islands",
+ "FJ": "Fiji",
+ "FI": "Finland",
+ "FR": "France",
+ "GF": "French Guiana",
+ "PF": "French Polynesia",
+ "TF": "French Southern Territories",
+ "GA": "Gabon",
+ "GM": "Gambia",
+ "GE": "Georgia",
+ "DE": "Germany",
+ "GH": "Ghana",
+ "GI": "Gibraltar",
+ "GR": "Greece",
+ "GL": "Greenland",
+ "GD": "Grenada",
+ "GP": "Guadeloupe",
+ "GU": "Guam",
+ "GT": "Guatemala",
+ "GG": "Guernsey",
+ "GN": "Guinea",
+ "GW": "Guinea-Bissau",
+ "GY": "Guyana",
+ "HT": "Haiti",
+ "HM": "Heard Island and McDonald Islands",
+ "VA": "Vatican City",
+ "HN": "Honduras",
+ "HK": "Hong Kong",
+ "HU": "Hungary",
+ "IS": "Iceland",
+ "IN": "India",
+ "ID": "Indonesia",
+ "IR": "Iran",
+ "IQ": "Iraq",
+ "IE": "Ireland",
+ "IM": "Isle of Man",
+ "IL": "Israel",
+ "IT": "Italy",
+ "JM": "Jamaica",
+ "JP": "Japan",
+ "JE": "Jersey",
+ "JO": "Jordan",
+ "KZ": "Kazakhstan",
+ "KE": "Kenya",
+ "KI": "Kiribati",
+ "KP": "North Korea",
+ "KR": "South Korea",
+ "KW": "Kuwait",
+ "KG": "Kyrgyzstan",
+ "LA": "Laos",
+ "LV": "Latvia",
+ "LB": "Lebanon",
+ "LS": "Lesotho",
+ "LR": "Liberia",
+ "LY": "Libya",
+ "LI": "Liechtenstein",
+ "LT": "Lithuania",
+ "LU": "Luxembourg",
+ "MO": "Macao",
+ "MK": "Macedonia, The Former Yugoslav Republic of",
+ "MG": "Madagascar",
+ "MW": "Malawi",
+ "MY": "Malaysia",
+ "MV": "Maldives",
+ "ML": "Mali",
+ "MT": "Malta",
+ "MH": "Marshall Islands",
+ "MQ": "Martinique",
+ "MR": "Mauritania",
+ "MU": "Mauritius",
+ "YT": "Mayotte",
+ "MX": "Mexico",
+ "FM": "Micronesia, Federated States of",
+ "MD": "Moldova",
+ "MC": "Monaco",
+ "MN": "Mongolia",
+ "ME": "Montenegro",
+ "MS": "Montserrat",
+ "MA": "Morocco",
+ "MZ": "Mozambique",
+ "MM": "Myanmar",
+ "NA": "Namibia",
+ "NR": "Nauru",
+ "NP": "Nepal",
+ "NL": "Netherlands",
+ "AN": "Netherlands Antilles",
+ "NC": "New Caledonia",
+ "NZ": "New Zealand",
+ "NI": "Nicaragua",
+ "NE": "Niger",
+ "NG": "Nigeria",
+ "NU": "Niue",
+ "NF": "Norfolk Island",
+ "MP": "Northern Mariana Islands",
+ "NO": "Norway",
+ "OM": "Oman",
+ "PK": "Pakistan",
+ "PW": "Palau",
+ "PS": "Palestine, State of",
+ "PA": "Panama",
+ "PG": "Papua New Guinea",
+ "PY": "Paraguay",
+ "PE": "Peru",
+ "PH": "Philippines",
+ "PN": "Pitcairn",
+ "PL": "Poland",
+ "PT": "Portugal",
+ "PR": "Puerto Rico",
+ "QA": "Qatar",
+ "RE": "Reunion",
+ "RO": "Romania",
+ "RU": "Russia",
+ "RW": "Rwanda",
+ "BL": "Saint Barthelemy",
+ "SH": "Saint Helena",
+ "KN": "Saint Kitts and Nevis",
+ "LC": "Saint Lucia",
+ "MF": "Saint Martin",
+ "PM": "Saint Pierre and Miquelon",
+ "VC": "Saint Vincent and the Grenadines",
+ "WS": "Samoa",
+ "SM": "San Marino",
+ "ST": "Sao Tome and Principe",
+ "SA": "Saudi Arabia",
+ "SN": "Senegal",
+ "RS": "Serbia",
+ "SC": "Seychelles",
+ "SL": "Sierra Leone",
+ "SG": "Singapore",
+ "SK": "Slovakia",
+ "SI": "Slovenia",
+ "SB": "Solomon Islands",
+ "SO": "Somalia",
+ "ZA": "South Africa",
+ "GS": "South Georgia and the South Sandwich Islands",
+ "ES": "Spain",
+ "LK": "Sri Lanka",
+ "SD": "Sudan",
+ "SR": "Suriname",
+ "SJ": "Svalbard and Jan Mayen",
+ "SZ": "Swaziland",
+ "SE": "Sweden",
+ "CH": "Switzerland",
+ "SY": "Syria",
+ "TW": "Taiwan",
+ "TJ": "Tajikistan",
+ "TZ": "Tanzania",
+ "TH": "Thailand",
+ "TL": "Timor-Leste",
+ "TG": "Togo",
+ "TK": "Tokelau",
+ "TO": "Tonga",
+ "TT": "Trinidad and Tobago",
+ "TN": "Tunisia",
+ "TR": "Turkey",
+ "TM": "Turkmenistan",
+ "TC": "Turks and Caicos Islands",
+ "TV": "Tuvalu",
+ "UG": "Uganda",
+ "UA": "Ukraine",
+ "AE": "United Arab Emirates",
+ "GB": "United Kingdom",
+ "UM": "United States Minor Outlying Islands",
+ "UY": "Uruguay",
+ "UZ": "Uzbekistan",
+ "VU": "Vanuatu",
+ "VE": "Venezuela",
+ "VN": "Vietnam",
+ "VG": "Virgin Islands, British",
+ "VI": "Virgin Islands, U.S.",
+ "WF": "Wallis and Futuna",
+ "EH": "Western Sahara",
+ "YE": "Yemen",
+ "ZM": "Zambia",
+ "ZW": "Zimbabwe"
+}
\ No newline at end of file
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 5d0819900..35a07a033 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -19,6 +19,10 @@ var TeacherRegistration = React.createClass({
this.setState({step: step});
},
render: function () {
+ var months = [
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 'August', 'September', 'October', 'November', 'December'];
+ var countries = require('./countries.json');
var classes = classNames(
'teacher-registration',
this.props.className);
@@ -53,7 +57,29 @@ var TeacherRegistration = React.createClass({
}
key="step2">
@@ -65,7 +91,10 @@ var TeacherRegistration = React.createClass({
}
key="step3">
@@ -77,7 +106,13 @@ var TeacherRegistration = React.createClass({
}
key="step4">
@@ -89,7 +124,27 @@ var TeacherRegistration = React.createClass({
}
key="step5">
@@ -101,7 +156,26 @@ var TeacherRegistration = React.createClass({
}
key="step6">
@@ -113,7 +187,6 @@ var TeacherRegistration = React.createClass({
}
key="step7">
@@ -125,7 +198,8 @@ var TeacherRegistration = React.createClass({
}
key="step8">
From 350a573fc50721dee582d0d9f5a5aef29927ae18 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Wed, 6 Apr 2016 10:24:35 -0400
Subject: [PATCH 05/55] Move formset into forms
---
src/components/{formset => forms}/formset.jsx | 0
src/components/{formset => forms}/formset.scss | 0
src/views/teacherregistration/teacherregistration.jsx | 2 +-
3 files changed, 1 insertion(+), 1 deletion(-)
rename src/components/{formset => forms}/formset.jsx (100%)
rename src/components/{formset => forms}/formset.scss (100%)
diff --git a/src/components/formset/formset.jsx b/src/components/forms/formset.jsx
similarity index 100%
rename from src/components/formset/formset.jsx
rename to src/components/forms/formset.jsx
diff --git a/src/components/formset/formset.scss b/src/components/forms/formset.scss
similarity index 100%
rename from src/components/formset/formset.scss
rename to src/components/forms/formset.scss
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 35a07a033..edde1913c 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -3,7 +3,7 @@ var React = require('react');
var render = require('../../lib/render.jsx');
-var formset = require('../../components/formset/formset.jsx');
+var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Page = require('../../components/page/www/page.jsx');
From 2ca846f826011fdc085f6c8e219d278b796fedb5 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Wed, 6 Apr 2016 10:56:43 -0400
Subject: [PATCH 06/55] Use Scratch components for the form and clean up
---
src/components/forms/textarea.jsx | 27 +++++
src/components/forms/textarea.scss | 34 ++++++
.../teacherregistration.jsx | 106 ++++++++++--------
3 files changed, 119 insertions(+), 48 deletions(-)
create mode 100644 src/components/forms/textarea.jsx
create mode 100644 src/components/forms/textarea.scss
diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx
new file mode 100644
index 000000000..cce32916b
--- /dev/null
+++ b/src/components/forms/textarea.jsx
@@ -0,0 +1,27 @@
+var React = require('react');
+var classNames = require('classnames');
+
+require('./textarea.scss');
+
+var TextArea = React.createClass({
+ type: 'TextArea',
+ getDefaultProps: function () {
+ return {
+ rows: 5,
+ cols: 40
+ }
+ },
+ render: function () {
+ var classes = classNames(
+ 'textarea',
+ this.props.className
+ );
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = TextArea;
diff --git a/src/components/forms/textarea.scss b/src/components/forms/textarea.scss
new file mode 100644
index 000000000..24d0fc3f6
--- /dev/null
+++ b/src/components/forms/textarea.scss
@@ -0,0 +1,34 @@
+@import "../../colors";
+
+$base-bg: $ui-white;
+$focus-bg: lighten($ui-blue, 35%);
+$fail-bg: lighten($ui-orange, 35%);
+$pass-bg: lighten($ui-aqua, 35%);
+
+.textarea {
+ transition: all 1s ease;
+ margin: .5em 0;
+ border: 1px solid $active-gray;
+ border-radius: 5px;
+ background-color: $base-bg;
+ padding: .75em 1em;
+ color: $type-gray;
+ font-size: .8rem;
+
+ &:focus {
+ transition: all 1s ease;
+ outline: none;
+ border: 1px solid $active-dark-gray;
+ background-color: $focus-bg;
+ }
+
+ &.fail {
+ border: 1px solid $active-dark-gray;
+ background-color: $fail-bg;
+ }
+
+ &.pass {
+ border: 1px solid $active-dark-gray;
+ background-color: $pass-bg;
+ }
+}
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index edde1913c..2cf985bc1 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -3,10 +3,14 @@ var React = require('react');
var render = require('../../lib/render.jsx');
+var Button = require('../../components/forms/button.jsx');
var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
+var Input = require('../../components/forms/input.jsx');
var Page = require('../../components/page/www/page.jsx');
+var Select = require('../../components/forms/select.jsx');
+var TextArea = require('../../components/forms/textarea.jsx');
var TeacherRegistration = React.createClass({
type: 'TeacherRegistration',
@@ -41,12 +45,12 @@ var TeacherRegistration = React.createClass({
key="step1">
Username
-
+
Password
-
+
Confirm Password
-
-
+
+ Next Step
Birth Month
-
+
{months.map(function (name, id) {
return ({name} );
})}
-
+
Birth Yeah
-
+
{Array.apply(null, Array(100)).map(function (v, id) {
return ({2016-id} );
})}
-
- Gender
-
-
-
-
+
+ Gender
+
+ Female
+
+ Male
+
+
Country
-
+
{Object.keys(countries).map(function (code, id) {
return ({countries[code]} );
})}
-
-
+
+ Next Step
First Name
-
+
Last Name
-
-
+
+ Next Step
Phone Number
-
-
+
+
Yes, I consent to lorem ipsum dolor sit amet,
consectetur adipiscing elit.
-
+ Next Step
Organization
-
+
Title / Position
-
+
Type of Organization
{['Elementary School', 'Middle School',
'High School', 'University / College',
'Museum', 'Library', 'Camp'].map(function (type, id) {
var typeId = 'organizationType' + id;
return [
- ,
{type}
];
})}
-
-
+
+
Website URL (not required)
-
-
+
+ Next Step
}
key="step6">
- Country
-
+ Country
+
{Object.keys(countries).map(function (code, id) {
return ({countries[code]} );
})}
-
- Address Line 1
-
- Address Line 2
-
- City
-
- Zip Code
-
- State / Province
-
+
+ Address Line 1
+
+ Address Line 2
+
+ City
+
+ Zip Code
+
+ State / Province
+
{['every','state','in','the','world'].map(function (name, id) {
return {name} ;
})}
-
-
+
+ Next Step
}
key="step7">
-
+ How do you use Scratch?
+
+ Next Step
}
key="step8">
- How do you use Scratch?
-
-
+ Email
+
+ Confirm Email
+
+ Next Step
Date: Wed, 6 Apr 2016 10:57:09 -0400
Subject: [PATCH 07/55] Allow other types other than form in FormStep
---
src/components/forms/formset.jsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/components/forms/formset.jsx b/src/components/forms/formset.jsx
index dec795256..3781d3dfa 100644
--- a/src/components/forms/formset.jsx
+++ b/src/components/forms/formset.jsx
@@ -36,6 +36,8 @@ module.exports = {
{React.Children.map(this.props.children, function (child){
if (child.type === 'form') {
return React.cloneElement(child, {onSubmit: this.onSubmit});
+ } else {
+ return child;
}
}, this)}
From 1eaf6effcc4a80addddb89b787f9723856be36ed Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 7 Apr 2016 13:45:30 -0400
Subject: [PATCH 08/55] Tiny bit of style
---
src/components/forms/label.jsx | 21 +
src/components/forms/label.scss | 3 +
.../teacherregistration.jsx | 417 +++++++++---------
.../teacherregistration.scss | 24 +
4 files changed, 259 insertions(+), 206 deletions(-)
create mode 100644 src/components/forms/label.jsx
create mode 100644 src/components/forms/label.scss
create mode 100644 src/views/teacherregistration/teacherregistration.scss
diff --git a/src/components/forms/label.jsx b/src/components/forms/label.jsx
new file mode 100644
index 000000000..83337dc2d
--- /dev/null
+++ b/src/components/forms/label.jsx
@@ -0,0 +1,21 @@
+var React = require('react');
+var classNames = require('classnames');
+
+require('./label.scss');
+
+var Label = React.createClass({
+ type: 'Label',
+ render: function () {
+ var classes = classNames(
+ 'label',
+ this.props.className
+ );
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = Label;
diff --git a/src/components/forms/label.scss b/src/components/forms/label.scss
new file mode 100644
index 000000000..81a732044
--- /dev/null
+++ b/src/components/forms/label.scss
@@ -0,0 +1,3 @@
+.label {
+ font-weight: bold;
+}
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 2cf985bc1..a6dc0185a 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -8,10 +8,13 @@ var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
+var Label = require('../../components/forms/label.jsx');
var Page = require('../../components/page/www/page.jsx');
var Select = require('../../components/forms/select.jsx');
var TextArea = require('../../components/forms/textarea.jsx');
+require('./teacherregistration.scss');
+
var TeacherRegistration = React.createClass({
type: 'TeacherRegistration',
getInitialState: function () {
@@ -29,217 +32,219 @@ var TeacherRegistration = React.createClass({
var countries = require('./countries.json');
var classes = classNames(
'teacher-registration',
+ 'inner',
this.props.className);
return (
-
-
- Creating a Teacher Account requires additional information
- for review.
- The approval process can take up to 24 hours
- }
- key="step1">
-
- Username
-
- Password
-
- Confirm Password
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step2">
-
- Birth Month
-
- {months.map(function (name, id) {
- return ({name} );
+
+
+
+ Creating a Teacher Account requires additional information
+ for review.
+ The approval process can take up to 24 hours
+ }
+ key="step1">
+
+ Username
+
+ Password
+
+ Confirm Password
+
+ Next Step
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step2">
+
+ Birth Month
+
+ {months.map(function (name, id) {
+ return ({name} );
+ })}
+
+ Birth Yeah
+
+ {Array.apply(null, Array(100)).map(function (v, id) {
+ return ({2016-id} );
+ })}
+
+ Gender
+
+ Female
+
+ Male
+
+
+ Country
+
+ {Object.keys(countries).map(function (code, id) {
+ return ({countries[code]} );
+ })}
+
+ Next Step
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step3">
+
+ First Name
+
+ Last Name
+
+ Next Step
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step4">
+
+ Phone Number
+
+
+
+ Yes, I consent to lorem ipsum dolor sit amet,
+ consectetur adipiscing elit.
+
+ Next Step
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step5">
+
+ Organization
+
+ Title / Position
+
+ Type of Organization
+ {['Elementary School', 'Middle School',
+ 'High School', 'University / College',
+ 'Museum', 'Library', 'Camp'].map(function (type, id) {
+ var typeId = 'organizationType' + id;
+ return [
+ ,
+ {type}
+ ];
})}
-
- Birth Yeah
-
- {Array.apply(null, Array(100)).map(function (v, id) {
- return ({2016-id} );
- })}
-
- Gender
-
- Female
-
- Male
-
-
- Country
-
- {Object.keys(countries).map(function (code, id) {
- return ({countries[code]} );
- })}
-
- Next Step
-
-
-
+
+ Website URL (not required)
+
+ Next Step
+
+
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }
+ key="step6">
+
+ Country
+
+ {Object.keys(countries).map(function (code, id) {
+ return ({countries[code]} );
+ })}
+
+ Address Line 1
+
+ Address Line 2
+
+ City
+
+ Zip Code
+
+ State / Province
+
+ {['every','state','in','the','world'].map(function (name, id) {
+ return {name} ;
+ })}
+
+ Next Step
+
+
+
+ Tell us a little how you plan to use Scratch.
+ Why do we ask for this information ?
+ }
+ key="step7">
+
+ How do you use Scratch?
+
+ Next Step
+
+
+
+ We will send you a confirmation email that will
+ allow you to access your Scratch Teacher Account.
+ }
+ key="step8">
+
+ Email
+
+ Confirm Email
+
+ Next Step
+
+
+
+ Lorem ipsum dolor sit amet
+ }
+ key="step8">
+
+
Confirm Your Email
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
-
}
- key="step3">
-
- First Name
-
- Last Name
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step4">
-
- Phone Number
-
-
-
- Yes, I consent to lorem ipsum dolor sit amet,
- consectetur adipiscing elit.
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step5">
-
- Organization
-
- Title / Position
-
- Type of Organization
- {['Elementary School', 'Middle School',
- 'High School', 'University / College',
- 'Museum', 'Library', 'Camp'].map(function (type, id) {
- var typeId = 'organizationType' + id;
- return [
- ,
- {type}
- ];
- })}
-
-
- Website URL (not required)
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step6">
-
- Country
-
- {Object.keys(countries).map(function (code, id) {
- return ({countries[code]} );
- })}
-
- Address Line 1
-
- Address Line 2
-
- City
-
- Zip Code
-
- State / Province
-
- {['every','state','in','the','world'].map(function (name, id) {
- return {name} ;
- })}
-
- Next Step
-
-
-
- Tell us a little how you plan to use Scratch.
- Why do we ask for this information ?
- }
- key="step7">
-
- How do you use Scratch?
-
- Next Step
-
-
-
- We will send you a confirmation email that will
- allow you to access your Scratch Teacher Account.
- }
- key="step8">
-
- Email
-
- Confirm Email
-
- Next Step
-
-
-
- Lorem ipsum dolor sit amet
- }
- key="step8">
-
-
Confirm Your Email
-
- Click the link in the confirmation email that we
- sent to the following address:
- {this.state.email}
-
-
-
Wrong email?
-
Having trouble?
+ Click the link in the confirmation email that we
+ sent to the following address:
+
{this.state.email}
+
+
-
-
-
Wait for Approval
-
- Your information is being reviewed. Please be
- patient, the approval process can take up to 24hrs.
-
-
-
-
+
+
Wait for Approval
+
+ Your information is being reviewed. Please be
+ patient, the approval process can take up to 24hrs.
+
+
+
+
+
);
}
});
diff --git a/src/views/teacherregistration/teacherregistration.scss b/src/views/teacherregistration/teacherregistration.scss
new file mode 100644
index 000000000..cdb1eeec5
--- /dev/null
+++ b/src/views/teacherregistration/teacherregistration.scss
@@ -0,0 +1,24 @@
+.teacher-registration {
+ .formset {
+ margin: 0 auto;
+ width: 50%;
+ }
+
+ .button,
+ .label {
+ display: block;
+ width: 100%;
+ }
+
+ input {
+ &[type=radio],
+ &[type=checkbox] {
+ + .label {
+ display: inline-block;
+ margin-right: 20px;
+ margin-left: 10px;
+ width: auto;
+ }
+ }
+ }
+}
From de3151924c4de794e6b56a79afb4fb98676b2cff Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Mon, 9 May 2016 10:42:01 -0400
Subject: [PATCH 09/55] Lint
---
src/components/forms/formset.jsx | 6 +++---
.../teacherregistration/teacherregistration.jsx | 16 ++++++++--------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/components/forms/formset.jsx b/src/components/forms/formset.jsx
index 3781d3dfa..348e12e1c 100644
--- a/src/components/forms/formset.jsx
+++ b/src/components/forms/formset.jsx
@@ -17,7 +17,7 @@ module.exports = {
getDefaultProps: function () {
return {
navigation: null
- }
+ };
},
onSubmit: function (e) {
e.preventDefault();
@@ -47,14 +47,14 @@ module.exports = {
FormSet: React.createClass({
type: 'FormSet',
propTypes: {
- step: function(props, propName, componentName) {
+ step: function (props, propName, componentName) {
var stepValidator = function (props, propName) {
if (props[propName] > -1 && props[propName] < props.children.length) {
return null;
} else {
return new Error('Prop `step` out of range');
}
- }
+ };
return (
React.PropTypes.number.isRequired(props, propName, componentName) ||
stepValidator(props, propName, componentName)
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index a6dc0185a..e7b49a21a 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -5,8 +5,8 @@ var render = require('../../lib/render.jsx');
var Button = require('../../components/forms/button.jsx');
var formset = require('../../components/forms/formset.jsx');
- var FormSet = formset.FormSet;
- var FormStep = formset.FormStep;
+var FormSet = formset.FormSet;
+var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
var Label = require('../../components/forms/label.jsx');
var Page = require('../../components/page/www/page.jsx');
@@ -20,7 +20,7 @@ var TeacherRegistration = React.createClass({
getInitialState: function () {
return {
step: 0
- }
+ };
},
setStep: function (step) {
this.setState({step: step});
@@ -69,7 +69,7 @@ var TeacherRegistration = React.createClass({
{months.map(function (name, id) {
return ({name} );
- })}
+ })}
Birth Yeah
@@ -88,7 +88,7 @@ var TeacherRegistration = React.createClass({
{Object.keys(countries).map(function (code, id) {
return ({countries[code]} );
- })}
+ })}
Next Step
@@ -123,7 +123,7 @@ var TeacherRegistration = React.createClass({
Yes, I consent to lorem ipsum dolor sit amet,
consectetur adipiscing elit.
- Next Step
+ Next Step
,
{type}
];
- })}
+ })}
Website URL (not required)
@@ -170,7 +170,7 @@ var TeacherRegistration = React.createClass({
{Object.keys(countries).map(function (code, id) {
return ({countries[code]} );
- })}
+ })}
Address Line 1
From b5c615b1fa6a8ac7ffe6778d70ce29e4c4504f20 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 12 May 2016 17:42:04 -0400
Subject: [PATCH 10/55] Use formsy-react for validation
Complete validation for username/password form (except for checking if a username exists).
---
package.json | 2 +
src/components/forms/checkbox-group.jsx | 18 +
src/components/forms/checkbox.jsx | 18 +
src/components/forms/form.jsx | 24 +
src/components/forms/formset.jsx | 19 +-
src/components/forms/input.jsx | 15 +-
src/components/forms/radio-group.jsx | 18 +
src/components/forms/select.jsx | 7 +-
src/components/forms/textarea.jsx | 13 +-
src/components/forms/validateMixin.jsx | 24 +
src/components/forms/validations.js | 10 +
.../teacherregistration.jsx | 528 +++++++++++-------
12 files changed, 451 insertions(+), 245 deletions(-)
create mode 100644 src/components/forms/checkbox-group.jsx
create mode 100644 src/components/forms/checkbox.jsx
create mode 100644 src/components/forms/form.jsx
create mode 100644 src/components/forms/radio-group.jsx
create mode 100644 src/components/forms/validateMixin.jsx
create mode 100644 src/components/forms/validations.js
diff --git a/package.json b/package.json
index 6b39e6348..08f47b80a 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,8 @@
"exenv": "1.2.0",
"fastly": "1.2.1",
"file-loader": "0.8.4",
+ "formsy-react": "0.18.0",
+ "formsy-react-components": "0.7.1",
"git-bundle-sha": "0.0.2",
"glob": "5.0.15",
"json-loader": "0.5.2",
diff --git a/src/components/forms/checkbox-group.jsx b/src/components/forms/checkbox-group.jsx
new file mode 100644
index 000000000..0845831a3
--- /dev/null
+++ b/src/components/forms/checkbox-group.jsx
@@ -0,0 +1,18 @@
+var classNames = require('classnames');
+var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
+var React = require('react');
+
+var CheckboxGroup = React.createClass({
+ type: 'CheckboxGroup',
+ render: function () {
+ var classes = classNames(
+ 'checkbox-group',
+ this.props.className
+ );
+ return (
+
+ );
+ }
+});
+
+module.exports = CheckboxGroup;
diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx
new file mode 100644
index 000000000..5de3c0102
--- /dev/null
+++ b/src/components/forms/checkbox.jsx
@@ -0,0 +1,18 @@
+var classNames = require('classnames');
+var FRCCheckbox = require('formsy-react-components').Checkbox;
+var React = require('react');
+
+var Checkbox = React.createClass({
+ type: 'Checkbox',
+ render: function () {
+ var classes = classNames(
+ 'checkbox',
+ this.props.className
+ );
+ return (
+
+ );
+ }
+});
+
+module.exports = Checkbox;
diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx
new file mode 100644
index 000000000..948def2a5
--- /dev/null
+++ b/src/components/forms/form.jsx
@@ -0,0 +1,24 @@
+var classNames = require('classnames');
+var Formsy = require('formsy-react');
+var React = require('react');
+var validations = require('./validations');
+
+for (var validation in validations) {
+ Formsy.addValidationRule(validation, validations[validation]);
+}
+
+var Form = React.createClass({
+ render: function () {
+ var classes = classNames(
+ 'form',
+ this.props.className
+ );
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = Form;
diff --git a/src/components/forms/formset.jsx b/src/components/forms/formset.jsx
index 348e12e1c..d366051c9 100644
--- a/src/components/forms/formset.jsx
+++ b/src/components/forms/formset.jsx
@@ -19,10 +19,6 @@ module.exports = {
navigation: null
};
},
- onSubmit: function (e) {
- e.preventDefault();
- this.props.onNextStep();
- },
render: function () {
var classes = classNames(
'step',
@@ -33,13 +29,7 @@ module.exports = {
{this.props.description}
{this.props.navigation}
- {React.Children.map(this.props.children, function (child){
- if (child.type === 'form') {
- return React.cloneElement(child, {onSubmit: this.onSubmit});
- } else {
- return child;
- }
- }, this)}
+ {this.props.children}
);
}
@@ -85,9 +75,10 @@ module.exports = {
{React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
- return React.cloneElement(child, {onNextStep: function () {
- this.props.onSetStep(this.props.step + 1);
- }.bind(this)});
+ var props = {
+ navigation: navigation
+ };
+ return React.cloneElement(child, props);
}
}, this)}
diff --git a/src/components/forms/input.jsx b/src/components/forms/input.jsx
index e35a09e2e..0b06c5438 100644
--- a/src/components/forms/input.jsx
+++ b/src/components/forms/input.jsx
@@ -1,22 +1,25 @@
-var React = require('react');
var classNames = require('classnames');
+var FRCInput = require('formsy-react-components').Input;
+var React = require('react');
+var validateMixin = require('./validateMixin.jsx');
require('./input.scss');
var Input = React.createClass({
type: 'Input',
- propTypes: {
-
+ getDefaultProps: function () {
+ return {};
},
render: function () {
var classes = classNames(
'input',
this.props.className
);
- return (
-
+ return (this.props.type === 'submit' || this.props.noformsy ?
+ :
+
);
}
});
-module.exports = Input;
+module.exports = validateMixin(Input);
diff --git a/src/components/forms/radio-group.jsx b/src/components/forms/radio-group.jsx
new file mode 100644
index 000000000..1918f5422
--- /dev/null
+++ b/src/components/forms/radio-group.jsx
@@ -0,0 +1,18 @@
+var classNames = require('classnames');
+var FRCRadioGroup = require('formsy-react-components').RadioGroup;
+var React = require('react');
+
+var RadioGroup = React.createClass({
+ type: 'RadioGroup',
+ render: function () {
+ var classes = classNames(
+ 'radio-group',
+ this.props.className
+ );
+ return (
+
+ );
+ }
+});
+
+module.exports = RadioGroup;
diff --git a/src/components/forms/select.jsx b/src/components/forms/select.jsx
index 62b6d9aa8..1a2d43833 100644
--- a/src/components/forms/select.jsx
+++ b/src/components/forms/select.jsx
@@ -1,5 +1,6 @@
-var React = require('react');
var classNames = require('classnames');
+var FRCSelect = require('formsy-react-components').Select;
+var React = require('react');
require('./select.scss');
@@ -14,9 +15,7 @@ var Select = React.createClass({
this.props.className
);
return (
-
- {this.props.children}
-
+
);
}
});
diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx
index cce32916b..8919ee2ef 100644
--- a/src/components/forms/textarea.jsx
+++ b/src/components/forms/textarea.jsx
@@ -1,25 +1,18 @@
-var React = require('react');
var classNames = require('classnames');
+var FRCTextarea = require('formsy-react-components').Textarea;
+var React = require('react');
require('./textarea.scss');
var TextArea = React.createClass({
type: 'TextArea',
- getDefaultProps: function () {
- return {
- rows: 5,
- cols: 40
- }
- },
render: function () {
var classes = classNames(
'textarea',
this.props.className
);
return (
-
- {this.props.children}
-
+
);
}
});
diff --git a/src/components/forms/validateMixin.jsx b/src/components/forms/validateMixin.jsx
new file mode 100644
index 000000000..72bcdc557
--- /dev/null
+++ b/src/components/forms/validateMixin.jsx
@@ -0,0 +1,24 @@
+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
new file mode 100644
index 000000000..38985788c
--- /dev/null
+++ b/src/components/forms/validations.js
@@ -0,0 +1,10 @@
+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/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index e7b49a21a..2ddccecf9 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -1,35 +1,337 @@
var classNames = require('classnames');
+var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var Button = require('../../components/forms/button.jsx');
+var Checkbox = require('../../components/forms/checkbox.jsx');
+var CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
+var Form = require('../../components/forms/form.jsx');
var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
var Label = require('../../components/forms/label.jsx');
var Page = require('../../components/page/www/page.jsx');
+var RadioGroup = require('../../components/forms/radio-group.jsx');
var Select = require('../../components/forms/select.jsx');
var TextArea = require('../../components/forms/textarea.jsx');
+var COUNTRIES = require('./countries.json');
require('./teacherregistration.scss');
+
+var UsernameStep = React.createClass({
+ render: function () {
+ return (
+
+ Creating a Teacher Account requires additional information
+ for review.
+ The approval process can take up to 24 hours
+ }>
+
+
+
+
+ Next Step
+
+
+ );
+ }
+});
+var DemographicsStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ var countryOptions = Object.keys(COUNTRIES).map(function (code) {
+ return {value: code, label: COUNTRIES[code]};
+ });
+ var monthOptions = [
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 'August', 'September', 'October', 'November', 'December'
+ ].map(function (label, id) {
+ return {value: id+1, label: label};
+ });
+ var yearOptions = Array.apply(null, Array(100)).map(function (v, id) {
+ var year = 2016 - id;
+ return {value: year, label: year};
+ });
+ return (
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }>
+
+
+
+
+
+
+ Next Step
+
+
+ );
+ }
+});
+var NameStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ return (
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }>
+
+ First Name
+
+ Last Name
+
+ Next Step
+
+
+ );
+ }
+});
+var PhoneNumberStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ return (
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }>
+
+ Phone Number
+
+
+
+ Yes, I consent to lorem ipsum dolor sit amet,
+ consectetur adipiscing elit.
+
+ Next Step
+
+
+ );
+ }
+});
+var OrganizationStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ var organizationOptions = [
+ 'Elementary School', 'Middle School', 'High School', 'University / College',
+ 'Museum', 'Library', 'Camp'
+ ].map(function (type) { return {value: type, label: type}; });
+ return (
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }>
+
+ Organization
+
+ Title / Position
+
+
+
+
+ Website URL (not required)
+
+ Next Step
+
+
+ );
+ }
+});
+var AddressStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ var countryOptions = Object.keys(COUNTRIES).map(function (code) {
+ return {value: code, label: COUNTRIES[code]};
+ });
+ var stateOptions = ['every','state','in','the','world'].map(function (name) {
+ return {value: name, label: name};
+ });
+ return (
+
+ Your responses to these questions will be kept private.
+ Why do we ask for this information ?
+ }>
+
+ Country
+
+ Address Line 1
+
+ Address Line 2
+
+ City
+
+ Zip Code
+
+ State / Province
+
+ Next Step
+
+
+ );
+ }
+});
+var UseScratchStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ return (
+
+ Tell us a little how you plan to use Scratch.
+ Why do we ask for this information ?
+ }>
+
+ How do you use Scratch?
+
+ Next Step
+
+
+ );
+ }
+});
+var EmailStep = React.createClass({
+ onSubmit: function () {
+ this.props.onNextStep();
+ },
+ render: function () {
+ return (
+
+ We will send you a confirmation email that will
+ allow you to access your Scratch Teacher Account.
+ }>
+
+ Email
+
+ Confirm Email
+
+ Next Step
+
+
+ );
+ }
+});
+var LastStep = React.createClass({
+ render: function () {
+ return (
+
+ Lorem ipsum dolor sit amet
+ }>
+
+
Confirm Your Email
+
+ Click the link in the confirmation email that we
+ sent to the following address:
+ {this.state.email}
+
+
+
+
+
Wait for Approval
+
+ Your information is being reviewed. Please be
+ patient, the approval process can take up to 24hrs.
+
+
+
+ );
+ }
+});
+
+
var TeacherRegistration = React.createClass({
type: 'TeacherRegistration',
getInitialState: function () {
return {
- step: 0
+ step: 0,
+ formData: {}
};
},
setStep: function (step) {
this.setState({step: step});
},
+ advanceStep: function (formData) {
+ formData = formData || {};
+ this.setState({
+ step: this.state.step + 1,
+ formData: defaults({}, formData, this.state.formData)
+ });
+ },
render: function () {
- var months = [
- 'January', 'February', 'March', 'April', 'May', 'June', 'July',
- 'August', 'September', 'October', 'November', 'December'];
- var countries = require('./countries.json');
var classes = classNames(
'teacher-registration',
'inner',
@@ -37,212 +339,16 @@ var TeacherRegistration = React.createClass({
return (
-
- Creating a Teacher Account requires additional information
- for review.
- The approval process can take up to 24 hours
- }
- key="step1">
-
- Username
-
- Password
-
- Confirm Password
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step2">
-
- Birth Month
-
- {months.map(function (name, id) {
- return ({name} );
- })}
-
- Birth Yeah
-
- {Array.apply(null, Array(100)).map(function (v, id) {
- return ({2016-id} );
- })}
-
- Gender
-
- Female
-
- Male
-
-
- Country
-
- {Object.keys(countries).map(function (code, id) {
- return ({countries[code]} );
- })}
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step3">
-
- First Name
-
- Last Name
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step4">
-
- Phone Number
-
-
-
- Yes, I consent to lorem ipsum dolor sit amet,
- consectetur adipiscing elit.
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step5">
-
- Organization
-
- Title / Position
-
- Type of Organization
- {['Elementary School', 'Middle School',
- 'High School', 'University / College',
- 'Museum', 'Library', 'Camp'].map(function (type, id) {
- var typeId = 'organizationType' + id;
- return [
- ,
- {type}
- ];
- })}
-
-
- Website URL (not required)
-
- Next Step
-
-
-
- Your responses to these questions will be kept private.
- Why do we ask for this information ?
- }
- key="step6">
-
- Country
-
- {Object.keys(countries).map(function (code, id) {
- return ({countries[code]} );
- })}
-
- Address Line 1
-
- Address Line 2
-
- City
-
- Zip Code
-
- State / Province
-
- {['every','state','in','the','world'].map(function (name, id) {
- return {name} ;
- })}
-
- Next Step
-
-
-
- Tell us a little how you plan to use Scratch.
- Why do we ask for this information ?
- }
- key="step7">
-
- How do you use Scratch?
-
- Next Step
-
-
-
- We will send you a confirmation email that will
- allow you to access your Scratch Teacher Account.
- }
- key="step8">
-
- Email
-
- Confirm Email
-
- Next Step
-
-
-
- Lorem ipsum dolor sit amet
- }
- key="step8">
-
-
Confirm Your Email
-
- Click the link in the confirmation email that we
- sent to the following address:
- {this.state.email}
-
-
-
-
-
Wait for Approval
-
- Your information is being reviewed. Please be
- patient, the approval process can take up to 24hrs.
-
-
-
+ step={this.state.step}>
+
+
+
+
+
+
+
+
+
);
From 175a71711d72c167196c6e1e5113be5005f817a0 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 12 May 2016 17:42:28 -0400
Subject: [PATCH 11/55] Update existing forms on the site to use formsy
Or not in the case of the search form
---
.../languagechooser/languagechooser.jsx | 25 +++++++++----------
src/components/login/login.jsx | 14 ++++-------
src/components/navigation/www/navigation.jsx | 4 ++-
3 files changed, 20 insertions(+), 23 deletions(-)
diff --git a/src/components/languagechooser/languagechooser.jsx b/src/components/languagechooser/languagechooser.jsx
index 765f883ba..8fbd213b5 100644
--- a/src/components/languagechooser/languagechooser.jsx
+++ b/src/components/languagechooser/languagechooser.jsx
@@ -4,6 +4,7 @@ var React = require('react');
var Api = require('../../mixins/api.jsx');
var jar = require('../../lib/jar.js');
var languages = require('../../../languages.json');
+var Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx');
/**
@@ -20,9 +21,8 @@ var LanguageChooser = React.createClass({
locale: window._locale
};
},
- onSetLanguage: function (e) {
- e.preventDefault();
- jar.set('scratchlanguage', e.target.value);
+ onSetLanguage: function (name, value) {
+ jar.set('scratchlanguage', value);
window.location.reload();
},
render: function () {
@@ -30,17 +30,16 @@ var LanguageChooser = React.createClass({
'language-chooser',
this.props.className
);
-
+ var languageOptions = Object.keys(this.props.languages).map(function (value) {
+ return {value: value, label: this.props.languages[value]};
+ }.bind(this));
return (
-
-
- {Object.keys(this.props.languages).map(function (value) {
- return
- {this.props.languages[value]}
- ;
- }.bind(this))}
-
-
+
+
+
);
}
});
diff --git a/src/components/login/login.jsx b/src/components/login/login.jsx
index d12ce2361..c9cb031d9 100644
--- a/src/components/login/login.jsx
+++ b/src/components/login/login.jsx
@@ -1,9 +1,9 @@
var React = require('react');
-var ReactDOM = require('react-dom');
var FormattedMessage = require('react-intl').FormattedMessage;
var log = require('../../lib/log.js');
+var Form = require('../forms/form.jsx');
var Input = require('../forms/input.jsx');
var Button = require('../forms/button.jsx');
var Spinner = require('../spinner/spinner.jsx');
@@ -21,13 +21,9 @@ var Login = React.createClass({
waiting: false
};
},
- handleSubmit: function (event) {
- event.preventDefault();
+ handleSubmit: function (formData) {
this.setState({waiting: true});
- this.props.onLogIn({
- 'username': ReactDOM.findDOMNode(this.refs.username).value,
- 'password': ReactDOM.findDOMNode(this.refs.password).value
- }, function (err) {
+ this.props.onLogIn(formData, function (err) {
if (err) log.error(err);
this.setState({waiting: false});
}.bind(this));
@@ -39,7 +35,7 @@ var Login = React.createClass({
}
return (
-
+
{error}
-
+
);
}
diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx
index 19073ca74..25fb403eb 100644
--- a/src/components/navigation/www/navigation.jsx
+++ b/src/components/navigation/www/navigation.jsx
@@ -11,6 +11,7 @@ var Api = require('../../../mixins/api.jsx');
var Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx');
+var Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx');
@@ -220,7 +221,8 @@ var Navigation = React.createClass({
+ name="q"
+ noformsy />
{this.props.session.status === sessionActions.Status.FETCHED ? (
From bcda08201451156f9ea28ad4c8f6d1e0a2fc6097 Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Fri, 13 May 2016 18:48:21 -0400
Subject: [PATCH 12/55] Add validation to the rest of the form
---
src/components/forms/checkbox-group.jsx | 3 +-
src/components/forms/checkbox.jsx | 3 +-
src/components/forms/form.jsx | 5 +
src/components/forms/radio-group.jsx | 3 +-
src/components/forms/select.jsx | 10 +-
src/components/forms/textarea.jsx | 3 +-
src/components/navigation/www/navigation.jsx | 1 -
.../teacherregistration.jsx | 175 +++++++++---------
8 files changed, 112 insertions(+), 91 deletions(-)
diff --git a/src/components/forms/checkbox-group.jsx b/src/components/forms/checkbox-group.jsx
index 0845831a3..87b08d41e 100644
--- a/src/components/forms/checkbox-group.jsx
+++ b/src/components/forms/checkbox-group.jsx
@@ -1,6 +1,7 @@
var classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react');
+var validateMixin = require('./validateMixin.jsx');
var CheckboxGroup = React.createClass({
type: 'CheckboxGroup',
@@ -15,4 +16,4 @@ var CheckboxGroup = React.createClass({
}
});
-module.exports = CheckboxGroup;
+module.exports = validateMixin(CheckboxGroup);
diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx
index 5de3c0102..38abfdd45 100644
--- a/src/components/forms/checkbox.jsx
+++ b/src/components/forms/checkbox.jsx
@@ -1,6 +1,7 @@
var classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react');
+var validateMixin = require('./validateMixin.jsx');
var Checkbox = React.createClass({
type: 'Checkbox',
@@ -15,4 +16,4 @@ var Checkbox = React.createClass({
}
});
-module.exports = Checkbox;
+module.exports = validateMixin(Checkbox);
diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx
index 948def2a5..319c559f4 100644
--- a/src/components/forms/form.jsx
+++ b/src/components/forms/form.jsx
@@ -8,6 +8,11 @@ for (var validation in validations) {
}
var Form = React.createClass({
+ getDefaultProps: function () {
+ return {
+ noValidate: true
+ };
+ },
render: function () {
var classes = classNames(
'form',
diff --git a/src/components/forms/radio-group.jsx b/src/components/forms/radio-group.jsx
index 1918f5422..430b27e59 100644
--- a/src/components/forms/radio-group.jsx
+++ b/src/components/forms/radio-group.jsx
@@ -1,6 +1,7 @@
var classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react');
+var validateMixin = require('./validateMixin.jsx');
var RadioGroup = React.createClass({
type: 'RadioGroup',
@@ -15,4 +16,4 @@ var RadioGroup = React.createClass({
}
});
-module.exports = RadioGroup;
+module.exports = validateMixin(RadioGroup);
diff --git a/src/components/forms/select.jsx b/src/components/forms/select.jsx
index 1a2d43833..d87e76c14 100644
--- a/src/components/forms/select.jsx
+++ b/src/components/forms/select.jsx
@@ -1,6 +1,8 @@
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');
require('./select.scss');
@@ -14,10 +16,14 @@ var Select = React.createClass({
'select',
this.props.className
);
+ var props = this.props;
+ if (this.props.required && !this.props.value) {
+ props = defaults({}, this.props, {value: this.props.options[0].value});
+ }
return (
-
+
);
}
});
-module.exports = Select;
+module.exports = validateMixin(Select);
diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx
index 8919ee2ef..56c1a8c25 100644
--- a/src/components/forms/textarea.jsx
+++ b/src/components/forms/textarea.jsx
@@ -1,6 +1,7 @@
var classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react');
+var validateMixin = require('./validateMixin.jsx');
require('./textarea.scss');
@@ -17,4 +18,4 @@ var TextArea = React.createClass({
}
});
-module.exports = TextArea;
+module.exports = validateMixin(TextArea);
diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx
index 25fb403eb..f7319d2d6 100644
--- a/src/components/navigation/www/navigation.jsx
+++ b/src/components/navigation/www/navigation.jsx
@@ -11,7 +11,6 @@ var Api = require('../../../mixins/api.jsx');
var Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx');
-var Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx');
diff --git a/src/views/teacherregistration/teacherregistration.jsx b/src/views/teacherregistration/teacherregistration.jsx
index 2ddccecf9..75091356e 100644
--- a/src/views/teacherregistration/teacherregistration.jsx
+++ b/src/views/teacherregistration/teacherregistration.jsx
@@ -12,7 +12,6 @@ var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
-var Label = require('../../components/forms/label.jsx');
var Page = require('../../components/page/www/page.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx');
var Select = require('../../components/forms/select.jsx');
@@ -32,7 +31,7 @@ var UsernameStep = React.createClass({
for review.
The approval process can take up to 24 hours
}>
-
+
+ required />
Next Step
@@ -74,8 +73,11 @@ var UsernameStep = React.createClass({
}
});
var DemographicsStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
+ getInitialState: function () {
+ return {otherDisabled: true};
+ },
+ onChooseGender: function (name, gender) {
+ this.setState({otherDisabled: gender !== 'other'});
},
render: function () {
var countryOptions = Object.keys(COUNTRIES).map(function (code) {
@@ -98,19 +100,24 @@ var DemographicsStep = React.createClass({
Your responses to these questions will be kept private.
Why do we ask for this information ?
}>
-
-
-
+
+
+
-
-
+ required />
+
+
Next Step
@@ -118,9 +125,6 @@ var DemographicsStep = React.createClass({
}
});
var NameStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
- },
render: function () {
return (
?
}>
-
- First Name
-
- Last Name
-
+
+
+
Next Step
@@ -141,9 +143,6 @@ var NameStep = React.createClass({
}
});
var PhoneNumberStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
- },
render: function () {
return (
?
}>
-
- Phone Number
-
-
-
- Yes, I consent to lorem ipsum dolor sit amet,
- consectetur adipiscing elit.
-
+
+
+
Next Step
@@ -167,13 +169,18 @@ var PhoneNumberStep = React.createClass({
}
});
var OrganizationStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
+ getInitialState: function () {
+ return {
+ otherDisabled: true
+ };
+ },
+ onChooseOrganization: function (name, values) {
+ this.setState({otherDisabled: values.indexOf('Other') === -1});
},
render: function () {
var organizationOptions = [
'Elementary School', 'Middle School', 'High School', 'University / College',
- 'Museum', 'Library', 'Camp'
+ 'Museum', 'Library', 'Camp', 'Other'
].map(function (type) { return {value: type, label: type}; });
return (
?
}>
-
- Organization
-
- Title / Position
-
+
+
+
-
-
- Website URL (not required)
-
+ name="organizationType"
+ value={[]}
+ options={organizationOptions}
+ onChange={this.onChooseOrganization}
+ required />
+
+
Next Step
@@ -200,9 +210,6 @@ var OrganizationStep = React.createClass({
}
});
var AddressStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
- },
render: function () {
var countryOptions = Object.keys(COUNTRIES).map(function (code) {
return {value: code, label: COUNTRIES[code]};
@@ -210,6 +217,8 @@ var AddressStep = React.createClass({
var stateOptions = ['every','state','in','the','world'].map(function (name) {
return {value: name, label: name};
});
+ var stateDefault = 'Please select one of...';
+ stateOptions = [{label: stateDefault}].concat(stateOptions);
return (
?
}>
-
- Country
-
- Address Line 1
-
- Address Line 2
-
- City
-
- Zip Code
-
- State / Province
-
+
+
+
+
+
+
+
Next Step
@@ -237,9 +240,6 @@ var AddressStep = React.createClass({
}
});
var UseScratchStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
- },
render: function () {
return (
Tell us a little how you plan to use Scratch.
Why do we ask for this information ?
- }>
-
- How do you use Scratch?
-
+
+ }>
+
+
Next Step
@@ -258,9 +258,6 @@ var UseScratchStep = React.createClass({
}
});
var EmailStep = React.createClass({
- onSubmit: function () {
- this.props.onNextStep();
- },
render: function () {
return (
confirmation email that will
allow you to access your Scratch Teacher Account.
}>
-
- Email
-
- Confirm Email
-
+
+
+
Next Step
@@ -293,7 +300,7 @@ var LastStep = React.createClass({
Click the link in the confirmation email that we
sent to the following address:
- {this.state.email}
+ {this.props.formData.email}
Wrong email?
@@ -340,15 +347,15 @@ var TeacherRegistration = React.createClass({
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
From fe74208f32a2e96321101bc90862adc5f5869eac Mon Sep 17 00:00:00 2001
From: Ray Schamp
Date: Thu, 2 Jun 2016 15:25:02 -0400
Subject: [PATCH 13/55] 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 ?
}>
-
+
41^@s6H9%1z00009a7bBm000XU
z000XU0RWnu7ytku07*naRCwB~op+cO)%o~8Gq-GE8w(4&uq)j{?1;S=Y_UX*@oU%E
zON^BWCTcX;VlWzeiyC`01`BFzM8z5vm7)j&0#cXlcjnaJALq=ycMEEM=049nbNB9@
zJ2U5;_dRdAy~0`H71k6NPm9+ze#oQ=M#a*9}nzsC%p3`?*F(m
zUf{X*6zftUk4G~pNx`Av4jk~4axP!~Hru^a1F?ZffQlfN&?UgSi}OSU^u=awY6aPg2O?77Vb?0?r9
z=xoWi{{Fw*o9XvX1DXNBh(G^{4q-@V(?pTN7=uy=c7vYU0y<%i+HqQOOBF@xIa9J%lBS~FcxVHS~=m+)}pQD?qhEw4K&;|)!o0=
z7-L9l%{}WnIif5i)S7$ObZ}&w;$G$(wdZU1I22M
zP4-&5&y)hKX}j@846yM@CxNwoq!{;qV?b-HS0X5te{Y2VDf!ojAJV4a)Dg+pwOAWq
ztRHN~qHTaSDKsJ4SpS*Z&Yjso9;a-qv1lEjHE8X|l9VA?)y_}&kP+d0LImZ5
zA0{(q7*D+VGF)_duknM{SfxQL0;NbPMJT0fhoXJQv}s&Dr=0sQ9m(;>??&|T2G}Ua
zPlt`9rmlup7DQZJzZ|U$IUzabpv_391CBd%E2hu*iiiF)>5rdw=S%>O
z+`o#|&5FO=^*FE1oX$R5y@f>(1d6~YQbrLXu~uP?5@5LX&NdeMoYFHA=zp_$uW}zxyUJ%E1Q}Sz@r^6ko_pf&sMF1_I9k0
z9QDQL#3tlt@oP*Rwl8fOYRfa!mS@N+!}9isOc(&Ud<&?R-;y$#hnlDEw7<2O
z<;$G{@yfxPn4qbR^jQl~_tm4{&4O=Nq$F%;K;tGaX<(hSegJy~XWU>`#&6c5P2ukW
zB>@OSI_-4N?Ag{$auS5JAY-=X4R#FQp@x-YYwIZAWjo@b1Ndatd&D1p!WVPq_Bnp6
zl=*#r6A~+h8y8X%G&Ny>-@kGhYL6+T*Q}zXtql{$WGed6ZUQ#T30^4Og%%YcBEf_I
zn8k#hMlkihcNpH(A5c`4X4(Jkr;saFu>Zq<wGwLu)(F1%I-;o|3#cEj0E{tcW6{d`o77k_K#g8p?1Yb86U1h+mautp-J
z;t(MiD50XhajXQUB<7T
zm;*!@LwmyPKz{8B)PAoJ@lF>}kw{AwBc%|@rVi;)EVj?SNki#EQl({-Z@D>{Jn{%lP@4$M`tXtxWAZHn>z
z|H{-QN29(@I?J-(-nX%ElhX;}m{XoQhjd*H26&*gioXsW!2KU~u}8XstW``Kz8lAV
z`w>qpmxwYD#*l4&3gWqBhEAq@#l0xAJs=5%R{&CCwc^8$97By_u9S#auucd>EEr@J
zZ?;8j)i3mJlq3WOmfywi)i$h9T^Qd7N*R>WNNIY#m(n1mW{B*^-Nd|O-*)XD$8PT>
zF*9)<@d;zFHb?dXh3)KgeH6#8eWM5iY*c9&kWD5c3tsqb?p1Whvly!g`|Xb{ozO#p
z!?puJ2}8MQ=fL#Kj3}8yCXkd4A4bVu=MW6qApiX@UwwpcKYN>W?J&wyUs6Ilt6H+G
zdhR*u>gsx*zmG8^gvjqR{rVw)`Ab?Joecv9@XX3I8&z}?D$T34+p=5#Z)ly@%w8J~
zqiJv@=byYilmGN8M;$PhOgdnn9Y=5r5k@IG(pm04>;fXG(cVl~#=5cNWkcwK72rO%
z&W{g6uXvr6hE(V%$DpH+CNpFxt*^g9W>^!gZ@x*_?AchUFv?joN!&(j^5gWqSzQQW
zIPTzMxPMyAiMu>O7`U0=T8Y&HAuUqgq!InO_J=97k9oFlD8vZMTJ7L{OT=MiDA9
zCzNaDfpwa~$x*HQc?|h1|8~BxIzJI&__p^7^nEcB<=QhR_J@6jplhM8v`iFqJ!_t#K}Z1*=`BhDtLU
z%_=Fms9(V4Z81U!CRL_*sztDo)ffR<2+}^lDTE6=Y2^f5D}(GM^x6#&FZ{`85IH9#
z@%fY4QwYqwQnJ@9LR?h?)rA;<6(aw=WkmtxV#REYV4S%-2{Wv0c-#*?oHTpD~7`
zI$vU!wuP9If}ZAvgo^Y3S8F>6Q#V^f-^UiggTkJyUm-}n+KHLt$Ah%goK(whr>
z`-3)?oY2nf_b!gq&gF5lzAd-_$$KBrGg>>>NgJ$kvwz8mQDlaX;F~E^s6X{o!hRK$
zZPrTrtWUjFpfSdJ7cNmO)+v8!c>AMHqF8hCVJC9u(=jLS`Ushf^h!V=ghr|K|4IQU
z=dUP(luiLGo>R@zxQvFWt7*Ncgm2#~BmB5OBja`G$ju;O94CQzXQnIVOwS*v00Q6H
zol%685+MX}923VeMhNuR3kU}{VER>{=FTJN>OxA0RD@8%DFBhUATGE!tX#9tjJtW~
zpz}~BKq`yUpfpIEV%rD*#3mJ82&qYX6MEYF&5S!_BQ_qE;Tb&+Wel4b*V+FPRjzY#
znzLu`MoGF2WhT=aX_Q(4xZg+=pVpGGfo6D#<*DW_(oA%*!o?Q%V9#l*M>o7AajK
zL?i*T?wgcuQ@&rcPG*1ie-~wkGmN3>t+$Ax2xAP^S}wjg$EP2M>{w#h?7B7l?)nsO
zMv*h$!w{RvP`Yj%Yo+9*Vu8cYz}8~3yF#=E5ZKba`s_br4EM7$<6FiteBIk@_{nE<
zz4IH=LmDXEWe+l&ZG#O0%7#otG`~c)a~47M{tVl5Yc|;C=Pdf*)9wOaeE#p7K>Yb{
z{Z}&+_YX;l95Z!|>su8w@2f68@qW!M55A933a}io`)KZc@?!?}F9!s%GJcGB;n&Vz
zccy<&1>md#>6HY+O@ToY)mMr@Y2wf45HI+ehSN@?`N_Z2Z|9xqeD{5V*abBM>AvrC
zZ?6BPbr~1m_y7K&lU%Gh1c_gn%rZZ~{I|XXu@)&MQ55BsfQcgPi!T!m
zs>4b}_Vc+A$0$*70i=;2eA-AUtkg&)*-#dkVGlC_r8F$&rrPaMwnL+F4d)wQZlF2Ft*Ynv~`Juyq~`h
zwtR1lb#9^QEVQLDkWE|OUZYWzqx<*c(fJ1x$NSPG=d$;*ml^f>y@YBirCV-6=>Z3m
z-e4a(*G(r_el26i2ujj#AZ0&lCrrSee?E5g>h7@*rLamDCKs(q`_I!_V+Rk0DN}$G
zz*|j+?LLMv!)v+nmN%S_ZZuN5%#78RI9B}eo|%M!rM9XBP{`x=AQ&(StCoQE!8X%l
z-|aIERuZI#arG51^VLD1wNw6yC1^s+@_X*W3W?E*mf4>>|3CSTkL-7N1t9%cQ{J=}
z(jcVe^>;goBF#BRpUI7nL|k~l-B>G8(x8?7A6WohiMgqf(h|pDZN!d;zRwr$4@Sv|
z4Y!?-)-k5b1ptjy1XAYn%BiC0w3U*bcG?Ny{rUXf`}j&pl+uhIJ(^jwX61ALLI|QL
zB93Eh6k+GgrR?KRT;MT^&~c26A<{X7c4@>!0l2{;Fw$Lze(q_aO~#VlY%{F2L~~~I
z?F~0jQ(c-5&{(6L2_~h_o;j0G8|yT(wixdcNn?>Y$?}gEyI^UjK=;q&EdZk>#-~RS
zf+JMmyNE64FIL!Ok42_al%zFbxCk?A
z7O9ytu?rXG$4}y_RMvqR&1BB98YuRqgp;VM*ICR3sJoL;gZ#8M8GDJes
zSYO5A6E^1R+y9N$PJl8uKhIT%jb&+DocHggw*Zp>VE%LO`zvD@R$a=Im+c0>oy=)F
zYZkU^q*h3y5n3U%KnjUKBaL!gfl(-}P)0Ml+AwP|@Qx~q?N~PvE2jXYwESyk2f0Xd
z{t3Tk%0q(R9CVwvZ0-NX0%+@skjhe7kw!{GHUlsih}mdcfiVhW8VN%|Fw6lef%o?l
z3m}vV8XBAuP?|<1k^co6Y8D_`a0Fv}Y|PCEcwsfY2BM
zLh4Ei=|638dlR6o=UO~w;J5(mAtU4a4>lPa5JwRCEsiT#BlhRMXg_S+$pNXi*z)5y
zss3~-*`bZ7-S@(nT%Y3yM}m|W85wKn4D6Eg-az5
z{{&-x8_KH8tr8}fQ;0?QKRYXT5=;~1DHdy@0;rWm`yyqCl!I9E#elPnL;MR3eA@V6|OpTlA%;4S~S^FbV(9+J%
zKG_C}$iU+F#hWsQ{?*mpj;)+N9dq>2P-LNe*YK{LJsU(h^R^$2i8blLgY)@cgo~Fb
zYrEmU0GmGl4kq2kfO-3N*AS+lq4cDJ?De?;U;XA{UI~XY`Sv+nUiT^K5PrL|k+YjR
zIC|G1v^;z#VUKH9MN07CFMNN7-Du=|M@co#~NWbe>-?%8=w0nHTghBsb(2q*ae`=f8%6bk@m^iM(wKeWH{Ie$-g74xgv1)ODMER5vU!wO7ooi8PV*%2
z#F5_@cKl(R3C_`g9x0m{qE`l
z;RL%ah5PRJbY9^af?TfG`8WOar+j|YLN<;RUGKd|$1AUps;dL7QSI$+pp}+lwMH&k
zLce|Yqu)MzW38oZN*R>Gz_Go8s!_h_R;Ns1GO4s)`8JjYn&Sr
zB=HLGl30oHW^(iF$K7RKdvhKL3ygMPl6L(V7?)ZNl`IPCX{?rXOt}ewE04L-r}fWh8$6g+r;e
zSI){>j0sRSg|=ZnrXqcOJJB)rHm;Eds1q1XdRi
z!a!nU$^0cP2rZFP(^TKD$QZROUAGKvT*L3QRC3?~&CnK!RgxtolE<4Q&(;eU+>nx{
z0Zm1E%CdCnvb=Ba77^(}1l39x)SBNL<@~WIXixLErwROBBw}J6bUXgD&*82+v1^;5
zH~=Ljrn9rx`PZyi#Er9W;J^`wvC+2MQnu;Ve0#+ttk%?>c@8!RST*&JsMc17OuY?T
zndS4`7yS8?Kf)YfTi<@2%kl$16J#)GtZnVyhF17Z5(G^c1NWGxVeVMU-g%8wODi^)
zBi+A-oC?^ftAlvq0w?rBqLVM97Wefx|nowhU_}d)2Sy
zz=u)@B~czJZ>(-CSWXMRV(Eef=p&9m*VQ8<>5*KVup5xpAdJng>u4&;fT_Tw{S}C1
zV6|6%e*BhgjHx|?D6)vClW^@tv@aFZJ@y5uQB9bPZ!Zh0q(%EUiL%zmPOMizZl55*
z%2)=M1kMU*ZH1R##-4W`oOj-TJ^s^R97^bOlu8F+HB;{q9I~qmW<2_$VB%Q8|Z+g~sltxH9Dq}N*Q2de5m
za77`iOA*T26D3Yy7F)b7K=yt7S8ZR>7&fAHoX93F_W^IeXU??6{@&
z(2UQlcz~wxUJ{rMXpLUUE1+(|Z!|V5ux%gFl5K}n85tum1E;d?wnG_IBd{eBBVpm@
zZQU)%9|$uA0i4MisQwAIqXW)8x9{UMGUTJV9uJ^oVnr*uGxMnRkj4PdL4lh*Z~{RVFky%{&ND8
zVtCC6Y+&;Z;gUl&)iQ80*5SY~%^$etSwWbJ#4
z&_WVOx2Wo}Zx^=8xy_%EHID^511~Ia!37yMP@zk9tAy}uM
zq^iM^wic@vBUfLF#ULW+I2kI3MrhfIwaQtZ-c_(BaTk*JGB1Q-rjnMcf!A>6eb~v}
zhbU53@ySO=@_%R5b4F>*e-`Ap{)s_ceDy_Kch!~rVw7O~?HR{`L_NmeiWS`d4@H1v
zyUmO8_ofJ?HNr>uWs#Gu^Ql;@!iJKr_K*sH$ZugU@JNYW?9VdVv
z|JBviTyn`JELpN7KmKJ=7EB0odr^)%PTY0FgTYfB!bgK@HL|Tck0%0Z62!wT?E89Ktlh@pvZVG?C2|MXh?3eSq
zw?QA*-(@1S)flTOFYU+LmNm?8oy*q2SO$#V)M@;-91B=LYp$JPwZo9g3%U<9DN|^F
zWjxTM6!xd0Xa*>uZ6WTcKm_X
zynXfooO;Z$n1c>NdlWk9zqYnE{y1d{N-D0~abr@cG;IeQ&FBIBX?yr`R*RVW3;uu&
zn@A0Mh#(B9+O-8%4E4&&3y_~hO)*Sk6b4b?8jK<|Dj#SYQXTR8J$-Bg#nhMfVri#k
z+{g^$H?&-Js^#tXx5z8P_fGHRs<-;{QfKe+x3Wud&3%$TUnFR1^nGVUFG~Qe^1)5=
z)|QKT*hsRp-(w(hq%xLt%Cgx(rvV|A_a2WelOR=o`1BnAQWXR}5&DFKNj}fZPhA~!
zc4ALH*$cK;0DZ_`U*j(g$}M2<06}5_?7oBG&5tErIl+FrI3X0Wpa7D^1n2PdQ2+oS
z07*naR0#6smr#PwD->I|%U&jj6@m|I3o~eU3qbh=J)#cRH#rPtUv_{8Av1o5l93A{}(cJ
z%0OC)41Lv(o*epzAHelDV4Iu!Y=fTHzoJxOtee4GI$Ow@9HT0?V9{T1r*hX_AzRAY
zr=MiRnHQqdg4HdnsVJ{N8|~6Ss;K?_{hQF`o9((jfSBa`HSYY2maS&v_QlkzMd%fd
zKR856%;$o&e&wN+lH0z$oqety#{&HsQP;UXW&xN2S!&mduTzYL6N-EBIWe&IQ
zHj0U-oI<&fT(|EQtUK&XRxMn}ro)GKLxGI~>({Dd+};#f0Qs3eu>!n8uxJ;k7qF-~
zUA5lLWfp!uA~h
zPVn8@mR>+ykzxQ~^=j&3FGb>NGZv*A%6B$3atwkKyqWrqMkl204uv`
zFRK(weKK5XC)mm;Z?3!9`WLS{MWb+5htMKFQ)=yJ(6?aNCX&orcl}BUpRw@Np_WMH
z-b*b|{`wmSD&C(#H+Tkst;b<+x(R#nMc4%kds_fS*ROmL6R8!8*DYr3fU(>-{zj~|
zR33kN{&%a+ybxJ9Tl^vDYwb;!b=l%8=SJ;~yPdKk0Wovq&)MpBbcvk+m6U?J@o;
zD>-$)mFzZNVpYN7$SZ+l_V?z#id@jn8i`E_a^;7yT#du52`DRVL#|s&%|qu?x!rNB
z+UY1dCtOR#$NNEv!fMz4YuB!&y1M$mTF3+t1OZ=u`6cFnL$R+-r}K=HIPMJPtW-aK
zrIrZeWFe`ik!0aURx4&s@d`jG@7GI7F~bBS}gBa
z#;Kd0$#&J_u*S0Vv{NAns6GGJSR1f<${!Hxni=-kKbW%RHOv?`lc^tH537KIz3<;x
z>+s*=`&iHUoe&qTHBW@U4@Gd`5sy0;L1|2K{f0u2PD(OaNz-USU7bM={}0wGCT|M|
zo-6Vw_ggb(vSQULhjLGc^JFKBM4Ou&Opyy_Ww$<5Mmbl_Xhzb>+igA!WmP_|bj#~0
zt}pB4wVQUw%AxMQOTJ|DThd6AMrm1yl@yP^L4%Mdp2!tV!x5dr72Ytm``#9o`i{0G
z%u3IsU7QXnh0Sb2bL-}mu56?J@rzhK@hMuLy^m^Q>?gM~@b#M+5D4r*-~N954|uR
z29(mOJ4$J^OHKbslr*sb^09z%Tofs)e*VmI8IFN5nIEva6PGaY=FvuF6!qzPAAeD3qY#LEOhfruUs{*u(^jL=dALU^MSVp>zTcIqWKcFq`;o_|Wf4l9
zN314x0tO0**QTd+5!c7-caf8A9j$
zABU1wtXxTXd3k{tyzMs3rI+^k8>)}vmwr8i#+E^RwACiG`TOb%2TuM@zO(D2Pcdi6
zD$acF%-$%ea(M8{>ED0-^ZIC-?>c`m4xI%~`JhmEGLIhnehbr%fHht22f}htr3Dmu
z;iquW*!6JRKjZ>%S%5xC)1H5;Jbm5$HFPN4bdxL1)z((TttSYt=uH47gkUyH`=E(%
z2ACL3os$<7n9d)FB2Vvxs0Zz*C{TZ*F9NWDQg#JYV&i6Hr9j4DBr`T1M#+XTF|gkj^GH?q=O4dq;E*f7h0qaOx&vB2
zadqozMt=ht<7Ri^NU1!pJ$*BZiWBz2X_LX28!{N9gez=fB8;+_tD$!K4@Z$ptqpoL
z&dq_%H?$XwwsTrtecc`y^5Ay|cNc^Z{`J~qo@*J%pFdr}bsZm)#&Dq;%P-Q+9K7v7
zj(O?@pm9B{yMWW5hAx%=Jt70;A^NX7t`~vMZ@_>D`*%l?)3EqLcg9{yu(LOcysCfU
z{|QZ8vk$ELS8t4(9=ZWw>**f5?SbJYIC8!sZeJU)(Pe)q@N5YNER-93y|hwK$ui96
zNyhB)sH@Kx`4Usb6?{dHwE;r8LW+@a`0>#f_2kfdEJ!I*0>W=m@*b$U{VP70T~B4L
zVyh-t+xdgbc>*iaCBqhQ?d7%ks;)*uTvAF&XBYiS1=1+iwPol?l@TKf)f@(e?V{+4gHeuO99
zJsWMn#!x=vT&z-DzjGZj<(BDc^o=(0|!>{%EB&wz3DcjGUbl((Au*eZVBlFQ!jK?X^$Sk
z3EK|fuOBp{rKYMZ1+Etef`C*iMHq&?f>9kE9njGOMGg%$lQneqKvByiHq#qLHbDL5
zlc_uo+8;AyMi@%QYigEh+MjU(V~gtI6{D5JiPHCwDhZWu$cGR
zO};FclNW7#)hu5o?EZ5cNObM7D)T+VW!ld`wvl;gEW_hGfee
zdvOu4jtS{w5%p7f`0L|6_2t;_^GDsojultHj+(CbLNu1jF^yzBgYn|WC-bwitN6>a
zXJG=1kzlmNe*HC8N|tTp3w0=8kwsYs_Yd$D6&y+{3n;C$67BQR=haKLEKTy#J~;Wf
zBzSy1DDs037KHYYL!2ULXy|1bbkoCFZ09m}Gg_}VB0N7gKhGgFl{A5YdzRio_-P&0
zEo+IJ*J0wAlK$01(y~oX@MK#(>5@v6bb;Qd?l_hGj=zPW^))0l(N5WZAW~b7-=>x;
zpIb`*id0_td@l-}?tvl)8ug8Y_8ur|c~Ks{2t_tP)u(pk-7ZuUR`uW4a{SZ8VvU
zBpv_k4zwg`$#hdw_o~ve2a0QT@4_4j&|@S)NfSmvy!--Eg~cEF!lrgT5qXO4|B@4K
zrepBqR2aqoz48~TPCgO2{m$-sj4O~*e2k+@3&&savwDVI=I&s5o3Es7d@R6M84|^n
zhWr>7+A_2{@YY&Y^8?d9t^MFXi-8(<1L1nyo!+lz*L@}_X#}Z2bJX$guzATwq;hpg
z8L>rE7sGxvgIj*T9i34suLN8O)-Bv{)n*-p5`R9I*a%XKmVk)p{NO{9$
zO%Q}+Hyp{jrE8eG+921qV?)Igx1PdJPrjA?j=u$|H1}M0ERWoBCIF9o(#&C-*6{L|
zt@$!&KeQ-oFBCcOi*|aVsF}V|WPpypTPly!biSa`ktC?pSS8Ra-SI{WS0kZ_OE8{8
z=o7`Y-;eTQBuW4eJ_zH+VRK!sAFQj8oZqzHTT`euYz#qpKg5%dV{m1=%cP|J_B*KP
z?1X*x#Rf?m(rpXXde#JL;a{R-1A3Tu64$TDVw6*QLAP|+Ij-dAsu>&P*-
zelzNaSFq{k3wia84Lu4RIHNgvScH_WVyv}v+;J!AigJQ})!0la`hkaN|7H<`o_Y$C
ziy_Dnh}c)ufzQmgO#N~#zZ~`?MoErqdX9top90%!#Csu{kTRFChF_M!JvnB!oXCFn
z{fVWUJAp~1`lt#kd;w8l-AIyGF{G^;Og7;V{jMS8R(_m(90rp(tsG)1Y@BweZU!9c(AE^xlO
z#h>3w|K9f>5=+tf4Xilf1Zpn11ifU5&v+D2T4e9@C;QZ}VSF`X2HOI*s*2P}Cy}}P
zZrX0Yi`O20i|N}Pi7>t}hksg-)P?30d<40s+btm9c`x
z5i6*%^<*ms5-nBO_7&7`cL)loA2lEO>S$L~(3cbWA?3jJ<~BBUe{{8LKd?i{bKJDWdE
zKb(fL6sWH`?ak9TZ>RHUiCgj}ybcZ8D30}=b6
zPN&sL_O0vzBE2ZFR|ss>9i{YC#&=ew?JGFgE#m5DephYihbaK1UEr?ywV6CRwvh#E
z+UV#4qb#2-{hWCNDi|F{2&s7Xfzz09{4Jg`(+(w;hBx1QlU*+Rimum-XLr-CdS+k})ifk1MKkNTmUZ}3RzI71%mdhq2ZQg{n*K+1AmNRzl;ev$$tp^&0Hr7LV2`q^C>jZ6$TAZ7*d_+gjr4PMVihB8ANd0l}c6D6cV1>?+4r
zoP08C7cU|G%}T71bkz=`cH%^=wa6$Un-*x{T>z;#V#suME;|Z#NMYX%k%VLkoo{w(
zDcqIgwU%@E(~C#=pr8v3VkIw3e}OS0#;~YkQFjzr3y$$9?~9;n#Ast1Dnio{+fu${e8O2E}1{En)$basBH0!ZrhStH!)w;y5wco*Q&
z#m$Tg3`$Do25F=(m%e1_TE3EsbV(Hxj=dSFH9{(s)F|ch;k)dzi*psXJP*5QQK2fc
zl-Rqz2PGXj67&B1aCr|DIq=-TbbS~>kBv7fK@NQOt9G%1>Yjv0BkGh)*mGNUIEa}wD@p#Bjfge3l*i=
zWvfvLZCJ4GE8448Gvl?L5K8+8K8CIVJ374ZyT@-CW1zH@>MO1w_sYuzC8gAEw;e%g
zDOzi69HTRCO}PT>?iu*SM({)i4WtNN+R+RC8*S(E$Cr-q6>YunTLUJ@=S+K|u)?^%
ztzVUx7k({}X^BFiQ_AHxP2u=s2PzJ!?m-~FBC)>_*p9wYS_1@8ggxjW_xbbB7ld}Z
z?fR@e(7gf<1pa{yZt1J|P6d!yD%$$g9l||EO{sjnf2sUTFO{EpG_OALD|S5WhI|3n
zCvQ82%7@PHyDGE%j>RNB?!Q<22mZUUz@A-ne74y9_b9DT#pO-=9_P=)NIiw$=i`-E
z0I`Ie@cdR$wk;`BFI3@k7pe*nKJ_>7nm`jr+pFDNR7>hNUu&Rp2
zTW*HeXJD5vN8NA(u~Jl?csxHl;z$nt0VwYuj~q-@;}rznf&?ger0-)gmsrvlcR#*n
z%^J+`5kKzHenh_Ry><=MsX}i0)6N`l#79(>tV1biA>gsAc4zro=hG+?qP5d7YVERo
zhnu3>@sd?oi3YK%{c(HHv)wL>cUG*!3u>dH0!1*kb=baPTR&_kI08
zw5o2BW-3Optwi!2$LAG*&WEX!vg~;bxreE3ihO?SABmO}3IUjFcVN)GbC3(a&Ob3J
znW>S!+OiMECRLw}R%Azw;$z+?dqGT?N)Z@S$N&^Q!mMW>qE$!|Y~^sq
z)Gp-u3D@$cw~yic
zp#UNXje>p+P6?<&{wC;Q#*Hi;S%`C3Upo(hRGPs9E1hZYO?(${Dp&@t9G2nmGwx=1
z<3Nvc|9BL-8;Wcs3QagfLPd|`D}1G53hkd~UZf)B&mZ=IBHJPq8Ljg(b3!AgR7BV?
z&}SVAg{ExSCUqJUDr
z_4UN|iXND1<>#Lhd7n6;|C0AoN|x-gM`C#cFbqha3{y_KnrWZxTjU$ZE;S}SV~Tck
z1g6^s{Cn4f2}39=gOU>HSLMEH4Cqn*dgtV;P38B=>&h~Cq^qnz_xYPKd;Bnzv;M!R
zO54~63D|W>U`?AR(;~OET)zONRGtJ(-f!{Z7KIb!tLkpGo@$rHRfyOB>in@)buXrX
zBKxec6)BVMWl|+G=}O
z#idUawXbc&70ujm=s?bzF!2AYs_xlmo(MYUPMS*lhZ^|0>U_U-bM+mcaK|gKq8UoF
z?z%-W%IyO$N!e-=7{NDK%PSR;{AN13YFuTtC2=>(*bDRv!(iG48nq
zCBG@@lO|1l`cU_jT2U$OwR29SO0}@C|A6%l?4vF{h?Xc%G$E8C5&|7{kjj>`;CFu~
zo62;1no{y)A7cy~n4h|m%SD}Ud~I=qz!*_bjKyfJZ?wTzzurex=pwE?kHQ)!9C;kp
zfK;sIq+adMcPOHsMZ(vV!9AP8-?xR%4g5^?eQQCA_;~xkkE<#@;QvNIM_YHFPWbxl
zLU!lsd+O&>SoTSKaax|Cd%x@2sv>0eBU@5ln&AK4<;EC8OIru&zx|C@#1JmOa}Jje
z_=NIo$i#0OIlZBcLl51C{o5{sA-*r7qICsb&iqQJ)9D{o6}P|XAY0kQ7b`Sdj;Q#r
zkC!sXg%?ijrX<{>l9IhDDi`Kg_4$%XPe1)6J8ukI4u{#_z&C4cl9ew!t;YsvlR}#i
zt!?26KH75bogI$R_b6{NJq1BPSXoKGi!Q`;c6Bo$ZP5z3mAx5`)HO7?RB9hS?UE%+z%-K@bag(XkF|tWJ0QmD{FXfM
z!tn|QYZWc?Ztzv1#}t~+CQksO@SqfJe4MHH$vKv3=e~sjDvD5Qk|12sW4(H|yY+6x
z@@do9Gza%g_#L;TU^$8}stsQZ(k!obf!=&o>U_IP{{$Y%w7dQXRi)dSnSAP$LRIbm
zN85SFM^&Z&|9$UFdLao(NFj6-Q4tY)TXfO2uDW(v6?Io-6-C!w6?<8GL)W&hV%N2-
zVqtB7f?ZG)E4>7gkdTl%b5HyIaqhiyCk5T*_r=$|W{6DY&bg;N=Q;1^^D)YPZ>><$
z^GnZ-ubQR&Q!3@FV&9DmRFvk2W8}uajI#Gp!Kh(HTO2>%E%P|*m3H2EVrM2_a2Sbi
zmtZX_#$f_UFm6hm>j5P6*pk~8Hf(GQq$Y9s0oJkL!-ZJ%0
z#sFLS7F0Dhi_jWP_}3q};R>1Zuk(D9(whER;WBJ6s7U)GM9MNfJPAmBfBg0j7S>EO
zZvA?ORY-<^qu4J)vb068aIoaPQ68Hz{F->_OVTMpOqT>IPofk(sXnzLnMSB{Mf~-rnDk0;t0mf`o^T4JeB_0@#9I&qw-%k-)=+BNAD49
zZbo-?ky%_!mxMi~VZknAp&&I5g5&;*JO@1A8Z3SAYv}wQ6EDWpJ%_106XR{bYmdA|)%q%}2xCYrkcZMFaaAOJ~3K~!f2
zNkuY=@U6Z?B7v8*zJH=CLDEZ7Aw`b~Yg84K78pMK0hZUBkjYk&0t%^Y5>xporQPkA
z)ZiB>6jz5?Wv|D%u;aV)qV3AB0v`l_@Q-b2=cdEr%wPHtgUT-ArZ-zTcah8OJLnY1
z>0gpk7lIADJvs$wk^eU$}|~ANh=z&S&^3~7+Wvdeum`rumY=oVzRSE)74@qDEEkE$=*d(wJ&kr3S703(xN27
z?X<9&mLvrfmS03HX6@qI6A3n~xdLMnp+-hUzdR^QwO!EXgz_UNX>|uoZ(NDd;7>`wr3
z$6(x;9rTg2@F)wP@TJ&1e{1BSn7FEmTrbJh+m&;}{zuUM+Dqh}F{Ovimz0utK`Txy
z2_}&i>ZcweO{F(@?Zi9D&IRU9K<)Y$cG-0{&meeoO|3sIN8}ezdX)1B9E9bEZJPm0fV#I^i2~&860WQLQ-~;g`LWOuc&+5
zawPGnq?IuSa(2YTJdA0;WExCnH70*AOz~;y1K1DYJ(*rMnWK_Cw@@))_6rOv%fu)Z
zD#?VatWr2dMKEI05*BKbb^#b%m3H5nf8jVBtc{?Ae&sR`}iT
za2&(VLkyic%b4|5hrco@|Jgp0xj9huA4-%8k1o?Z@v^eW`Okm)4#8ZPik22))zw@v
zy_vOZH~NUJU`BPE-#_PZ);AAOw(}$&`s8pjgrKPSVM_qQDt{bOgJVE=1^C+MBQutXxzNPff
zSMj&Wd(+U|?r)J)*hbQqfDcrol~f_wR?$D7`_5kryt8$xw!L@{8e{P4Z$*ksT$99G
ze*+m6SNGcgT!2i4T&l=A!A2{pNn+XsR0!nv77jys^y^4)XoJVWp9|Lfl*bG2r>Yr@
z5yZYal|=m$IOknU#`0w(9fynoUa#`%0m>YGzwxvG9bP8;)x5x}mZ3
z-rt{)cv6!TDS7^&YE{bA7RuUx2U{o`NkM|r(PMeNu7D5SZAesB<7ma46){$i9L<w4|E$(zND=WetNvKLf5v`~D%y
z`*S%A|N93n#nJc6Th8W*-5U7EdnXb<&~W;39yM)>E8i;O!+*}j7=!D&G&eW1Di)*v
z1s5QF6gQML7aqlLy@hh)OuA%&5BsPP`WZMNkEidP38y6xN|KZUPYOJvsMmsR>RQNb
zZXs*h<>(imqwJC^FtHd)DPsOsKx<9&PgT^m6tcE49wMv3XLtd|DwR))>hL<+R4(?g
zZ!1-8qwSoAQcHx)!03KtWim^PyL8jmDnq#?qRIxi>
z_TQ7{9rlAauLgDxqn-Gek+hA}|ou?OxU-kFIp$zD~e1C-fbzaK4ORf!!p!R!^s
zvTgClROK(_^`$4%FS{qT5<~OkPGx;=_iqZqXuYro+XDUCipX)xP$c;4G8iu-OS7
zJ<#Ly+XdAJE6)9Qf|v7FaNUbzkV-PNa3~+#^Z`pFD6Ek(t?JDr6L^_<;h;;|KPw;P
zk&x)LE58sVrB#G|c0e|h8{w7(KOxr6q~wItY*ghZT!WBXDFum?cYPrDo_ejq*ESUO
zqXC)HgsiQtWx#*||C6fiS6_X_xN+l{HfEz~BD%H&z45*_w=fJmflwTjk*n^w2XsVqXi|$mXZgaV#p`jm0IqQkz>%m&ssc
zwg7%YYj7Mpus>e@HQVp&k*GF&w)h>!?b1PJFM~e~WsZS!gTU1KD7EyMnmnKDkA9G^
zlDi@Fu4K10bH$8PFtKciB0hX@ZjX?to`~)qWdh_z|KD>1G2#X%~(S9Evd?W|I
ztx?7a%lSjyfQW{G!O-vO8z}2%*l_eAl)wBYiB<56jb`Q$1)iFi(X~_FJBG!tft$ZH9$F$DU!>K0W(lzik02>`MkeY_P9xF
zdtR-TvLYXT;?eYX&=c(h5UgnLwZ6xF4fj!>sJCjUX)C`#n20K{NdYq9@lH3K7cU9P
zWa|HQLt%9Ug|(8)_IT-@?KS5vbUEW4m!|q`uAW`Q%XzD~Zk!2)JC#X^l7`27QRLMJ
zsI)OwjXA)5mGVRN)C3@;9sEh*k(3@^<-j@H7cqWgC(|b$j;X0ZXJ_|J00LhwJ&mfx
z7vZg1m39r?+57O?d=xih*f19IK3ZS(Us+m3`;XR|#fum7(MKP#V#SK|EraLr^2?FP
z!QVniX@z4)j2MwRr~h8kHAj#+%ELj>`kBj0HJZiS?#Ab3dr_NRMF*KUi6nR)88U$`
zv^tnN17d%am=%J(!~JN<;U
z{@qYo?T3)AkD{6Y#f`;qYigjq-4@IED5@J^u#xK*Mo^Jck!H{J;@*NI$L28g>^h#k
z_1`@6)^8cCdsih6KHu>_UrT$+;J7hd$Dy)3H&kMl%7i%=TaMj|Ck3VXMR-d1k$}L<
zO0s4^CVKC^Fvh`&;KflmYY33t(E`PJepROm2Rl-v%47XAdE-xhroD+Bx~U>}*9mZX
ztw>AtpWr&Wt3u025-~Uy4kCk4RoZX=Odu|jBuWOcqPdzM3%BR%!^YDb?}yM1aj6)n
zG+HH)`B)}IxqjwdZoMjWAMbaCV0+PkL1G41g*-zKl)78{4Jgj=_y0fNeqc2m1un$}
z{z;6&>N+T_oS5>NECUPc;M1w^qU(K>SZkb(8#kJCI!~gd-m(~Egyr0M$|`>U@flo2
za}Q8a_Cn4@U%Np4ojZ}OEI4IiknHjYMIpEeO9Oi`s~eydWcXg|#Gus!@4QpF_`8d(
zP_g^p%i7ldwI_aB-qeI#vxdf;`)nVzyzqRt)=?d>RyS^Z@=$J
z_XW?}e7l&I+;X-ZdIh}$v%q04KrsM~S09
z)7@Un6i)A~oHpG&m%Rg*J=MsQ?kjk>oVjifuH9w@hwoLzF8^$gz}|^&y*-l&*IwRJ
z)Ku#b!S%Gn^8yT4lJKODt7?=dX?rHU;;(9ZQ=>GBWE&n(NAc7Dt}(dTY1pI?dE0
z3sFjuOeVv9rQ$c0%=EdiI~1N9@`aP6)o(uj*|k>18hK
zKWimT4E<&Ow>yd+hxnuqvU%h78csWHZ(`pKN4K@HrlOL9s&ZcWN${ruYw?s{VJ7Te
z-1Cz{v;KJ#02dtMqo95USDS#g%fM);+uY@|HhyL^KymL~U`f9G6bkv*o69Jz|1%es
zlu=k%xW&EY`t|DpIC{UTP@7L{LuF+_+WUeqHRFamZKavNuWP#WI!l5c98t9mRDmO4
zkBlIs0a7ZIVAkJfQPMA;gO|=Tz`OZoj1=^%th8;`+>D(29?q`2L1h)omM#1L(tcZm
z(z-xVQPKZ|qOV%D3V_1G!u0nyH)9eBC@H~YWNfkjNML9L1rKZgrJsiist}{O5dZo=
zn$-tNoa?I+BXJx_EG8+)7EC(6XaDi?W85CQBP1d<8jV7s(Y{%&GB#(We8nY+{rPR1
z@5f~zcTK3Lks1Ge|9xl{@efwbz$_`I@cp--wGBGEFhzw(9pmWE1h2L3OiR+Ce4kw*
z9;ZF2NOmQOf44ZT0I9jdx1`cGL6s1SWA~`W7{eX2egua|Nu;JYKPyeNFt~YZ>Ls#v
z6|0ltm#NbFQm$>C!bfQ}9UUEM$u7rnP)bp_ejVCNV!R~uFZD?}+uK4_TWxXO;5o3{
z3N6QcMWLGf8c4vLWnENE{51zY`y3+%mNVkF2XNB^-`S+j;^iO`I1a37GF(%-jM)5l
za4xx?$6ok`TWmG*zQ?@wHSJ=)PJ!}Robb4(OF({2nwGx!
zW-%>ad_+Gq8T%h-v;6C8FV~<|npJEXV-n!b9=trcKIAxb+
zwFQF2Xl8d+;^_BR>1FB0_cvVH(yjk^uwwle&@^BRcVb5H2>|nsq4C7*y_4&D&+GL=%j~{NgU6!9WWa*;)cwaZ9M4+Tt^{-Sjh-*
zgSbrqHq)HxiqJ8oc0)5)H;&=V%3P|izlO`MyvF{It~M({1KhUq0XX?;7Pn7e+M&;}
z^PV*%l1Zz0s13`S6o0E%Ovo1;(NEDo?qM9Cz!M&g36G~51WzQ3ZV
zAfNb7yQEgUz$e~sv-ti8P?D8Ne7C*)!7V{&Ze9CzS49p6e|J^jZf>LYg*#&UjZ8#NGy^^FUg3!RsnV1hgNPT{5E9Qn9F}K`|*>1aD`_HHi8|YtF
z<|ELinuu&$}o*Vl)ghV(;e!)HG=*^Z_Scpe1<>-ftprL1XCAqI=^5Xx}S
zB^!NRH@oE!Li+-bhQSqa-oJMl7u+>4j4Euwg9#dt?~7;YquCX;0vIp51Hx85~E2
zXS*1`=*5U`?!?_d5J%8D9$GthVC2w}lmy#^Yf{Tv(jFt)lE3=_*l7UFdGkb!fs>CN
zi5`6cS}Cp=B)MX_z3vL+QNP1%
zZUxstEL(0*S-QHcR-q7h5B(jJnPpeFn2Y!2*SJN6K=5b%dOP?ds=i_5
z@ys)r>#pnd{X1M!&UME*G;DVLttn~d8v)@eq#IG>F$^ejxaZO#D8NxFlu0ABw@>ds
zT6dcOw4DHCCb+{~oblPH$6BFD`r8K;0gU_;FdxI#G9^lC6N3(GS{)KzoczpGCkk752w!KSP@!jru9_17#M-jBUAG%DGNNIGQ2
zUG^F_n2k$5A?6N5c+#H$QhR!#H2aRS%s?Wk_-fTAl+x_GT^YNND8?AWg6|r_;uSo}
zkDEtQR(~;a-70(3BqUl2X0AFh5`qQ#a9;R2!yC#se8`*LLK}T3TV$f7Ar^~;)>UyA
zw03OBm`sKhWlmJpw;RgYr#*QuDCajf+|4%Q@?h~Ydt4O2PQIs{NhXoM`c<056-p5w
zRn1#pw6imyQB)(i?Ms*Qwij$j3aVcPby6Ef3#y)n)#EM45QLCY+v`u<`w(tPDQewX
ze{X8{t41qKZmzYVe(hCEqANlf3Sxr>(Z04G&y)Wr?f2@-9&tqi!;ty
z(tt6VZ`ZVhMJ{uf)U&BQ;Y%#pa7y`#9oxpf=Yo=QRwbo%K@o<>*PO(01D?hc{>m>?
zD}V4jtUS(Ve8ZW3)DYB~OWCuGQUxWxUJ~Wk9Ju}p)*H&%8~WK73cBMJ7h<9jK=jXk
z`=OJ9j88xI&u(p_+Yk+Y{bM6+UxCn)2UkdzZD)D@JV)@|As##Y!v)Xbhtt|9tn=95
z`81(3fy_zB-*dIh#xvUMk2|$=|)PrS(Ak*@IU@xCpxV$
zciq{mDzlk87c+HgPgUdQW^$?^Ki^Owf)(G70Ay;lmof#_NNIo8uh2kD+iJpqkqA_k
zb?^Nqi~u64&e{cMc^6cAn3Y|ilC~{{NDu#oUHR=6%byH<8^SLC+9&UsawA$-S4Va{
z4^JgoUAvx|%8FjwFAmaH8c7}6M|ncy8Dq1BLNWC?@RIX*uX8vua|mNv=a4yW0H3{O
zkMxvDCHTHv*^DQxFvfs>StxB-v$37|-)*!P=bw2hZGDfM8!IR%NM)TTUfh83Jd&Pf
z{*2NsqNFR%8^WzGclza%KF2OpxMvMIZzwe4c{YUgAr}qA4tSQk&)b;|O`ZPDRUz{(
zBYJK^rM4(iX{t+edE~Me*#FcIIby#W7JXLFQaU
z`RRl|bJBIS>@%^97hii90Z1vBgNHWHw(~O#XDnQ9U(BQSHpr2F?;Qm-d}{kJn=-Is>a{lx`yfj)scz0kB7E3Ccz%t
zbzP3#@dxz&y{Ibp5&>wF{{5A<)p%|P@NNRLuG!|Bl*SK3BF|P5%u$x;(OTe0OVTQ-
z$#NZv3N^(jw6OV*s*)07+K$Qc{{3y_5D`Aru8Y^+YA1j|DmqY+^@OtOvO?h{6}S9L
zbK1V^*@-m>&!a-)9={uk
z>EtZ43VQp71Pl=gpztKOW_)i68Lrt{3s*
ztF{vgB-Q-{r{xQF%l6%Cly7B)mDC=XDY!1*im%;VB7t7)AY%FR6{+5mt2L`dh6RSstT;TWt+8#JX^1Z5XIJ(#4H*!2(G>v
zQ&$@fOsA`h?2dMl2P@nw&LXDsG4Xg9`l1z7R8xNc%#^BeG70zGV>|gSyI^85TOc-L
z2IixW`cyR@dh1aBc+?+R(Ym77YEFNQ#}6qU
zzI$q7`voYqApnHxQ8QpQG4!pyI-1%Ca1O`(=&Wjed=dWbL+B>U$5U052J~WBzZCBi_|T#vw~^u-f_`p?cw
zTV-l$Rxy`-nZSGe_~XEy2(rJ02>x93a3h@wNh0CVnUJ(6Jf8h)WlBj%DLl_3kw`FR
z^5h;^@7^{4hE6Zt%iZ*bP<3a(EUtO^qZ6ol=1KlNa(@=h_YkrRsT3c-dLnO5JPIQv
z2fz0QyB>IJXaHI|sF=%cJBj~X_5iPZJ|i9OIt^F_`h8!SSc0ApdN}9`&>yDLcJ3BX
z+{~SJLIdree%dFBd(Cs+*0tZ6|AnBg>`5|rtnM9?l|Z8p)cs$gxWmux1#ta2QPGqr
zFdfT#`Q6`wE>BT_TEqHI*!8HM?`O^yXysqRVn4E+g^v%XWebYReJLx)0jFU94-LR4
z_gflk{5hLIJ9K#}#Se$duDJO)AOJ~3K~zsfB_bEssabnp3)%5>
z6tlHcv+apRDYe*yhHOyFihHn>+Ot$)U+S>YznAoaVs?IN8z-t>8@%@YH$&Cn6K}48
zsiRpIU&6)}wU(og_QJj}N$^a)QUC_P}L?E{|w$OgHnz893ZcZ@igM5DD1J2dXtv;89%8}_?s-;4Cc
zh81`4pTr=h-M5~{hb$n|)tvP07_Q&Fg?C%3c>l*>5pK7gZo4Po`$=I(G(5Id_dgDB
z!1y9Yj~gZZ`A2eL7CMrp7k)DG`8%HM`TYT0*Cwr$#3+p`yxvjV
z=dHc5
zoeDFimfQa8!#^sbb7;4?Ut>}lZae!2%v@mP^gvn`$7pL$jsh)lj3lOglv}6t
z6^#+JFPOy*pXKxL1G{t1m1T%^htaia4Vy-eAgd^ks@dzga!?@^Wx3Qg*0Rd`H^sSk
zu(?IBP0@kWt?z*29@{DuJO1sHHPqD9gbnrT?UtDO@XKbjA;fzvIhOi+PPWVZw_6BQ
z1vhNAM<@wt|4po0LPKi9@&@}}p$yfw1gc1)lkQWw=HD;gg6t9tKyHdp
zh57;k%IQW_g-thA+x)%u#^mMF`r?ZzaZx`L?bg(U<Eu39Tw|c2(esT{Qo3}?*CrIfvq&k{3@FQp
ztnUU9L1_b59=3qeW(1!f^H=%04JpaEkb?7b4S}VXOx5Mbs;ce#t)z`bxwS15ZkO#8
zWE7+)&$`+$^-Jyj7Xqih{rojgKzaEuRmDz6aZent?d}0gt^G}}?)%&eunOucTIy7|s
z$n@hjaQs%H*zvw3!M|GC73#XDlxhQ%*HBUv=j!{Gz{GLBjy>T_*_)E7G1j$JhuY+}
zmf41VslhLO8f%}<8(1-+h+QfUVe*}Ka9_p6X;6k#q@k!rDV*TTo8D2(?*8zh|f(bxM
zliG&(-#lKPF#gRC^&@3~!=@~|ttzl@K`*VoL{Q6NBS#e`@cJCM
z(m!1>_AEWv`t&vU?|jN)ztc|c8%5XUyW-@GhIqM;+bS@MWgAQQV%Z>+R*W06l#!*i
zq0+K85+`1UuA0s3n@Tx+v)}yMbXH1;%jKldC!Nm@4wJ
z-EajeJZ~8KiC$E7g@^g^N6fTonA>i{Oqqgy_g&n0JdFgSwH3A3s!+Z=T=lyRei0rM
z8b1oKB9@>Tt9n=U{n?`|$Kltlt+ZEGqB}cK?X5O!4Oe;@QlB9K7np@gfslF1Hs8r7c#%Z4yw$6;$%`umo!M55RB3_I_WV_A59P~(6=z8<+hjA%QIs=VNDh&L55Dqv_#wfx
ziv=;K9b>K_!}S%
zSD@hidF^#%$ue?8B0Ta83@w$^#Dyj!hyaB46?-(fBG(>Q^!52aCky6(e$n}VAbX$v
zQJXjWR^0w0a0k-%Sx3-btrW4MV#>c<2(k(3JM9E&Ok0cqOuz!jwB%>>`j6V%aKn>^
z^G-2%p5fz143iEteXA0iRGuuD06IF7Y+Ien+jEzaRZ<)-f??s3V~uGBwk%_l8OEqx
z%VAR?tG`}D|9%8#$;m5C3vbo>pS8+`omqUYHxrAK?D%kS6Nlx&G+9=L7-NwH6}NQ
zwwGT(IhS}vJ9gkug&mBBvxN-a}Ps3tqh4N=Z$HCzu>-P>=+
zoO>?j-g_};oPqi9!(LI)){RHQ>I*PySK08<3leQ!q>?X%qXvI>2MyygJ_By(L)F*(
zB30#*yC3YKs{Bjd@47A(6Ay()YHZ!RmexL9^)UfxZNqmM_`n0cSAM`q@-$B3dn^!+r4UE9Dd{Weg$p)HKM
zt$r6R*%F0fQ((
zDH9$CWeUOl5foEuK{8Txl_m)uMKR-~P*Trhg_O+5@%Zg@!G3$V7&8i?C$qjI#uGoj
z$=upSEN%H7>9sSwWLU~rq?@X5W>FDl%ot2-8|K3Wm}8D1BR?OnsVU9sS_6;1ypBT;
z%%h`IhGHqZ?oi0Q&znMWQY*ubBXjv+VRKk_k(V3irWrr+;r%W*oi>cxmZU$4T5_>%
zZ7t7u9_EZQIQ-D7$$WH;B^v#_PEBzZ&rO*}J&>;jO4{p3`Fri4{8ZY(uY*ZIS;!==
zCF_BjV`5A>YYHnjt@MdNf8vdly?V;CUICFpnBgVESa{(ggb-<#^1oD6G%z-?Adg6i
zvMLX4`mog!zNv?jwn!=hQbm97Sx4Hx-|~ndH^cDY#dd4d{oiS=iTx$U%}3o#?dDp$
z1yL#QL0Mu?i@sbXxpTRQ_!LS8_8Z8v*FMYH7res}hYn%Is?B6)#~4ym$mfgdC@srj
z;DB5{`g{XJ1{JV+O&k5T-+`5X`GgVoWOM5JQS7z*6``tf2mlkv7e^d%Rchc1X;=4~2HwrZtELe9m&5Z_7ljExdlVCroGBDEf?|5g
z!*l%=Uq?_>*$u_?2e2z8bA{lwL5k_O4x%J~G7X)%tZeJ#`h_$2srg&HWIF;6i9p5_
z5|wf)@22XTtgpwp^imS5SL3y{QMh0M?Q7TiytqJ1h3k`OSDwFswO8k|VqHf#>wD7R
zc|M9AltD{_5WWJlG>jOO$KqFBfusM7^sTm{6F{tdBqyKs3_@u#BC_Aoq&yOX2rP9x
zB+~2mBbAsz6re@Asxt|@7Z%ET9)>8b>;t7$!YUh^lr~|Y>iplT3ez1$_8)!b0Hi+w
z_&`=P2hiK+Uvfq?XVJT=aq#(pB6Rdxq<>s&Cx*ytogxVzx@Q4Zl?AM;Z9@oUBY+Ug
z*Z$9JzbKv5z&D`5xEJyp`1p63rm2y;{N<__yDG#bsY7b9=Cn^
zB1<dJW;^55P@l}BXRb(U6Rh@;FROVOOc9zPQpYl;!DG?^MKUY#&FU5#BX@DL7PpZO+
zZYZ);xQ^TN{caq}huLEXWhVd)4E?p{hHL#(Ea@*!GDu3NRCR6Nvp-YXM#Iltq2XMR
zT2h)<8h*#{*?$Dblf9wPzyHH{jyZf7bsIa#&Wcf8mCxdDHqyU;4i#nDEL_~cz^Z)K
z*R|nf@5~3!UPaSRf8~tR^4NE;>zH=_#H~_Qb{O!Q3F*G%mdLZ-cs5|-RM_NGW1aT#
z`I`;oXQvZWjjfW2`~<&lw6C070%c2JV*;1~
zEb{r&=!f<7erZ2r_2=or!+oKbvX}3e3GB;s-_(9fM)pzsI{F*4Vd7a4TCW?i8B&}9
zk{!^6sTN(!v|g{9KE4NOLU6G6-o5zVcYp1rD_KYXbaZs2VQE9Ro#BcJ%8_sa-LD!YlByf9NA;A
zmwXB9+u@iW;M0a~qpY`UPXOhxcnl28g_nN|f7pr!?C9v|6(#*e%C=fFH~@nF$3L6<
zQ04tOEAg5`EkjfZLyi2X}(JjHnnW-FyR6
zKM!69e=mJE@&SGIU%$IKJHVs)F!`j^Ki~OELwh1hV(XB^_B?z6TpNj{(0Kom37cQe
z+`|3$-J8*&_(|$vCzherHE@f+26R_5@9leZn@rvqay35`4&&lSm+;r3pU8IM+Mi09
zHePer`~|*lbI|d1UX}L5?dqqMu+we@SPHVC2rTXatU$KApFKZ-csbUj;evypz7^r-(cYA(-W>!sh1Y
z{!gm5b#-+<$veHrB%xHqaMRy1B$CNqKR+vig3=8I4T-u?u_k5K(lBclcyP^~+xYMI
zDa?Qf)By=->&~T2k%2;JUbtt0RVBXVittMtr7>EfTtnuGxfmo}k2MqP@WO3`5!P*K
z>C9Jobsp?LbUFtOgVCkI6yaNy{|w6AzI;}C|LIK?S4y(8B_~u0<~%O>yjJkL-=*5`
z?nyABJay9yPFROxPre+Cj#*c1Oj=_6qaUpKLSeSO|3Ccj1I^9Nj2SbAoSfXW``_8w
z$tRzEPGP@7#*Vcjpz9m$iZxwNhjE~DtsUNfqe^|z2krdluI)JPVBN&
zQByD|b%=^#LsP=Hs{*-Z4PaPX-w`pDRKD4yO=?v2_ZjFDPf9gJ$W{9w_4rAHu#b
z8-6{mmff~h4008Z)d{|6^Hou`-3yJ$75u5xV_<>D!cPQGy<0<*fAyspPF`2b9Hn?x
zDK-lc4#4o?3A~8SbxpUGy9Z4+XAWlm0-H?RZ8yxJhi*aD*063}ZMZknu^5+3o5t*W
z?m;Cz4w`l$)33UU&dr{M80^Ke`Ij;akwySVE*~VjiL(=YT
zqtvHwMJ+`H0umNSt{#x>@0aZ7%MGYTHCIH7Nvy*JJ|3p=tm1P4)R|t;H5YXlZVtw7i_*Lx*CPFSnELh!L!>tLLScUZkkFnEoXt46Lb1RkgMo);&{;`jtZU
zx7YnkRjD80BeIt7O#G`)aW`#7c6E{6znD&GSh*H5W1X~g8B9FRZlgZrkq-nvt=X08
z(p(z>%szQGC7C4@WfrlnV;xnwRjh7X&A_}GR<^BVU~V<5I@VB=Q_YIj6%5K7#L~@6
z8C^V@_T$@m|5l9-idTXY04aR**Y7w=g=UkU^jBDABC5jaC}*G9;0Q_^Rh8Y)Xt;Hz
z;*h-*$4n4hI!N(SoySufC5{%HTIzA)01w?MczCAABmWi*sEnxU`Y7!IN-eamu{+C|DJpOn}6?gu8Ozk?%nP>GtNx%K{Q-~|&Y$#$-t_W-AiIfNa{cHBbyYrsL3UIe-_)qjjl
zKq{edGd-q`ki31L;I@?>$AQ4I*l3X+a3V&f|AscvU6ljyo{(=3%@9X8bM;xpX=Q
z9eOBlzV;d$>guQ-I54tRFsL?#d{c4!1;qJEsCEK)
z{I7d$aRA-*$I(>IeEJunoINLi)G?wWk(9jfj%4AtlHVUDIC`jHy9|L+f>F61OTP8E
z^GU(yOY90-;=eycfya%*eEKOvgv7k{CdT!*96u*Z-aQIvYQntmLeCES(f}^)TmI5cMm-bKJbBF!M-?9xa6C7V4_{R-`@0VGt^7eP1B*SUs~DdNW*gZ}9!T%!?-zb1CfF^KoyN4IyRMNUEc#e!M}|IdgKBbYpSGPwl>
zbiOl>?04U_+f?b2obw4qgPvxe6Hj8t?M72oZ6(gN4-$p0$O*~#lJQhkR{bKBdVKzP
zuKDP7-0U1)_zp(6g~Wtnc6%8I49dn6Ypuk-XD}CCP5T?Kp=E-+%WpzFzcRdky`!AZ
zJ%h>0!cp=lwSH*|8Jz$k9mtYWcE9}XGRc*H7u-74W1GRY5^LphkMnO5G`6Hd_!Pwjj2MAA^GwXW
z_rlxnV7fZ3j$Fe=%!L>9iLyp*w!)v+)nPVo?&&J)>(-g7s5?5h
z{mm29{;+^Q?{<+zXfa`qLu-wjkwyE$1@;_XD}*N?Lo#KQB!7e8o+~|8Z?N09(taKn
zo+sEV`$hPphuJJ(s7xhFrGm0j+}vD}GiQ=9c`_zHA4*Fx_4Vn4zgzo7?|*4&DFAOZ
z7P516U+xY_4C$pHob`d*eWsz5%y8pozAliR5`oBV{TR!`YX9o}9lb<%TL%xN%
zKKc9$77i`J^CX5OUZ>)*&mN~}htp6>Q}^-x$i%*>37}V$bJ8Tt-FIVFt+HHsA1LQA
z|2#4J{>CE?qvX}seir2%+uZ!iQO<4?Ky(EQl69W&s4176aim~yrN^=sfl?kLssuNl
z=W*L(g0Ft`!hL->`F;DX{jTr755mKkIOfQsF;mXoBFaiHC@*DYR=&EM5r%hRtw@|JWM}f$-Z`hUgu99
z5xe%(5}HDpV>yMmg@qJO{55V-F~;*KJme7Eq9WYP3=1UNZ)m_DrN?KDAccc4g73dwhv((vIU4P1jz9X^u#!~oaVwYJU=zr3ee=yYr=O1b{(B44
ztXqevti-HciitGP$8kv=YQZ*Uc
z5Rb)aS@1mi=xGtbO9`HbTqJO|_i)Dwj1s7CJ;YprNqV6mM_?>rK_K6+Kr5>5xYM2w
zXJpv#6_4X&Wnq9pPd&xbefCEAD6<3#2bJ^qye;+tjdOntJEX_IQuN^|?`O$-?~jD-of^8M*tv)^@GzyJ08?d9{h=m)`GYyG5i
zrU1voajip>6SMc<&60WheTD!4AOJ~3K~#1R?!vJ{1o8DAOXf}F@FP!V<0eSV`4`#Q
zf=8zcoM|4WUi8_293k+YdWy`+lQEV5kG1m-kE6IB_q%te-X%B5y%%gS#ip1JrUeMW
zKxhdNYDfr#5{kd{0Fyv$zY-vX&_Ye<5Fk|3!5ECe*amD|DDJjw$+je0UAo(8zdvSn
z?@l@y6MlZ4dvvmNy1m_**_qG0pZC*`*r-t?uDur1(2z9#isbs=xbqiD$t766d^s<@
z^b$Lb8_N;D_!Zx*9>J%JTcI<~A>(tHdT>8x&3uzrUVVeJ&pw;I_S!3@Jh~^Ld6+UjxF^h`8pqlUvAI{Q!^5TZxjA
zGbavU`Y)?Eb_T@s^yx%5ZnPac)3i0i_b3I!hr_~!PhTb&k@-O^&0+LFQ&8X;Z}@AiHDaAbcsOdIqY}f(ainyWp2IHXZUh~Ik!uj?$UlFpbHDp)zx(T^>(`xU<{_M
z9rE)rSy^cXLABfA&}C)e&U&yFY>Z*{r*jxPZYRpA
ziV8|gOPwS~hwUJS;HnP6;0GV1BW0#*Qv3<2*DpF^1Wk(<15;ebR|p~9HBrTe?kJ&x`0Cx{l-$|y(o8-^63wFP^pq>le_3z)mKZhTFI9s-Cp--Pm
z0Cpah9}fC``&Mzp5l2u_QNifZqtmD@?MWW!RY^Y~h)YEzwX|7^&MUZn!H!SFH6S4r
zm3@j_y0+823x&|m>MbOTmc!z8zI8dYc&MmDf!i(r@xNPDqt~a=eOnvQjH|Dw
zckfFxV2xeC(knjdhH`#=&DTkUGbPYAnp7m;q%q1m4{X
zMis-$7r~Xka(2Xj#5>(QOzd&RgeD9KgD27eTO1sg00p)3vh1_$3?~^7Y&`jE8S5{g$m2KOR0P7wo
z%RQOUg}qMws(u^=j(%*+Dm=!;4EZiWw>j;)r?`!!hDhoO0d+=McNDVAK(alnZ_5s#
ztUXy_CGhB-30m4bqS3(4WwQkVS~@&@WysCaNC64y5@NI=A#AGWS8u0gU@`>d+878Mwj>0-7GOzx?6+X(c!ZUVG4rDL$Z1Q`e4rblaESPQS{-VvYO
z0g|^T5ksoXcEG^st6a*e6a6S
z+%wnGQeGDn)l-V7Ls6q$P}Ec;wauZd{cfM0!3yj4wu1hGGhjV$SK`ks!LMXv7LWE!>arLDSSbsYJknCb0ovo7M;8^8ADyC
z$3A`&*%&r76;jk1l>4#+M0tfAdPZ7F@LEyljY1QTb$LkH$)C(6U
zGZ?u6vRZlT)dI8<{Nm^ij1eT(CI4Qms=o!1f;hsbOHrX`$lbuxzPk8Hkcqng1XM5s
z0K*C95&o;s$9MVuMoacEAF!8xizZz{n~Ct2J`T^#0Hn)wx_{Uu3@jhcCY1<{z6oDZ
zQ4-^n!+K+k;fWcWSX~>(gK$@%f3F-?b1&H&jwi}OubJZAhpoG@8G>I%{cgEyhe)u==lp?JYD#*%u`OP_
z7!2$(@t7WeXGz;dRQVF&3%fjV1Aq?v^{&HfPMP%Mhls=I=fp@*Us-hJQi|e$kP{LqES@J&!SlWMogjUXV!45z7mYVGXhTw@=P=1
z%x4%96tj`t&3k;T52RTa+bOXL7GySC%-5dX9FU4g#N)~{22hl5O)poUHHZuUwu;6!
zh0z{DdL*=G|3D-X8bn;(Ukrx#SI=b0=B2bGTB%(1KEK)Z3|ng;<@co4VPw`$GVJrV
zPR*#?2)DldF73YH#>=i|LENJ#q8&$LQZim62<SDHV29m|g
zHuCQK%aBr|q@wn+KR7+MbK(bgekDRhc2?y8zI^m)PBz<_-Zs67)|%rFKep#d@-6U}
zshw1}wS^>RY7&aCg{%0RZul8oT*B-pVe5?BC@U-b$yQ)IkHkTTalw`?6b>3>mFW(S
zF#%wub;w%;%LDDU1!LLS4E*8?_B{LSjGwEbs*2lgzJ=Rvy_xkn?U3*@{$8pT+PM0I
zMhZHO;Om`j^3U!Ie6kaMdWGSxD0!FH_jKp)hO)aGQYdec24e)D%*Krd}aq}bOw_u_h4L~aotcz;rxKg
zrD-POkUPku@jF4Y!+x8VCde5aCB6ZaXC3=e*d@T&miHLhQA}xjvyBl*W!LL&J0;LF
zNvTjmI^&9>PktF6@F?0GZW#f$HbdIGrV{p!n%}RTWehk-s5mWs?MdRCSN<6<#a-=Is(D?|Lt2mw`9=-IQuZLJ1dtWs)w
z15qA@hubBW+!DcP-`@4Z?LjvHnDoG)0z3`gKgTotwaYLbEWh_I#M+LufUmPB`w|rJ4#84ge;t{s27i+5IS^FdBpsfX82F-Odxgek!Ep0xPSn
zR%ECCLK+esFnsqY?_KvI|2W4dJL+@mKmNt!`z~PF)7I^#omdnMpR_+g(Y9jPvd$V~
zg-o;GX8Nk7AQ~CQskCFXQ!r`iiZUbz0NH6+J1dWo2W|%JlvcDzmc3Qt0)mPq6}=g7
z_ic#U+VnygQiM~!z>}|C1zjyAS^Wnv|Ed4-+a6{B3ub)GqA$P10CQjckbRDy+GA%4
zs?i20ww(d^_$eaQG_k4?2qU_6{{O`a+^}#V1p@}4d_RPKg1dY;14#Kkm<_~oa;To)
zvkm8j+iGhmFE9V;
zpFa|z_S|z}QXNV=<+?IRp)uM=N{?wD-+>UC)5czcG0EwFSY8c4+8KaMS?lXfaU#1F
zQ@-by49#yq8t|93rY!-qa~s5vcHMT0zME(CpV?}P$kPR^)x$Z`{F`F4g+*ph_A~!w
zPT2Wv9W}JwDw9qSAswL6*eSXC!S$STtTip%|KD1+G)W?!HLNJ9Ll8)Jv9e+e_a1vM
zHLaUC@4x4ujN;rq&ScV{$tW%GTrH{K_k&t(zOZqC&WNGzE5Vq&hnaA!l|b$@{}Hy#
zIfpiBD2N(_u*(39O~%Pn0vUAv6a%gftJ9>Yv0Y#?sw54-NaOOhgU&B)@d@Q;A$>#b
zvK(4Amr~Ndm72vl44YDiOei~=E5*3_Eo8YFz)z84dY;EVf4rPG*Uih=s6TC5z~NV(
zK_nXCwYG(9U00Ox_g=XbCKy5IdfzQK>{?rgsknCy4g%{GicCC%>JMt}9>#Nk~?q^y`N`?*N
zuL~FU*s0gmRCCrjXHlGCtGU|}Ai#Z{RoZIXefIoHB=ZkMW|UZGunO;})!5E{)62jq
zp)tniVb8r2i7s~eGerKTibHDbLqic
z&Qsw|fO54EW1y+hE4fnNVAm4{R_hUbuZL5p>p0RpNvYn*BkCIFm;-GPP}^!gXgH?v
zP9|^qJtH>Oqg>Qq3W>i`@aY_h6pArIGtStbDJ4--Qz3fuF4q$udOaQ2{1H{XF4_5n
zpv+J3k`zckf%n3V7%6EN$!(C>;9<19y8v-{CrShbM?9Jm9tY(on0j(2W|P1upL0(a
zyx-Zv*WVYBZHy~?nQ{l{V)ZpaDXY|rAIt)z2*sWn>fx%b-O0N1SqYMiddfF2;iWv
zG{Z}yY;3hPA(HhWlEqwQ>T}nA1{Fc`2LWG@JpVz~sP6Lqrpx;WB;&vv_MwMu0GeA`
zh(sa`E-XN6FY^F!!`yCGY10M8WjiSG_N>A`W;M1MsS=@ZyGwR3Fj`<@+cu4L%#wG0>vS
zNGK2-UVbt&E8iv}Jfg-pE3^r<*j}GXBJSd9;R;6xVUuQLa@5yag*awV6vQ)v{8$7n
zJ;;gzQN|zJz>?Q1$SP21k%iJ8CgLFy9`!4tlvFiRP?2ENrW&$)^~rb_$=sll;nIEo
zv=qLYo_Pzf!;t9Mmi)e;S4IxN2fG|eu_^}x87U~Z=dNE+gHbBw03J<*2>lD(_Hkg(
z)gZJNJ`ZX~D(#_M#VKOiUVkCUudH-~osGeSgFy-dV4T%9*e;Yn>9Iq5yt|hNH5#?<
zc?NL9+-_Fs9YW#V_0Ncc@1WHu=lP3+?l})}S%8KauSCI)xSTc%|L^RTgt~WLp~Hmxe&fE+n91_=!O4^*_(*
z8}ET|+n!lB6{CyJ_8_Wx+b6h3>yhVX8l
zY7ow78g|BGDJ79e1g$kwrflrSJ8u2rd~^hKJPF}y4WCZ!ehgkxu)6;|2k{
zV`jR2u3-3yo!5@n*w|>g8cjRXDeXNc2Y#wonPX9ly(=yZ1*}SU4lw)kf|MamkHW9n7d9<=pe+
zSInQe0<=bLy9uo|Sy_Jp+BoInqu6cdK3sA3v(DwE;h(SB-GE3BcLA9p)gVCCb74;Q
zwR{p8#kDWBaD8M3#WBfcwFh$kkQE%YPd|S7OiPy&k?!7ZZhqBX;7FwV-MyiK!r{ZW
z+kRJcxuKBZW|L`f41RuShCyLR29n_Q6MAS^Ij&o1;QsB6U}qHAng_{s1zYZ3Lxf10
zky^&%)D0clZ6J>Ul$MsJPhoDo6;t1^<0#{O$FSSz-W26!@##0Uv~~(tFVOA~1eHw^
z$|}}I#Wr$6;pbHT1-J^hXF6O8<~5Din1WLJ63>%F&yz%a_r8S!bB4r>5wsmU1CHo!
z6)w#Q;+^jGQAuR5z?`1(_^W!PNQ2=r)+0??7^0y2fLWmN;+ku9A+5p|Fp<(j$|#6j
zvSfFwagSD8sqiAUskJ4XjahfG!aicp|2@|n>8_vEPxmSo46YLFKENjr$@;b&-d&)n
zuJ@eq(@xy$q!48N@e*6?N7>_F@%W?Z
zO0e*GO$}_?w8JQ4zw&&J*mo$#z_L{hEM2qJYNS;%m%Kwn$x|(yUib;e_-5n82)Z??2JUT;O9?2)xG!qUQidcee^FEg-7WON9FZ
zv$Ff~doePjv;rtS*g{H8A6arD14?UOYcQ2H(4b(OVuMsPOM?)}hw(L9bhjFJv)U?!
zHiCkfLK(pseLe19-%gtpv?P3dlVXKUVM(`aYey+VWvS2jVXerT?`UnU#G{OX!`E}j
zh<2XbJb-nz5lb8PDs&*0Ls9G6Shb;%2_t&b*c_*+H4(-g*4H)@Pe`(}qpYiKp|V##
zTDxUcc9-K9*2b));`ovF+&}rP-8S^4kME@%e+(XPdVri2WyI!vNLF(zx}$^a@)F`A
z!rq+;=C)6!Nkkd7*IsFR<;~xJ1H{o%vTE%b>S}9|La@uOyCxgGQr2kI)YN19{Qx+a
zBHA}@P6pS6?fl9GVeJlt%|EbYqM&eK6_2NA;&=iEIZgIg_v!eyC{+gUAV=oTknc
zD|5OPda6>47lMbB;yM?Hxm5_J`GR@EXL%+ow1L`|I+Rv)3I|kqU5ld{{59)bnRG$4
zsxmnP=w`JIwnKIVWki#c9Szgqv$Lb&9bYL!hw$(POwL-yStai=yr>y7;tMm8mAt=v4hs{svwB}uP9@BGx`4|L{fdi!WjOC%C!mzd4gkJHNX@jVBkA9_
zh*%V&QIF0M&G9DzIjw+i>-%)z6`Ey$(AuZFS;vIq62er*FkkVj_|2;b(hS5-6+)d{|@
z>4OyZ_#?3xW5$f7wXHq9^9Srec+N9l|Esx~t4j`ILr1$S!DeYp-wAAzQWPiJ@U47f
zP(=|l*@$Mgt$2;*!COWV&$77?aT8}~YzX7C$MZ_-t0Z&+KcetDeQrK>9=Rzfafvo`
zD8*Q%*j)(ztrVyFKIdC@son-m=YMfy=mvQeo;it>7b$C|`vc
zy*Fm^Z!vwwkT4#%+%uE=CV@9dL0n>1OIRT>I|Q(!uLOqGB!
z0%xVpU=fvjv%rTNV<^+#>{)vU#lnD8XAab9I5*s18L)v1V+VwwA_)K)$k*}wKNOu91TB3}$N_3?V
z&Z)U!#XK6;e#1wvzsa%ZUCgLm4zr)#7`MG_w-IBRP~7?)L0h4pLZc-?5F>n)6y%gh
z=DHllj=12pIcq}gS7*XQD$OCW&-rb|0($Q>4ztfgnDVhy*C%-Mr6nv`)xfj2UCw>V
zb^+5NFl`dnOUy!d*Pr4#@KPVsE-=^mm|;GqSU|BvUkyB-$tvtRed+*UGNYWmV@XCl
z-;=Z_R&YSCbGUx&8R7pwJNag2H+;dn>mQ|WwxlQ~(iZ|JD$F+mGfl!KiCC5F_`x86
z=)r0%(^1^BD?OHp{rKhJ;fyOC3&wES&VQmQ(aH^9-pG}MBV4!25_>i_Cee-~mr9}X
z6sB_|d+bz5d5On?yZ7PPg9kBZK{Hv0Y`|B6lFi0Qes}!Nc%H}jkrgC-g$Kl9UfT5W
zfJp;Fa3!8lM5B19|RAcl=r)jpXZk$+HIwe)UXBdGLhKD`yJsUYaay5gfl>
zFqb!m7V|~5W@0QCq-4$y%lURt0b{cao*%b&W2H&RCX|-8Sc#j#Wb`ujAd<~^{wqJB
zs(d6-*%)HpK}QN^0751aa4981DysUFGn;oEM`E`QgNi;0)yc@5;Yg$Sd;MKxk>!47
z>okO=2vE+3^5hIC7AfI7zm9|E?8|c#pX1bFr&{yvnin`_@hKEkjG$Lp
zISo2JZfrb>86hz-i;`NOp6&R-KtPunKxn0v>Bs-Tdd1)NK9VsdV`vxcY-roS{{0T1
zwxgCm>~sOwF1#(Qcofb6+`hb&n%0(bb{g8ibsJiUMT%LrdL3V{k8<$d-yxcF5JH84
zS@3!M5~wCN=8_ECYaO`6_u{4i$p0+hlmjK+c~c28&2OGWhXHO1R0PodIo1`zkrDkvwe
zc=PLA`bXCCuajU-U!%Yv*Mfi#V{R+AKtUY-1X%
zJAfyh<&T~Q*B>FMZc4UaC2iuBkWNCYg4vWG)(9L_CV6vlFoTrg48RvY2E)OV4&lAI
z@1m6A(0z`u{n{7c3X%jUaV!(cn0lvAeoY>LbOASe{(U;*hEb7f&fKGcQ4{xMfxi41?EFHVowy9EFbfNz&t512iZ6P
z3lzMm!j0PQR$)KG3?MVgdE)!g^yz$K6C7|Pyu6q#=l=nWVaOv7a$frp@YXCSt4RBG
zABZZ6sTGi8>BZDLe>ejWJy?xXkKYZrog&VOLr8+O;;oA_#VKi*#8xhgdF|6S_=ptU{0+(k9)xpO7G
zO7p01Y!5q4n{`&e}!$OMG7b#^*l^Qrb_2O)v_q#!@S{
zi{YcBN$dP!$Ct_HF{QcV`F+CxbKEUqwvXcK?sxw#=l{G}aPnwLW5SZFQVW#!?ZepT
zS_@S0|HjtsE6-J2F$DyhPPy}ptdZ7478%_&DF%iHN75NdF$ah)6hx}uDI
z^ZKHdwxhMn50}bjjHOv|eR~0q9@@yL{fF~i=iWU1X*+AHe?TOB`jqBTQIdm@))FK%
zuJbFKO^8Gv1CgsFY^cOMAfQIU0Ec3Z5)hFvMA#(!l@e}LaG8Q%D)c}yT7@H?00f@r
zP+)A;7>0F6IqUnH^ew<%C3qdHQNRBqq^@#F5@e**dMCwY{&Q+*?588cN`wzC7jiUK3
zC*p8c=6r_|YnjX|2&E!{-AO6@KK5_O;QC8RWp0=Ar_-(0-TI$Yq5o58N#DW>gc5X!
zM0nj&QDiR?A#2dUP++2^T|#&pHf%6yxT4mKzGXCQOyNDZ!(tv@{0HXM=F}?zdmqodYYh2lGu?-Z#v6_AUU9I`vXo
z6FY~8IiCY4-OdO5Y!|rfB-a?@KT+!%1D*(2*qD37d*EMtaqmBm=CDydRa19QcD7%9
z*+uT)SaccKX-OinzI3W-Y0=oY#+)$CUVumv=1OgEp=U6THQV~`c?al1sOTc~ee9aY
zIBvJ&$c^RN6EA&Ykr*9*2hV@?JXieoiX;jvfP1^6%xlkEOKEm#8p^pXzKzkhjZU81
z;QW;u(yCMj%IfgZh0{-c0A_yK&$8OKr5bjPA^+4Rv^_hXbB(`h&`Tf}WY|SYG
z(CZeBjs8@Y->W*)q_MoAmDr)s%I{%-u`c$@E_;hRV-Is%;U&o2e6E{!I+u=kl(-+~
zzEyu@zm0$5{TH4kCokKo%G|@c2^8P`YY*OQ>BX)8Uc&XgmysQbFnvou&K;~d>cCO_
z>Zw{cc*-uLy1jrOGpE~Un%LMJrpkweYY&g|^78Hov;$1E)=*Kg-5i}*S>89SUmFFk
zaKR}1$vYF|$2MV%M{{Qtes;`x3X{II0QQi|@X&={+ZKH8^mMDOOXfjKJY{qL
zBg%<6mB48Gwe6A_Fb1YyltelA9k(x~MWsZ%C~b*WI%OwSMOEafTn-+4Fel&o>*VLZ
zs0#}0Q0BhneaVUBrlFiUB8StDJKZ9+spl^ru0J8EMAyb;;2GR!_mDb@P!VTxN~Yz$
ze3R?G)9*;J+B&zL)D8t#4h46KO+m(|5VXyB(pF}=c6vAkzw68@JkO)z(m$sdon@LB
zRJ%y`>epxBp;-LQI66HqGHB;{t<_5=7yYCWK*DvmpKd?D(l#GnDYrG|02#Ma5(G+Z
zxq8K`f4!Q9{3=$R`*;4YVclk=BXFuHXqpTyo`$_JWgpXZ6@M2Kjc#pr~`
zh}r}fK3~VXFCNahe?E%%ci%#12h%G1Q{20px4v%R!u~B`n(ra`TbS8eL2IW(NW+XL
zY#zscO-*zxTuA=jd$Z|=8z?C$+94}+ZEdYZk&EpFc3X!%zKSBk*ZY@Tf^KimXeFwj
zF@xA&{xkzPj=A$on6&d;3iCTaD~>v76PJ8+5)HpQn_LnOXH4d3M+fw0)eA4OFEFIR
zaK?)@Jh8W8!BAW2RJpzFbiZ6OxG}I`pxcDZCOb=T=20(^o72hd|2mZF^?eXhke3(b
zv@^#td2}wtci({dcurVh*t_;R7G8Y@&9~T&vipEn7*Mg2ijq1C3VfPca(Hv$G0gd4
zvTFb~QZ5{xTv?WFTTcfzK-XAft+v|28b~MjR3d4!RlXH|2!YpYpi3!sC}*QZkvkKe
zA&U9;cm7VN-^r~f+=|wQ)_5z+x2&)zXN5yKW8G0^3%8_SY^4<|n^%Tro)86gHprS5
zlahLC({hDQ&&wCm;JaM?0VN)EB=h{t#!YT*Y*AL6_#?zveVN>v5
zEsYjLk?YpkfLmA3f^Yvu)Sc3Fjd@-i)D^$)N1!J}{`qO`7j;dz&IU5-H!zV8k;bkak9!vi3`nsojz}D-IY#ZI3{l+Biid|>MZ5q
znqCa-Um8xvRdX|i`|e9(^=ka;>K$?mc&<8i@UOPT@bV%>Tf*?$$q}lGEz6?>YRnWq4>j1W`}mVS`5_Mpba}MPn#x
ztReT}^D&zmY~$-cjMnS_%&NW~hhIH~KQFnKX#
zohU64I^WKI0y&2^^v;Jj4%G0@u9yLXA*V1!=^g02gS)p%VcMPivTyI?0HBP8VE`3G
z<)t$$rJ&?GrPf`T=){=NyL47@DNGEJ<#E3YiwT+l)u_h`vxENyt+S;>o?bq`sa`uR^
z@7{K?hzfLjmL%w3@(UQ*RB$e0wjU^(+V?t0X
zY}S(SeV4@~5WX$VJ#f%KW_&OM_$w`KS={vS5#(pLvvyNC$NzdXM;=f{@pBJjUYn5w
zXihqr&8Hv3<6B;3&it9M5$@V>Cv5vabgUC&Bukb^#*DH1x1J~1b;8Y|NVZ7@5Ef-UBo4&~8#oIcy&b@)&CclFDsMc`$dyTsCdn
zL_#LG=e>Kl^oUDQT7?5bH~?fsnY&+bt+kVLrlY{~ey77|Z`$#<{TtENg4TjqejcXa
zf-ZLuxL^Xb-1iAZMMeLU)py$kCoyKsn6&qo%Kp6cmDhjt4mPHx1vzB~9kXVUx8HtX
z4Ap)5@D4;O2UnX->u>OWbO6YgL?e~@>kE(NTGN-FDTP(i|vkw6FMjenf$
zN=g~~!~U$BGLFH+X0f2|O!ipuEwA>P!o0zIabw~N^1Q6D^V>?jRw>u8|8aPQ#XP^L
znQ>kj5uy2}X&{G64ljg
zefC+5kgPcN*kmX5lQXB(0W@duU_O~Wn=_k}uEPU{N4kSh%`I+AGQHSn)D6I~cC|B4
z{HK5;4}6z1PnyNW*PLRfioT`UN}qj_yr`c{t}wSj*rFqg7TNba>nwq{lf?MFU0sWS
z$#**DE~9AL)N9L1%!0Qu34w{`V8$Mj(Q1pvp`#*9#K6FZOFyJjbdvBB)HKy3JF&EY
zM>u_x8BpdPI-oE8a0w8iz&%-cmCw6tM=^BaT*jUp>(R>lqbTim1^_7~*(WXGA7}Sx
zbEB{xKxw$_sT#ZNP|2m2RN5{_ob{%vEQ`nf_$8wU*U@nN`Doy{L*Jr#!h>`)ZNcaS
z<^3-++iL#4-9cdO!=F+<<^V*Z&Ylyk@UU_g`w&*lqFt>6Ezw#M%k9VVa>Zlcu4U}!
z-*Mljy=lpda@qAh?@fZ4?R_|X?eAG!@Eh`Csh!^rE|CamrD*x|GoI^T%9r0aQ&(cw
z*Ktd_+%LZj
z*SuN?+Kz6aJvXOIQ365M;K6)5b0!mk|Gurb{Wl$KdoP#VZHjp+i$|o*{rBA#zArrc
zReA^+Mab+BrBO<;@4@}~?Fs!UeCHLsXP&YaR$pKao=W`%r|?$CeEu=(4%+-S1Su#i
zK75&tK2UPn={`bOE6-VHSpZ0VlZEF8Ygie6^;|MUk0@#RV9bR5Fd~8JJE}*kEq)5h
zD}9t!JU{n&TH~#uV!N@ukw-szlu-jlC6jqn(hJ`mW&V*RKq?B{qm|d=puyju2IgiQ
zP=e#vKf~$^;J>tU@)$5&{$wqG`)%cqzCIF(aNRHW=YYMxuzNhW-_9(5A)_B@W!uI(
zy&YZt4?S)VF8fC%8#d>%_xOQ)zH~h;?Fk1ENn6`N*lE9%G6al92QuQ~CHMl*_whvXs|mgrf(THvN>kfp*x2Oa
zJt$)(ep|vI_mczofg%-q6
zHcKy90{Fsb!?q2yw6!3lU{&2JCR9zZaFw=qf2EQ$fUYRB(FW5c2&j!+0t9eY@cDOp
zRP*P(JnGv`N&wIx_1So$31u{$x+E;GEu9p#Z=vC~rz9+Rk}I!0fa?18@J{Wg1<=yV
zqS=z+iXnXq`RI;^S;?F9y68gWnzfX^J_F&F09*H;5@x+2{Nz+t*x+Jp_%Ocxav`$c
ziIiV^Ek!4vZ0k7A|05-Re`}hzJiTLsfXltuZ