diff --git a/src/components/card/card.scss b/src/components/card/card.scss index a3e581c5b..b8637421f 100644 --- a/src/components/card/card.scss +++ b/src/components/card/card.scss @@ -21,31 +21,13 @@ } } - .form { - position: relative; - padding: 3rem 4rem; - - .card-button { - margin: 0 0 -3rem -4rem; - } - - .form-group { - margin-bottom: 1.2rem; - - &.has-error { - .input { - border: 1px solid $ui-orange; - } - } - } - } - .validation-message { $arrow-border-width: 1rem; display: block; position: absolute; + top: 0; left: 0; - transform: translate(20rem, -4rem); + transform: translate(16rem, 0); margin-left: $arrow-border-width; border: 1px solid $active-gray; border-radius: 5px; @@ -77,6 +59,27 @@ } } + .form { + padding: 3rem 4rem; + + .card-button { + margin: 0 0 -3rem -4rem; + } + + .row { + margin-bottom: 1.2rem; + + &.has-error { + .input { + border: 1px solid $ui-orange; + } + } + + .col-sm-9 { + position: relative; + } + } + } } @media only screen and (max-width: $mobile - 1) { diff --git a/src/components/deck/deck.scss b/src/components/deck/deck.scss index 30991a092..e06d880a8 100644 --- a/src/components/deck/deck.scss +++ b/src/components/deck/deck.scss @@ -21,7 +21,7 @@ } .input { - width: $cols5; + width: 100%; } } diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx index c423ded01..f0aa295b8 100644 --- a/src/components/forms/checkbox.jsx +++ b/src/components/forms/checkbox.jsx @@ -15,9 +15,7 @@ var Checkbox = React.createClass({ this.props.className ); return ( -
- -
+ ); } }); diff --git a/src/components/forms/input.jsx b/src/components/forms/input.jsx index d1d9d5aa7..3244ade3c 100644 --- a/src/components/forms/input.jsx +++ b/src/components/forms/input.jsx @@ -29,14 +29,14 @@ var Input = React.createClass({ }, render: function () { var classes = classNames( - 'input', this.state.status, - this.props.className + this.props.className, + {'no-label': (typeof this.props.label === 'undefined')} ); - return (this.props.type === 'submit' || this.props.noformsy ? - : + return ( ); diff --git a/src/components/forms/input.scss b/src/components/forms/input.scss index d33a02705..214365f28 100644 --- a/src/components/forms/input.scss +++ b/src/components/forms/input.scss @@ -12,7 +12,7 @@ $pass-bg: lighten($ui-aqua, 35%); .input { transition: all .5s ease; - margin: .75rem 0; + margin-bottom: .75rem; border: 1px solid $active-gray; border-radius: 5px; background-color: $base-bg; diff --git a/src/components/forms/phone-input.jsx b/src/components/forms/phone-input.jsx index 061a5a61d..a4e26eb50 100644 --- a/src/components/forms/phone-input.jsx +++ b/src/components/forms/phone-input.jsx @@ -42,7 +42,7 @@ var PhoneInput = React.createClass({ return (
+ {this.renderHelp()} + {this.renderErrorMessage()}
- {this.renderHelp()} - {this.renderErrorMessage()}
); } diff --git a/src/components/forms/phone-input.scss b/src/components/forms/phone-input.scss index b2a0ff733..99baf386b 100644 --- a/src/components/forms/phone-input.scss +++ b/src/components/forms/phone-input.scss @@ -1,11 +1,11 @@ @import "../../colors"; .input-group { - margin: .75rem 0; width: 100%; } .react-tel-input { + margin-bottom: .75rem; width: 100%; input { diff --git a/src/components/forms/row.scss b/src/components/forms/row.scss index 10a8e12e8..43373ddd2 100644 --- a/src/components/forms/row.scss +++ b/src/components/forms/row.scss @@ -4,8 +4,19 @@ * the formsy-react-components */ -.form-group { +.row { .required-symbol { display: none; } + + label { + display: inline-block; + margin-bottom: .75rem; + } + + &.no-label { + label { + display: none; + } + } } diff --git a/src/components/forms/select.scss b/src/components/forms/select.scss index 32354051b..4ed307a89 100644 --- a/src/components/forms/select.scss +++ b/src/components/forms/select.scss @@ -8,7 +8,7 @@ select { transition: all .5s ease; - margin: .75rem 0; + 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; diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx index 9ac9e01fb..ec90f8b59 100644 --- a/src/components/forms/textarea.jsx +++ b/src/components/forms/textarea.jsx @@ -11,11 +11,13 @@ var TextArea = React.createClass({ type: 'TextArea', render: function () { var classes = classNames( - 'textarea', + 'textarea-row', this.props.className ); return ( - + ); } }); diff --git a/src/components/forms/textarea.scss b/src/components/forms/textarea.scss index fa8503cca..d1782d37f 100644 --- a/src/components/forms/textarea.scss +++ b/src/components/forms/textarea.scss @@ -2,7 +2,7 @@ .textarea { transition: all 1s ease; - margin: .75rem 0; + margin-bottom: .75rem; border: 1px solid $active-gray; border-radius: 5px; background-color: $ui-light-gray; diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx index 02d36e681..ce7219f53 100644 --- a/src/components/navigation/www/navigation.jsx +++ b/src/components/navigation/www/navigation.jsx @@ -11,6 +11,7 @@ var api = require('../../../lib/api'); var Avatar = require('../../avatar/avatar.jsx'); var Button = require('../../forms/button.jsx'); var Dropdown = require('../../dropdown/dropdown.jsx'); +var Form = require('../../forms/form.jsx'); var Input = require('../../forms/input.jsx'); var log = require('../../../lib/log.js'); var Login = require('../../login/login.jsx'); @@ -170,6 +171,9 @@ var Navigation = React.createClass({ this.props.dispatch(sessionActions.refreshSession()); this.closeRegistration(); }, + onSearchSubmit: function (formData) { + window.location.href = '/search/projects?q=' + formData.q; + }, render: function () { var classes = classNames({ 'logged-in': this.props.session.session.user @@ -216,14 +220,13 @@ var Navigation = React.createClass({
  • -
    +
  • {this.props.session.status === sessionActions.Status.FETCHED ? ( this.props.session.session.user ? [ diff --git a/src/components/navigation/www/navigation.scss b/src/components/navigation/www/navigation.scss index 49451d283..6f67405f4 100644 --- a/src/components/navigation/www/navigation.scss +++ b/src/components/navigation/www/navigation.scss @@ -47,12 +47,18 @@ width: 100%; } - form { + .form { margin: 0; } - input, - button { + .row { + .help-block { + display: none; + } + } + + .input, + .button { display: inline-block; margin-top: 5px; outline: none; diff --git a/src/components/registration/steps.jsx b/src/components/registration/steps.jsx index bd956a8f5..5cb9ee1c1 100644 --- a/src/components/registration/steps.jsx +++ b/src/components/registration/steps.jsx @@ -27,6 +27,23 @@ var Tooltip = require('../../components/tooltip/tooltip.jsx'); require('./steps.scss'); var DEFAULT_COUNTRY = 'us'; +var getCountryOptions = function (defaultCountry) { + var options = countryData.countryOptions.concat({ + label: , + disabled: true, + selected: true + }); + if (typeof defaultCountry !== 'undefined') { + return options.sort(function (a, b) { + if (a.disabled) return -1; + if (b.disabled) return 1; + if (a.value === defaultCountry) return -1; + if (b.value === defaultCountry) return 1; + return 0; + }.bind(this)); + } + return options; +}; var NextStepButton = React.createClass({ getDefaultProps: function () { @@ -124,12 +141,14 @@ module.exports = {
    - {formatMessage({id: 'registration.createUsername'})} - {this.props.usernameHelp ? ( -

    {this.props.usernameHelp}

    - ):( - null - )} +
    + {formatMessage({id: 'registration.createUsername'})} + {this.props.usernameHelp ? ( +

    {this.props.usernameHelp}

    + ):( + null + )} +
    ,

    {this.props.classroom.educator.username}

    ,

    - {formatMessage({id: 'registration.classroomInviteStepDescription'})} + {formatMessage({id: 'registration.classroomInviteNewStudentStepDescription'})}

    ,
    @@ -842,6 +850,47 @@ module.exports = { ); } })), + ClassInviteExistingStudentStep: intl.injectIntl(React.createClass({ + getDefaultProps: function () { + return { + classroom: null, + onHandleLogOut: function () {}, + studentUsername: null, + waiting: false + }; + }, + onNextStep: function () { + this.props.onNextStep(); + }, + render: function () { + var formatMessage = this.props.intl.formatMessage; + return ( + + {this.props.waiting ? [ + + ] : [ +

    {this.props.studentUsername}

    , +

    + {formatMessage({id: 'registration.classroomInviteExistingStudentStepDescription'})} +

    , + +
    +

    {this.props.classroom.title}

    + +

    {formatMessage({id: 'registration.invitedBy'})}

    +

    {this.props.classroom.educator.username}

    +
    + +
    , +

    {formatMessage({id: 'registration.notYou'})}

    , + + ]} +
    + ); + } + })), ClassWelcomeStep: intl.injectIntl(React.createClass({ getDefaultProps: function () { return { diff --git a/src/components/registration/steps.scss b/src/components/registration/steps.scss index 6deca7956..21d0ff0a5 100644 --- a/src/components/registration/steps.scss +++ b/src/components/registration/steps.scss @@ -29,6 +29,15 @@ color: $ui-dark-gray; } + &.class-invite-step { + text-align: center; + + > p a { + text-decoration: underline; + color: $ui-white; + font-weight: inherit; + } + } &.class-invite-step, &.class-welcome-step { @@ -41,6 +50,12 @@ } } + &.username-step { + .username-label { + margin-bottom: .75rem; + } + } + &.demographics-step { .gender-input { margin-top: -5.5rem; @@ -66,26 +81,12 @@ margin-bottom: 1.25rem; } } - - .validation-message { - margin-top: .5rem; - } - - .checkbox-row { - .validation-message { - margin-top: 0; - } - } } &.organization-step { - .validation-message { - transform: translate(16rem, -4rem); - } - .checkbox-group { .validation-message { - transform: translate(16rem, -16rem); + transform: translate(16rem, 8rem); } } @@ -100,14 +101,6 @@ } } - &.address-step { - .select { - .validation-message { - transform: translate(0, .5rem); - } - } - } - &.usescratch-step { .form { .form-group { @@ -119,12 +112,6 @@ } } } - - - } - - .validation-message { - margin-top: .75rem; } p { diff --git a/src/l10n.json b/src/l10n.json index 80c0e4a29..098cccf48 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -111,19 +111,24 @@ "registration.choosePasswordStepDescription": "Type in a new password for your account. You will use this password the next time you log into Scratch.", "registration.choosePasswordStepTitle": "Create a password", "registration.choosePasswordStepTooltip": "Don't use your name or anything that's easy for someone else to guess.", - "registration.classroomInviteStepDescription": "has invited you to join the class:", + "registration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class", + "registration.classroomInviteExistingStudentStepDescription": "you have been invited to join the class:", + "registration.classroomInviteNewStudentStepDescription": "has invited you to join the class:", "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.invitedBy": "invited by", "registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account", "registration.lastStepDescription": "We are currently processing your application. ", "registration.mustBeNewStudent": "You must be a new student to complete your registration", "registration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.", "registration.newPassword": "New Password", "registration.nextStep": "Next Step", + "registration.notYou": "Not you? Log in as another user", "registration.personalStepTitle": "Personal Information", "registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure", + "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 24 hours.", diff --git a/src/redux/session.js b/src/redux/session.js index 6447bc943..553228d6b 100644 --- a/src/redux/session.js +++ b/src/redux/session.js @@ -79,6 +79,12 @@ module.exports.refreshSession = function () { body.flags.must_complete_registration && window.location.pathname !== '/classes/complete_registration') { return window.location = '/classes/complete_registration'; + } else if ( + body.flags && + body.flags.must_reset_password && + !body.flags.must_complete_registration && + window.location.pathname !== '/classes/student_password_reset/') { + return window.location = '/classes/student_password_reset/'; } else { dispatch(tokenActions.getToken()); dispatch(module.exports.setSession(body)); diff --git a/src/views/studentcompleteregistration/studentcompleteregistration.jsx b/src/views/studentcompleteregistration/studentcompleteregistration.jsx index 4d1289981..50df16443 100644 --- a/src/views/studentcompleteregistration/studentcompleteregistration.jsx +++ b/src/views/studentcompleteregistration/studentcompleteregistration.jsx @@ -6,6 +6,7 @@ var render = require('../../lib/render.jsx'); var sessionStatus = require('../../redux/session').Status; var api = require('../../lib/api'); var intl = require('../../lib/intl.jsx'); +var log = require('../../lib/log.js'); var Deck = require('../../components/deck/deck.jsx'); var Progression = require('../../components/progression/progression.jsx'); @@ -33,17 +34,16 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ }); }, componentDidUpdate: function (prevProps) { - if (prevProps.session.session !== this.props.session.session && - this.props.session.session.permissions && - this.props.session.session.permissions.student) { - var classroomId = this.props.session.session.user.classroomId; + if (prevProps.studentUsername !== this.props.studentUsername && this.props.newStudent) { + this.setState({waiting: true}); api({ - uri: '/classrooms/' + classroomId + uri: '/classrooms/' + this.props.classroomId }, function (err, body, res) { - if (err || res.statusCode === 404) { + this.setState({waiting: false}); + if (err || res.statusCode !== 200) { return this.setState({ registrationErrors: { - __all__: this.props.intl.formatMessage({id: 'studentRegistration.classroomApiGeneralError'}) + __all__: this.props.intl.formatMessage({id: 'registration.classroomApiGeneralError'}) } }); } @@ -51,6 +51,18 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ }.bind(this)); } }, + handleLogOut: function (e) { + e.preventDefault(); + api({ + host: '', + method: 'post', + uri: '/accounts/logout/', + useCsrf: true + }, function (err) { + if (err) return log.error(err); + window.location = '/'; + }.bind(this)); + }, register: function (formData) { this.setState({waiting: true}); formData = defaults({}, formData || {}, this.state.formData); @@ -65,7 +77,7 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ country: formData.user.country, is_robot: formData.user.isRobot }; - if (this.props.session.session.flags.must_reset_password) { + if (this.props.must_reset_password) { submittedData.password = formData.user.password; } api({ @@ -88,36 +100,36 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ var demographicsDescription = this.props.intl.formatMessage({ id: 'registration.studentPersonalStepDescription'}); var registrationErrors = this.state.registrationErrors; - var sessionFetched = this.props.session.status === sessionStatus.FETCHED; - if (sessionFetched && - !(this.props.session.session.permissions.student && - this.props.session.session.flags.must_complete_registration)) { + if (!this.props.newStudent) { registrationErrors = { __all__: this.props.intl.formatMessage({id: 'registration.mustBeNewStudent'}) }; } return ( - {sessionFetched && this.state.classroom ? - (registrationErrors ? - -
      - {Object.keys(registrationErrors).map(function (field) { - var label = field + ': '; - if (field === '__all__') { - label = ''; - } - return (
    • {label}{registrationErrors[field]}
    • ); - })} -
    -
    - : + {registrationErrors ? ( + +
      + {Object.keys(registrationErrors).map(function (field) { + var label = field + ': '; + if (field === '__all__') { + label = ''; + } + return (
    • {label}{registrationErrors[field]}
    • ); + })} +
    +
    + ) : ( + this.state.waiting || !this.state.classroom ? ( + + ) : ( - - {this.props.session.session.flags.must_reset_password ? + {this.props.must_reset_password ? @@ -132,9 +144,7 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ waiting={this.state.waiting} /> ) - : - - } + )}
    ); } @@ -142,7 +152,14 @@ var StudentCompleteRegistration = intl.injectIntl(React.createClass({ var mapStateToProps = function (state) { return { - session: state.session + classroomId: state.session.session.user && state.session.session.user.classroomId, + must_reset_password: state.session.session.flags && state.session.session.flags.must_reset_password, + newStudent: ( + state.session.session.permissions && + state.session.session.permissions.student && + state.session.session.flags.must_complete_registration), + sessionFetched: state.session.status === sessionStatus.FETCHED, + studentUsername: state.session.session.user && state.session.session.user.username }; }; diff --git a/src/views/studentregistration/l10n.json b/src/views/studentregistration/l10n.json deleted file mode 100644 index ede12528d..000000000 --- a/src/views/studentregistration/l10n.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "studentRegistration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class" -} diff --git a/src/views/studentregistration/studentregistration.jsx b/src/views/studentregistration/studentregistration.jsx index bbfd7f719..5400b11d6 100644 --- a/src/views/studentregistration/studentregistration.jsx +++ b/src/views/studentregistration/studentregistration.jsx @@ -35,14 +35,16 @@ var StudentRegistration = intl.injectIntl(React.createClass({ }); }, componentDidMount: function () { + this.setState({waiting: true}); api({ uri: '/classrooms/' + this.props.classroomId, params: {token: this.props.classroomToken} }, function (err, body, res) { + this.setState({waiting: false}); if (err) { return this.setState({ registrationError: this.props.intl.formatMessage({ - id: 'studentRegistration.classroomApiGeneralError' + id: 'registration.classroomApiGeneralError' }) }); } @@ -104,9 +106,9 @@ var StudentRegistration = intl.injectIntl(React.createClass({ : - +