Unverified Commit f51ece25 authored by Paul Kaplan's avatar Paul Kaplan Committed by GitHub

Merge pull request #5083 from LLK/release/2021-03-03

[master] Release 2021-03-03
parents 3aaca755 c43d68c5
This diff is collapsed.
......@@ -17,6 +17,21 @@ const validationHOCFactory = require('./validations.jsx').validationHOCFactory;
require('./row.scss');
require('./phone-input.scss');
const Formsy = require('formsy-react');
const libphonenumber = require('google-libphonenumber');
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
Formsy.addValidationRule('isPhone', (values, value) => {
if (typeof value === 'undefined') return true;
if (value && value.national_number === '+') return true;
try {
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
return phoneNumberUtil.isValidNumber(parsed);
} catch (err) {
return false;
}
});
class PhoneInput extends React.Component {
constructor (props) {
super(props);
......
const defaults = require('lodash.defaultsdeep');
const intl = require('../../lib/intl.jsx');
const libphonenumber = require('google-libphonenumber');
const omit = require('lodash.omit');
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const PropTypes = require('prop-types');
const React = require('react');
module.exports.validations = {
notEquals: (values, value, neq) => (value !== neq),
notEqualsField: (values, value, field) => (value !== values[field]),
isPhone: (values, value) => {
if (typeof value === 'undefined') return true;
if (value && value.national_number === '+') return true;
try {
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
return phoneNumberUtil.isValidNumber(parsed);
} catch (err) {
return false;
}
}
notEqualsField: (values, value, field) => (value !== values[field])
};
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
......
/* eslint-disable react/no-multi-comp */
const bindAll = require('lodash.bindall');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intl = require('../../lib/intl.jsx');
const Card = require('../../components/card/card.jsx');
const Checkbox = require('../../components/forms/checkbox.jsx');
const Form = require('../../components/forms/form.jsx');
const PhoneInput = require('../../components/forms/phone-input.jsx');
const Slide = require('../../components/slide/slide.jsx');
const StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx');
const Tooltip = require('../../components/tooltip/tooltip.jsx');
const {DEFAULT_COUNTRY, NextStepButton} = require('./steps.jsx');
require('./steps.scss');
/*
* This step is separate from the other steps because it includes a large library
* for phone number validation.
*/
class PhoneNumberStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleValidSubmit'
]);
}
handleValidSubmit (formData, reset, invalidate) {
if (!formData.phone || formData.phone.national_number === '+') {
return invalidate({
phone: this.props.intl.formatMessage({id: 'form.validationRequired'})
});
}
return this.props.onNextStep(formData);
}
render () {
return (
<Slide className="registration-step phone-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.phoneNumber" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
}
title={'?'}
/>
</p>
<Card>
<Form onValidSubmit={this.handleValidSubmit}>
<PhoneInput
required
defaultCountry={this.props.defaultCountry}
label={
this.props.intl.formatMessage({id: 'teacherRegistration.phoneNumber'})
}
name="phone"
/>
<Checkbox
name="phoneConsent"
required="isFalse"
validationErrors={{
isFalse: this.props.intl.formatMessage({
id: 'teacherRegistration.validationPhoneConsent'
})
}}
valueLabel={
this.props.intl.formatMessage({id: 'teacherRegistration.phoneConsent'})
}
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
</Card>
<StepNavigation
active={this.props.activeStep}
steps={this.props.totalSteps - 1}
/>
</Slide>
);
}
}
PhoneNumberStep.propTypes = {
activeStep: PropTypes.number,
defaultCountry: PropTypes.string,
intl: intlShape,
onNextStep: PropTypes.func,
totalSteps: PropTypes.number,
waiting: PropTypes.bool
};
PhoneNumberStep.defaultProps = {
defaultCountry: DEFAULT_COUNTRY,
waiting: false
};
const IntlPhoneNumberStep = injectIntl(PhoneNumberStep);
module.exports = IntlPhoneNumberStep;
......@@ -20,7 +20,6 @@ const CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
const Form = require('../../components/forms/form.jsx');
const GeneralError = require('../../components/forms/general-error.jsx');
const Input = require('../../components/forms/input.jsx');
const PhoneInput = require('../../components/forms/phone-input.jsx');
const RadioGroup = require('../../components/forms/radio-group.jsx');
const Select = require('../../components/forms/select.jsx');
const Slide = require('../../components/slide/slide.jsx');
......@@ -703,93 +702,6 @@ NameStep.defaultProps = {
const IntlNameStep = injectIntl(NameStep);
/*
* PHONE NUMBER STEP
*/
class PhoneNumberStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleValidSubmit'
]);
}
handleValidSubmit (formData, reset, invalidate) {
if (!formData.phone || formData.phone.national_number === '+') {
return invalidate({
phone: this.props.intl.formatMessage({id: 'form.validationRequired'})
});
}
return this.props.onNextStep(formData);
}
render () {
return (
<Slide className="registration-step phone-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.phoneNumber" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
}
title={'?'}
/>
</p>
<Card>
<Form onValidSubmit={this.handleValidSubmit}>
<PhoneInput
required
defaultCountry={this.props.defaultCountry}
label={
this.props.intl.formatMessage({id: 'teacherRegistration.phoneNumber'})
}
name="phone"
/>
<Checkbox
name="phoneConsent"
required="isFalse"
validationErrors={{
isFalse: this.props.intl.formatMessage({
id: 'teacherRegistration.validationPhoneConsent'
})
}}
valueLabel={
this.props.intl.formatMessage({id: 'teacherRegistration.phoneConsent'})
}
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
</Card>
<StepNavigation
active={this.props.activeStep}
steps={this.props.totalSteps - 1}
/>
</Slide>
);
}
}
PhoneNumberStep.propTypes = {
activeStep: PropTypes.number,
defaultCountry: PropTypes.string,
intl: intlShape,
onNextStep: PropTypes.func,
totalSteps: PropTypes.number,
waiting: PropTypes.bool
};
PhoneNumberStep.defaultProps = {
defaultCountry: DEFAULT_COUNTRY,
waiting: false
};
const IntlPhoneNumberStep = injectIntl(PhoneNumberStep);
/*
* ORGANIZATION STEP
*/
......@@ -1666,11 +1578,12 @@ RegistrationError.propTypes = {
const IntlRegistrationError = injectIntl(RegistrationError);
module.exports.DEFAULT_COUNTRY = DEFAULT_COUNTRY;
module.exports.NextStepButton = NextStepButton;
module.exports.UsernameStep = IntlUsernameStep;
module.exports.ChoosePasswordStep = IntlChoosePasswordStep;
module.exports.DemographicsStep = IntlDemographicsStep;
module.exports.NameStep = IntlNameStep;
module.exports.PhoneNumberStep = IntlPhoneNumberStep;
module.exports.OrganizationStep = IntlOrganizationStep;
module.exports.AddressStep = IntlAddressStep;
module.exports.UseScratchStep = IntlUseScratchStep;
......
import Plot from 'react-plotly.js';
const Plotly = require('plotly.js/lib/core');
// Load in the trace type for choropleth
Plotly.register([
require('plotly.js/lib/choropleth')
]);
// create plotly bundle that only has choropleth plots
import createPlotlyComponent from 'react-plotly.js/factory';
const Plot = createPlotlyComponent(Plotly);
const React = require('react');
const PropTypes = require('prop-types');
......
......@@ -12,6 +12,7 @@ const sessionActions = require('../../redux/session.js');
const Deck = require('../../components/deck/deck.jsx');
const Progression = require('../../components/progression/progression.jsx');
const Steps = require('../../components/registration/steps.jsx');
const PhoneNumberStep = require('../../components/registration/phone-number-step.jsx');
const render = require('../../lib/render.jsx');
......@@ -119,7 +120,7 @@ class TeacherRegistration extends React.Component {
waiting={this.state.waiting}
onNextStep={this.handleAdvanceStep}
/>
<Steps.PhoneNumberStep
<PhoneNumberStep
defaultCountry={this.state.formData.countryCode}
waiting={this.state.waiting}
onNextStep={this.handleAdvanceStep}
......
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const defaults = require('lodash.defaults');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const gitsha = require('git-bundle-sha');
const path = require('path');
const webpack = require('webpack');
// Plugins
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// PostCss
const autoprefixer = require('autoprefixer');
let routes = require('./src/routes.json');
const templateConfig = require('./src/template-config.js'); // eslint-disable-line global-require
......@@ -15,6 +20,9 @@ if (process.env.NODE_ENV !== 'production') {
routes = routes.filter(route => !process.env.VIEW || process.env.VIEW === route.view);
const pageRoutes = routes.filter(function (route) {
return !route.redirect;
});
let VersionPlugin = function (options) {
this.options = options || {};
return this;
......@@ -48,27 +56,20 @@ VersionPlugin.prototype.apply = function (compiler) {
};
// Prepare all entry points
let entry = {
common: [
// Vendor
'react',
'react-dom',
'react-intl',
'redux',
// Init
'./src/init.js'
]
};
routes.forEach(function (route) {
if (!route.redirect) {
entry[route.name] = `./src/views/${route.view}.jsx`;
}
let entry = {};
pageRoutes.forEach(function (route) {
entry[route.name] = [
'./src/init.js',
`./src/views/${route.view}.jsx`
];
});
// Config
module.exports = {
entry: entry,
devtool: process.env.NODE_ENV === 'production' ? 'none' : 'eval',
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'js/[name].bundle.js'
......@@ -129,12 +130,20 @@ module.exports = {
node: {
fs: 'empty'
},
optimization: {
splitChunks: {
cacheGroups: {
common: {
chunks: 'all',
name: 'common',
minChunks: pageRoutes.length // Extract only chunks common to all html pages
}
}
}
},
plugins: [
new VersionPlugin({length: 5})
].concat(routes
.filter(function (route) {
return !route.redirect;
})
].concat(pageRoutes
.map(function (route) {
return new HtmlWebpackPlugin(defaults({}, {
title: route.title,
......@@ -165,30 +174,22 @@ module.exports = {
new CopyWebpackPlugin([{
from: 'node_modules/scratch-gui/dist/static/assets',
to: 'static/assets'
}])
}]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"',
'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"',
'process.env.RECAPTCHA_SITE_KEY': '"' +
(process.env.RECAPTCHA_SITE_KEY || '6Lf6kK4UAAAAABKTyvdSqgcSVASEnMrCquiAkjVW') + '"',
'process.env.ASSET_HOST': '"' + (process.env.ASSET_HOST || 'https://assets.scratch.mit.edu') + '"',
'process.env.BACKPACK_HOST': '"' + (process.env.BACKPACK_HOST || 'https://backpack.scratch.mit.edu') + '"',
'process.env.CLOUDDATA_HOST': '"' + (process.env.CLOUDDATA_HOST || 'clouddata.scratch.mit.edu') + '"',
'process.env.PROJECT_HOST': '"' + (process.env.PROJECT_HOST || 'https://projects.scratch.mit.edu') + '"',
'process.env.STATIC_HOST': '"' + (process.env.STATIC_HOST || 'https://cdn2.scratch.mit.edu') + '"',
'process.env.SCRATCH_ENV': '"' + (process.env.SCRATCH_ENV || 'development') + '"',
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"'
})
])
.concat(process.env.NODE_ENV === 'production' ? [
new webpack.optimize.UglifyJsPlugin({
sourceMap: true
})
.concat(process.env.ANALYZE_BUNDLE === 'true' ? [
new BundleAnalyzerPlugin()
] : [])
.concat([
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"',
'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"',
'process.env.RECAPTCHA_SITE_KEY': '"' +
(process.env.RECAPTCHA_SITE_KEY || '6Lf6kK4UAAAAABKTyvdSqgcSVASEnMrCquiAkjVW') + '"',
'process.env.ASSET_HOST': '"' + (process.env.ASSET_HOST || 'https://assets.scratch.mit.edu') + '"',
'process.env.BACKPACK_HOST': '"' + (process.env.BACKPACK_HOST || 'https://backpack.scratch.mit.edu') + '"',
'process.env.CLOUDDATA_HOST': '"' + (process.env.CLOUDDATA_HOST || 'clouddata.scratch.mit.edu') + '"',
'process.env.PROJECT_HOST': '"' + (process.env.PROJECT_HOST || 'https://projects.scratch.mit.edu') + '"',
'process.env.STATIC_HOST': '"' + (process.env.STATIC_HOST || 'https://cdn2.scratch.mit.edu') + '"',
'process.env.SCRATCH_ENV': '"' + (process.env.SCRATCH_ENV || 'development') + '"',
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
filename: 'js/common.bundle.js'
})
])
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment