Merge branch 'develop' into llk-release-2019-08-29
20
package-lock.json
generated
|
@ -17654,14 +17654,14 @@
|
|||
"dev": true
|
||||
},
|
||||
"url-loader": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.0.1.tgz",
|
||||
"integrity": "sha512-nd+jtHG6VgYx/NnXxXSWCJ7FtHIhuyk6Pe48HKhq29Avq3r5FSdIrenvzlbb67A3SNFaQyLk0/lMZfubj0+5ww==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.1.0.tgz",
|
||||
"integrity": "sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^1.1.0",
|
||||
"loader-utils": "^1.2.3",
|
||||
"mime": "^2.4.4",
|
||||
"schema-utils": "^1.0.0"
|
||||
"schema-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
|
@ -17669,6 +17669,16 @@
|
|||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-g6SViEZAfGNrToD82ZPUjq52KUPDYc+fN5+g6Euo5mLokl/9Yx14z0Cu4RR1m55HtBXejO0sBt+qw79axN+Fiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.1.0",
|
||||
"ajv-keywords": "^3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
"source-map-support": "0.3.2",
|
||||
"style-loader": "0.12.3",
|
||||
"tap": "14.2.0",
|
||||
"url-loader": "2.0.1",
|
||||
"url-loader": "2.1.0",
|
||||
"watch": "0.16.0",
|
||||
"webpack": "2.7.0",
|
||||
"webpack-dev-middleware": "2.0.4",
|
||||
|
|
|
@ -256,6 +256,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
div.cards + div.faq {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.faq {
|
||||
p {
|
||||
margin-bottom: 1.25rem;
|
||||
|
|
|
@ -8,19 +8,16 @@ require('./formik-forms.scss');
|
|||
require('../forms/row.scss');
|
||||
|
||||
const FormikCheckboxSubComponent = ({
|
||||
className,
|
||||
field,
|
||||
id,
|
||||
label,
|
||||
labelClassName,
|
||||
...props
|
||||
}) => (
|
||||
<div className="checkbox">
|
||||
<input
|
||||
checked={field.value}
|
||||
className={classNames(
|
||||
'formik-checkbox',
|
||||
className
|
||||
)}
|
||||
className="formik-checkbox"
|
||||
id={id}
|
||||
name={field.name}
|
||||
type="checkbox"
|
||||
|
@ -32,8 +29,9 @@ const FormikCheckboxSubComponent = ({
|
|||
{label && (
|
||||
<label
|
||||
className={classNames(
|
||||
'formik-checkbox-label',
|
||||
'formik-label',
|
||||
'formik-checkbox-label'
|
||||
labelClassName
|
||||
)}
|
||||
htmlFor={id}
|
||||
>
|
||||
|
@ -44,7 +42,6 @@ const FormikCheckboxSubComponent = ({
|
|||
);
|
||||
|
||||
FormikCheckboxSubComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
field: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
onBlur: PropTypes.function,
|
||||
|
@ -52,31 +49,32 @@ FormikCheckboxSubComponent.propTypes = {
|
|||
value: PropTypes.bool
|
||||
}),
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string
|
||||
label: PropTypes.string,
|
||||
labelClassName: PropTypes.string
|
||||
};
|
||||
|
||||
|
||||
const FormikCheckbox = ({
|
||||
className,
|
||||
id,
|
||||
label,
|
||||
labelClassName,
|
||||
name,
|
||||
...props
|
||||
}) => (
|
||||
<Field
|
||||
className={className}
|
||||
component={FormikCheckboxSubComponent}
|
||||
id={id}
|
||||
label={label}
|
||||
labelClassName={labelClassName}
|
||||
name={name}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
FormikCheckbox.propTypes = {
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
labelClassName: PropTypes.string,
|
||||
name: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
@import "../../colors";
|
||||
|
||||
.formik-checkbox-label {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
input[type="checkbox"].formik-checkbox {
|
||||
display: block;
|
||||
float: left;
|
||||
|
@ -14,26 +10,19 @@ input[type="checkbox"].formik-checkbox {
|
|||
height: 1.25rem;
|
||||
appearance: none;
|
||||
|
||||
&:focus:checked {
|
||||
transition: all .5s ease;
|
||||
&:focus {
|
||||
transition: all .25s ease;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
|
||||
}
|
||||
|
||||
&:focus:not(:checked) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
background-color: $ui-blue;
|
||||
text-align: center;
|
||||
text-indent: .125rem;
|
||||
line-height: 1.25rem;
|
||||
font-size: .75rem;
|
||||
|
||||
&:after {
|
||||
color: $type-white;
|
||||
content: "\2714";
|
||||
}
|
||||
background-image: url("/svgs/forms/checkmark.svg");
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ require('./formik-input.scss');
|
|||
const FormikInput = ({
|
||||
className,
|
||||
error,
|
||||
onSetRef,
|
||||
toolTip,
|
||||
validationClassName,
|
||||
wrapperClassName,
|
||||
...props
|
||||
|
@ -29,21 +31,32 @@ const FormikInput = ({
|
|||
{fail: error},
|
||||
className
|
||||
)}
|
||||
/* formik uses "innerRef" to return the actual input element */
|
||||
innerRef={onSetRef}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
{error ? (
|
||||
<ValidationMessage
|
||||
className={validationClassName}
|
||||
message={error}
|
||||
mode="error"
|
||||
/>
|
||||
) : toolTip && (
|
||||
<ValidationMessage
|
||||
className={validationClassName}
|
||||
message={toolTip}
|
||||
mode="info"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
FormikInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
// error and toolTip can be false, in which case we ignore them
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
onSetRef: PropTypes.func,
|
||||
toolTip: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
type: PropTypes.string,
|
||||
validationClassName: PropTypes.string,
|
||||
wrapperClassName: PropTypes.string
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
border-radius: .5rem;
|
||||
background-color: $ui-white;
|
||||
margin-bottom: .5rem;
|
||||
transition: all .5s ease;
|
||||
transition: all .5s ease, font-size 0s;
|
||||
border: 1px solid $active-gray;
|
||||
padding: 0 1rem;
|
||||
color: $type-gray;
|
||||
|
@ -15,6 +15,7 @@
|
|||
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
|
||||
outline: none;
|
||||
border: 1px solid $ui-blue;
|
||||
transition: all .5s ease, font-size 0s;
|
||||
}
|
||||
|
||||
&.fail {
|
||||
|
@ -25,4 +26,9 @@
|
|||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
font-style: italic;
|
||||
color: $type-gray-75percent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,24 +10,26 @@ require('./formik-radio-button.scss');
|
|||
require('../forms/row.scss');
|
||||
|
||||
const FormikRadioButtonSubComponent = ({
|
||||
buttonValue,
|
||||
children,
|
||||
className,
|
||||
field,
|
||||
field, // field.value is the current selected value of the entire radio group
|
||||
id,
|
||||
label,
|
||||
labelClassName,
|
||||
value,
|
||||
...props
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
<input
|
||||
checked={buttonValue === field.value}
|
||||
checked={value === field.value}
|
||||
className={classNames(
|
||||
'formik-radio-button',
|
||||
className
|
||||
)}
|
||||
id={id}
|
||||
name={field.name}
|
||||
type="radio"
|
||||
value={buttonValue}
|
||||
value={value}
|
||||
onBlur={field.onBlur} /* eslint-disable-line react/jsx-handler-names */
|
||||
onChange={field.onChange} /* eslint-disable-line react/jsx-handler-names */
|
||||
{...props}
|
||||
|
@ -39,7 +41,7 @@ const FormikRadioButtonSubComponent = ({
|
|||
'formik-radio-label',
|
||||
labelClassName
|
||||
)}
|
||||
htmlFor={buttonValue}
|
||||
htmlFor={id}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
|
@ -49,7 +51,6 @@ const FormikRadioButtonSubComponent = ({
|
|||
);
|
||||
|
||||
FormikRadioButtonSubComponent.propTypes = {
|
||||
buttonValue: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
field: PropTypes.shape({
|
||||
|
@ -58,6 +59,7 @@ FormikRadioButtonSubComponent.propTypes = {
|
|||
onChange: PropTypes.function,
|
||||
value: PropTypes.string
|
||||
}),
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
labelClassName: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
|
@ -65,21 +67,24 @@ FormikRadioButtonSubComponent.propTypes = {
|
|||
|
||||
|
||||
const FormikRadioButton = ({
|
||||
buttonValue,
|
||||
className,
|
||||
id,
|
||||
isCustomInput,
|
||||
label,
|
||||
name,
|
||||
onSetCustom,
|
||||
onSetCustomRef,
|
||||
value,
|
||||
...props
|
||||
}) => (
|
||||
<Field
|
||||
buttonValue={buttonValue}
|
||||
className={className}
|
||||
component={FormikRadioButtonSubComponent}
|
||||
id={id}
|
||||
label={label}
|
||||
labelClassName={isCustomInput ? 'formik-radio-label-other' : ''}
|
||||
name={name}
|
||||
value={value}
|
||||
{...props}
|
||||
>
|
||||
{isCustomInput && (
|
||||
|
@ -91,18 +96,20 @@ const FormikRadioButton = ({
|
|||
onChange={event => onSetCustom(event.target.value)}
|
||||
onFocus={event => onSetCustom(event.target.value)}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
onSetRef={onSetCustomRef}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
|
||||
FormikRadioButton.propTypes = {
|
||||
buttonValue: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
isCustomInput: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onSetCustom: PropTypes.func,
|
||||
onSetCustomRef: PropTypes.func,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const classNames = require('classnames');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
import {Field} from 'formik';
|
||||
|
@ -7,6 +6,7 @@ const ValidationMessage = require('../forms/validation-message.jsx');
|
|||
|
||||
require('../forms/select.scss');
|
||||
require('../forms/row.scss');
|
||||
require('./formik-select.scss');
|
||||
|
||||
const FormikSelect = ({
|
||||
className,
|
||||
|
@ -27,9 +27,7 @@ const FormikSelect = ({
|
|||
return (
|
||||
<div className="select row-with-tooltip">
|
||||
<Field
|
||||
className={classNames(
|
||||
className
|
||||
)}
|
||||
className={className}
|
||||
component="select"
|
||||
{...props}
|
||||
>
|
||||
|
@ -39,6 +37,7 @@ const FormikSelect = ({
|
|||
<ValidationMessage
|
||||
className={validationClassName}
|
||||
message={error}
|
||||
mode="error"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
12
src/components/formik-forms/formik-select.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
@import "../../colors";
|
||||
|
||||
.select {
|
||||
.fail {
|
||||
border: 1px solid $ui-orange;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 .25rem $ui-orange-25percent;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.input::placeholder {
|
||||
font-style: italic;
|
||||
color: $type-gray-75percent;
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
margin-bottom: .75rem;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
|
||||
background: $ui-light-gray url("../../../static/svgs/forms/caret.svg") no-repeat right center;
|
||||
padding-right: 4rem;
|
||||
padding-left: 1rem;
|
||||
width: 100%;
|
||||
|
@ -42,7 +42,7 @@
|
|||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center;
|
||||
background: $ui-light-gray url("../../../static/svgs/forms/caret-hover.svg") no-repeat right center;
|
||||
}
|
||||
|
||||
> option {
|
||||
|
|
|
@ -5,14 +5,24 @@ const React = require('react');
|
|||
require('./validation-message.scss');
|
||||
|
||||
const ValidationMessage = props => (
|
||||
<div className={classNames(['validation-message', props.className])}>
|
||||
<div
|
||||
className={classNames(
|
||||
'validation-message',
|
||||
{
|
||||
'validation-error': props.mode === 'error',
|
||||
'validation-info': props.mode === 'info'
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.message}
|
||||
</div>
|
||||
);
|
||||
|
||||
ValidationMessage.propTypes = {
|
||||
className: PropTypes.string,
|
||||
message: PropTypes.string
|
||||
message: PropTypes.string,
|
||||
mode: PropTypes.string
|
||||
};
|
||||
|
||||
module.exports = ValidationMessage;
|
||||
|
|
|
@ -39,6 +39,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
.validation-left {
|
||||
$arrow-border-width: 1rem;
|
||||
left: unset;
|
||||
right: 0;
|
||||
margin-left: unset;
|
||||
margin-right: $arrow-border-width;
|
||||
transform: translate(-16rem, 0);
|
||||
|
||||
&:before {
|
||||
left: unset;
|
||||
right: -$arrow-border-width / 2;
|
||||
border-top: 1px solid $active-gray;
|
||||
border-right: 1px solid $active-gray;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media #{$intermediate-and-smaller} {
|
||||
.validation-message {
|
||||
position: relative;
|
||||
|
@ -52,3 +70,21 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
background-color: $ui-orange;
|
||||
|
||||
&:before {
|
||||
background-color: $ui-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.validation-info {
|
||||
background-color: $ui-blue;
|
||||
box-shadow: 0 0 4px 2px rgba(0, 0, 0, .15);
|
||||
font-weight: 500;
|
||||
|
||||
&:before {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const MediaQuery = require('react-responsive').default;
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
|
||||
require('./info-button.scss');
|
||||
|
||||
|
@ -22,25 +25,38 @@ class InfoButton extends React.Component {
|
|||
this.setState({visible: true});
|
||||
}
|
||||
render () {
|
||||
const messageJsx = this.state.visible && (
|
||||
<div className="info-button-message">
|
||||
{this.props.message}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="info-button"
|
||||
onClick={this.handleShowMessage}
|
||||
onMouseOut={this.handleHideMessage}
|
||||
onMouseOver={this.handleShowMessage}
|
||||
>
|
||||
{this.state.visible && (
|
||||
<div className="info-button-message">
|
||||
{this.props.message}
|
||||
<MediaQuery minWidth={frameless.desktop}>
|
||||
{messageJsx}
|
||||
</MediaQuery>
|
||||
</div>
|
||||
)}
|
||||
{/* for small screens, add additional position: relative element,
|
||||
so info message can position itself relative to the width which
|
||||
encloses info-button -- rather than relative to info-button itself */}
|
||||
<MediaQuery maxWidth={frameless.desktop - 1}>
|
||||
<div style={{position: 'relative'}}>
|
||||
{messageJsx}
|
||||
</div>
|
||||
</MediaQuery>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InfoButton.propTypes = {
|
||||
message: PropTypes.string
|
||||
message: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
module.exports = InfoButton;
|
||||
|
|
|
@ -9,17 +9,9 @@
|
|||
margin-left: .375rem;
|
||||
border-radius: 50%;
|
||||
background-color: $ui-blue;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "?";
|
||||
color: $ui-white;
|
||||
font-family: verdana;
|
||||
font-weight: 400;
|
||||
top: -.125rem;
|
||||
left: .325rem;
|
||||
font-size: .75rem;
|
||||
}
|
||||
background-image: url("/svgs/info-button/info-button.svg");
|
||||
background-size: cover;
|
||||
top: .125rem;
|
||||
}
|
||||
|
||||
.info-button-message {
|
||||
|
@ -41,7 +33,7 @@
|
|||
line-height: 1.25rem;
|
||||
text-align: left;
|
||||
font-size: .875rem;
|
||||
z-index: 1;
|
||||
z-index: 2;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
|
@ -65,11 +57,12 @@
|
|||
|
||||
@media #{$intermediate-and-smaller} {
|
||||
.info-button-message {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
transform: none;
|
||||
margin: inherit;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
/* since we're positioning message relative to info-button's parent,
|
||||
we need to center this element within its width. */
|
||||
margin: 0 calc((100% - 16.5rem) / 2);;
|
||||
top: .125rem;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
|
|
|
@ -87,8 +87,10 @@ class BirthDateStep extends React.Component {
|
|||
return (
|
||||
<JoinFlowStep
|
||||
description={this.props.intl.formatMessage({id: 'registration.private'})}
|
||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||
descriptionClassName="join-flow-birthdate-description"
|
||||
headerImgSrc="/images/join-flow/birthdate-header.png"
|
||||
infoMessage={this.props.intl.formatMessage({id: 'registration.birthDateStepInfo'})}
|
||||
innerClassName="join-flow-inner-birthdate-step"
|
||||
title={this.props.intl.formatMessage({id: 'registration.birthDateStepTitle'})}
|
||||
waiting={isSubmitting}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -106,16 +108,21 @@ class BirthDateStep extends React.Component {
|
|||
'join-flow-select-month',
|
||||
{fail: errors.birth_month}
|
||||
)}
|
||||
error={errors.birth_month}
|
||||
/* hide month (left side) error, if year (right side) error exists */
|
||||
error={errors.birth_year ? null : errors.birth_month}
|
||||
id="birth_month"
|
||||
name="birth_month"
|
||||
options={birthMonthOptions}
|
||||
validate={this.validateSelect}
|
||||
validationClassName="validation-birthdate-input"
|
||||
validationClassName={classNames(
|
||||
'validation-birthdate-month',
|
||||
'validation-left'
|
||||
)}
|
||||
/>
|
||||
<FormikSelect
|
||||
className={classNames(
|
||||
'join-flow-select',
|
||||
'join-flow-select-year',
|
||||
{fail: errors.birth_year}
|
||||
)}
|
||||
error={errors.birth_year}
|
||||
|
@ -123,7 +130,7 @@ class BirthDateStep extends React.Component {
|
|||
name="birth_year"
|
||||
options={birthYearOptions}
|
||||
validate={this.validateSelect}
|
||||
validationClassName="validation-birthdate-input"
|
||||
validationClassName="validation-birthdate-year"
|
||||
/>
|
||||
</div>
|
||||
</JoinFlowStep>
|
||||
|
|
|
@ -29,7 +29,7 @@ class CountryStep extends React.Component {
|
|||
this.countryOptions = [...countryData.registrationCountryOptions];
|
||||
this.countryOptions.unshift({
|
||||
disabled: true,
|
||||
label: this.props.intl.formatMessage({id: 'registration.selectCountry'}),
|
||||
label: this.props.intl.formatMessage({id: 'general.country'}),
|
||||
value: 'null'
|
||||
});
|
||||
}
|
||||
|
@ -68,7 +68,9 @@ class CountryStep extends React.Component {
|
|||
return (
|
||||
<JoinFlowStep
|
||||
description={this.props.intl.formatMessage({id: 'registration.countryStepDescription'})}
|
||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||
descriptionClassName="join-flow-country-description"
|
||||
headerImgSrc="/images/join-flow/country-header.png"
|
||||
innerClassName="join-flow-inner-country-step"
|
||||
title={this.props.intl.formatMessage({id: 'registration.countryStepTitle'})}
|
||||
waiting={isSubmitting}
|
||||
onSubmit={handleSubmit}
|
||||
|
|
|
@ -9,6 +9,7 @@ const FormattedMessage = require('react-intl').FormattedMessage;
|
|||
|
||||
const JoinFlowStep = require('./join-flow-step.jsx');
|
||||
const FormikInput = require('../../components/formik-forms/formik-input.jsx');
|
||||
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');
|
||||
|
||||
require('./join-flow-steps.scss');
|
||||
|
||||
|
@ -16,13 +17,21 @@ class EmailStep extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleSetEmailRef',
|
||||
'handleValidSubmit',
|
||||
'validateEmailIfPresent',
|
||||
'validateEmail',
|
||||
'validateForm'
|
||||
]);
|
||||
}
|
||||
validateEmailIfPresent (email) {
|
||||
if (!email) return null; // skip validation if email is blank; null indicates valid
|
||||
componentDidMount () {
|
||||
// automatically start with focus on username field
|
||||
if (this.emailInput) this.emailInput.focus();
|
||||
}
|
||||
handleSetEmailRef (emailInputRef) {
|
||||
this.emailInput = emailInputRef;
|
||||
}
|
||||
validateEmail (email) {
|
||||
if (!email) return this.props.intl.formatMessage({id: 'general.required'});
|
||||
const isValidLocally = emailValidator.validate(email);
|
||||
if (isValidLocally) {
|
||||
return null; // TODO: validate email address remotely
|
||||
|
@ -40,6 +49,8 @@ class EmailStep extends React.Component {
|
|||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
subscribe: false
|
||||
}}
|
||||
validate={this.validateForm}
|
||||
validateOnBlur={false}
|
||||
|
@ -51,6 +62,7 @@ class EmailStep extends React.Component {
|
|||
errors,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
setFieldError,
|
||||
validateField
|
||||
} = props;
|
||||
return (
|
||||
|
@ -72,8 +84,8 @@ class EmailStep extends React.Component {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||
innerContentClassName="modal-inner-content-email"
|
||||
headerImgSrc="/images/join-flow/email-header.png"
|
||||
innerClassName="join-flow-inner-email-step"
|
||||
nextButton={this.props.intl.formatMessage({id: 'registration.createAccount'})}
|
||||
title={this.props.intl.formatMessage({id: 'registration.emailStepTitle'})}
|
||||
waiting={isSubmitting}
|
||||
|
@ -89,10 +101,21 @@ class EmailStep extends React.Component {
|
|||
id="email"
|
||||
name="email"
|
||||
placeholder={this.props.intl.formatMessage({id: 'general.emailAddress'})}
|
||||
validate={this.validateEmailIfPresent}
|
||||
validate={this.validateEmail}
|
||||
validationClassName="validation-full-width-input"
|
||||
onBlur={() => validateField('email')} // eslint-disable-line react/jsx-no-bind
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onBlur={() => validateField('email')}
|
||||
onFocus={() => setFieldError('email', null)}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
onSetRef={this.handleSetEmailRef}
|
||||
/>
|
||||
<div className="join-flow-email-checkbox-row">
|
||||
<FormikCheckbox
|
||||
id="subscribeCheckbox"
|
||||
label={this.props.intl.formatMessage({id: 'registration.receiveEmails'})}
|
||||
name="subscribe"
|
||||
/>
|
||||
</div>
|
||||
</JoinFlowStep>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -11,10 +11,12 @@ const JoinFlowStep = require('./join-flow-step.jsx');
|
|||
require('./join-flow-steps.scss');
|
||||
|
||||
const GenderOption = ({
|
||||
id,
|
||||
label,
|
||||
onSetFieldValue,
|
||||
selectedValue,
|
||||
value
|
||||
value,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -29,17 +31,20 @@ const GenderOption = ({
|
|||
/* eslint-enable react/jsx-no-bind */
|
||||
>
|
||||
<FormikRadioButton
|
||||
buttonValue={value}
|
||||
className={classNames(
|
||||
'join-flow-radio'
|
||||
)}
|
||||
id={id}
|
||||
label={label}
|
||||
name="gender"
|
||||
value={value}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
GenderOption.propTypes = {
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onSetFieldValue: PropTypes.func,
|
||||
selectedValue: PropTypes.string,
|
||||
|
@ -50,9 +55,13 @@ class GenderStep extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleSetCustomRef',
|
||||
'handleValidSubmit'
|
||||
]);
|
||||
}
|
||||
handleSetCustomRef (customInputRef) {
|
||||
this.customInput = customInputRef;
|
||||
}
|
||||
handleValidSubmit (formData, formikBag) {
|
||||
formikBag.setSubmitting(false);
|
||||
if (!formData.gender || formData.gender === 'null') {
|
||||
|
@ -80,25 +89,34 @@ class GenderStep extends React.Component {
|
|||
} = props;
|
||||
return (
|
||||
<JoinFlowStep
|
||||
className="join-flow-gender-step"
|
||||
description={this.props.intl.formatMessage({id: 'registration.genderStepDescription'})}
|
||||
descriptionClassName="join-flow-gender-description"
|
||||
infoMessage={this.props.intl.formatMessage({id: 'registration.genderStepInfo'})}
|
||||
innerClassName="join-flow-inner-gender-step"
|
||||
title={this.props.intl.formatMessage({id: 'registration.genderStepTitle'})}
|
||||
waiting={isSubmitting}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<GenderOption
|
||||
id="GenderRadioOptionFemale"
|
||||
label={this.props.intl.formatMessage({id: 'general.female'})}
|
||||
selectedValue={values.gender}
|
||||
value="Female"
|
||||
onSetFieldValue={setFieldValue}
|
||||
/>
|
||||
<GenderOption
|
||||
id="GenderRadioOptionMale"
|
||||
label={this.props.intl.formatMessage({id: 'general.male'})}
|
||||
selectedValue={values.gender}
|
||||
value="Male"
|
||||
onSetFieldValue={setFieldValue}
|
||||
/>
|
||||
<GenderOption
|
||||
label={this.props.intl.formatMessage({id: 'general.nonBinary'})}
|
||||
selectedValue={values.gender}
|
||||
value="Non-binary"
|
||||
onSetFieldValue={setFieldValue}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'col-sm-9',
|
||||
|
@ -108,26 +126,32 @@ class GenderStep extends React.Component {
|
|||
{'gender-radio-row-selected': (values.gender === values.custom)}
|
||||
)}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onClick={() => setFieldValue('gender', values.custom, false)}
|
||||
onClick={() => {
|
||||
setFieldValue('gender', values.custom, false);
|
||||
if (this.customInput) this.customInput.focus();
|
||||
}}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
>
|
||||
<FormikRadioButton
|
||||
isCustomInput
|
||||
buttonValue={values.custom}
|
||||
className={classNames(
|
||||
'join-flow-radio'
|
||||
)}
|
||||
id="GenderRadioOptionCustom"
|
||||
label={this.props.intl.formatMessage({id: 'registration.genderOptionAnother'})}
|
||||
name="gender"
|
||||
value={values.custom}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onSetCustom={newCustomVal => setValues({
|
||||
gender: newCustomVal,
|
||||
custom: newCustomVal
|
||||
})}
|
||||
onSetCustomRef={this.handleSetCustomRef}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
</div>
|
||||
<GenderOption
|
||||
id="GenderRadioOptionPreferNot"
|
||||
label={this.props.intl.formatMessage({id: 'registration.genderOptionPreferNotToSay'})}
|
||||
selectedValue={values.gender}
|
||||
value="Prefer not to say"
|
||||
|
|
|
@ -11,29 +11,32 @@ require('./join-flow-step.scss');
|
|||
|
||||
const JoinFlowStep = ({
|
||||
children,
|
||||
className,
|
||||
innerClassName,
|
||||
description,
|
||||
descriptionClassName,
|
||||
footerContent,
|
||||
headerImgSrc,
|
||||
infoMessage,
|
||||
innerContentClassName,
|
||||
nextButton,
|
||||
onSubmit,
|
||||
title,
|
||||
waiting
|
||||
}) => (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="join-flow-outer-content">
|
||||
{headerImgSrc && (
|
||||
<div className="join-flow-header-image">
|
||||
<img src={headerImgSrc} />
|
||||
<div className="join-flow-header-image-wrapper">
|
||||
<img
|
||||
className="join-flow-header-image"
|
||||
src={headerImgSrc}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<ModalInnerContent
|
||||
className={classNames(
|
||||
'join-flow-inner-content',
|
||||
className,
|
||||
innerContentClassName
|
||||
innerClassName
|
||||
)}
|
||||
>
|
||||
{title && (
|
||||
|
@ -43,7 +46,12 @@ const JoinFlowStep = ({
|
|||
/>
|
||||
)}
|
||||
{description && (
|
||||
<div className="join-flow-description">
|
||||
<div
|
||||
className={classNames(
|
||||
'join-flow-description',
|
||||
descriptionClassName
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
{infoMessage && (
|
||||
<InfoButton message={infoMessage} />
|
||||
|
@ -62,17 +70,18 @@ const JoinFlowStep = ({
|
|||
content={nextButton}
|
||||
waiting={waiting}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
JoinFlowStep.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
descriptionClassName: PropTypes.string,
|
||||
footerContent: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
headerImgSrc: PropTypes.string,
|
||||
infoMessage: PropTypes.string,
|
||||
innerContentClassName: PropTypes.string,
|
||||
innerClassName: PropTypes.string,
|
||||
nextButton: PropTypes.node,
|
||||
onSubmit: PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
@import "../../colors";
|
||||
@import "../../frameless";
|
||||
|
||||
.join-flow-outer-content {
|
||||
/* hopefully this lets text expand the height of the modal, if need be */
|
||||
min-height: 32.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.join-flow-inner-content {
|
||||
box-shadow: none;
|
||||
width: calc(100% - 5.875rem);
|
||||
/* must use padding for top, rather than margin, because margins will collapse */
|
||||
margin: 0 auto;
|
||||
padding: 2.3125rem 0 2.5rem;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.join-flow-title {
|
||||
color: $type-gray;
|
||||
font-size: 1.875rem;
|
||||
|
@ -15,25 +33,21 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.join-flow-inner-content {
|
||||
box-shadow: none;
|
||||
width: calc(100% - 5.875rem);
|
||||
/* must use padding for top, rather than margin, because margins will collapse */
|
||||
margin: 0 auto;
|
||||
padding: 2.3125rem 0 2.5rem;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
/* overflow will only work if this class is set on parent of img, not img itself */
|
||||
.join-flow-header-image {
|
||||
.join-flow-header-image-wrapper {
|
||||
width: 100%;
|
||||
height: 7.5rem;
|
||||
min-height: 7.5rem;
|
||||
max-height: 8.75rem;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
border-top-left-radius: 1rem;
|
||||
border-top-right-radius: 1rem;
|
||||
}
|
||||
|
||||
.join-flow-header-image {
|
||||
width: 27.5rem;
|
||||
}
|
||||
|
||||
.join-flow-footer-message {
|
||||
width: 100%;
|
||||
padding: 1.125rem 1.5rem 1.125rem;
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.join-flow-input-password {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.join-flow-password-confirm {
|
||||
margin-bottom: .6875rem;
|
||||
}
|
||||
|
@ -38,8 +42,13 @@
|
|||
transform: translate(21.5625rem, 0);
|
||||
}
|
||||
|
||||
.validation-birthdate-input {
|
||||
transform: translate(8.75rem, .25rem);
|
||||
.validation-birthdate-month {
|
||||
transform: translate(-9.25rem, 0);
|
||||
width: 7.25rem;
|
||||
}
|
||||
|
||||
.validation-birthdate-year {
|
||||
transform: translate(8.75rem, 0);
|
||||
width: 7.25rem;
|
||||
}
|
||||
|
||||
|
@ -55,9 +64,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select .join-flow-select {
|
||||
height: 3.5rem;
|
||||
background-color: white;
|
||||
border-color: $box-shadow-light-gray;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding-right: 3.25rem;
|
||||
}
|
||||
|
||||
.select .join-flow-select-month {
|
||||
width: 9.125rem;
|
||||
margin-right: .5rem;
|
||||
width: 9.125rem;
|
||||
}
|
||||
|
||||
.select .join-flow-select-year {
|
||||
width: 9.125rem;
|
||||
}
|
||||
|
||||
.select .join-flow-select-country {
|
||||
|
@ -74,24 +96,68 @@
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.join-flow-gender-step {
|
||||
height: 27.375rem;
|
||||
.join-flow-inner-username-step {
|
||||
padding-top: 2.75rem;
|
||||
}
|
||||
|
||||
.join-flow-inner-birthdate-step {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 2.25rem;
|
||||
}
|
||||
|
||||
.join-flow-inner-gender-step {
|
||||
/* need height so that flex will adjust children proportionately */
|
||||
height: 27.25rem;
|
||||
padding-top: 2.625rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.join-flow-inner-country-step {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.join-flow-inner-email-step {
|
||||
padding-top: 3rem;
|
||||
}
|
||||
|
||||
.join-flow-inner-welcome-step {
|
||||
padding-top: 3rem;
|
||||
}
|
||||
|
||||
.join-flow-birthdate-description {
|
||||
margin-top: 1.25rem;
|
||||
margin-right: -.5rem;
|
||||
margin-bottom: 2rem;
|
||||
margin-left: -.5rem;
|
||||
}
|
||||
|
||||
.join-flow-gender-description {
|
||||
margin-top: .625rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.join-flow-country-description {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.gender-radio-row {
|
||||
transition: all .125s ease;
|
||||
width: 20.875rem;
|
||||
height: 2.85rem;
|
||||
background-color: $ui-gray;
|
||||
border-radius: .5rem;
|
||||
margin-bottom: 0.375rem;
|
||||
margin: 0 auto 0.375rem;
|
||||
padding-left: 0.8125rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gender-radio-row-selected {
|
||||
.gender-radio-row:hover {
|
||||
background-color: $ui-blue-10percent;
|
||||
}
|
||||
|
||||
.gender-radio-row-selected, .gender-radio-row-selected:hover {
|
||||
transition: all .125s ease;
|
||||
background-color: $ui-blue-25percent;
|
||||
}
|
||||
|
@ -106,6 +172,11 @@
|
|||
padding-top: 2.9rem;
|
||||
}
|
||||
|
||||
.join-flow-email-checkbox-row {
|
||||
font-size: .75rem;
|
||||
margin: .25rem .125rem;
|
||||
}
|
||||
|
||||
a.join-flow-link:link, a.join-flow-link:visited, a.join-flow-link:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -21,16 +21,34 @@ class UsernameStep extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'handleChangeShowPassword',
|
||||
'handleFocused',
|
||||
'handleSetUsernameRef',
|
||||
'handleValidSubmit',
|
||||
'validatePasswordIfPresent',
|
||||
'validatePasswordConfirmIfPresent',
|
||||
'validateUsernameIfPresent',
|
||||
'validateForm'
|
||||
]);
|
||||
this.state = {
|
||||
focused: null,
|
||||
showPassword: false
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
// automatically start with focus on username field
|
||||
if (this.usernameInput) this.usernameInput.focus();
|
||||
}
|
||||
handleChangeShowPassword () {
|
||||
this.setState({showPassword: !this.state.showPassword});
|
||||
}
|
||||
// track the currently focused input field, to determine whether each field should
|
||||
// display a tooltip. (We only display it if a field is focused and has never been touched.)
|
||||
handleFocused (fieldName) {
|
||||
this.setState({focused: fieldName});
|
||||
}
|
||||
handleSetUsernameRef (usernameInputRef) {
|
||||
this.usernameInput = usernameInputRef;
|
||||
}
|
||||
// we allow username to be empty on blur, since you might not have typed anything yet
|
||||
validateUsernameIfPresent (username) {
|
||||
if (!username) return null; // skip validation if username is blank; null indicates valid
|
||||
|
@ -109,7 +127,9 @@ class UsernameStep extends React.Component {
|
|||
handleSubmit,
|
||||
isSubmitting,
|
||||
setFieldError,
|
||||
setFieldTouched,
|
||||
setFieldValue,
|
||||
touched,
|
||||
validateField,
|
||||
values
|
||||
} = props;
|
||||
|
@ -118,6 +138,7 @@ class UsernameStep extends React.Component {
|
|||
description={this.props.intl.formatMessage({
|
||||
id: 'registration.usernameStepDescriptionNonEducator'
|
||||
})}
|
||||
innerClassName="join-flow-inner-username-step"
|
||||
title={this.props.intl.formatMessage({id: 'general.joinScratch'})}
|
||||
waiting={isSubmitting}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -133,15 +154,21 @@ class UsernameStep extends React.Component {
|
|||
error={errors.username}
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder={this.props.intl.formatMessage({id: 'general.username'})}
|
||||
toolTip={this.state.focused === 'username' && !touched.username &&
|
||||
this.props.intl.formatMessage({id: 'registration.usernameAdviceShort'})}
|
||||
validate={this.validateUsernameIfPresent}
|
||||
validationClassName="validation-full-width-input"
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onBlur={() => validateField('username')}
|
||||
onChange={e => {
|
||||
setFieldValue('username', e.target.value);
|
||||
setFieldTouched('username');
|
||||
setFieldError('username', null);
|
||||
}}
|
||||
onFocus={() => this.handleFocused('username')}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
onSetRef={this.handleSetUsernameRef}
|
||||
/>
|
||||
<div className="join-flow-password-section">
|
||||
<div className="join-flow-input-title">
|
||||
|
@ -149,11 +176,16 @@ class UsernameStep extends React.Component {
|
|||
</div>
|
||||
<FormikInput
|
||||
className={classNames(
|
||||
'join-flow-input'
|
||||
'join-flow-input',
|
||||
{'join-flow-input-password':
|
||||
!values.showPassword && values.password.length > 0}
|
||||
)}
|
||||
error={errors.password}
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder={this.props.intl.formatMessage({id: 'general.password'})}
|
||||
toolTip={this.state.focused === 'password' && !touched.password &&
|
||||
this.props.intl.formatMessage({id: 'registration.passwordAdviceShort'})}
|
||||
type={values.showPassword ? 'text' : 'password'}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
validate={password => this.validatePasswordIfPresent(password, values.username)}
|
||||
|
@ -161,19 +193,34 @@ class UsernameStep extends React.Component {
|
|||
onBlur={() => validateField('password')}
|
||||
onChange={e => {
|
||||
setFieldValue('password', e.target.value);
|
||||
setFieldTouched('password');
|
||||
setFieldError('password', null);
|
||||
}}
|
||||
onFocus={() => this.handleFocused('password')}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
<FormikInput
|
||||
className={classNames(
|
||||
'join-flow-input',
|
||||
'join-flow-password-confirm',
|
||||
{fail: errors.passwordConfirm}
|
||||
{
|
||||
'join-flow-input-password':
|
||||
!values.showPassword && values.passwordConfirm.length > 0,
|
||||
'fail': errors.passwordConfirm
|
||||
}
|
||||
)}
|
||||
error={errors.passwordConfirm}
|
||||
id="passwordConfirm"
|
||||
name="passwordConfirm"
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'registration.confirmPasswordInstruction'
|
||||
})}
|
||||
toolTip={
|
||||
this.state.focused === 'passwordConfirm' && !touched.passwordConfirm &&
|
||||
this.props.intl.formatMessage({
|
||||
id: 'registration.confirmPasswordInstruction'
|
||||
})
|
||||
}
|
||||
type={values.showPassword ? 'text' : 'password'}
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
validate={() =>
|
||||
|
@ -181,13 +228,13 @@ class UsernameStep extends React.Component {
|
|||
values.passwordConfirm)
|
||||
}
|
||||
validationClassName="validation-full-width-input"
|
||||
onBlur={() =>
|
||||
validateField('passwordConfirm')
|
||||
}
|
||||
onBlur={() => validateField('passwordConfirm')}
|
||||
onChange={e => {
|
||||
setFieldValue('passwordConfirm', e.target.value);
|
||||
setFieldTouched('passwordConfirm');
|
||||
setFieldError('passwordConfirm', null);
|
||||
}}
|
||||
onFocus={() => this.handleFocused('passwordConfirm')}
|
||||
/* eslint-enable react/jsx-no-bind */
|
||||
/>
|
||||
<div className="join-flow-input-title">
|
||||
|
|
|
@ -27,8 +27,6 @@ class WelcomeStep extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
}}
|
||||
validate={this.validateForm}
|
||||
validateOnBlur={false}
|
||||
validateOnChange={false}
|
||||
|
@ -44,7 +42,8 @@ class WelcomeStep extends React.Component {
|
|||
description={this.props.intl.formatMessage({
|
||||
id: 'registration.welcomeStepDescriptionNonEducator'
|
||||
})}
|
||||
headerImgSrc="/images/hoc/getting-started.jpg"
|
||||
headerImgSrc="/images/join-flow/welcome-header.png"
|
||||
innerClassName="join-flow-inner-welcome-step"
|
||||
nextButton={
|
||||
<React.Fragment>
|
||||
<FormattedMessage id="registration.makeProject" />
|
||||
|
|
|
@ -6,15 +6,14 @@ const JoinFlow = require('../../join-flow/join-flow.jsx');
|
|||
require('./modal.scss');
|
||||
|
||||
const JoinModal = ({
|
||||
isOpen,
|
||||
onCompleteRegistration, // eslint-disable-line no-unused-vars
|
||||
onRequestClose,
|
||||
...modalProps
|
||||
}) => (
|
||||
<Modal
|
||||
isOpen
|
||||
useStandardSizes
|
||||
className="mod-join"
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
{...modalProps}
|
||||
>
|
||||
|
@ -25,7 +24,6 @@ const JoinModal = ({
|
|||
);
|
||||
|
||||
JoinModal.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onCompleteRegistration: PropTypes.func,
|
||||
onRequestClose: PropTypes.func
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ const TTTModal = props => (
|
|||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="ideas.downloadPDF" />
|
||||
<FormattedMessage id="general.downloadPDF" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@ const TTTModal = props => (
|
|||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="ideas.downloadPDF" />
|
||||
<FormattedMessage id="general.downloadPDF" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,8 +23,6 @@ const AccountNav = require('./accountnav.jsx');
|
|||
|
||||
require('./navigation.scss');
|
||||
|
||||
const USE_SCRATCH3_REGISTRATION = false;
|
||||
|
||||
class Navigation extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
@ -48,7 +46,7 @@ class Navigation extends React.Component {
|
|||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.user !== this.props.user) {
|
||||
this.props.closeAccountMenus();
|
||||
this.props.handleCloseAccountNav();
|
||||
if (this.props.user) {
|
||||
const intervalId = setInterval(() => {
|
||||
this.props.getMessageCount(this.props.user.username);
|
||||
|
@ -198,17 +196,6 @@ class Navigation extends React.Component {
|
|||
<FormattedMessage id="general.joinScratch" />
|
||||
</a>
|
||||
</li>,
|
||||
(
|
||||
USE_SCRATCH3_REGISTRATION ? (
|
||||
<Scratch3Registration
|
||||
key="scratch3registration"
|
||||
/>
|
||||
) : (
|
||||
<Registration
|
||||
key="registration"
|
||||
/>
|
||||
)
|
||||
),
|
||||
<li
|
||||
className="link right login-item"
|
||||
key="login"
|
||||
|
@ -225,7 +212,20 @@ class Navigation extends React.Component {
|
|||
key="login-dropdown"
|
||||
/>
|
||||
</li>
|
||||
]) : []}
|
||||
]) : []
|
||||
}
|
||||
{this.props.registrationOpen && (
|
||||
this.props.useScratch3Registration ? (
|
||||
<Scratch3Registration
|
||||
isOpen
|
||||
key="scratch3registration"
|
||||
/>
|
||||
) : (
|
||||
<Registration
|
||||
key="registration"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
<CanceledDeletionModal />
|
||||
</NavigationBox>
|
||||
|
@ -235,7 +235,6 @@ class Navigation extends React.Component {
|
|||
|
||||
Navigation.propTypes = {
|
||||
accountNavOpen: PropTypes.bool,
|
||||
closeAccountMenus: PropTypes.func,
|
||||
getMessageCount: PropTypes.func,
|
||||
handleCloseAccountNav: PropTypes.func,
|
||||
handleLogOut: PropTypes.func,
|
||||
|
@ -250,12 +249,14 @@ Navigation.propTypes = {
|
|||
educator_invitee: PropTypes.bool,
|
||||
student: PropTypes.bool
|
||||
}),
|
||||
registrationOpen: PropTypes.bool,
|
||||
searchTerm: PropTypes.string,
|
||||
session: PropTypes.shape({
|
||||
status: PropTypes.string
|
||||
}),
|
||||
setMessageCount: PropTypes.func,
|
||||
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
useScratch3Registration: PropTypes.bool,
|
||||
user: PropTypes.shape({
|
||||
classroomId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
thumbnailUrl: PropTypes.string,
|
||||
|
@ -273,15 +274,14 @@ const mapStateToProps = state => ({
|
|||
accountNavOpen: state.navigation && state.navigation.accountNavOpen,
|
||||
session: state.session,
|
||||
permissions: state.permissions,
|
||||
registrationOpen: state.navigation.registrationOpen,
|
||||
searchTerm: state.navigation.searchTerm,
|
||||
unreadMessageCount: state.messageCount.messageCount,
|
||||
user: state.session && state.session.session && state.session.session.user
|
||||
user: state.session && state.session.session && state.session.session.user,
|
||||
useScratch3Registration: state.navigation.useScratch3Registration
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
closeAccountMenus: () => {
|
||||
dispatch(navigationActions.closeAccountMenus());
|
||||
},
|
||||
getMessageCount: username => {
|
||||
dispatch(messageCountActions.getCount(username));
|
||||
},
|
||||
|
|
|
@ -28,10 +28,6 @@ Registration.propTypes = {
|
|||
isOpen: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isOpen: state.navigation.registrationOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleCloseRegistration: () => {
|
||||
dispatch(navigationActions.setRegistrationOpen(false));
|
||||
|
@ -42,6 +38,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps,
|
||||
() => ({}),
|
||||
mapDispatchToProps
|
||||
)(Registration);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"general.confirmEmail": "Confirm Email",
|
||||
"general.contactUs": "Contact Us",
|
||||
"general.contact": "Contact",
|
||||
"general.downloadPDF": "Download PDF",
|
||||
"general.emailUs": "Email Us",
|
||||
"general.conferences": "Conferences",
|
||||
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
|
||||
|
@ -62,6 +63,7 @@
|
|||
"general.noDeletionTitle": "Your Account Will Not Be Deleted",
|
||||
"general.noDeletionDescription": "Your account was scheduled for deletion but you logged in. Your account has been reactivated. If you didn’t request for your account to be deleted, you should {resetLink} to make sure your account is secure.",
|
||||
"general.noDeletionLink": "change your password",
|
||||
"general.nonBinary": "Non-binary",
|
||||
"general.notRequired": "Not Required",
|
||||
"general.okay": "Okay",
|
||||
"general.other": "Other",
|
||||
|
@ -156,9 +158,10 @@
|
|||
"registration.generalError": "Sorry, an unexpected error occurred.",
|
||||
"registration.classroomInviteExistingStudentStepDescription": "you have been invited to join the class:",
|
||||
"registration.classroomInviteNewStudentStepDescription": "Your teacher has invited you to join a class:",
|
||||
"registration.confirmPasswordInstruction": "Type password again",
|
||||
"registration.confirmYourEmail": "Confirm Your Email",
|
||||
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
|
||||
"registration.createAccount": "Create Account",
|
||||
"registration.createAccount": "Create Your Account",
|
||||
"registration.createUsername": "Create a username",
|
||||
"registration.genderStepTitle": "What's your gender?",
|
||||
"registration.genderStepDescription": "Scratch welcomes people of all genders. We will always keep this information private.",
|
||||
|
@ -178,20 +181,23 @@
|
|||
"registration.nextStep": "Next Step",
|
||||
"registration.notYou": "Not you? Log in as another user",
|
||||
"registration.optIn": "Send me updates on using Scratch in educational settings",
|
||||
"registration.passwordAdviceShort": "Write it down so you remember. Don’t share it with others!",
|
||||
"registration.personalStepTitle": "Personal Information",
|
||||
"registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
|
||||
"registration.private": "Scratch will always keep this information private.",
|
||||
"registration.receiveEmails": "I'd like to receive emails from the Scratch Team about project ideas, events, and more.",
|
||||
"registration.selectCountry": "select country",
|
||||
"registration.studentPersonalStepDescription": "This information will not appear on the Scratch website.",
|
||||
"registration.showPassword": "Show password",
|
||||
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to one day.",
|
||||
"registration.usernameStepDescriptionNonEducator": "Create projects, share ideas, make friends. It’s free!",
|
||||
"registration.usernameStepRealName": "Please do not use any portion of your real name in your username.",
|
||||
"registration.usernameAdviceShort": "Don't use your real name",
|
||||
"registration.studentUsernameStepDescription": "You can make games, animations, and stories using Scratch. Setting up an account is easy and it's free. Fill in the form below to get started.",
|
||||
"registration.studentUsernameStepHelpText": "Already have a Scratch account?",
|
||||
"registration.studentUsernameStepTooltip": "You'll need to create a new Scratch account to join this class.",
|
||||
"registration.studentUsernameFieldHelpText": "For safety, don't use your real name!",
|
||||
"registration.acceptTermsOfUse": "By creating an account, I accept and agree to the {touLink}.",
|
||||
"registration.acceptTermsOfUse": "By creating an account, you accept and agree to the {touLink}.",
|
||||
"registration.usernameStepTitle": "Request a Teacher Account",
|
||||
"registration.usernameStepTitleScratcher": "Create a Scratch Account",
|
||||
"registration.validationMaxLength": "Sorry, you have exceeded the maximum character limit.",
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports.validateUsernameRemotely = username => (
|
|||
uri: `/accounts/checkusername/${username}/`
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
resolve({valid: false, errMsgId: 'general.apiError'});
|
||||
resolve({valid: false, errMsgId: 'general.error'});
|
||||
}
|
||||
switch (body.msg) {
|
||||
case 'valid username':
|
||||
|
|
|
@ -18,6 +18,7 @@ const Types = keyMirror({
|
|||
});
|
||||
|
||||
module.exports.getInitialState = () => ({
|
||||
useScratch3Registration: false,
|
||||
accountNavOpen: false,
|
||||
canceledDeletionOpen: false,
|
||||
loginError: null,
|
||||
|
@ -96,11 +97,6 @@ module.exports.handleCompleteRegistration = () => (dispatch => {
|
|||
dispatch(module.exports.setRegistrationOpen(false));
|
||||
});
|
||||
|
||||
module.exports.closeAccountMenus = () => (dispatch => {
|
||||
dispatch(module.exports.setAccountNavOpen(false));
|
||||
dispatch(module.exports.setRegistrationOpen(false));
|
||||
});
|
||||
|
||||
module.exports.handleLogIn = (formData, callback) => (dispatch => {
|
||||
dispatch(module.exports.setLoginError(null));
|
||||
formData.useMessages = true; // NOTE: this may or may not be being used anywhere else
|
||||
|
|
|
@ -141,7 +141,7 @@ const ConferenceSplash = () => (
|
|||
<FormattedMessage id="conference-2019.website" />
|
||||
</a>
|
||||
</section>
|
||||
<section className="conf2019-panel mod-uk">
|
||||
<section className="conf2019-panel">
|
||||
<FlexRow className="conf2019-panel-title">
|
||||
<img
|
||||
alt="EU Flag"
|
||||
|
@ -237,7 +237,7 @@ const ConferenceSplash = () => (
|
|||
<FormattedMessage id="conference-2019.website" />
|
||||
</a>
|
||||
</section>
|
||||
<section className="conf2019-panel mod-kenya mod-last">
|
||||
<section className="conf2019-panel mod-last">
|
||||
<FlexRow className="conf2019-panel-title">
|
||||
<img
|
||||
alt="Kenya Flag"
|
||||
|
@ -249,8 +249,89 @@ const ConferenceSplash = () => (
|
|||
</div>
|
||||
</FlexRow>
|
||||
<p className="conf2019-panel-desc">
|
||||
<FormattedMessage id="conference-2019.kenyaPostpone" />
|
||||
<FormattedMessage id="conference-2019.kenyaDesc" />
|
||||
</p>
|
||||
<table className="conf2019-panel-details">
|
||||
<tbody>
|
||||
<tr className="conf2019-panel-row">
|
||||
<td className="conf2019-panel-row-icon">
|
||||
<img
|
||||
alt="Calendar Icon"
|
||||
className="conf2019-panel-row-icon-image"
|
||||
src="/svgs/conference/index/calendar-icon-solid.svg"
|
||||
/>
|
||||
</td>
|
||||
<td><FormattedMessage id="conference-2019.date" /></td>
|
||||
<td>
|
||||
<FormattedDate
|
||||
day="2-digit"
|
||||
month="long"
|
||||
value={new Date(2019, 9, 16)}
|
||||
year="numeric"
|
||||
/>
|
||||
{' - '}
|
||||
<FormattedDate
|
||||
day="2-digit"
|
||||
month="long"
|
||||
value={new Date(2019, 9, 18)}
|
||||
year="numeric"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="conf2019-panel-row">
|
||||
<td className="conf2019-panel-row-icon">
|
||||
<img
|
||||
alt="Map Icon"
|
||||
className="conf2019-panel-row-icon-image"
|
||||
src="/svgs/conference/index/map-icon-solid.svg"
|
||||
/>
|
||||
</td>
|
||||
<td><FormattedMessage id="conference-2019.location" /></td>
|
||||
<td>{'Nairobi, Kenya'}</td>
|
||||
</tr>
|
||||
<tr className="conf2019-panel-row">
|
||||
<td className="conf2019-panel-row-icon">
|
||||
<img
|
||||
alt="Audience Icon"
|
||||
className="conf2019-panel-row-icon-image"
|
||||
src="/svgs/conference/index/audience-icon-solid.svg"
|
||||
/>
|
||||
</td>
|
||||
<td><FormattedMessage id="conference-2019.audience" /></td>
|
||||
<td><FormattedMessage id="conference-2019.kenyaAudience" /></td>
|
||||
</tr>
|
||||
<tr className="conf2019-panel-row">
|
||||
<td className="conf2019-panel-row-icon">
|
||||
<img
|
||||
alt="Language Icon"
|
||||
className="conf2019-panel-row-icon-image"
|
||||
src="/svgs/conference/index/language-icon-solid.svg"
|
||||
/>
|
||||
</td>
|
||||
<td><FormattedMessage id="conference-2019.language" /></td>
|
||||
<td><FormattedMessage id="general.english" /></td>
|
||||
</tr>
|
||||
<tr className="conf2019-panel-row">
|
||||
<td className="conf2019-panel-row-icon">
|
||||
<img
|
||||
alt="Language Icon"
|
||||
className="conf2019-panel-row-icon-image"
|
||||
src="/svgs/conference/index/hashtag-icon-solid.svg"
|
||||
/>
|
||||
</td>
|
||||
<td><FormattedMessage id="conference-2019.hashtag" /></td>
|
||||
<td>#scratchafrica #scratch2019nbo</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<a
|
||||
className="button mod-2019-conf mod-2019-conf-website-button"
|
||||
href="https://www.scratchafrica.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="conference-2019.website" />
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
<div className="conf2019-title-band conf2019-mailing-list">
|
||||
|
|
|
@ -119,12 +119,6 @@ h1.title-banner-h1.mod-2019 {
|
|||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.mod-kenya {
|
||||
.conf2019-panel-desc {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.conf2019-mailing-list {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
"conference-2019.ukDesc": "Hosted by Raspberry Pi, the 2019 Scratch Conference Europe will take place in Cambridge, UK, from Friday 23 August to Sunday 25 August. The schedule is full of exciting participatory activities led by members of the Scratch community. Participants can look forward to workshops, talks, and keynotes across a range of topics, including the new Scratch 3.0, as well as plenty of informal opportunities to chat and connect!",
|
||||
"conference-2019.ukAudience": "Education professionals and volunteers",
|
||||
|
||||
"conference-2019.kenyaTitle": "Scratch2019NBO",
|
||||
"conference-2019.kenyaTitle": "Scratch Conference Africa: Scratch2019NBO",
|
||||
"conference-2019.kenyaSubTitle": "Waves of Innovation",
|
||||
"conference-2019.kenyaDesc": "In recognition of Africa's technological contributions to the world and the potential of the youth of Africa, Scratch2019NBO will be held in Nairobi, Kenya. Join educators from around the world to share lessons, empower young people, and celebrate accomplishments in creative coding.",
|
||||
"conference-2019.kenyaPostpone": "The Scratch2019NBO conference, originally planned for Nairobi, Kenya in July 2019, has been postponed. Information about future plans will be available later this year.",
|
||||
"conference-2019.kenyaAudience": "Educators, students, and enthusiasts",
|
||||
"conference-2019.kenyaAudience": "Educators",
|
||||
|
||||
"conference-2019.chileDesc": "Scratch al Sur Conferencia Chile 2019 is an event aimed at teachers of all educational areas and levels, who seek to innovate in the classroom through creative learning, thus supporting public policies that are promoted through the National Plan of Digital Languages, launched by the Chilean government as of 2019. Various workshops, panels, experiences, stands, a presentation of the new Scratch 3.0, Makey-Makey, and much more will be offered.",
|
||||
"conference-2019.chileAudience": "Teachers and policy makers",
|
||||
|
|
|
@ -140,7 +140,7 @@ const Credits = () => (
|
|||
{' '}
|
||||
Ben Berg, Amos Blanton, Karen Brennan, Juanita Buitrago, Leo Burd,
|
||||
Gaia Carini, Kasia Chmielinski, Michelle Chung, Shane Clements,
|
||||
Hannah Cole, Sayamindu Dasgupta, Margarita Dekoli, Evelyn Eastmond,
|
||||
Hannah Cole, Sayamindu Dasgupta, Margarita Dekoli,
|
||||
Dave Feinberg, Chris Graves, Megan Haddadi, Connor Hudson,
|
||||
Christina Huang, Tony Hwang, Abdulrahman Idlbi, Randy Jou, Lily Kim,
|
||||
Tauntaun Kim, Saskia Leggett, Tim Mickel, Amon Millner, Ricarose Roque,
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
"ideas.seeAllTutorials": "See All Tutorials",
|
||||
"ideas.cardsTitle": "Get the Entire Collection of Coding Cards",
|
||||
"ideas.cardsText": "With the Scratch Coding Cards, you can learn to create interactive games, stories, music, animations, and more!",
|
||||
"ideas.downloadPDF": "Download PDF",
|
||||
"ideas.starterProjectsTitle": "Starter Projects",
|
||||
"ideas.starterProjectsText": "You can play with Starter Projects and remix them to make your own creations.",
|
||||
"ideas.starterProjectsButton": "Explore Starter Projects",
|
||||
|
|
3
src/views/microbit/l10n-static.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cards.microbit-cardsLink": "https://resources.scratch.mit.edu/www/cards/en/microbit-cards.pdf"
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
"microbit.headerText": "{microbitLink} is a tiny circuit board designed to help kids learn to code and create with technology. It has many features including an LED display, buttons, and a motion sensor. You can connect it to Scratch and build creative projects that combine the magic of the digital and physical worlds.",
|
||||
"microbit.gettingStarted": "Getting Started",
|
||||
"microbit.installMicrobitHex": "Install Scratch micro:bit HEX",
|
||||
"microbit.cardsDescription": "These cards show how to start making projects with micro:bit and Scratch.",
|
||||
"microbit.connectUSB": "Connect a micro:bit to your computer with a USB cable",
|
||||
"microbit.downloadCardsTitle": "Download micro:bit Cards",
|
||||
"microbit.downloadHex": "Download the Scratch micro:bit HEX file",
|
||||
"microbit.dragDropHex": "Drag and drop the HEX file onto your micro:bit",
|
||||
"microbit.connectingMicrobit": "Connecting micro:bit to Scratch",
|
||||
|
|
|
@ -17,6 +17,7 @@ const ExtensionRequirements = require('../../components/extension-landing/extens
|
|||
const ExtensionSection = require('../../components/extension-landing/extension-section.jsx');
|
||||
const InstallScratchLink = require('../../components/extension-landing/install-scratch-link.jsx');
|
||||
const ProjectCard = require('../../components/extension-landing/project-card.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
|
||||
const Steps = require('../../components/steps/steps.jsx');
|
||||
const Step = require('../../components/steps/step.jsx');
|
||||
|
@ -269,6 +270,35 @@ class MicroBit extends ExtensionLanding {
|
|||
/>
|
||||
</Steps>
|
||||
</ExtensionSection>
|
||||
<ExtensionSection className="cards">
|
||||
<FlexRow
|
||||
as="section"
|
||||
className="cards-row"
|
||||
>
|
||||
<div className="cards-image-column">
|
||||
<img
|
||||
className="cards-image"
|
||||
src="/images/microbit/microbit-with-scratch.png"
|
||||
/>
|
||||
</div>
|
||||
<div className="cards-description-column">
|
||||
<h2>
|
||||
<FormattedMessage id="microbit.downloadCardsTitle" />
|
||||
</h2>
|
||||
<p>
|
||||
<FormattedMessage id="microbit.cardsDescription" />
|
||||
</p>
|
||||
<p>
|
||||
<a href={this.props.intl.formatMessage({id: 'cards.microbit-cardsLink'})}>
|
||||
<Button className="download-cards-button large">
|
||||
<FormattedMessage id="general.downloadPDF" />
|
||||
</Button>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<hr />
|
||||
</ExtensionSection>
|
||||
<ExtensionSection className="faq">
|
||||
<h2><FormattedMessage id="microbit.troubleshootingTitle" /></h2>
|
||||
<h3 className="faq-title"><FormattedMessage id="microbit.checkOSVersionTitle" /></h3>
|
||||
|
|
|
@ -14,4 +14,60 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
/* ends with <hr>, so no need for extra padding */
|
||||
padding-bottom: 0;
|
||||
/* slightly more padding on top, since <hr> at bottom has its own extra padding */
|
||||
padding-top: 4.5rem;
|
||||
}
|
||||
|
||||
.cards-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.cards-image-column {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.cards-image {
|
||||
width: calc(100% - 4rem);
|
||||
margin-top: 1rem;
|
||||
margin-right: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cards-description-column {
|
||||
width: 50%;
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.download-cards-button {
|
||||
min-width: 10rem;
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
background-image: url("/svgs/extensions/download-white.svg");
|
||||
background-repeat: no-repeat;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-right: .75rem;
|
||||
vertical-align: text-top;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media #{$medium-and-smaller} {
|
||||
.microbit {
|
||||
.cards-image-column {
|
||||
width: calc(100% - 2rem);
|
||||
}
|
||||
.cards-description-column {
|
||||
width: calc(100% - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const ProjectInfo = require('../../lib/project-info');
|
|||
const PreviewPresentation = require('./presentation.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
const Registration = require('../../components/registration/registration.jsx');
|
||||
const Scratch3Registration = require('../../components/registration/scratch3-registration.jsx');
|
||||
const ConnectedLogin = require('../../components/login/connected-login.jsx');
|
||||
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
|
||||
const NotAvailable = require('../../components/not-available/not-available.jsx');
|
||||
|
@ -751,7 +752,15 @@ class Preview extends React.Component {
|
|||
onUpdateProjectThumbnail={this.props.handleUpdateProjectThumbnail}
|
||||
onUpdateProjectTitle={this.handleUpdateProjectTitle}
|
||||
/>
|
||||
{this.props.registrationOpen && (
|
||||
this.props.useScratch3Registration ? (
|
||||
<Scratch3Registration
|
||||
isOpen
|
||||
/>
|
||||
) : (
|
||||
<Registration />
|
||||
)
|
||||
)}
|
||||
<CanceledDeletionModal />
|
||||
</React.Fragment>
|
||||
}
|
||||
|
@ -822,6 +831,7 @@ Preview.propTypes = {
|
|||
projectInfo: projectShape,
|
||||
projectNotAvailable: PropTypes.bool,
|
||||
projectStudios: PropTypes.arrayOf(PropTypes.object),
|
||||
registrationOpen: PropTypes.bool,
|
||||
remixProject: PropTypes.func,
|
||||
remixes: PropTypes.arrayOf(PropTypes.object),
|
||||
replies: PropTypes.objectOf(PropTypes.array),
|
||||
|
@ -835,6 +845,7 @@ Preview.propTypes = {
|
|||
shareProject: PropTypes.func.isRequired,
|
||||
toggleStudio: PropTypes.func.isRequired,
|
||||
updateProject: PropTypes.func.isRequired,
|
||||
useScratch3Registration: PropTypes.bool,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
banned: PropTypes.bool,
|
||||
|
@ -927,9 +938,11 @@ const mapStateToProps = state => {
|
|||
projectInfo: state.preview.projectInfo,
|
||||
projectNotAvailable: state.preview.projectNotAvailable,
|
||||
projectStudios: state.preview.projectStudios,
|
||||
registrationOpen: state.navigation.registrationOpen,
|
||||
remixes: state.preview.remixes,
|
||||
replies: state.preview.replies,
|
||||
sessionStatus: state.session.status, // check if used
|
||||
useScratch3Registration: state.navigation.useScratch3Registration,
|
||||
user: state.session.session.user,
|
||||
userOwnsProject: userOwnsProject,
|
||||
userPresent: userPresent,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"tips.tttBody": "What do you want to make with Scratch? For each activity, you can try the <strong>Tutorial</strong>, download a set of <strong>Activity Cards</strong>, or view the <strong>Educator Guide</strong>.",
|
||||
"tips.cardsHeader": "Get the Entire Collection of Activity Cards",
|
||||
"tips.cardsBody": "With the Scratch Activity Cards, you can learn to create interactive games, stories, music, animations, and more!",
|
||||
"tips.cardsDownload": "Download PDF",
|
||||
"tips.cardsPurchase": "Purchase Printed Set",
|
||||
"tips.starterProjectsHeader": "Starter Projects",
|
||||
"tips.starterProjectsBody": "You can play with Starter Projects to get ideas for making your own projects.",
|
||||
|
|
|
@ -134,7 +134,7 @@ class Tips extends React.Component {
|
|||
})}
|
||||
>
|
||||
<Button className="tips-button">
|
||||
<FormattedMessage id="tips.cardsDownload" />
|
||||
<FormattedMessage id="general.downloadPDF" />
|
||||
</Button>
|
||||
</a>
|
||||
<a
|
||||
|
|
BIN
static/images/join-flow/birthdate-header.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
static/images/join-flow/country-header.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/join-flow/email-header.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
static/images/join-flow/welcome-header.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
static/images/microbit/microbit-with-scratch.png
Normal file
After Width: | Height: | Size: 78 KiB |
1
static/svgs/forms/caret-hover.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="48" height="64"><path style="isolation:isolate" fill="#231f20" opacity=".1" d="M.01 0h48v64h-48z"/><path d="M24 37.58a1.88 1.88 0 0 1-1.33-.58l-5.11-5.11a1.89 1.89 0 0 1 0-2.65c.73-.73 12.14-.73 12.87 0a1.87 1.87 0 0 1 0 2.64L25.32 37a1.86 1.86 0 0 1-1.32.58z" fill="#b3b3b3"/></svg>
|
After Width: | Height: | Size: 350 B |
1
static/svgs/forms/caret.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="48" height="64"><path d="M24 37.43a1.88 1.88 0 0 1-1.33-.55l-5.11-5.11a1.87 1.87 0 0 1 0-2.64c.73-.73 12.14-.73 12.87 0a1.87 1.87 0 0 1 0 2.64l-5.11 5.11a1.86 1.86 0 0 1-1.32.55z" fill="#b3b3b3"/><path style="isolation:isolate" fill="#231f20" opacity=".1" d="M.01 0h1v64h-1z"/></svg>
|
After Width: | Height: | Size: 350 B |
|
@ -1 +0,0 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><title>carot</title><rect x="0.01" width="48" height="48" fill="#231f20" opacity="0.1"/><path d="M24,29.43a1.87,1.87,0,0,1-1.33-.55l-5.11-5.11a1.87,1.87,0,0,1,0-2.65c0.73-.73,12.14-0.73,12.87,0a1.87,1.87,0,0,1,0,2.65l-5.11,5.11A1.87,1.87,0,0,1,24,29.43Z" fill="#b3b3b3"/></svg>
|
Before Width: | Height: | Size: 393 B |
|
@ -1 +0,0 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><title>carot-hover</title><path d="M24,29.43a1.87,1.87,0,0,1-1.33-.55l-5.11-5.11a1.87,1.87,0,0,1,0-2.65c0.73-.73,12.14-0.73,12.87,0a1.87,1.87,0,0,1,0,2.65l-5.11,5.11A1.87,1.87,0,0,1,24,29.43Z" fill="#b3b3b3"/><rect x="0.01" width="1" height="48" fill="#231f20" opacity="0.1"/></svg>
|
Before Width: | Height: | Size: 398 B |
1
static/svgs/forms/checkmark.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M7.861 15.403a1.66 1.66 0 0 1-1.177-.488L3.488 11.72a1.663 1.663 0 0 1 0-2.354 1.663 1.663 0 0 1 2.354 0l2.02 2.02 6.297-6.297a1.665 1.665 0 0 1 2.354 2.354l-7.475 7.473a1.66 1.66 0 0 1-1.177.488" id="a"/></defs><use fill="#FFF" xlink:href="#a" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 393 B |
1
static/svgs/info-button/info-button.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0" fill="#4794FF"/><path d="M9.988 13.388a1.33 1.33 0 1 1-.001 2.662 1.33 1.33 0 0 1 .001-2.662zm2.68-8.768c.83.53 1.335 1.347 1.418 2.305.084.97-.28 1.95-.949 2.56-.596.543-1.148.852-1.59 1.101-.698.39-.698.41-.698.772 0 .553-.014 1.002-1.002 1.002-.918 0-1.002-.449-1.002-1.002 0-1.559.956-2.092 1.722-2.52.391-.22.795-.445 1.22-.833.176-.16.335-.523.302-.906-.029-.332-.196-.597-.496-.788-.845-.537-2.063-.325-2.869.195-.985.637-.819 1.547-1.755 1.291-1.062-.29-.915-1-.529-1.697.418-.756 1.117-1.317 1.898-1.662 1.471-.65 3.13-.58 4.33.182z" fill="#FFF"/></g></svg>
|
After Width: | Height: | Size: 745 B |
5
test/__mocks__/react-responsive.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// __mocks__/react-responsive.js
|
||||
|
||||
const MediaQuery = ({children}) => children;
|
||||
|
||||
export default MediaQuery;
|
85
test/unit/components/formik-input.test.jsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FormikInput from '../../../src/components/formik-forms/formik-input.jsx';
|
||||
import {Formik} from 'formik';
|
||||
|
||||
describe('FormikInput', () => {
|
||||
test('No validation message with empty error, empty toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error=""
|
||||
toolTip=""
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('No validation message with false error, false toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error={false}
|
||||
toolTip={false}
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('No validation message with nonexistent error or toolTip', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput />
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('Validation message shown when error given', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error="There was an error"
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('Tooltip shown when toolTip given', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
toolTip="Have fun out there!"
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(false);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('If both error and toolTip messages, error takes precedence', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikInput
|
||||
error="There was an error"
|
||||
toolTip="Have fun out there!"
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-error').exists()).toEqual(true);
|
||||
expect(component.find('div.validation-info').exists()).toEqual(false);
|
||||
});
|
||||
});
|
60
test/unit/components/formik-select.test.jsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import React from 'react';
|
||||
import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import FormikSelect from '../../../src/components/formik-forms/formik-select.jsx';
|
||||
import {Formik} from 'formik';
|
||||
import {Field} from 'formik';
|
||||
|
||||
describe('FormikSelect', () => {
|
||||
test('No validation message without an error', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error=""
|
||||
options={[]}
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(false);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('Validation message shown when error present', () => {
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error="uh oh. error"
|
||||
options={[]}
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find('ValidationMessage').exists()).toEqual(true);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('list of options passed to formik', () => {
|
||||
const optionList = [
|
||||
{
|
||||
disabled: false,
|
||||
label: 'option1',
|
||||
value: 'value1'
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
label: 'option2',
|
||||
value: 'value2'
|
||||
}
|
||||
|
||||
];
|
||||
const component = mountWithIntl(
|
||||
<Formik>
|
||||
<FormikSelect
|
||||
error=""
|
||||
options={optionList}
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
expect(component.find(Field).exists()).toEqual(true);
|
||||
expect(component.find(Field).prop('children').length).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -11,6 +11,15 @@ describe('InfoButton', () => {
|
|||
);
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
});
|
||||
test('mouseOver on info button makes info message visible', () => {
|
||||
const component = mountWithIntl(
|
||||
<InfoButton
|
||||
message="Here is some info about something!"
|
||||
/>
|
||||
);
|
||||
component.find('div.info-button').simulate('mouseOver');
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
});
|
||||
test('clicking on info button makes info message visible', () => {
|
||||
const component = mountWithIntl(
|
||||
<InfoButton
|
||||
|
@ -26,7 +35,7 @@ describe('InfoButton', () => {
|
|||
message="Here is some info about something!"
|
||||
/>
|
||||
);
|
||||
component.find('div.info-button').simulate('click');
|
||||
component.find('div.info-button').simulate('mouseOver');
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(true);
|
||||
component.find('div.info-button').simulate('mouseOut');
|
||||
expect(component.find('div.info-button-message').exists()).toEqual(false);
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('JoinFlowStep', () => {
|
|||
{...props}
|
||||
/>
|
||||
);
|
||||
expect(component.find('div.join-flow-header-image').exists()).toEqual(true);
|
||||
expect(component.find('img.join-flow-header-image').exists()).toEqual(true);
|
||||
expect(component.find({src: props.headerImgSrc}).exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-title').exists()).toEqual(true);
|
||||
|
@ -39,7 +39,7 @@ describe('JoinFlowStep', () => {
|
|||
<JoinFlowStep />
|
||||
);
|
||||
|
||||
expect(component.find('div.join-flow-header-image').exists()).toEqual(false);
|
||||
expect(component.find('img.join-flow-header-image').exists()).toEqual(false);
|
||||
expect(component.find('.join-flow-inner-content').exists()).toEqual(true);
|
||||
expect(component.find('.join-flow-title').exists()).toEqual(false);
|
||||
expect(component.find('div.join-flow-description').exists()).toEqual(false);
|
||||
|
|
241
test/unit/redux/navigation.test.js
Normal file
|
@ -0,0 +1,241 @@
|
|||
const {
|
||||
handleToggleAccountNav,
|
||||
navigationReducer,
|
||||
setAccountNavOpen,
|
||||
setCanceledDeletionOpen,
|
||||
setLoginError,
|
||||
setLoginOpen,
|
||||
setRegistrationOpen,
|
||||
setSearchTerm,
|
||||
toggleLoginOpen
|
||||
} = require('../../../src/redux/navigation');
|
||||
|
||||
|
||||
describe('unit test lib/validate.js', () => {
|
||||
test('initialState', () => {
|
||||
let defaultState;
|
||||
/* navigationReducer(state, action) */
|
||||
expect(navigationReducer(defaultState, {type: 'anything'})).toBeDefined();
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).accountNavOpen).toBe(false);
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).canceledDeletionOpen).toBe(false);
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).loginError).toBe(null);
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).loginOpen).toBe(false);
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).registrationOpen).toBe(false);
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).searchTerm).toBe('');
|
||||
expect(navigationReducer(defaultState, {type: 'anything'}).useScratch3Registration).toBe(false);
|
||||
});
|
||||
|
||||
// handleToggleAccountNav
|
||||
|
||||
test('handleToggleAccountNav can toggle on', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: false
|
||||
};
|
||||
const action = handleToggleAccountNav();
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('handleToggleAccountNav can toggle off', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: true
|
||||
};
|
||||
const action = handleToggleAccountNav();
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(false);
|
||||
});
|
||||
|
||||
// setAccountNavOpen
|
||||
|
||||
test('setAccountNavOpen opens account nav, if it is closed', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: false
|
||||
};
|
||||
const action = setAccountNavOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setAccountNavOpen leaves account nav open, if it is already open', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: true
|
||||
};
|
||||
const action = setAccountNavOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setAccountNavOpen closes account nav, if it is open', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: true
|
||||
};
|
||||
const action = setAccountNavOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('setAccountNavOpen leaves account nav closed, if it is already closed', () => {
|
||||
const initialState = {
|
||||
accountNavOpen: false
|
||||
};
|
||||
const action = setAccountNavOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.accountNavOpen).toBe(false);
|
||||
});
|
||||
|
||||
// setCanceledDeletionOpen
|
||||
|
||||
test('setCanceledDeletionOpen opens account nav, if it is closed', () => {
|
||||
const initialState = {
|
||||
canceledDeletionOpen: false
|
||||
};
|
||||
const action = setCanceledDeletionOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.canceledDeletionOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setCanceledDeletionOpen leaves account nav open, if it is already open', () => {
|
||||
const initialState = {
|
||||
canceledDeletionOpen: true
|
||||
};
|
||||
const action = setCanceledDeletionOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.canceledDeletionOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setCanceledDeletionOpen closes account nav, if it is open', () => {
|
||||
const initialState = {
|
||||
canceledDeletionOpen: true
|
||||
};
|
||||
const action = setCanceledDeletionOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.canceledDeletionOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('setCanceledDeletionOpen leaves account nav closed, if it is already closed', () => {
|
||||
const initialState = {
|
||||
canceledDeletionOpen: false
|
||||
};
|
||||
const action = setCanceledDeletionOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.canceledDeletionOpen).toBe(false);
|
||||
});
|
||||
|
||||
// setLoginError
|
||||
|
||||
test('setLoginError sets login error', () => {
|
||||
const initialState = {
|
||||
loginError: null
|
||||
};
|
||||
const action = setLoginError('Danger! Error! Mistake!');
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginError).toBe('Danger! Error! Mistake!');
|
||||
});
|
||||
|
||||
// setLoginOpen
|
||||
|
||||
test('setLoginOpen opens account nav, if it is closed', () => {
|
||||
const initialState = {
|
||||
loginOpen: false
|
||||
};
|
||||
const action = setLoginOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setLoginOpen leaves account nav open, if it is already open', () => {
|
||||
const initialState = {
|
||||
loginOpen: true
|
||||
};
|
||||
const action = setLoginOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setLoginOpen closes account nav, if it is open', () => {
|
||||
const initialState = {
|
||||
loginOpen: true
|
||||
};
|
||||
const action = setLoginOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('setLoginOpen leaves account nav closed, if it is already closed', () => {
|
||||
const initialState = {
|
||||
loginOpen: false
|
||||
};
|
||||
const action = setLoginOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(false);
|
||||
});
|
||||
|
||||
// setRegistrationOpen
|
||||
|
||||
test('setRegistrationOpen opens account nav, if it is closed', () => {
|
||||
const initialState = {
|
||||
registrationOpen: false
|
||||
};
|
||||
const action = setRegistrationOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.registrationOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setRegistrationOpen leaves account nav open, if it is already open', () => {
|
||||
const initialState = {
|
||||
registrationOpen: true
|
||||
};
|
||||
const action = setRegistrationOpen(true);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.registrationOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('setRegistrationOpen closes account nav, if it is open', () => {
|
||||
const initialState = {
|
||||
registrationOpen: true
|
||||
};
|
||||
const action = setRegistrationOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.registrationOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('setRegistrationOpen leaves account nav closed, if it is already closed', () => {
|
||||
const initialState = {
|
||||
registrationOpen: false
|
||||
};
|
||||
const action = setRegistrationOpen(false);
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.registrationOpen).toBe(false);
|
||||
});
|
||||
|
||||
// setSearchTerm
|
||||
|
||||
test('setSearchTerm sets search term', () => {
|
||||
const initialState = {
|
||||
searchTerm: null
|
||||
};
|
||||
const action = setSearchTerm('outer space');
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.searchTerm).toBe('outer space');
|
||||
});
|
||||
|
||||
// toggleLoginOpen
|
||||
|
||||
test('toggleLoginOpen can toggle on', () => {
|
||||
const initialState = {
|
||||
loginOpen: false
|
||||
};
|
||||
const action = toggleLoginOpen();
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(true);
|
||||
});
|
||||
|
||||
test('toggleLoginOpen can toggle off', () => {
|
||||
const initialState = {
|
||||
loginOpen: true
|
||||
};
|
||||
const action = toggleLoginOpen();
|
||||
const resultState = navigationReducer(initialState, action);
|
||||
expect(resultState.loginOpen).toBe(false);
|
||||
});
|
||||
});
|