diff --git a/.travis.yml b/.travis.yml index 33d4eb9fa..479e987fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,15 +14,30 @@ env: global: - CXX=g++-4.8 - API_HOST_master=https://api.scratch.mit.edu - - API_HOST_STAGING=https://api-staging.scratch.mit.edu + - API_HOST_STAGING=https://api.scratch.ly - API_HOST_VAR=API_HOST_$TRAVIS_BRANCH - API_HOST=${!API_HOST_VAR} - API_HOST=${API_HOST:-$API_HOST_STAGING} + - ASSET_HOST_master=https://assets.scratch.mit.edu + - ASSET_HOST_STAGING=https://assets.scratch.ly + - ASSET_HOST_VAR=ASSET_HOST_$TRAVIS_BRANCH + - ASSET_HOST=${!ASSET_HOST_VAR} + - ASSET_HOST=${ASSET_HOST:-$ASSET_HOST_STAGING} + - BACKPACK_HOST_master=https://backpack.scratch.mit.edu + - BACKPACK_HOST_STAGING=https://backpack.scratch.ly + - BACKPACK_HOST_VAR=BACKPACK_HOST_$TRAVIS_BRANCH + - BACKPACK_HOST=${!BACKPACK_HOST_VAR} + - BACKPACK_HOST=${BACKPACK_HOST:-$BACKPACK_HOST_STAGING} - ROOT_URL_master=https://scratch.mit.edu - ROOT_URL_STAGING=https://scratch.ly - ROOT_URL_VAR=ROOT_URL_$TRAVIS_BRANCH - ROOT_URL=${!ROOT_URL_VAR} - ROOT_URL=${ROOT_URL:-$ROOT_URL_STAGING} + - PROJECT_HOST_master=https://projects.scratch.mit.edu + - PROJECT_HOST_STAGING=https://projects.scratch.ly + - PROJECT_HOST_VAR=PROJECT_HOST_$TRAVIS_BRANCH + - PROJECT_HOST=${!PROJECT_HOST_VAR} + - PROJECT_HOST=${PROJECT_HOST:-$PROJECT_HOST_STAGING} - PATH=$PATH:$PWD/test/integration/node_modules/chromedriver/bin - AWS_ACCESS_KEY_ID=$EB_AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY=$EB_AWS_SECRET_ACCESS_KEY @@ -69,6 +84,7 @@ addons: install: - sudo -H pip install -r requirements.txt - npm --production=false install + - npm --production=false update jobs: include: - stage: test diff --git a/.tx/config b/.tx/config index 1a91f05e5..54ca458e0 100644 --- a/.tx/config +++ b/.tx/config @@ -1,6 +1,6 @@ [main] host = https://www.transifex.com -lang_map = zh_CN:zh-cn, zh_TW:zh-tw, pt_BR:pt-br +lang_map = zh_CN:zh-cn, zh_TW:zh-tw, pt_BR:pt-br, es_419:es-419, aa_DJ:aa-dj [scratch-website.explore-l10njson] file_filter = localizations/explore/.json @@ -156,6 +156,23 @@ source_file = src/views/microbit/l10n.json source_lang = en type = KEYVALUEJSON +[scratch-website.3faq-l10njson] +file_filter = localizations/preview-faq/.json +source_file = src/views/preview-faq/l10n.json +source_lang = en +type = KEYVALUEJSON + +[scratch-website.search-l10njson] +file_filter = localizations/search/.json +source_file = src/views/search/l10n.json +source_lang = en +type = KEYVALUEJSON + +[scratch-website.wedo2-legacy-l10njson] +source_file = src/views/wedo2-legacy/l10n.json +source_lang = en +type = KEYVALUEJSON + [scratch-website.parents-l10njson] source_file = src/views/parents/l10n.json source_lang = en diff --git a/Makefile b/Makefile index 21100b6f4..f68ed91c9 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,25 @@ ESLINT=./node_modules/.bin/eslint NODE= NODE_OPTIONS=--max_old_space_size=8000 node SASSLINT=./node_modules/.bin/sass-lint -v +SCRATCH_DOCKER_CONFIG=./node_modules/.bin/docker_config.sh S3CMD=s3cmd sync -P --delete-removed --add-header=Cache-Control:no-cache,public,max-age=3600 TAP=./node_modules/.bin/tap WATCH= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/watch WEBPACK= NODE_OPTIONS=--max_old_space_size=8000 ./node_modules/.bin/webpack + +# ------------------------------------ + +$(SCRATCH_DOCKER_CONFIG): + npm install scratch-docker + +docker-up: $(SCRATCH_DOCKER_CONFIG) + $(SCRATCH_DOCKER_CONFIG) network create + docker-compose up + +docker-down: + docker-compose down + # ------------------------------------ build: diff --git a/README.md b/README.md index 210e72876..25e52a316 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,17 @@ To stop the process that is making the site available to your web browser (creat `npm start` can be configured with the following environment variables -| Variable | Default | Description | -| ------------- | ----------------------------- | ---------------------------------------------- | -| `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests | -| `SENTRY_DSN` | `''` | DSN for Sentry | -| `FALLBACK` | `''` | Pass-through location for old site | -| `GA_TRACKER` | `''` | Where to log Google Analytics data | -| `NODE_ENV` | `null` | If not `production`, app acts like development | -| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) | +| Variable | Default | Description | +| --------------- | ---------------------------------- | ---------------------------------------------- | +| `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests | +| `ASSETS_HOST` | `https://assets.scratch.mit.edu` | Hostname for asset requests | +| `BACKPACK_HOST` | `https://backpack.scratch.mit.edu` | Hostname for backpack requests | +| `PROJECTS_HOST` | `https://projects.scratch.mit.edu` | Hostname for project requests | +| `SENTRY_DSN` | `''` | DSN for Sentry | +| `FALLBACK` | `''` | Pass-through location for old site | +| `GA_TRACKER` | `''` | Where to log Google Analytics data | +| `NODE_ENV` | `null` | If not `production`, app acts like development | +| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) | **NOTE:** Because by default `API_HOST=https://api.scratch.mit.edu`, please be aware that, by default, you will be seeing and interacting with real data on the Scratch website. diff --git a/docker-compose.yml b/docker-compose.yml index e7919d96f..66e728695 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,9 @@ volumes: runtime_data: networks: - scratch-api_scratch_network: - external: true + default: + external: + name: scratchapi_scratch_network services: app: @@ -13,7 +14,7 @@ services: hostname: scratch-www-app environment: - API_HOST=http://localhost:8491 - - FALLBACK=http://localhost:8080 + - FALLBACK=http://scratchr2-app:8080 - USE_DOCKER_WATCHOPTIONS=true build: context: ./ @@ -35,5 +36,3 @@ services: - runtime_data:/runtime ports: - "8333:8333" - networks: - - scratch-api_scratch_network diff --git a/package.json b/package.json index e8bbee511..5817bc436 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "lodash.defaults": "4.0.1", "newrelic": "1.25.4", "raven": "0.10.0", + "scratch-docker": "^1.0.2", "scratch-parser": "^4.2.0", "scratch-storage": "^0.5.1" }, @@ -77,7 +78,6 @@ "lodash.merge": "3.3.2", "lodash.omit": "3.1.0", "lodash.range": "3.0.1", - "lodash.truncate": "4.4.2", "minilog": "2.0.8", "node-dir": "0.1.16", "node-sass": "4.6.1", @@ -100,7 +100,7 @@ "redux-thunk": "2.0.1", "sass-lint": "1.5.1", "sass-loader": "6.0.6", - "scratch-gui": "latest", + "scratch-gui": "develop", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "slick-carousel": "1.6.0", "source-map-support": "0.3.2", diff --git a/src/_colors.scss b/src/_colors.scss index 5ef54a7ce..33c5560c3 100644 --- a/src/_colors.scss +++ b/src/_colors.scss @@ -28,6 +28,7 @@ $ui-coral-dark: hsla(350, 100, 60, 1); // #FF3355 More Blocks tertiary $ui-white: hsla(0, 100%, 100%, 1); //#FFF $ui-white-15percent: hsla(0, 100%, 100%, .15); //#FFF $ui-light-primary: hsl(215, 100, 95); +$ui-light-primary-transparent: hsla(215, 100, 95, 0); $ui-border: hsla(0, 0, 85, 1); //#D9D9D9 diff --git a/src/_frameless.scss b/src/_frameless.scss index 854e109c1..0881a3ec6 100644 --- a/src/_frameless.scss +++ b/src/_frameless.scss @@ -38,6 +38,31 @@ $desktop: 942px; $tablet: 640px; $mobile: 480px; +/* Media Queries */ + +/* Width */ +/* +* ... small | medium | intermediate | big ... +* ... medium-and-smaller | +* ... intermediate-and-smaller | +*/ + +$small: "only screen and (max-width : #{$mobile}-1)"; +$medium: "only screen and (min-width : #{$mobile}) and (max-width : #{$tablet}-1)"; +$intermediate: "only screen and (min-width : #{$tablet}) and (max-width : #{$desktop}-1)"; +$big: "only screen and (min-width : #{$desktop})"; + +$medium-and-smaller: "only screen and (max-width : #{$tablet}-1)"; +$intermediate-and-smaller: "only screen and (max-width : #{$desktop}-1)"; + +$medium-and-intermediate: "only screen and (min-width : #{$mobile}) and (max-width : #{$desktop}-1)"; + +/* Height */ + +$small-height: "only screen and (max-height : #{$mobile} - 1)"; +$medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$tablet} - 1)"; + + // // Column-widths in a function, in ems // @@ -48,7 +73,7 @@ $mobile: 480px; //4 columns @mixin submobile ($parent-selector, $child-selector) { - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { #{$parent-selector} { text-align: center; } @@ -64,7 +89,7 @@ $mobile: 480px; //6 columns @mixin mobile ($parent-selector, $child-selector) { - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { #{$parent-selector} { text-align: center; } @@ -80,7 +105,7 @@ $mobile: 480px; //8 columns @mixin tablet ($parent-selector, $child-selector) { - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { #{$parent-selector} { text-align: center; } @@ -94,7 +119,7 @@ $mobile: 480px; //12 columns @mixin desktop ($parent-selector, $child-selector) { - @media only screen and (min-width: $desktop) { + @media #{$big} { #{$child-selector} { margin: 0 auto; width: $desktop; diff --git a/src/components/box/box.scss b/src/components/box/box.scss index 4b9392771..bc981dd94 100644 --- a/src/components/box/box.scss +++ b/src/components/box/box.scss @@ -7,9 +7,9 @@ $base-bg: $ui-white; display: inline-block; border: 1px solid $ui-border; border-radius: 10px 10px 0 0; - + //4 columns - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { width: $cols4; .box-header { @@ -22,7 +22,7 @@ $base-bg: $ui-white; } //6 columns - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { width: $cols6; .box-header { @@ -35,7 +35,7 @@ $base-bg: $ui-white; } //8 columns - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { width: $cols8; .box-header { @@ -48,7 +48,7 @@ $base-bg: $ui-white; } //12 columns - @media only screen and (min-width: $desktop) { + @media #{$big} { width: $cols12; .box-header { diff --git a/src/components/card/card.scss b/src/components/card/card.scss index b2b70c22c..d23f5f2e3 100644 --- a/src/components/card/card.scss +++ b/src/components/card/card.scss @@ -65,7 +65,7 @@ margin: 0 0 -3rem -4rem; } - .row { + .row { margin-bottom: 1.2rem; &.has-error { @@ -81,7 +81,7 @@ } } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .card { width: 22.5rem; @@ -95,7 +95,7 @@ } } -@media only screen and (max-width: $tablet - 1) { +@media #{$medium-and-smaller} { .card { .input { width: 90%; @@ -103,7 +103,7 @@ } } -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .card { .validation-message { position: relative; diff --git a/src/components/dropdown/dropdown.scss b/src/components/dropdown/dropdown.scss index 6aea2f067..02b691f83 100644 --- a/src/components/dropdown/dropdown.scss +++ b/src/components/dropdown/dropdown.scss @@ -9,7 +9,8 @@ border-radius: 0 0 5px 5px; background-color: $ui-blue; padding: 10px; - max-width: 260px; + min-width: 9rem; + max-width: 16.25rem; overflow: visible; color: $type-white; font-size: .8125rem; @@ -33,8 +34,8 @@ } input { - // 100% minus border and padding margin-bottom: 12px; + // 100% minus border and padding width: calc(100% - 30px); } @@ -88,8 +89,4 @@ content: ""; } } - - @media only screen and (max-width: $tablet - 1) { - min-width: 160px; - } } diff --git a/src/components/extension-landing/extension-landing.jsx b/src/components/extension-landing/extension-landing.jsx index a666875b5..44cb3fb01 100644 --- a/src/components/extension-landing/extension-landing.jsx +++ b/src/components/extension-landing/extension-landing.jsx @@ -10,8 +10,16 @@ class ExtensionLanding extends React.Component { 'onSetOS' ]); + // @todo use bowser for browser detection + let detectedOS = OS_ENUM.WINDOWS; + if (window.navigator && window.navigator.platform) { + if (window.navigator.platform === 'MacIntel') { + detectedOS = OS_ENUM.MACOS; + } + } + this.state = { - OS: OS_ENUM.WINDOWS + OS: detectedOS }; } diff --git a/src/components/extension-landing/extension-landing.scss b/src/components/extension-landing/extension-landing.scss index e9555bdba..536c6d736 100644 --- a/src/components/extension-landing/extension-landing.scss +++ b/src/components/extension-landing/extension-landing.scss @@ -10,7 +10,11 @@ padding: 4rem 0; } - h2 { + h1 { + font-size: 2rem; + } + + h1, h2 { margin-bottom: 1rem; } @@ -46,6 +50,7 @@ } .screenshot { + border: 1px solid $ui-border; border-radius: .5rem; } @@ -84,15 +89,15 @@ margin-bottom: 5rem; align-items: flex-start; - h2 { + h1, h2 { display: flex; margin-bottom: 2rem; color: $ui-white; - } - h2 img { - padding-right: .5rem; - max-height: 100%; + img { + padding-right: .5rem; + max-height: 100%; + } } span { diff --git a/src/components/extension-landing/install-scratch-link.jsx b/src/components/extension-landing/install-scratch-link.jsx index 07e0fddd0..28ac6a5a0 100644 --- a/src/components/extension-landing/install-scratch-link.jsx +++ b/src/components/extension-landing/install-scratch-link.jsx @@ -35,7 +35,10 @@ const InstallScratchLink = ({ : } - + @@ -50,6 +53,7 @@ const InstallScratchLink = ({
(
- + {props.imageAlt}

{props.title}

@@ -20,6 +23,7 @@ const ProjectCard = props => ( ProjectCard.propTypes = { cardUrl: PropTypes.string, description: PropTypes.string, + imageAlt: PropTypes.string, imageSrc: PropTypes.string, title: PropTypes.string }; diff --git a/src/components/flex-row/flex-row.scss b/src/components/flex-row/flex-row.scss index 4b0f28069..eff8370e3 100644 --- a/src/components/flex-row/flex-row.scss +++ b/src/components/flex-row/flex-row.scss @@ -25,7 +25,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { flex-direction: column; &.uneven { diff --git a/src/components/footer/conference/footer.scss b/src/components/footer/conference/footer.scss index 3694c2d87..3eafcc4cb 100644 --- a/src/components/footer/conference/footer.scss +++ b/src/components/footer/conference/footer.scss @@ -51,7 +51,7 @@ justify-content: space-between; align-items: flex-start; - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { align-items: center; } } @@ -103,7 +103,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { text-align: center; .family { @@ -122,7 +122,7 @@ } } - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { ul { li { margin-left: 0; diff --git a/src/components/forms/inplace-input.scss b/src/components/forms/inplace-input.scss index 33bc254b3..65b3094e1 100644 --- a/src/components/forms/inplace-input.scss +++ b/src/components/forms/inplace-input.scss @@ -36,7 +36,7 @@ } .inplace-textarea { - transition: all 1s ease; + transition: all .2s ease; border: 2px dashed $ui-blue-25percent; border-radius: 8px; background-color: $ui-light-gray; @@ -49,7 +49,7 @@ resize: none; &:focus { - transition: all 1s ease; + transition: all .2s ease; outline: none; border: 2px solid $ui-blue; box-shadow: 0 0 0 4px $ui-blue-25percent; diff --git a/src/components/forms/select.scss b/src/components/forms/select.scss index 04433fdcc..4159086b7 100644 --- a/src/components/forms/select.scss +++ b/src/components/forms/select.scss @@ -13,9 +13,9 @@ border-radius: 5px; background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center; padding-right: 4rem; + padding-left: 1rem; width: 100%; height: 3rem; - text-indent: 1rem; color: $type-gray; font-size: .875rem; appearance: none; diff --git a/src/components/forms/validations.jsx b/src/components/forms/validations.jsx index c83ce63cb..c882aa40d 100644 --- a/src/components/forms/validations.jsx +++ b/src/components/forms/validations.jsx @@ -28,8 +28,8 @@ module.exports.validationHOCFactory = defaultValidationErrors => (Component => { diff --git a/src/components/grid/grid.scss b/src/components/grid/grid.scss index 253210619..3cd926af2 100644 --- a/src/components/grid/grid.scss +++ b/src/components/grid/grid.scss @@ -7,7 +7,7 @@ $thumbnail-width: 220px; $thumbnail-inner-width: 204px; - + $project-height: 208px; $gallery-height: 164px; @@ -94,21 +94,21 @@ } //4 columns - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .flex-row { width: $cols4; } } //6 columns - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { .flex-row { width: $cols6; } } // 8 columns - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { .flex-row { width: $cols9; } diff --git a/src/components/intro/intro.jsx b/src/components/intro/intro.jsx index efe0473a7..243cc4bef 100644 --- a/src/components/intro/intro.jsx +++ b/src/components/intro/intro.jsx @@ -3,7 +3,7 @@ const connect = require('react-redux').connect; const PropTypes = require('prop-types'); const React = require('react'); -const sessionActions = require('../../redux/session.js'); +const navigationActions = require('../../redux/navigation.js'); const IframeModal = require('../modal/iframe/modal.jsx'); const Registration = require('../registration/registration.jsx'); @@ -15,10 +15,7 @@ class Intro extends React.Component { super(props); bindAll(this, [ 'handleShowVideo', - 'handleCloseVideo', - 'handleJoinClick', - 'handleCloseRegistration', - 'handleCompleteRegistration' + 'handleCloseVideo' ]); this.state = { videoOpen: false @@ -30,17 +27,6 @@ class Intro extends React.Component { handleCloseVideo () { this.setState({videoOpen: false}); } - handleJoinClick (e) { - e.preventDefault(); - this.setState({registrationOpen: true}); - } - handleCloseRegistration () { - this.setState({registrationOpen: false}); - } - handleCompleteRegistration () { - this.props.dispatch(sessionActions.refreshSession()); - this.closeRegistration(); - } render () { return (
({ session: state.session }); -const ConnectedIntro = connect(mapStateToProps)(Intro); +const mapDispatchToProps = dispatch => ({ + handleOpenRegistration: event => { + event.preventDefault(); + dispatch(navigationActions.handleOpenRegistration()); + } +}); + + +const ConnectedIntro = connect( + mapStateToProps, + mapDispatchToProps +)(Intro); module.exports = ConnectedIntro; diff --git a/src/components/login/canceled-deletion-modal.jsx b/src/components/login/canceled-deletion-modal.jsx new file mode 100644 index 000000000..c98028f7d --- /dev/null +++ b/src/components/login/canceled-deletion-modal.jsx @@ -0,0 +1,60 @@ +const React = require('react'); +const connect = require('react-redux').connect; +const FormattedMessage = require('react-intl').FormattedMessage; +const PropTypes = require('prop-types'); +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; + +const navigationActions = require('../../redux/navigation.js'); +const Modal = require('../modal/base/modal.jsx'); + +const CanceledDeletionModal = ({ + canceledDeletionOpen, + handleCloseCanceledDeletion, + intl +}) => ( + +

+

+ + {intl.formatMessage({id: 'general.noDeletionLink'})} + + }} + /> +

+
+); + +CanceledDeletionModal.propTypes = { + canceledDeletionOpen: PropTypes.bool, + handleCloseCanceledDeletion: PropTypes.func, + intl: intlShape +}; + +const mapStateToProps = state => ({ + canceledDeletionOpen: state.navigation && state.navigation.canceledDeletionOpen +}); + +const mapDispatchToProps = dispatch => ({ + handleCloseCanceledDeletion: () => { + dispatch(navigationActions.setCanceledDeletionOpen(false)); + } +}); + +const ConnectedCanceledDeletionModal = connect( + mapStateToProps, + mapDispatchToProps +)(CanceledDeletionModal); + +module.exports = injectIntl(ConnectedCanceledDeletionModal); diff --git a/src/components/login/connected-login.jsx b/src/components/login/connected-login.jsx new file mode 100644 index 000000000..9f5ec9eba --- /dev/null +++ b/src/components/login/connected-login.jsx @@ -0,0 +1,34 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +const connect = require('react-redux').connect; + +const Login = require('./login.jsx'); + +require('./login-dropdown.scss'); + +const ConnectedLogin = ({ + error, + onLogIn +}) => ( + +); + +ConnectedLogin.propTypes = { + error: PropTypes.string, + onLogIn: PropTypes.func +}; + +const mapStateToProps = state => ({ + error: state.navigation && state.navigation.loginError +}); + +const mapDispatchToProps = () => ({}); + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(ConnectedLogin); diff --git a/src/components/login/login-dropdown.jsx b/src/components/login/login-dropdown.jsx new file mode 100644 index 000000000..1f387cf48 --- /dev/null +++ b/src/components/login/login-dropdown.jsx @@ -0,0 +1,50 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +const connect = require('react-redux').connect; + +const navigationActions = require('../../redux/navigation.js'); +const Dropdown = require('../dropdown/dropdown.jsx'); +const ConnectedLogin = require('./connected-login.jsx'); + +require('./login-dropdown.scss'); + +const LoginDropdown = ({ + isOpen, + onClose, + onLogIn +}) => ( + + + +); + +LoginDropdown.propTypes = { + isOpen: PropTypes.bool, + onClose: PropTypes.func, + onLogIn: PropTypes.func +}; + +const mapStateToProps = state => ({ + isOpen: state.navigation && state.navigation.loginOpen +}); + +const mapDispatchToProps = dispatch => ({ + onClose: () => { + dispatch(navigationActions.setLoginOpen(false)); + }, + onLogIn: (formData, callback) => { + dispatch(navigationActions.handleLogIn(formData, callback)); + } +}); + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(LoginDropdown); diff --git a/src/components/login/login-dropdown.scss b/src/components/login/login-dropdown.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/login/login.jsx b/src/components/login/login.jsx index 58586e5e5..116d3fd9a 100644 --- a/src/components/login/login.jsx +++ b/src/components/login/login.jsx @@ -3,8 +3,6 @@ const FormattedMessage = require('react-intl').FormattedMessage; const PropTypes = require('prop-types'); const React = require('react'); -const log = require('../../lib/log.js'); - const Form = require('../forms/form.jsx'); const Input = require('../forms/input.jsx'); const Button = require('../forms/button.jsx'); @@ -24,8 +22,7 @@ class Login extends React.Component { } handleSubmit (formData) { this.setState({waiting: true}); - this.props.onLogIn(formData, err => { - if (err) log.error(err); + this.props.onLogIn(formData, () => { this.setState({waiting: false}); }); } @@ -48,9 +45,6 @@ class Login extends React.Component { key="usernameInput" maxLength="30" name="username" - ref={input => { - this.username = input; - }} type="text" />
- - - - + + {waitingToClose ? [ - {waitingToClose ? [ - - ] : [ - - ]} - - -
+ ] : [ + + ]} + + ); diff --git a/src/components/modal/addtostudio/studio-button.jsx b/src/components/modal/addtostudio/studio-button.jsx index 137c4cc8a..bfeb35147 100644 --- a/src/components/modal/addtostudio/studio-button.jsx +++ b/src/components/modal/addtostudio/studio-button.jsx @@ -1,29 +1,36 @@ -const truncateAtWordBoundary = require('../../../lib/truncate').truncateAtWordBoundary; const PropTypes = require('prop-types'); const React = require('react'); const classNames = require('classnames'); + const Spinner = require('../../spinner/spinner.jsx'); +const AnimateHOC = require('./animate-hoc.jsx'); require('./modal.scss'); const StudioButton = ({ hasRequestOutstanding, - id, includesProject, title, - onToggleStudio + onClick, + wasClicked }) => { const checkmark = ( checkmark-icon ); const plus = ( plus-icon ); @@ -35,8 +42,7 @@ const StudioButton = ({ {'studio-selector-button-selected': includesProject && !hasRequestOutstanding} )} - data-id={id} - onClick={onToggleStudio} + onClick={onClick} >
- {truncateAtWordBoundary(title, 25)} + {title}
{(hasRequestOutstanding ? - () : + : (includesProject ? checkmark : plus))}
@@ -63,10 +70,10 @@ const StudioButton = ({ StudioButton.propTypes = { hasRequestOutstanding: PropTypes.bool, - id: PropTypes.number, includesProject: PropTypes.bool, - onToggleStudio: PropTypes.func, - title: PropTypes.string + onClick: PropTypes.func, + title: PropTypes.string, + wasClicked: PropTypes.bool }; -module.exports = StudioButton; +module.exports = AnimateHOC(StudioButton); diff --git a/src/components/modal/base/modal.jsx b/src/components/modal/base/modal.jsx index 613233a15..156d435ce 100644 --- a/src/components/modal/base/modal.jsx +++ b/src/components/modal/base/modal.jsx @@ -7,7 +7,7 @@ const ReactModal = require('react-modal'); require('./modal.scss'); -ReactModal.setAppElement(document.getElementById('view')); +ReactModal.setAppElement(document.getElementById('app')); /** * Container for pop up windows (See: registration window) @@ -23,11 +23,19 @@ class Modal extends React.Component { return this.modal.portal.requestClose(); } render () { + // bodyOpenClassName prop cannot be blank string or null here; both cause + // an error, because ReactModal does not correctly handle them. + // If we're not setting it to a class name, we must omit the prop entirely. + const bodyOpenClassNameProp = this.props.useStandardSizes ? + {bodyOpenClassName: classNames('overflow-hidden')} : {}; return ( ( + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+ + + +
+
+
+
+); + + +DeleteModal.propTypes = { + intl: intlShape, + onDelete: PropTypes.func, + onReport: PropTypes.func, + onRequestClose: PropTypes.func +}; + +module.exports = injectIntl(DeleteModal); diff --git a/src/components/modal/comments/modal.scss b/src/components/modal/comments/modal.scss new file mode 100644 index 000000000..40a526fe3 --- /dev/null +++ b/src/components/modal/comments/modal.scss @@ -0,0 +1,44 @@ +@import "../../../colors"; +@import "../../../frameless"; + +$medium-and-small: "screen and (max-width : #{$tablet}-1)"; + +.mod-report * { + box-sizing: border-box; +} + +.mod-report { + margin: 100px auto; + outline: none; + padding: 0; + width: 36.25rem; /* 580px; */ + user-select: none; +} + +.report-modal-header { + border-radius: 1rem 1rem 0 0; + box-shadow: inset 0 -1px 0 0 $ui-coral-dark; + background-color: $ui-coral; + padding-top: .75rem; + width: 100%; + height: 3rem; + box-sizing: border-box; +} + +.report-content-label { + text-align: center; + color: $type-white; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 1rem; + font-weight: bold; +} + +.report-modal-content { + margin: 1rem auto; + width: 80%; + font-size: .875rem; + + .instructions { + line-height: 1.5rem; + } +} diff --git a/src/components/modal/comments/report-comment.jsx b/src/components/modal/comments/report-comment.jsx new file mode 100644 index 000000000..197484dfe --- /dev/null +++ b/src/components/modal/comments/report-comment.jsx @@ -0,0 +1,84 @@ +const PropTypes = require('prop-types'); +const React = require('react'); +const FormattedMessage = require('react-intl').FormattedMessage; +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; +const Modal = require('../base/modal.jsx'); + +const Button = require('../../forms/button.jsx'); +const FlexRow = require('../../flex-row/flex-row.jsx'); + +require('../../forms/button.scss'); +require('./modal.scss'); + +const ReportModal = ({ + intl, + isConfirmed, + onReport, + onRequestClose, + ...modalProps +}) => ( + +
+
+
+ +
+
+ +
+
+
+ {isConfirmed ? ( + + ) : ( + + )} +
+
+
+ +
+ + {isConfirmed ? null : ( + + )} +
+
+
+
+); + + +ReportModal.propTypes = { + intl: intlShape, + isConfirmed: PropTypes.bool, + isOwnSpace: PropTypes.bool, + onReport: PropTypes.func, + onRequestClose: PropTypes.func, + type: PropTypes.string +}; + +module.exports = injectIntl(ReportModal); diff --git a/src/components/modal/report/modal.jsx b/src/components/modal/report/modal.jsx index 7b78379a0..99e564468 100644 --- a/src/components/modal/report/modal.jsx +++ b/src/components/modal/report/modal.jsx @@ -1,10 +1,12 @@ const bindAll = require('lodash.bindall'); const PropTypes = require('prop-types'); const React = require('react'); +const connect = require('react-redux').connect; const FormattedMessage = require('react-intl').FormattedMessage; const injectIntl = require('react-intl').injectIntl; const intlShape = require('react-intl').intlShape; const Modal = require('../base/modal.jsx'); +const classNames = require('classnames'); const Form = require('../../forms/form.jsx'); const Button = require('../../forms/button.jsx'); @@ -12,6 +14,7 @@ const Select = require('../../forms/select.jsx'); const Spinner = require('../../spinner/spinner.jsx'); const TextArea = require('../../forms/textarea.jsx'); const FlexRow = require('../../flex-row/flex-row.jsx'); +const previewActions = require('../../../redux/preview.js'); require('../../forms/button.scss'); require('./modal.scss'); @@ -68,12 +71,24 @@ class ReportModal extends React.Component { constructor (props) { super(props); bindAll(this, [ - 'handleReportCategorySelect' + 'handleCategorySelect', + 'handleValid', + 'handleInvalid' ]); - this.state = {reportCategory: this.props.report.category}; + this.state = { + category: '', + notes: '', + valid: false + }; } - handleReportCategorySelect (name, value) { - this.setState({reportCategory: value}); + handleCategorySelect (name, value) { + this.setState({category: value}); + } + handleValid () { + this.setState({valid: true}); + } + handleInvalid () { + this.setState({valid: false}); } lookupPrompt (value) { const prompt = REPORT_OPTIONS.find(item => item.value === value).prompt; @@ -82,90 +97,145 @@ class ReportModal extends React.Component { render () { const { intl, + isConfirmed, + isError, + isOpen, + isWaiting, onReport, // eslint-disable-line no-unused-vars - report, + onRequestClose, type, ...modalProps } = this.props; + const submitEnabled = this.state.valid && !isWaiting; + const submitDisabledParam = submitEnabled ? {} : {disabled: 'disabled'}; const contentLabel = intl.formatMessage({id: `report.${type}`}); return (
-
-
+
+
{contentLabel}
-
- - - - ) - }} - /> -