diff --git a/.eslintignore b/.eslintignore index b34309e15..7f1b4c54e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ node_modules/* static/* build/* intl/* +locales/* **/*.min.js diff --git a/.travis.yml b/.travis.yml index f0faf2485..99aaa1b5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,8 @@ env: - SENTRY_DSN_VAR=SENTRY_DSN_$TRAVIS_BRANCH - SENTRY_DSN=${!SENTRY_DSN_VAR} - SENTRY_DSN=${SENTRY_DSN:-$SENTRY_DSN_STAGING} + # SMARTY_STREETS_API_KEY + - secure: "uQKNgJaJEju8ErGUxIbLE0Y6ee4j6OFFbBpqyuKrNMk6apvvvXLp3lTdGZJq6j/ZwQeQ384m5bbfmhFwr7piPFj7W/zBXVKcifbF6ShfP7skMl834Kkefs3uEWU0VZw3nURgzNInSOPqqGLsISFywpwBXUWKfL0Q87KHNU0/I0EkwvImm3SAlNpR38m3yKcMF3h0zK8Fh2mO7iyHEIhtssdWabaRjf3t6Mr5vikACeXYJg+k4oEQZtsnSNnlLYWumdEDsxwonMozGKUBqlXwhHCdYNOJ1DUGuntbXOnylLt1/LA9I9B4hWQOrRDwqjyIOI+2dpADoCN040+Zr1VSrJhk7Wb7ogeaQLzZ4W/3dX54rbsnFHa+MuKqOsAxQ0Tjfk5xWq/pbLRsAyW6Pl7Q1v4yWOQ2COnM/tfJ6UaH9bxppOyKsX8n33rFjlvZU6CtY1GGa7fpB2zOKI5B5OovLjHeokIe/Tx+4coEDZqt44qkTGWr/eWDxrvkQqpQ29F9My3wBgB3gdou+3lWExS0a9M2wwp4EIduXEKNZXLGDuVefH5f3eFy09wH+nhctmMF8uhMbPefFubEi7fqXTkxntmDTy+/pD2A2w1jJhBwLhwlik335k+Wrbl3dclt7cjJ6fRVX9b+AuBCbGr633vM4xbk90whwXizSECIt5InGSw=" - SKIP_CLEANUP=true - NODE_ENV=production - WWW_VERSION=${TRAVIS_COMMIT:0:5} diff --git a/bin/configure-fastly.js b/bin/configure-fastly.js index 266deeaaf..011ce3a50 100644 --- a/bin/configure-fastly.js +++ b/bin/configure-fastly.js @@ -44,9 +44,13 @@ var getStaticPaths = function (pathToStatic) { * the express route and a static view file associated with the route */ var getViewPaths = function (routes) { - return routes.map(function (route) { - return route.pattern; - }); + return routes.reduce(function (paths, route) { + var path = route.routeAlias || route.pattern; + if (paths.indexOf(path) === -1) { + paths.push(path); + } + return paths; + }, []); }; /* diff --git a/bin/lib/locale-compare.js b/bin/lib/locale-compare.js index 0a064b8aa..5a2e3bc81 100644 --- a/bin/lib/locale-compare.js +++ b/bin/lib/locale-compare.js @@ -76,12 +76,14 @@ Helpers.getMD5Map = function (ICUIdMap) { * key: '' * value: translated version of string */ -Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) { +Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds, separator) { var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui'); var jsFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/djangojs.po'); var pyFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/django.po'); var translations = {}; + separator = separator || ':'; + try { fs.accessSync(jsFile, fs.R_OK); var jsTranslations = po2icu.poFileToICUSync(lang, jsFile); @@ -104,7 +106,7 @@ Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) { var translationsByView = {}; for (var id in translations) { - var ids = id.split('-'); // [viewName, stringId] + var ids = id.split(separator); // [viewName, stringId] var viewName = ids[0]; var stringId = ids[1]; @@ -127,20 +129,24 @@ Helpers.writeTranslationsToJS = function (outputDir, viewName, translationObject // Returns a FormattedMessage id with english string as value. Use for default values in translations // Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' } -Helpers.idToICUMap = function (viewName, ids) { +Helpers.idToICUMap = function (viewName, ids, separator) { var idsToICU = {}; + separator = separator || ':'; + for (var id in ids) { - idsToICU[viewName + '-' + id] = ids[id]; + idsToICU[viewName + separator + id] = ids[id]; } return idsToICU; }; // Reuturns reverse (i.e. english string with message key as the value) object for searching po files. // Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' } -Helpers.icuToIdMap = function (viewName, ids) { +Helpers.icuToIdMap = function (viewName, ids, separator) { var icuToIds = {}; + separator = separator || ':'; + for (var id in ids) { - icuToIds[ids[id]] = viewName + '-' + id; + icuToIds[ids[id]] = viewName + separator + id; } return icuToIds; }; diff --git a/package.json b/package.json index ca92315d2..fa3b9c2d9 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,12 @@ "exenv": "1.2.0", "fastly": "1.2.1", "file-loader": "0.8.4", + "formsy-react": "0.18.0", + "formsy-react-components": "0.7.1", "git-bundle-sha": "0.0.2", "glob": "5.0.15", + "google-libphonenumber": "1.0.21", + "iso-3166-2": "0.4.0", "json-loader": "0.5.2", "json2po-stream": "1.0.3", "jsx-loader": "0.13.2", @@ -60,13 +64,14 @@ "po2icu": "0.0.2", "postcss-loader": "0.8.2", "raven-js": "3.0.4", - "react": "0.14.0", - "react-dom": "0.14.0", + "react": "15.1.0", + "react-dom": "15.0.1", "react-intl": "2.1.2", - "react-modal": "0.6.1", + "react-modal": "1.3.0", "react-onclickoutside": "4.1.1", - "react-redux": "4.4.0", - "react-slick": "0.9.2", + "react-redux": "4.4.5", + "react-slick": "0.12.2", + "react-telephone-input": "3.4.5", "redux": "3.5.2", "redux-thunk": "2.0.1", "sass-lint": "1.5.1", diff --git a/src/_colors.scss b/src/_colors.scss index 6e5d66e26..7a8947665 100644 --- a/src/_colors.scss +++ b/src/_colors.scss @@ -1,7 +1,7 @@ /* UI Primary Colors */ $ui-blue: hsla(200, 90, 55, 1); // #25AFF4 $ui-orange: hsla(35, 90, 55, 1); // #F49D25 -$ui-light-gray: hsla(0, 0, 98, 1); +$ui-light-gray: hsla(0, 0, 98, 1); //#FAFAFA $ui-gray: hsla(0, 0, 95, 1); //#F2F2F2 $ui-dark-gray: hsla(0, 0, 70, 1); //#B3B3B3 diff --git a/src/_frameless.scss b/src/_frameless.scss index c8e2b9457..854e109c1 100644 --- a/src/_frameless.scss +++ b/src/_frameless.scss @@ -34,6 +34,10 @@ $cols10: (10 * ($column + $gutter) - $gutter) / $em; $cols11: (11 * ($column + $gutter) - $gutter) / $em; $cols12: (12 * ($column + $gutter) - $gutter) / $em; +$desktop: 942px; +$tablet: 640px; +$mobile: 480px; + // // Column-widths in a function, in ems // @@ -42,50 +46,67 @@ $cols12: (12 * ($column + $gutter) - $gutter) / $em; width: ($cols * ($column + $gutter) - $gutter) / $em; } -$desktop: 942px; -$tablet: 640px; -$mobile: 480px; - //4 columns -@media only screen and (max-width: $mobile - 1) { - #view { - text-align: center; - } +@mixin submobile ($parent-selector, $child-selector) { + @media only screen and (max-width: $mobile - 1) { + #{$parent-selector} { + text-align: center; + } - .inner { - margin: 0 auto; - width: 100%; + #{$child-selector} { + margin: 0 auto; + width: 100%; + } + + @content; } } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { - #view { - text-align: center; - } +@mixin mobile ($parent-selector, $child-selector) { + @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + #{$parent-selector} { + text-align: center; + } - .inner { - margin: 0 auto; - width: $mobile; + #{$child-selector} { + margin: 0 auto; + width: $mobile; + } + + @content; } } //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { - #view { - text-align: center; - } +@mixin tablet ($parent-selector, $child-selector) { + @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + #{$parent-selector} { + text-align: center; + } - .inner { - margin: 0 auto; - width: $tablet; + #{$child-selector} { + margin: 0 auto; + width: $tablet; + } } } //12 columns -@media only screen and (min-width: $desktop) { - .inner { - margin: 0 auto; - width: $desktop; +@mixin desktop ($parent-selector, $child-selector) { + @media only screen and (min-width: $desktop) { + #{$child-selector} { + margin: 0 auto; + width: $desktop; + } } } + +@mixin responsive-layout ($parent-selector, $child-selector) { + @include submobile($parent-selector, $child-selector); + @include mobile($parent-selector, $child-selector); + @include tablet($parent-selector, $child-selector); + @include desktop($parent-selector, $child-selector); +} + +@include responsive-layout("#view", ".inner"); diff --git a/src/components/adminpanel/adminpanel.jsx b/src/components/adminpanel/adminpanel.jsx index 31c3264ca..222258176 100644 --- a/src/components/adminpanel/adminpanel.jsx +++ b/src/components/adminpanel/adminpanel.jsx @@ -19,8 +19,8 @@ var AdminPanel = React.createClass({ render: function () { // make sure user is present before checking if they're an admin. Don't show anything if user not an admin. var showAdmin = false; - if (this.props.session.user) { - showAdmin = this.props.session.permissions.admin; + if (this.props.session.session.user) { + showAdmin = this.props.session.session.permissions.admin; } if (!showAdmin) return false; diff --git a/src/components/box/box.jsx b/src/components/box/box.jsx index b85d8e71b..543bc15a2 100644 --- a/src/components/box/box.jsx +++ b/src/components/box/box.jsx @@ -7,6 +7,7 @@ var Box = React.createClass({ type: 'Box', propTypes: { title: React.PropTypes.string.isRequired, + subtitle: React.PropTypes.string, moreTitle: React.PropTypes.string, moreHref: React.PropTypes.string, moreProps: React.PropTypes.object @@ -20,6 +21,7 @@ var Box = React.createClass({

{this.props.title}

+
{this.props.subtitle}

{this.props.moreTitle} diff --git a/src/components/box/box.scss b/src/components/box/box.scss index 0828c16cb..4b9392771 100644 --- a/src/components/box/box.scss +++ b/src/components/box/box.scss @@ -13,7 +13,8 @@ $base-bg: $ui-white; width: $cols4; .box-header { - h4 { + h4, + h5 { line-height: .9rem; font-size: .9rem; } @@ -25,7 +26,8 @@ $base-bg: $ui-white; width: $cols6; .box-header { - h4 { + h4, + h5 { line-height: 1rem; font-size: 1rem; } @@ -37,7 +39,8 @@ $base-bg: $ui-white; width: $cols8; .box-header { - h4 { + h4, + h5 { line-height: 1.1rem; font-size: 1.1rem; } @@ -49,7 +52,8 @@ $base-bg: $ui-white; width: $cols12; .box-header { - h4 { + h4, + h5 { line-height: 1.1rem; font-size: 1.1rem; } @@ -72,17 +76,25 @@ $base-bg: $ui-white; height: 20px; overflow: hidden; - h4 { + h4, + h5 { display: inline-block; float: left; } + h5 { + margin: 0; + padding-left: 5px; + text-transform: none; + letter-spacing: normal; + font-weight: normal; + } + p { display: inline-block; float: right; margin: 1px 0 0 0; padding: 0; - font-size: .85rem; } } diff --git a/src/components/card/card.jsx b/src/components/card/card.jsx new file mode 100644 index 000000000..5ac22fae1 --- /dev/null +++ b/src/components/card/card.jsx @@ -0,0 +1,17 @@ +var classNames = require('classnames'); +var React = require('react'); + +require('./card.scss'); + +var Card = React.createClass({ + displayName: 'Card', + render: function () { + return ( +

+ {this.props.children} +
+ ); + } +}); + +module.exports = Card; diff --git a/src/components/card/card.scss b/src/components/card/card.scss new file mode 100644 index 000000000..e9527167d --- /dev/null +++ b/src/components/card/card.scss @@ -0,0 +1,8 @@ +@import "../../colors"; +@import "../../frameless"; + +.card { + border-radius: 8px / $em; + box-shadow: 0 0 0 .125rem $active-gray; + background-color: $ui-white; +} diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx index 4120b835d..fbebed63d 100644 --- a/src/components/carousel/carousel.jsx +++ b/src/components/carousel/carousel.jsx @@ -55,7 +55,7 @@ var Carousel = React.createClass({ } return ( - +
+ + + + {this.props.children} +
+
+ ); + } +}); + +module.exports = Deck; diff --git a/src/components/deck/deck.scss b/src/components/deck/deck.scss new file mode 100644 index 000000000..c8ff26caf --- /dev/null +++ b/src/components/deck/deck.scss @@ -0,0 +1,151 @@ +@import "../../colors"; +@import "../../frameless"; + +@include responsive-layout (".deck", ".slide"); + +.deck { + min-height: 100vh; + + img { + 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; + } + } + } + + .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; + } + } + } +} diff --git a/src/components/dropdown/dropdown.scss b/src/components/dropdown/dropdown.scss index 78ef27a81..4b0c56cb5 100644 --- a/src/components/dropdown/dropdown.scss +++ b/src/components/dropdown/dropdown.scss @@ -15,6 +15,10 @@ font-size: .8125rem; font-weight: normal; + &.staging { + background-color: $ui-orange; + } + &.open { display: block; } @@ -53,7 +57,7 @@ text-decoration: none; } } - } + } &.with-arrow { $arrow-border-width: 14px; @@ -61,12 +65,12 @@ border-radius: 5px; overflow: visible; - &:before { + &:before { display: block; position: absolute; top: -$arrow-border-width / 2; right: 10%; - + transform: rotate(45deg); border-top: 1px solid $active-gray; diff --git a/src/components/flex-row/flex-row.scss b/src/components/flex-row/flex-row.scss index 5f3b310f8..4b0f28069 100644 --- a/src/components/flex-row/flex-row.scss +++ b/src/components/flex-row/flex-row.scss @@ -12,7 +12,28 @@ justify-content: center; } + &.uneven { + align-items: flex-start; + + .short { + width: $cols3; + } + + .long { + width: $cols8; + text-align: left; + } + } + @media only screen and (max-width: $tablet - 1) { flex-direction: column; + + &.uneven { + .short, + .long { + margin: auto; + width: 90%; + } + } } } diff --git a/src/components/footer/conference/footer.jsx b/src/components/footer/conference/footer.jsx index ba01d8030..cc1340eae 100644 --- a/src/components/footer/conference/footer.jsx +++ b/src/components/footer/conference/footer.jsx @@ -13,36 +13,48 @@ var ConferenceFooter = React.createClass({

Sponsors

-
  • +
  • MIT Office of Digital Learning
  • -
  • - - Scratch Foundation +
  • + + Intel
  • -
  • +
  • LEGO Foundation
  • -
  • +
  • Google
  • -
  • +
  • Siegel Family Endowment
  • +
  • + + No Starch Press + +
  • +
  • + + Scratch Foundation + +
  • diff --git a/src/components/footer/conference/footer.scss b/src/components/footer/conference/footer.scss index 0cadb4934..4d40c51cc 100644 --- a/src/components/footer/conference/footer.scss +++ b/src/components/footer/conference/footer.scss @@ -23,7 +23,19 @@ img { margin: 20px 0; max-width: 180px; - max-height: 30px; + max-height: 25px; + } + } + + .nostarch { + img { + max-height: 40px; + } + } + + .intel { + img { + max-height: 50px } } } diff --git a/src/components/footer/www/footer.jsx b/src/components/footer/www/footer.jsx index d36023f79..9388c2a42 100644 --- a/src/components/footer/www/footer.jsx +++ b/src/components/footer/www/footer.jsx @@ -31,6 +31,11 @@ var Footer = React.createClass({ +
    + + + +
    diff --git a/src/components/forms/charcount.jsx b/src/components/forms/charcount.jsx new file mode 100644 index 000000000..af6192ebe --- /dev/null +++ b/src/components/forms/charcount.jsx @@ -0,0 +1,28 @@ +var classNames = require('classnames'); +var React = require('react'); + +require('./charcount.scss'); + +var CharCount = React.createClass({ + type: 'CharCount', + getDefaultProps: function () { + return { + maxCharacters: 0, + currentCharacters: 0 + }; + }, + render: function () { + var classes = classNames( + 'char-count', + this.props.className, + {overmax: (this.props.currentCharacters > this.props.maxCharacters)} + ); + return ( +

    + {this.props.currentCharacters}/{this.props.maxCharacters} +

    + ); + } +}); + +module.exports = CharCount; diff --git a/src/components/forms/charcount.scss b/src/components/forms/charcount.scss new file mode 100644 index 000000000..ff5d85e94 --- /dev/null +++ b/src/components/forms/charcount.scss @@ -0,0 +1,11 @@ +@import "../../colors"; + +.char-count { + letter-spacing: 1px; + color: lighten($type-gray, 30%); + font-weight: 500; + + &.overmax { + color: $ui-orange; + } +} diff --git a/src/components/forms/checkbox-group.jsx b/src/components/forms/checkbox-group.jsx new file mode 100644 index 000000000..29321d268 --- /dev/null +++ b/src/components/forms/checkbox-group.jsx @@ -0,0 +1,25 @@ +var classNames = require('classnames'); +var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); + +require('./row.scss'); +require('./checkbox-group.scss'); + +var CheckboxGroup = React.createClass({ + type: 'CheckboxGroup', + render: function () { + var classes = classNames( + 'checkbox-group', + this.props.className + ); + return ( +
    + +
    + ); + } +}); + +module.exports = inputHOC(defaultValidationHOC(CheckboxGroup)); diff --git a/src/components/forms/checkbox-group.scss b/src/components/forms/checkbox-group.scss new file mode 100644 index 000000000..29613c177 --- /dev/null +++ b/src/components/forms/checkbox-group.scss @@ -0,0 +1,11 @@ +.checkbox-group { + .row { + .col-sm-9 { + flex-flow: column wrap; + + .checkbox { + margin: .5rem 0; + } + } + } +} diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx new file mode 100644 index 000000000..c423ded01 --- /dev/null +++ b/src/components/forms/checkbox.jsx @@ -0,0 +1,25 @@ +var classNames = require('classnames'); +var FRCCheckbox = require('formsy-react-components').Checkbox; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); + +require('./row.scss'); +require('./checkbox.scss'); + +var Checkbox = React.createClass({ + type: 'Checkbox', + render: function () { + var classes = classNames( + 'checkbox-row', + this.props.className + ); + return ( +
    + +
    + ); + } +}); + +module.exports = inputHOC(defaultValidationHOC(Checkbox)); diff --git a/src/components/forms/checkbox.scss b/src/components/forms/checkbox.scss new file mode 100644 index 000000000..df8a17237 --- /dev/null +++ b/src/components/forms/checkbox.scss @@ -0,0 +1,43 @@ +@import "../../colors"; +@import "../../frameless"; + +.row { + .checkbox { + label { + font-weight: 300; + } + + input { + &[type=checkbox] { + display: block; + float: left; + margin-right: 1rem; + border: 1px solid $active-gray; + border-radius: 3px; + width: 1.25rem; + height: 1.25rem; + appearance: none; + + &:checked, + &:focus { + transition: all .5s ease; + outline: none; + box-shadow: 0 0 0 .25rem $active-gray; + } + + &:checked { + background-color: $ui-blue; + text-align: center; + text-indent: .125rem; + line-height: 1.25rem; + font-size: .75rem; + + &:after { + color: $type-white; + content: "\2714"; + } + } + } + } + } +} diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx new file mode 100644 index 000000000..17ada223a --- /dev/null +++ b/src/components/forms/form.jsx @@ -0,0 +1,47 @@ +var classNames = require('classnames'); +var Formsy = require('formsy-react'); +var omit = require('lodash.omit'); +var React = require('react'); +var validations = require('./validations.jsx').validations; + +for (var validation in validations) { + Formsy.addValidationRule(validation, validations[validation]); +} + +var Form = React.createClass({ + getDefaultProps: function () { + return { + noValidate: true, + onChange: function () {} + }; + }, + getInitialState: function () { + return { + allValues: {} + }; + }, + onChange: function (currentValues, isChanged) { + this.setState({allValues: omit(currentValues, 'all')}); + this.props.onChange(currentValues, isChanged); + }, + render: function () { + var classes = classNames( + 'form', + this.props.className + ); + return ( + + {React.Children.map(this.props.children, function (child) { + if (!child) return child; + if (child.props.name === 'all') { + return React.cloneElement(child, {value: this.state.allValues}); + } else { + return child; + } + }.bind(this))} + + ); + } +}); + +module.exports = Form; diff --git a/src/components/forms/general-error.jsx b/src/components/forms/general-error.jsx new file mode 100644 index 000000000..d9e20463b --- /dev/null +++ b/src/components/forms/general-error.jsx @@ -0,0 +1,22 @@ +var Formsy = require('formsy-react'); +var React = require('react'); + +require('./general-error.scss'); + +/* + * A special formsy-react component that only outputs + * error messages. If you want to display errors that + * don't apply to a specific field, insert one of these, + * give it a name, and apply your validation error to + * the name of the GeneralError component. + */ +module.exports = Formsy.HOC(React.createClass({ + render: function () { + if (!this.props.showError()) return null; + return ( +

    + {this.props.getErrorMessage()} +

    + ); + } +})); diff --git a/src/components/forms/general-error.scss b/src/components/forms/general-error.scss new file mode 100644 index 000000000..c5da01c49 --- /dev/null +++ b/src/components/forms/general-error.scss @@ -0,0 +1,9 @@ +@import "../../colors"; + +.general-error { + border: 1px solid $active-gray; + border-radius: 4px; + background-color: $ui-orange; + padding: 1rem; + color: $type-white; +} diff --git a/src/components/forms/input-hoc.jsx b/src/components/forms/input-hoc.jsx new file mode 100644 index 000000000..1532eaefd --- /dev/null +++ b/src/components/forms/input-hoc.jsx @@ -0,0 +1,20 @@ +var React = require('react'); + +module.exports = function InputComponentMixin (Component) { + var InputComponent = React.createClass({ + getDefaultProps: function () { + return { + messages: { + 'general.notRequired': 'Not Required' + } + }; + }, + render: function () { + return ( + + ); + } + }); + return InputComponent; +}; diff --git a/src/components/forms/input.jsx b/src/components/forms/input.jsx index e35a09e2e..d1d9d5aa7 100644 --- a/src/components/forms/input.jsx +++ b/src/components/forms/input.jsx @@ -1,22 +1,46 @@ -var React = require('react'); var classNames = require('classnames'); +var FRCInput = require('formsy-react-components').Input; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); require('./input.scss'); +require('./row.scss'); var Input = React.createClass({ type: 'Input', - propTypes: { - + getDefaultProps: function () { + return {}; + }, + getInitialState: function () { + return { + status: '' + }; + }, + onValid: function () { + this.setState({ + status: 'pass' + }); + }, + onInvalid: function () { + this.setState({ + status: 'fail' + }); }, render: function () { var classes = classNames( 'input', + this.state.status, this.props.className ); - return ( - + return (this.props.type === 'submit' || this.props.noformsy ? + : + ); } }); -module.exports = Input; +module.exports = inputHOC(defaultValidationHOC(Input)); diff --git a/src/components/forms/input.scss b/src/components/forms/input.scss index 19669f365..265b7750d 100644 --- a/src/components/forms/input.scss +++ b/src/components/forms/input.scss @@ -1,30 +1,34 @@ @import "../../colors"; +@import "../../frameless"; -$base-bg: $ui-white; -$focus-bg: lighten($ui-blue, 35%); -$fail-bg: lighten($ui-orange, 35%); +$base-bg: $ui-light-gray; $pass-bg: lighten($ui-aqua, 35%); +.row { + label { + font-weight: 500; + } +} + .input { - transition: all 1s ease; - margin: .5em 0; + transition: all .5s ease; + margin: .75rem 0; border: 1px solid $active-gray; border-radius: 5px; background-color: $base-bg; - padding: .75em 1em; + padding: 0 1rem; + height: 3rem; color: $type-gray; - font-size: .8rem; + font-size: .875rem; &:focus { - transition: all 1s ease; + transition: all .5s ease; outline: none; - border: 1px solid $active-dark-gray; - background-color: $focus-bg; + border: 1px solid $ui-blue; } &.fail { - border: 1px solid $active-dark-gray; - background-color: $fail-bg; + border: 1px solid $ui-orange; } &.pass { diff --git a/src/components/forms/phone-input.jsx b/src/components/forms/phone-input.jsx new file mode 100644 index 000000000..061a5a61d --- /dev/null +++ b/src/components/forms/phone-input.jsx @@ -0,0 +1,68 @@ +var classNames = require('classnames'); +var React = require('react'); +var FormsyMixin = require('formsy-react').Mixin; +var ReactPhoneInput = require('react-telephone-input/lib/withStyles'); +var allCountries = require('react-telephone-input/lib/country_data').allCountries; +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var validationHOCFactory = require('./validations.jsx').validationHOCFactory; +var Row = require('formsy-react-components').Row; +var ComponentMixin = require('formsy-react-components').ComponentMixin; +var inputHOC = require('./input-hoc.jsx'); + +var allIso2 = allCountries.map(function (country) {return country.iso2;}); + +require('./row.scss'); +require('./phone-input.scss'); + +var PhoneInput = React.createClass({ + displayName: 'PhoneInput', + mixins: [ + FormsyMixin, + ComponentMixin + ], + getDefaultProps: function () { + return { + validations: { + isPhone: true + }, + flagsImagePath: '/images/flags.png', + defaultCountry: 'us' + }; + }, + onChangeInput: function (number, country) { + var value = {national_number: number, country_code: country}; + this.setValue(value); + this.props.onChange(this.props.name, value); + }, + render: function () { + var defaultCountry = PhoneInput.getDefaultProps().defaultCountry; + if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) { + defaultCountry = this.props.defaultCountry.toLowerCase(); + } + return ( + +
    + +
    + {this.renderHelp()} + {this.renderErrorMessage()} +
    + ); + } +}); + +var phoneValidationHOC = validationHOCFactory({ + isPhone: 'Please enter a valid phone number' +}); + +module.exports = inputHOC(defaultValidationHOC(phoneValidationHOC(PhoneInput))); diff --git a/src/components/forms/phone-input.scss b/src/components/forms/phone-input.scss new file mode 100644 index 000000000..b2a0ff733 --- /dev/null +++ b/src/components/forms/phone-input.scss @@ -0,0 +1,42 @@ +@import "../../colors"; + +.input-group { + margin: .75rem 0; + width: 100%; +} + +.react-tel-input { + width: 100%; + + input { + &[type=tel] { + background-color: $ui-light-gray; + height: 3rem; + + &:focus { + outline: none; + } + } + } + + .flag-dropdown { + background-color: $ui-light-gray; + height: 3rem; + + .selected-flag { + background-color: $ui-light-gray; + height: 100%; + + &:hover, + &:focus, + &:active { + background-color: $active-gray; + } + } + + .country-list { + top: 3rem; + background-color: $ui-light-gray; + } + } +} diff --git a/src/components/forms/radio-group.jsx b/src/components/forms/radio-group.jsx new file mode 100644 index 000000000..fcdf928a6 --- /dev/null +++ b/src/components/forms/radio-group.jsx @@ -0,0 +1,23 @@ +var classNames = require('classnames'); +var FRCRadioGroup = require('formsy-react-components').RadioGroup; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); + +require('./row.scss'); +require('./radio-group.scss'); + +var RadioGroup = React.createClass({ + type: 'RadioGroup', + render: function () { + var classes = classNames( + 'radio-group', + this.props.className + ); + return ( + + ); + } +}); + +module.exports = inputHOC(defaultValidationHOC(RadioGroup)); diff --git a/src/components/forms/radio-group.scss b/src/components/forms/radio-group.scss new file mode 100644 index 000000000..dbb4626e2 --- /dev/null +++ b/src/components/forms/radio-group.scss @@ -0,0 +1,46 @@ +@import "../../colors"; + +.row { + .radio { + label { + font-weight: 300; + } + } + + .col-sm-9 { + display: flex; + flex-flow: row wrap; + + input { + &[type="radio"] { + margin-top: 1px; + border: 1px solid $active-gray; + border-radius: 50%; + width: .875rem; + height: .875rem; + appearance: none; + + &:checked, + &:focus { + outline: none; + } + + &:checked { + transition: all .25s ease; + box-shadow: 0 0 0 .25rem $active-gray; + background-color: $ui-blue; + + &:after { + display: block; + transform: translate(.25rem, .25rem); + border-radius: 50%; + background-color: $ui-white; + width: .25rem; + height: .25rem; + content: ""; + } + } + } + } + } +} diff --git a/src/components/forms/row.scss b/src/components/forms/row.scss new file mode 100644 index 000000000..10a8e12e8 --- /dev/null +++ b/src/components/forms/row.scss @@ -0,0 +1,11 @@ +/* + * Styles for the Row component used by formsy-react-components + * Should be imported for each component that extends one of + * the formsy-react-components + */ + +.form-group { + .required-symbol { + display: none; + } +} diff --git a/src/components/forms/select.jsx b/src/components/forms/select.jsx index 62b6d9aa8..a17053942 100644 --- a/src/components/forms/select.jsx +++ b/src/components/forms/select.jsx @@ -1,6 +1,11 @@ -var React = require('react'); var classNames = require('classnames'); +var defaults = require('lodash.defaultsdeep'); +var FRCSelect = require('formsy-react-components').Select; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); +require('./row.scss'); require('./select.scss'); var Select = React.createClass({ @@ -13,12 +18,16 @@ var Select = React.createClass({ 'select', this.props.className ); + var props = this.props; + if (this.props.required && !this.props.value) { + props = defaults({}, this.props, {value: this.props.options[0].value}); + } return ( - +
    + +
    ); } }); -module.exports = Select; +module.exports = inputHOC(defaultValidationHOC(Select)); diff --git a/src/components/forms/select.scss b/src/components/forms/select.scss index 34e1dd16c..e85704973 100644 --- a/src/components/forms/select.scss +++ b/src/components/forms/select.scss @@ -1,9 +1,42 @@ @import "../../colors"; +@import "../../frameless"; .select { - background-color: $ui-white; - width: 220px; - height: 28px; - line-height: 28px; - font-size: 1em; + label { + font-weight: 500; + } + + select { + transition: all .5s ease; + margin: .75rem 0; + border: 1px solid $active-gray; + border-radius: 5px; + background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center; + padding-right: 4rem; + width: 100%; + height: 3rem; + text-indent: 1rem; + font-size: .875rem; + appearance: none; + + &::-ms-expand { + display: none; + } + + &:focus { + transition: all .5s ease; + outline: none; + border: 1px solid $ui-blue; + } + + &:focus, + &:hover { + background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center; + } + + > option { + background-color: $ui-white; + width: 100%; + } + } } diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx new file mode 100644 index 000000000..9ac9e01fb --- /dev/null +++ b/src/components/forms/textarea.jsx @@ -0,0 +1,23 @@ +var classNames = require('classnames'); +var FRCTextarea = require('formsy-react-components').Textarea; +var React = require('react'); +var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +var inputHOC = require('./input-hoc.jsx'); + +require('./row.scss'); +require('./textarea.scss'); + +var TextArea = React.createClass({ + type: 'TextArea', + render: function () { + var classes = classNames( + 'textarea', + this.props.className + ); + return ( + + ); + } +}); + +module.exports = inputHOC(defaultValidationHOC(TextArea)); diff --git a/src/components/forms/textarea.scss b/src/components/forms/textarea.scss new file mode 100644 index 000000000..fa8503cca --- /dev/null +++ b/src/components/forms/textarea.scss @@ -0,0 +1,25 @@ +@import "../../colors"; + +.textarea { + transition: all 1s ease; + margin: .75rem 0; + border: 1px solid $active-gray; + border-radius: 5px; + background-color: $ui-light-gray; + padding: .75rem 1rem; + width: 100%; + min-height: 15rem; + line-height: 1.75em; + color: $type-gray; + font-size: .875rem; + + &:focus { + transition: all 1s ease; + outline: none; + border: 1px solid $ui-blue; + } + + &.fail { + border: 1px solid $ui-orange; + } +} diff --git a/src/components/forms/validations.jsx b/src/components/forms/validations.jsx new file mode 100644 index 000000000..6410322b9 --- /dev/null +++ b/src/components/forms/validations.jsx @@ -0,0 +1,47 @@ +var defaults = require('lodash.defaultsdeep'); +var libphonenumber = require('google-libphonenumber'); +var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); +var React = require('react'); + +module.exports = {}; + +module.exports.validations = { + notEquals: function (values, value, neq) { + return value !== neq; + }, + notEqualsField: function (values, value, field) { + return value !== values[field]; + }, + isPhone: function (values, value) { + if (typeof value === 'undefined') return true; + if (value && value.national_number === '+') return true; + try { + var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); + } catch (err) { + return false; + } + return phoneNumberUtil.isValidNumber(parsed); + } +}; + +module.exports.validationHOCFactory = function (defaultValidationErrors) { + return function (Component) { + var ValidatedComponent = React.createClass({ + render: function () { + var validationErrors = defaults( + {}, + defaultValidationErrors, + this.props.validationErrors + ); + return ( + + ); + } + }); + return ValidatedComponent; + }; +}; + +module.exports.defaultValidationHOC = module.exports.validationHOCFactory({ + isDefaultRequiredValue: 'This field is required' +}); diff --git a/src/components/grid/grid.json b/src/components/grid/grid.json new file mode 100644 index 000000000..c48b916b2 --- /dev/null +++ b/src/components/grid/grid.json @@ -0,0 +1,130 @@ +[ + { + "id": 1, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 2, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 3, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 4, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 5, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 6, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 7, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 8, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 9, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 10, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 11, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 12, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 13, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 14, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 15, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + }, + { + "id": 16, + "type": "project", + "title": "Project", + "thumbnailUrl": "", + "creator": "", + "href": "#" + } +] diff --git a/src/components/grid/grid.jsx b/src/components/grid/grid.jsx new file mode 100644 index 000000000..e198d2b44 --- /dev/null +++ b/src/components/grid/grid.jsx @@ -0,0 +1,67 @@ +var classNames = require('classnames'); +var React = require('react'); + +var Thumbnail = require('../thumbnail/thumbnail.jsx'); +var FlexRow = require('../flex-row/flex-row.jsx'); + +require('./grid.scss'); + +var Grid = React.createClass({ + type: 'Grid', + getDefaultProps: function () { + return { + items: require('./grid.json'), + itemType: 'projects', + showLoves: false, + showFavorites: false, + showRemixes: false, + showViews: false + }; + }, + render: function () { + var classes = classNames( + 'grid', + this.props.className + ); + return ( +
    + + {this.props.items.map(function (item) { + var href = '/' + this.props.itemType + '/' + item.id + '/'; + + if (this.props.itemType == 'projects') { + return ( + + ); + } + else { + return ( + + ); + } + }.bind(this))} + +
    + ); + } +}); + +module.exports = Grid; diff --git a/src/components/grid/grid.scss b/src/components/grid/grid.scss new file mode 100644 index 000000000..1f72e99d5 --- /dev/null +++ b/src/components/grid/grid.scss @@ -0,0 +1,58 @@ +@import "../../frameless"; + +.grid { + display: inline-block; + width: 100%; + + $project-width: 200px; + $project-height: 150px; + + $gallery-width: 200px; + $gallery-height: 118px; + + .flex-row { + margin: 0 auto; + padding: 12px; + width: (96px + (4 * $project-width)) / $em; + justify-content: flex-start; + } + + .thumbnail { + padding: 12px; + + &.project { + width: $project-width; + + img { + width: $project-width; + height: $project-height; + } + } + + &.gallery { + width: $gallery-width; + + img { + width: $gallery-width; + height: $gallery-height; + } + } + } + + &.column { + flex-direction: column; + justify-content: center; + } + + @media only screen and (max-width: $tablet - 1) { + flex-direction: column; + } + + @media only screen and (max-width: $desktop - 1) { + .flex-row { + padding: 12px 0; + width: 100%; + justify-content: space-around; + } + } +} diff --git a/src/components/informationpage/informationpage.jsx b/src/components/informationpage/informationpage.jsx new file mode 100644 index 000000000..da9585343 --- /dev/null +++ b/src/components/informationpage/informationpage.jsx @@ -0,0 +1,37 @@ +var classNames = require('classnames'); +var React = require('react'); +var TitleBanner = require('../../components/title-banner/title-banner.jsx'); + +require('./informationpage.scss'); + +/** + * Container for a table of contents + * alongside a long body of text + */ +var InformationPage = React.createClass({ + type: 'InformationPage', + propTypes: { + title: React.PropTypes.string.isRequired + }, + render: function () { + var classes = classNames( + 'info-outer', + 'inner', + this.props.className + ); + return ( +
    + +
    +

    {this.props.title}

    +
    +
    +
    + {this.props.children} +
    +
    + ); + } +}); + +module.exports = InformationPage; diff --git a/src/components/informationpage/informationpage.scss b/src/components/informationpage/informationpage.scss new file mode 100644 index 000000000..b270c886f --- /dev/null +++ b/src/components/informationpage/informationpage.scss @@ -0,0 +1,100 @@ +#view { + padding: 0; +} + +.information-page { + @import "../../colors"; + @import "../../frameless"; + + $ui-secondary: darken($ui-blue, 10%); + + .title-banner { + &.masthead { + background-color: $ui-secondary; + padding-bottom: .5rem; + + h1 { + margin: 0 0 1rem 0; + text-align: left; + color: $ui-white; + } + + p { + margin: 0; + width: $cols6; + text-align: left; + color: $ui-white; + + a { + border-bottom: 1px solid $ui-white; + color: $ui-white; + } + } + } + + &.faq-banner { + margin-bottom: 0; + background-color: $ui-gray; + } + } + + .info-outer { + margin: 0 auto; + + nav { + float: right; + border-left: 1px solid $ui-border; + width: 30%; + + ol { + list-style: none; + } + } + + .info-inner { + float: left; + width: 60%; + + p { + margin-bottom: 1.25rem; + margin-left: 0; + max-width: $cols8; + text-align: left; + } + + dl { + dt { + margin-bottom: .25rem; + font-size: 1.1rem; + } + + dd { + margin-bottom: 1.25rem; + margin-left: 0; + max-width: $cols8; + text-align: left; + } + } + + ul { + max-width: $cols8; + } + + section { + margin-bottom: 3rem; + + ul { + max-width: $cols8; + } + + .nav-spacer { + display: block; + visibility: hidden; + margin-top: -50px; // height of nav bar + height: 50px; + } + + } + } + } +} diff --git a/src/components/languagechooser/languagechooser.jsx b/src/components/languagechooser/languagechooser.jsx index 765f883ba..bb6dc5910 100644 --- a/src/components/languagechooser/languagechooser.jsx +++ b/src/components/languagechooser/languagechooser.jsx @@ -1,28 +1,26 @@ var classNames = require('classnames'); var React = require('react'); -var Api = require('../../mixins/api.jsx'); var jar = require('../../lib/jar.js'); var languages = require('../../../languages.json'); +var Form = require('../forms/form.jsx'); var Select = require('../forms/select.jsx'); +require('./languagechooser.scss'); + /** * Footer dropdown menu that allows one to change their language. */ var LanguageChooser = React.createClass({ type: 'LanguageChooser', - mixins: [ - Api - ], getDefaultProps: function () { return { languages: languages, locale: window._locale }; }, - onSetLanguage: function (e) { - e.preventDefault(); - jar.set('scratchlanguage', e.target.value); + onSetLanguage: function (name, value) { + jar.set('scratchlanguage', value); window.location.reload(); }, render: function () { @@ -30,17 +28,17 @@ var LanguageChooser = React.createClass({ 'language-chooser', this.props.className ); - + var languageOptions = Object.keys(this.props.languages).map(function (value) { + return {value: value, label: this.props.languages[value]}; + }.bind(this)); return ( -
    - -
    +
    + + - + {this.state.waiting ? [ ] : [ ]} - + {error} -
    +
    ); } diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx index c2d063d6f..0e1d68c6e 100644 --- a/src/components/modal/modal.jsx +++ b/src/components/modal/modal.jsx @@ -11,6 +11,7 @@ var defaultStyle = { backgroundColor: 'rgba(0, 0, 0, .75)' }, content: { + position: 'absolute', overflow: 'visible', borderRadius: '6px', width: 500, diff --git a/src/components/modal/modal.scss b/src/components/modal/modal.scss index fae33030d..bafe217d6 100644 --- a/src/components/modal/modal.scss +++ b/src/components/modal/modal.scss @@ -1,6 +1,10 @@ @import "../../colors"; .ReactModal__Content { + &:focus { + outline: none; + } + iframe { border: 0; } diff --git a/src/components/navigation/container/navigation.scss b/src/components/navigation/container/navigation.scss index 987b2b3b9..abcc7937a 100644 --- a/src/components/navigation/container/navigation.scss +++ b/src/components/navigation/container/navigation.scss @@ -11,8 +11,12 @@ box-shadow: 0 0 3px $box-shadow-gray; background-color: $ui-blue; + &.staging { + background-color: $ui-orange; + } - width: 100%; + + width: 100%; /* NOTE: Height should match offset settings in main.scss file */ height: 50px; @@ -35,7 +39,7 @@ .ie9 & { display: table-row; } - + > li { display: inline-block; @@ -73,9 +77,9 @@ .link { > a { display: block; - padding: 13px 15px 3px 15px; + padding: 13px 15px 4px 15px; height: 33px; - + text-decoration: none; white-space: nowrap; color: $type-white; diff --git a/src/components/navigation/www/navigation.jsx b/src/components/navigation/www/navigation.jsx index ffeaf21d3..02d36e681 100644 --- a/src/components/navigation/www/navigation.jsx +++ b/src/components/navigation/www/navigation.jsx @@ -7,8 +7,9 @@ var injectIntl = ReactIntl.injectIntl; var sessionActions = require('../../../redux/session.js'); -var Api = require('../../../mixins/api.jsx'); +var api = require('../../../lib/api'); var Avatar = require('../../avatar/avatar.jsx'); +var Button = require('../../forms/button.jsx'); var Dropdown = require('../../dropdown/dropdown.jsx'); var Input = require('../../forms/input.jsx'); var log = require('../../../lib/log.js'); @@ -23,9 +24,6 @@ Modal.setAppElement(document.getElementById('view')); var Navigation = React.createClass({ type: 'Navigation', - mixins: [ - Api - ], getInitialState: function () { return { accountNavOpen: false, @@ -43,19 +41,19 @@ var Navigation = React.createClass({ }; }, componentDidMount: function () { - if (this.props.session.user) { + if (this.props.session.session.user) { this.getMessageCount(); var intervalId = setInterval(this.getMessageCount, 120000); // check for new messages every 2 mins. this.setState({'messageCountIntervalId': intervalId}); } }, componentDidUpdate: function (prevProps) { - if (prevProps.session.user != this.props.session.user) { + if (prevProps.session.session.user != this.props.session.session.user) { this.setState({ 'loginOpen': false, 'accountNavOpen': false }); - if (this.props.session.user) { + if (this.props.session.session.user) { this.getMessageCount(); var intervalId = setInterval(this.getMessageCount, 120000); this.setState({'messageCountIntervalId': intervalId}); @@ -80,13 +78,13 @@ var Navigation = React.createClass({ } }, getProfileUrl: function () { - if (!this.props.session.user) return; - return '/users/' + this.props.session.user.username + '/'; + if (!this.props.session.session.user) return; + return '/users/' + this.props.session.session.user.username + '/'; }, getMessageCount: function () { - this.api({ + api({ method: 'get', - uri: '/users/' + this.props.session.user.username + '/messages/count' + uri: '/users/' + this.props.session.session.user.username + '/messages/count' }, function (err, body) { if (err) return this.setState({'unreadMessageCount': 0}); if (body) { @@ -109,7 +107,7 @@ var Navigation = React.createClass({ handleLogIn: function (formData, callback) { this.setState({'loginError': null}); formData['useMessages'] = true; - this.api({ + api({ method: 'post', host: '', uri: '/accounts/login/', @@ -141,7 +139,7 @@ var Navigation = React.createClass({ }, handleLogOut: function (e) { e.preventDefault(); - this.api({ + api({ host: '', method: 'post', uri: '/accounts/logout/', @@ -174,14 +172,18 @@ var Navigation = React.createClass({ }, render: function () { var classes = classNames({ - 'logged-in': this.props.session.user + 'logged-in': this.props.session.session.user }); var messageClasses = classNames({ 'message-count': true, 'show': this.state.unreadMessageCount > 0 }); + var dropdownClasses = classNames({ + 'user-info': true, + 'open': this.state.accountNavOpen + }); var formatMessage = this.props.intl.formatMessage; - var createLink = this.props.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home'; + var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home'; return ( -