Merge branch 'develop' of https://github.com/LLK/scratch-www into issue/gh-715

* 'develop' of https://github.com/LLK/scratch-www:
  Pluralize example headlines on educators page
  Educator page language adjustments as per feedback. Resolves GH-734
  fix typo
  Update image format for API change
  Fix lint errors, l10n issues
  revert irrelevant change
  Remove IE weird input additions, fallback to mobile error style on IE, fix some mobile error issues
  Update description of demographics step
  Add student registration flow
  Scope message strings more reusably
  Make registration styles reusable
  Parse with babel-eslint
  Fix whitespace

# Conflicts:
#	src/l10n.json
This commit is contained in:
Matthew Taylor 2016-07-21 10:27:10 -04:00
commit 102de9a087
19 changed files with 851 additions and 547 deletions

View file

@ -1,4 +1,5 @@
{
"parser": "babel-eslint",
"rules": {
"curly": [2, "multi-line"],
"eol-last": [2],
@ -14,16 +15,12 @@
},
"env": {
"browser": true,
"es6": true,
"node": true
},
"globals": {
"formatMessage": true
},
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"jsx": true
},
"plugins": [
"react",
"json"

View file

@ -34,6 +34,7 @@
"async": "1.5.2",
"autoprefixer": "6.3.6",
"babel-core": "6.10.4",
"babel-eslint": "5.0.4",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",

View file

@ -5,4 +5,111 @@
border-radius: 8px / $em;
box-shadow: 0 0 0 .125rem $active-gray;
background-color: $ui-white;
.card-button {
display: block;
border-radius: .5rem;
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: none;
background-color: $ui-aqua;
width: 23.75rem;
height: 4rem;
&:hover {
box-shadow: none;
}
}
.form {
padding: 3rem 4rem;
.card-button {
margin: 0 0 -3rem -4rem;
}
.form-group {
margin-bottom: 1.2rem;
&.has-error {
.input {
border: 1px solid $ui-orange;
}
}
}
}
.help-block {
$arrow-border-width: 1rem;
display: block;
position: absolute;
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
}
@media only screen and (max-width: $mobile - 1) {
.card {
width: 22.5rem;
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
}
}
@media only screen and (max-width: $tablet - 1) {
.card {
.input {
width: 90%;
}
}
}
@media only screen and (max-width: $desktop - 1) {
.card {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
}

View file

@ -10,7 +10,7 @@ var Deck = React.createClass({
<div className={classNames(['deck', this.props.className])}>
<div className="inner">
<a href="/" aria-label="Scratch">
<img src="/images/logo_sm.png" />
<img className="logo" src="/images/logo_sm.png" />
</a>
{this.props.children}
</div>

View file

@ -6,146 +6,22 @@
.deck {
min-height: 100vh;
img {
.logo {
margin-left: 2px;
padding: 12px 0;
width: 76px;
}
.step-navigation {
margin-top: 2rem;
text-align: center;
}
.slide {
max-width: 28.75rem;
h2,
.description {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
}
}
.card {
margin: 0 auto;
width: 23.75rem;
}
.form {
padding: 3rem 4rem;
.form-group {
margin-bottom: 1.2rem;
&.has-error {
.input {
border: 1px solid $ui-orange;
}
}
}
.button {
margin: 0 0 -3rem -4rem;
border-radius: .5rem;
box-shadow: none;
width: 23.75rem;
height: 4rem;
&.card-button {
display: block;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: $ui-aqua;
}
&:hover {
box-shadow: none;
}
}
width: 23.75rem;
}
.input {
width: $cols5;
}
.help-block {
$arrow-border-width: 1rem;
display: block;
position: absolute;
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
}
@media only screen and (max-width: $mobile - 1) {
.deck {
.card {
width: 22.5rem;
}
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
}
}
@media only screen and (max-width: $tablet - 1) {
.deck {
.input {
width: 90%;
}
}
}
@media only screen and (max-width: $desktop - 1) {
.deck {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
}

View file

@ -35,4 +35,9 @@ $pass-bg: lighten($ui-aqua, 35%);
border: 1px solid $active-dark-gray;
background-color: $pass-bg;
}
/* IE10/11-specific style resets */
&::-ms-reveal, &::-ms-clear {
display: none;
}
}

View file

@ -6,6 +6,7 @@ var intl = require('../../lib/intl.jsx');
var log = require('../../lib/log');
var smartyStreets = require('../../lib/smarty-streets');
var Avatar = require('../../components/avatar/avatar.jsx');
var Button = require('../../components/forms/button.jsx');
var Card = require('../../components/card/card.jsx');
var CharCount = require('../../components/forms/charcount.jsx');
@ -23,6 +24,8 @@ var StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx
var TextArea = require('../../components/forms/textarea.jsx');
var Tooltip = require('../../components/tooltip/tooltip.jsx');
require('./steps.scss');
var DEFAULT_COUNTRY = 'us';
var NextStepButton = React.createClass({
@ -34,7 +37,7 @@ var NextStepButton = React.createClass({
},
render: function () {
return (
<Button type="submit" disabled={this.props.waiting} className="card-button">
<Button type="submit" disabled={this.props.waiting} className="card-button" {... this.props}>
{this.props.waiting ?
<Spinner /> :
this.props.text
@ -79,16 +82,16 @@ module.exports = {
return this.props.onNextStep(formData);
case 'username exists':
return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameExists'})
'user.username': formatMessage({id: 'registration.validationUsernameExists'})
});
case 'bad username':
return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameVulgar'})
'user.username': formatMessage({id: 'registration.validationUsernameVulgar'})
});
case 'invalid username':
default:
return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameInvalid'})
'user.username': formatMessage({id: 'registration.validationUsernameInvalid'})
});
}
}.bind(this));
@ -96,14 +99,14 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="username-step">
<h2><intl.FormattedMessage id="teacherRegistration.usernameStepTitle" /></h2>
<Slide className="registration-step username-step">
<h2><intl.FormattedMessage id="registration.usernameStepTitle" /></h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.usernameStepDescription" />
<intl.FormattedMessage id="registration.usernameStepDescription" />
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'general.createUsername'})}
<Input label={formatMessage({id: 'registration.createUsername'})}
className={this.state.validUsername}
type="text"
name="user.username"
@ -114,13 +117,13 @@ module.exports = {
}}
validationErrors={{
matchRegexp: formatMessage({
id: 'teacherRegistration.validationUsernameRegexp'
id: 'registration.validationUsernameRegexp'
}),
minLength: formatMessage({
id: 'teacherRegistration.validationUsernameMinLength'
id: 'registration.validationUsernameMinLength'
}),
maxLength: formatMessage({
id: 'teacherRegistration.validationUsernameMaxLength'
id: 'registration.validationUsernameMaxLength'
})
}}
required />
@ -134,24 +137,24 @@ module.exports = {
}}
validationErrors={{
minLength: formatMessage({
id: 'teacherRegistration.validationPasswordLength'
id: 'registration.validationPasswordLength'
}),
notEquals: formatMessage({
id: 'teacherRegistration.validationPasswordNotEquals'
id: 'registration.validationPasswordNotEquals'
}),
notEqualsField: formatMessage({
id: 'teacherRegistration.validationPasswordNotUsername'
id: 'registration.validationPasswordNotUsername'
})
}}
required />
<Checkbox label={formatMessage({id: 'teacherRegistration.showPassword'})}
<Checkbox label={formatMessage({id: 'registration.showPassword'})}
value={this.state.showPassword}
onChange={this.onChangeShowPassword}
help={null}
name="showPassword" />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -163,7 +166,8 @@ module.exports = {
getDefaultProps: function () {
return {
defaultCountry: DEFAULT_COUNTRY,
waiting: false
waiting: false,
description: null
};
},
getInitialState: function () {
@ -191,14 +195,18 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="demographics-step">
<Slide className="registration-step demographics-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.personalStepTitle" />
<intl.FormattedMessage id="registration.personalStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.personalStepDescription" />
{this.props.description ?
this.props.description
:
<intl.FormattedMessage id="registration.personalStepDescription" />
}
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
@ -234,7 +242,7 @@ module.exports = {
label="I'm a robot!"
name="user.isRobot" />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -251,14 +259,14 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="name-step">
<Slide className="registration-step name-step">
<h2>
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
@ -271,7 +279,7 @@ module.exports = {
name="user.name.last"
required />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -297,14 +305,14 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="phone-step">
<Slide className="registration-step phone-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
@ -319,7 +327,7 @@ module.exports = {
isFalse: formatMessage({id: 'teacherRegistration.validationPhoneConsent'})
}} />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -373,14 +381,14 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="organization-step">
<Slide className="registration-step organization-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
@ -417,7 +425,7 @@ module.exports = {
placeholder={'http://'} />
</div>
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -487,14 +495,14 @@ module.exports = {
return 0;
}.bind(this));
return (
<Slide className="address-step">
<Slide className="registration-step address-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.addressStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
@ -528,7 +536,7 @@ module.exports = {
required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -558,14 +566,14 @@ module.exports = {
var textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : '';
return (
<Slide className="usescratch-step">
<Slide className="registration-step usescratch-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
@ -585,7 +593,7 @@ module.exports = {
<CharCount maxCharacters={this.props.maxCharacters}
currentCharacters={this.state.characterCount} />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -625,14 +633,14 @@ module.exports = {
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="email-step">
<Slide className="registration-step email-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
@ -652,7 +660,7 @@ module.exports = {
required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
@ -669,7 +677,7 @@ module.exports = {
},
render: function () {
return (
<Slide className="last-step">
<Slide className="registration-step last-step">
<h2>
<intl.FormattedMessage id="registration.lastStepTitle" />
</h2>
@ -707,10 +715,99 @@ module.exports = {
);
}
})),
ClassInviteStep: React.createClass({
getDefaultProps: function () {
return {
classroom: {
title: '',
thumbnail: '',
educator: {
username: '',
profile: {
images: ''
}
}
},
messages: {
'general.getStarted': 'Get Started',
'registration.classroomInviteStepDescription': 'has invited you to join the class:'
},
waiting: false
};
},
onNextStep: function () {
this.props.onNextStep();
},
render: function () {
return (
<Slide className="registration-step class-invite-step">
<Avatar className="invite-avatar" src={this.props.classroom.educator.profile.images['50x50']} />
<h2>{this.props.classroom.educator.username}</h2>
<p className="description">
{this.props.messages['registration.classroomInviteStepDescription']}
</p>
<Card>
<div className="contents">
<h3>{this.props.classroom.title}</h3>
<img className="class-image" src={this.props.classroom.images['250x150']} />
</div>
<NextStepButton onClick={this.onNextStep}
waiting={this.props.waiting}
text={this.props.messages['general.getStarted']} />
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
</Slide>
);
}
}),
ClassWelcomeStep: React.createClass({
getDefaultProps: function () {
return {
classroom: {
title: '',
thumbnail: '',
educator: {
username: '',
profile: {
images: ''
}
}
},
messages: {
'registration.goToClass': 'Go to Class',
'registration.welcomeStepDescription': 'You have successfully set up a Scratch account! ' +
'You are now a member of the class:',
'registration.welcomeStepPrompt': 'To get started, click on the button below.',
'registration.welcomeStepTitle': 'Hurray! Welcome to Scratch!'
}
};
},
onNextStep: function () {
this.props.onNextStep();
},
render: function () {
return (
<Slide className="registration-step class-welcome-step">
<h2>{this.props.messages['registration.welcomeStepTitle']}</h2>
<p className="description">{this.props.messages['registration.welcomeStepDescription']}</p>
<Card>
<div className="contents">
<h3>{this.props.classroom.title}</h3>
<img className="class-image" src={this.props.classroom.images['250x150']} />
<p>{this.props.messages['registration.welcomeStepPrompt']}</p>
</div>
<NextStepButton onClick={this.onNextStep}
waiting={this.props.waiting}
text={this.props.messages['registration.goToClass']} />
</Card>
</Slide>
);
}
}),
RegistrationError: intl.injectIntl(React.createClass({
render: function () {
return (
<Slide className="error-step">
<Slide className="registration-step error-step">
<h2>Something went wrong</h2>
<Card>
<h4>There was an error while processing your registration</h4>

View file

@ -0,0 +1,254 @@
@import "../../colors";
@import "../../frameless";
.registration-step {
.demographics-checkbox-is-robot {
display: none;
}
.invite-avatar {
display: block;
margin: 0 auto 1rem auto;
border: 2px solid $ui-white;
border-radius: 8px;
}
.gender-input,
.other-input {
float: right;
width: 90%;
.row {
margin-left: .5rem;
}
}
&.class-invite-step,
&.class-welcome-step {
.card {
text-align: center;
.contents {
padding: 2rem 0;
}
}
}
&.username-step,
&.name-step,
&.address-step,
&.email-step {
.help-block {
transform: translate(15.75rem, -4rem);
}
}
&.demographics-step {
.gender-input {
margin-top: -5.5rem;
}
.help-block {
transform: translate(13rem, -2rem);
}
.radio {
margin-right: 2.5rem;
line-height: 3rem;
input {
margin-right: 1rem;
}
}
}
&.phone-step {
.form-group {
margin-bottom: 2rem;
}
input {
&[type=checkbox] {
margin-bottom: 1.25rem;
}
}
.help-block {
margin-top: .5rem;
}
.checkbox-row {
.help-block {
margin-top: 0;
}
}
}
&.organization-step {
.help-block {
transform: translate(16rem, -4rem);
}
.checkbox-group {
.help-block {
transform: translate(16rem, -16rem);
}
}
.organization-type,
.url-input {
p {
margin: .25rem 0;
text-align: left;
color: $ui-dark-gray;
}
}
input {
&[value="8"] {
margin: 1rem 0;
}
}
.other-input {
margin-top: -5.75rem;
}
}
&.address-step {
.select {
.help-block {
transform: translate(0, .5rem);
}
}
}
&.usescratch-step {
.form {
.form-group {
margin-bottom: 0;
&.has-error {
.textarea {
border: 1px solid $ui-orange;
}
}
}
}
.help-block {
margin-top: .75rem;
}
p {
&.char-count {
margin-top: 0;
margin-bottom: 1rem;
text-align: right;
}
}
}
&.last-step,
&.error-step {
&.slide {
max-width: 38.75rem;
}
.card {
margin: 1rem auto;
padding: 1.5rem;
width: initial;
h4,
p {
text-align: left;
}
p {
margin: 0;
}
}
}
}
@media only screen and (max-width: $mobile - 1) {
.registration-step {
&.demographics-step {
.radio {
width: 100%;
text-align: left;
}
}
&.last-step,
&.error-step {
.card {
margin: 0 auto;
width: 18.75rem;
}
}
}
}
@media only screen and (max-width: $desktop - 1) {
.registration-step {
.form {
text-align: left;
}
&.username-step,
&.demographics-step,
&.name-step,
&.address-step,
&.email-step {
.help-block {
transform: none;
}
}
&.phone-step {
.checkbox,
.help-block {
text-align: left;
}
.checkbox {
margin-bottom: 1rem;
}
}
&.organization-step {
.checkbox-group {
text-align: left;
}
}
}
}
/* IE10 and IE11 fallback */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.registration-step {
&.username-step,
&.demographics-step,
&.name-step,
&.phone-step,
&.organization-step,
&.address-step,
&.email-step {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
}
}

View file

@ -1,7 +1,28 @@
@import "../../frameless";
@import "../../colors";
.slide {
padding: 10px;
> h2,
> .description {
text-align: center;
color: $type-white;
}
> .description {
margin-top: 0;
margin-bottom: 2rem;
}
.card {
margin: 0 auto;
}
.step-navigation {
margin-top: 2rem;
text-align: center;
}
}
@media only screen and (max-width: $tablet - 1) {

View file

@ -12,7 +12,6 @@
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.country": "Country",
"general.create": "Create",
"general.createUsername": "Create a Username",
"general.credits": "Credits",
"general.discuss": "Discuss",
"general.dmca": "DMCA",
@ -77,9 +76,6 @@
"general.username": "Username",
"general.validationEmail": "Please enter a valid email address",
"general.validationEmailMatch": "The emails do not match",
"general.validationUsernameExists": "Sorry, that username already exists",
"general.validationUsernameVulgar": "Hmm, that looks inappropriate",
"general.validationUsernameInvalid": "Invalid username",
"general.viewAll": "View All",
"general.website": "Website",
"general.whatsHappening": "What's Happening?",
@ -109,13 +105,34 @@
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account",
"registration.lastStepDescription": "We are currently processing your application. ",
"registration.checkOutResources": "Get Started with Resources",
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>.",
"registration.confirmYourEmail": "Confirm Your Email",
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
"registration.createUsername": "Create a Username",
"registration.goToClass": "Go to Class",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account",
"registration.lastStepDescription": "We are currently processing your application. ",
"registration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.",
"registration.nextStep": "Next Step",
"registration.notOnWebsite": "This information will not appear on the Scratch website.",
"registration.personalStepTitle": "Personal Information",
"registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"registration.showPassword": "Show password",
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to 24 hours.",
"registration.usernameStepTitle": "Request a Teacher Account",
"registration.validationPasswordLength": "Passwords must be at least six characters",
"registration.validationPasswordNotEquals": "Your password may not be \"password\"",
"registration.validationPasswordNotUsername": "Your password may not be your username",
"registration.validationUsernameRegexp": "Your username may only contain letters, numbers, \"-\", and \"_\"",
"registration.validationUsernameMinLength": "Usernames must be at least 3 characters",
"registration.validationUsernameMaxLength": "Usernames must be at most 20 characters",
"registration.validationUsernameExists": "Sorry, that username already exists",
"registration.validationUsernameVulgar": "Hmm, that looks inappropriate",
"registration.validationUsernameInvalid": "Invalid username",
"registration.waitForApproval": "Wait for Approval",
"registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to 24 hours. You will receive an email indicating your account has been upgraded once your account has been approved.",
"registration.checkOutResources": "Get Started with Resources",
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>."
"registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
"registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!"
}

View file

@ -12,109 +12,16 @@
"title": "About"
},
{
"name": "developers",
"pattern": "^/developers/?$",
"view": "developers/developers",
"title": "Developers"
"name": "guidelines",
"pattern": "^/community_guidelines/?$",
"view": "guidelines/guidelines",
"title": "Scratch Community Guidelines"
},
{
"name": "hoc",
"pattern": "^/hoc/?$",
"view": "hoc/hoc",
"title": "Hour of Code"
},
{
"name": "explore",
"pattern": "^/explore/:projects/:all/?$",
"routeAlias": "^/explore(?!/ajax)",
"view": "explore/explore",
"title": "Explore"
},
{
"name": "explore-redirect",
"pattern": "^/explore/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-projects-redirect",
"pattern": "^/explore/projects/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-studios-redirect",
"pattern": "^/explore/studios/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/studios/all"
},
{
"name": "search",
"pattern": "^/search/:projects?$/?$",
"routeAlias": "^/search",
"view": "search/search",
"title": "Search"
},
{
"name": "credits",
"pattern": "^/info/credits/?$",
"view": "credits/credits",
"title": "Credits"
},
{
"name": "faq",
"pattern": "^/info/faq/?$",
"view": "faq/faq",
"title": "FAQ"
},
{
"name": "educator-landing",
"pattern": "^/educators/?$",
"view": "teachers/landing/landing",
"title": "Educators"
},
{
"name": "teacher-faq",
"pattern": "^/educators/faq/?$",
"view": "teachers/faq/faq",
"title": "Teacher Accounts FAQ"
},
{
"name": "cards",
"pattern": "^/info/cards/?$",
"view": "cards/cards",
"title": "Cards"
},
{
"name": "communityblocks-interviews",
"pattern": "^/info/communityblocks-interviews/?$",
"view": "communityblocks-interviews/communityblocks-interviews",
"title": "Community Blocks Beta Tester Interviews"
},
{
"name": "jobs",
"pattern": "^/jobs/?$",
"view": "jobs/jobs",
"title": "Jobs"
},
{
"name": "teacherregistration",
"pattern": "^/educators/register$",
"view": "teacherregistration/teacherregistration",
"title": "Teacher Registration",
"viewportWidth": "device-width"
},
{
"name": "teacherwaitingroom",
"pattern": "^/educators/waiting",
"view": "teacherwaitingroom/teacherwaitingroom",
"title": "Thank you for requesting a Scratch Teacher Account"
},
{
"name": "wedo2",
"pattern": "^/wedo/?$",
"view": "wedo2/wedo2",
"title": "LEGO WeDo 2.0"
"name": "student-registration",
"pattern": "^/classes/:id/register/:token",
"view": "studentregistration/studentregistration",
"title": "Class Registration"
},
{
"name": "conference-index",
@ -157,9 +64,10 @@
"viewportWidth": "device-width"
},
{
"name": "donate",
"pattern": "^/info/donate/?",
"redirect": "https://secure.donationpay.org/scratchfoundation/"
"name": "developers",
"pattern": "^/developers/?$",
"view": "developers/developers",
"title": "Developers"
},
{
"name": "dmca",
@ -168,10 +76,72 @@
"title": "DMCA"
},
{
"name": "guidelines",
"pattern": "^/community_guidelines/?$",
"view": "guidelines/guidelines",
"title": "Scratch Community Guidelines"
"name": "educator-landing",
"pattern": "^/educators/?$",
"view": "teachers/landing/landing",
"title": "Educators"
},
{
"name": "teacher-faq",
"pattern": "^/educators/faq/?$",
"view": "teachers/faq/faq",
"title": "Teacher Accounts FAQ"
},
{
"name": "teacherregistration",
"pattern": "^/educators/register$",
"view": "teacherregistration/teacherregistration",
"title": "Teacher Registration",
"viewportWidth": "device-width"
},
{
"name": "teacherwaitingroom",
"pattern": "^/educators/waiting",
"view": "teacherwaitingroom/teacherwaitingroom",
"title": "Thank you for requesting a Scratch Teacher Account"
},
{
"name": "explore",
"pattern": "^/explore/:projects/:all/?$",
"routeAlias": "^/explore(?!/ajax)",
"view": "explore/explore",
"title": "Explore"
},
{
"name": "hoc",
"pattern": "^/hoc/?$",
"view": "hoc/hoc",
"title": "Hour of Code"
},
{
"name": "cards",
"pattern": "^/info/cards/?$",
"view": "cards/cards",
"title": "Cards"
},
{
"name": "communityblocks-interviews",
"pattern": "^/info/communityblocks-interviews/?$",
"view": "communityblocks-interviews/communityblocks-interviews",
"title": "Community Blocks Beta Tester Interviews"
},
{
"name": "credits",
"pattern": "^/info/credits/?$",
"view": "credits/credits",
"title": "Credits"
},
{
"name": "faq",
"pattern": "^/info/faq/?$",
"view": "faq/faq",
"title": "FAQ"
},
{
"name": "jobs",
"pattern": "^/jobs/?$",
"view": "jobs/jobs",
"title": "Jobs"
},
{
"name": "privacypolicy",
@ -179,10 +149,46 @@
"view": "privacypolicy/privacypolicy",
"title": "Privacy Policy"
},
{
"name": "search",
"pattern": "^/search/:projects?$/?$",
"routeAlias": "^/search",
"view": "search/search",
"title": "Search"
},
{
"name": "terms",
"pattern": "^/terms_of_use/?$",
"view": "terms/terms",
"title": "Scratch Terms of Use"
},
{
"name": "wedo2",
"pattern": "^/wedo/?$",
"view": "wedo2/wedo2",
"title": "LEGO WeDo 2.0"
},
{
"name": "donate",
"pattern": "^/info/donate/?",
"redirect": "https://secure.donationpay.org/scratchfoundation/"
},
{
"name": "explore-redirect",
"pattern": "^/explore/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-projects-redirect",
"pattern": "^/explore/projects/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-studios-redirect",
"pattern": "^/explore/studios/?$",
"routeAlias": "^/explore(?!/ajax)",
"redirect": "/explore/studios/all"
}
]

View file

@ -0,0 +1,3 @@
{
"studentRegistration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class"
}

View file

@ -0,0 +1,124 @@
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var api = require('../../lib/api');
var intl = require('../../lib/intl.jsx');
var Deck = require('../../components/deck/deck.jsx');
var Progression = require('../../components/progression/progression.jsx');
var Steps = require('../../components/registration/steps.jsx');
require('./studentregistration.scss');
var StudentRegistration = intl.injectIntl(React.createClass({
type: 'StudentRegistration',
getDefaultProps: function () {
return {
classroomId: null,
classroomToken: null
};
},
getInitialState: function () {
return {
formData: {},
registrationError: null,
step: 0,
waiting: false
};
},
advanceStep: function (formData) {
formData = formData || {};
this.setState({
step: this.state.step + 1,
formData: defaults({}, formData, this.state.formData)
});
},
componentDidMount: function () {
api({
uri: '/classrooms/' + this.props.classroomId + '/' + this.props.classroomToken
}, function (err, body, res) {
if (err) {
return this.setState({
registrationError: this.props.intl.formatMessage({
id: 'studentRegistration.classroomApiGeneralError'
})
});
}
if (res.statusCode === 404) {
// TODO: Use react-router for this
return window.location = '/404';
}
this.setState({classroom: body});
}.bind(this));
},
register: function (formData) {
this.setState({waiting: true});
formData = defaults({}, formData || {}, this.state.formData);
api({
host: '',
uri: '/classes/register_new_student/',
method: 'post',
useCsrf: true,
formData: {
username: formData.user.username,
password: formData.user.password,
birth_month: formData.user.birth.month,
birth_year: formData.user.birth.year,
gender: (
formData.user.gender === 'other' ?
formData.user.genderOther :
formData.user.gender
),
country: formData.user.country,
is_robot: formData.user.isRobot,
classroom_id: this.props.classroomId,
classroom_token: this.props.classroomToken
}
}, function (err, res) {
this.setState({waiting: false});
if (err) return this.setState({registrationError: err});
if (res[0].success) return this.advanceStep(formData);
this.setState({registrationError: res[0].msg});
}.bind(this));
},
goToClass: function () {
window.location = '/classes/' + this.props.classroomId + '/';
},
render: function () {
var demographicsDescription = this.props.intl.formatMessage({id: 'registration.notOnWebsite'});
return (
<Deck className="student-registration">
{this.state.registrationError ?
<Steps.RegistrationError {... this.state} />
:
<Progression {... this.state}>
<Steps.ClassInviteStep classroom={this.state.classroom}
messages={this.props.messages}
onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.DemographicsStep description={demographicsDescription}
onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.ClassWelcomeStep classroom={this.state.classroom}
messages={this.props.messages}
onNextStep={this.goToClass}
waiting={this.state.waiting} />
</Progression>
}
</Deck>
);
}
}));
var [classroomId, _, classroomToken] = document.location.pathname.split('/')
.filter(function (p) {
if (p) return p;
})
.slice(-3);
var props = {classroomId, classroomToken};
render(<StudentRegistration {... props} />, document.getElementById('app'));

View file

@ -0,0 +1,13 @@
@import "../../colors";
@import "../../frameless";
@include responsive-layout (".student-registration", ".slide");
html,
body {
background-color: darken($ui-purple, 8%);
}
.teacher-registration {
background-color: $ui-purple;
}

View file

@ -1,19 +1,6 @@
{
"teacherRegistration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to 24 hours.",
"teacherRegistration.usernameStepTitle": "Request a Teacher Account",
"teacherRegistration.validationUsernameRegexp": "Your username may only contain letters, numbers, \"-\", and \"_\"",
"teacherRegistration.validationUsernameMinLength": "Usernames must be at least 3 characters",
"teacherRegistration.validationUsernameMaxLength": "Usernames must be at most 20 characters",
"teacherRegistration.validationPasswordLength": "Passwords must be at least six characters",
"teacherRegistration.validationPasswordNotEquals": "Your password may not be \"password\"",
"teacherRegistration.validationPasswordNotUsername": "Your password may not be your username",
"teacherRegistration.showPassword": "Show password",
"teacherRegistration.nextStep": "Next Step",
"teacherRegistration.personalStepTitle": "Personal Information",
"teacherRegistration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"teacherRegistration.nameStepTitle": "First &amp; Last Name",
"teacherRegistration.nameStepDescription": "Your name will not be displayed publicly, and will be kept confidential and secure.",
"teacherRegistration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.",
"teacherRegistration.firstName": "First Name",
"teacherRegistration.lastName": "Last Name",
"teacherRegistration.phoneStepTitle": "Phone Number",

View file

@ -79,28 +79,28 @@ var TeacherRegistration = React.createClass({
<Steps.RegistrationError {... this.state} />
:
<Progression {... this.state}>
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.DemographicsStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.OrganizationStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.AddressStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.UseScratchStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.EmailStep onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.AddressStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.UseScratchStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.EmailStep onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.TeacherApprovalStep email={this.state.formData.user && this.state.formData.user.email} />
</Progression>
}

View file

@ -10,209 +10,4 @@ body {
.teacher-registration {
background-color: $ui-purple;
.demographics-checkbox-is-robot {
display: none;
}
.gender-input,
.other-input {
float: right;
width: 90%;
.row {
margin-left: .5rem;
}
}
.username-step,
.name-step,
.address-step,
.email-step {
.help-block {
transform: translate(15.75rem, -4rem);
}
}
.demographics-step {
.gender-input {
margin-top: -5.5rem;
}
.help-block {
transform: translate(13rem, -2rem);
}
.radio {
margin-right: 2.5rem;
line-height: 3rem;
input {
margin-right: 1rem;
}
}
}
.phone-step {
.form-group {
margin-bottom: 2rem;
}
input {
&[type=checkbox] {
margin-bottom: 1.25rem;
}
}
.help-block {
margin-top: .5rem;
}
.checkbox-row {
.help-block {
margin-top: 0;
}
}
}
.organization-step {
.help-block {
transform: translate(16rem, -4rem);
}
.checkbox-group {
.help-block {
transform: translate(16rem, -16rem);
}
}
.organization-type,
.url-input {
p {
margin: .25rem 0;
text-align: left;
color: $ui-dark-gray;
}
}
input {
&[value="8"] {
margin: 1rem 0;
}
}
.other-input {
margin-top: -5.75rem;
}
}
.address-step {
.select {
.help-block {
transform: translate(0, .5rem);
}
}
}
.usescratch-step {
.form {
.form-group {
margin-bottom: 0;
&.has-error {
.textarea {
border: 1px solid $ui-orange;
}
}
}
}
.help-block {
margin-top: .75rem;
}
p {
&.char-count {
margin-top: 0;
margin-bottom: 1rem;
text-align: right;
}
}
}
.last-step,
.error-step {
&.slide {
max-width: 38.75rem;
}
.card {
margin: 1rem auto;
padding: 1.5rem;
width: initial;
h4,
p {
text-align: left;
}
p {
margin: 0;
}
}
}
}
@media only screen and (max-width: $mobile - 1) {
.teacher-registration {
.demographics-step {
.radio {
width: 100%;
text-align: left;
}
}
.last-step,
.error-step {
.card {
margin: 0 auto;
width: 18.75rem;
}
}
}
}
@media only screen and (max-width: $desktop - 1) {
.teacher-registration {
.form {
text-align: left;
}
.username-step,
.demographics-step,
.name-step {
.help-block {
transform: none;
}
}
.phone-step {
.checkbox,
.help-block {
text-align: left;
}
.checkbox {
margin-bottom: 1rem;
}
}
.organization-step {
.checkbox-group {
text-align: left;
}
}
}
}

View file

@ -1,16 +1,17 @@
{
"teacherlanding.title": "Scratch for Educators",
"teacherlanding.intro": "Your students can use Scratch to code their own interactive stories, animations, and games. In the process, they learn to think creatively, reason systematically, and work collaboratively — essential skills for everyone in todays society.",
"teacherlanding.inPracticeAnchor": "In Practice",
"teacherlanding.inPracticeAnchor": "Who Uses Scratch?",
"teacherlanding.resourcesAnchor": "Resources",
"teacherlanding.inPracticeTitle": "Who Uses Scratch?",
"teacherlanding.inPracticeIntro": "Educators are using Scratch in a wide variety of: ",
"teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers",
"teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)",
"teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts",
"teacherlanding.ingridTitle": "Instructional Technology Specialist",
"teacherlanding.dylanTitle": "Educational Technologist",
"teacherlanding.afterSchoolTitle": "After-School Program",
"teacherlanding.elementaryTitle": "Elementary Schools",
"teacherlanding.middleTitle": "Middle Schools",
"teacherlanding.communityTitle": "Community Programs",
"teacherlanding.afterSchoolTitle": "After-School Programs",
"teacherlanding.resourcesTitle": "Educator Resources",
"teacherlanding.scratchEdTitle": "A Community for Educators",
"teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.",

View file

@ -62,34 +62,34 @@ var Landing = injectIntl(React.createClass({
<p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p>
</FlexRow>
<FlexRow className="stories">
<a href="//bit.ly/28SBsa9" className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" />
<div className="story-info">
<p className="name">Ingrid Gustafson</p>
<p><FormattedMessage id="teacherlanding.ingridTitle" /></p>
</div>
</a>
<a href="//bit.ly/28Q5l6P" className="story">
<img src="/images/teachers/stories/dylan.jpg" alt="dylan's story" />
<img src="/images/teachers/stories/dylan.jpg" alt="Elementary Schools" />
<div className="story-info">
<p className="name">Dylan Ryder</p>
<p><FormattedMessage id="teacherlanding.dylanTitle" /></p>
<p className="name"><FormattedMessage id="teacherlanding.elementaryTitle" /></p>
<p>The School at Columbia University</p>
</div>
</a>
<a href="//bit.ly/28SBsa9" className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="Middle Schools" />
<div className="story-info">
<p className="name"><FormattedMessage id="teacherlanding.middleTitle" /></p>
<p>Cambridge Public Schools</p>
</div>
</a>
<a href="//bit.ly/28SC1AY" className="story">
<img src="/images/teachers/stories/plug-in-studio.jpg"
alt="plug in studio's story" />
alt="Community Programs" />
<div className="story-info">
<p className="name">Plug-In Studios</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
<p className="name"><FormattedMessage id="teacherlanding.communityTitle" /></p>
<p>Plug-In Studio</p>
</div>
</a>
<a href="//bit.ly/28UzapJ" className="story">
<img src="/images/teachers/stories/ghana-code-club.jpg"
alt="ghana code club's story" />
alt="After-School Programs" />
<div className="story-info">
<p className="name">Ghana Code Club</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
<p className="name"><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
<p>Ghana Code Club</p>
</div>
</a>
</FlexRow>